uniapp+php服务端实现苹果iap内购的消耗性项目和非续期订阅项目,前后端代码加逻辑分析

 前言:
    公司的项目app在上架苹果商店时发现人家要求里面的部分购买项目必须使用iap购买的方式,使用原本的微信支付方式审核不给通过,无奈只能重新研究这个东西。做起来还是有点麻烦,主要是网上的文章很少,不能直接硬抄。自己做完总结一下,希望对小伙伴们有帮助。
    1、代码有部分因为隐私性会省略,但我会注释说明这部分代码是做什么的,大致应该怎么写,小伙伴们可以根据自己的具体情况继续写。
    2、当前代码写的有些混乱,只是正好满足当前app需求,对于iap内购的许多其他功能和小细节也没有深度研究,后续如果有机会会继续钻研这个东西,本篇文章只是记录一下开发过程与实现的代码,为以后相关开发作参考。
    3、本人小白一枚,写的代码质量不是很好,如果哪里有错误,希望大佬发现后给予斧正。
    4、对于在apple开发者中心进行协议,税务,银行账户等信息的配置与设置本文并没有给出教程,小伙伴可以自行根据网上教程设置,那些内容相对代码来说还是比较规范的,本文就不在细数。

 1、首先看一下iap内购的订单支付流程图:

a4421eb2c82646deb4e86930a2f18f79.png

分析:
    由图我们可以发现:在我们支付时,会先由app端发送请求至开发者服务器,在开发者服务器上创建订单号,插入订单记录,然后返回我们的订单号至app端,在app端拿到订单号就会调用sdk发起支付,这里的支付完成后会自动由苹果服务器返回给我们一个支付的票据,与微信支付不同(微信支付完成后会返回给开发者服务器支付结果来校验),iap支付会将支付的票据给app端,这时我们需要再次向开发者服务器把票据发送过去,让开发者服务器拿到这个票据请求苹果服务器验证支付的有效性,最终校验成功后修改订单记录并发放购买的内容。

总结一下:
    服务器端:需要两个接口,一个用来创建订单号,一个用来向苹果服务器发送请求验证app传过来的票据
    app端:引入sdk,并完成整个支付流程

(在研究这种使用原生的方式的过程中,发现貌似还有另一种不用写服务器端的的方式就可以完成支付,好像是unipay,但是那个没理解了,就放弃了,有需要的可以看一下https://doc.dcloud.net.cn/uniCloud/uni-pay/uni-app.html,如果使用可以实现,小编恳求发我看看,如果是小编当初理解有误,就请当没看到这段话...)

2、接下来我们看看uniapp在开发iap时官方文档是怎么写的:

uniapp官方网址:uni.requestPayment(OBJECT) | uni-app官网 (dcloud.net.cn)https://uniapp.dcloud.net.cn/api/plugins/payment.html

5b3a5c63e53a47e6bff0cf83faa9e79d.png

说明:
    这是官方文档的支付流程介绍,看这个有没有很迷,小编刚开始看这个的时候也是不理解,但这个确实就是支付的流程
    1、2、3都是我们调用sdk实现,然后我们应该向开发者服务器发送请求获取订单号,将订单号获取到再进行4,返回值就是获取到的支付票据信息,我们再执行5、6,如此就是整个流程了,只不过他这个写的只是app端要做的事,我们整个盘下来的话思路就很清晰了

3、支付流程明白了我们就可以开始代码的编写了:

说明:
    小编找到网上有大佬把代码封装好也说可以直接复制使用,于是借鉴了过来,但是发现这个与自己代码的逻辑还是有不同之处,于是将大佬的代码改了很多以适应,最终把代码改的相当不优雅,因此下面代码大家可以借鉴实现的逻辑,但是想要直接使用还是不太现实的

    借鉴大佬的文章:一:https://blog.csdn.net/lonerwolfs/article/details/130292489 二:https://blog.csdn.net/weixin_41258075/article/details/131202351

3.1:前端代码都有这些: 

 charge.vue充值页面中调用(记得引入下面的js文件):

this._iap = new realize()
this._iap.init(money);    //自己改的需要,将充值金额直接传过来,方便后面判断

ApplePay.js引入sdk,处理app端所有的支付逻辑: 

/* 
    1、class Iap{}这个类中的是uniapp官方文档中写明的支付方法,可以获取支付通道,拉起支付等;
    2、class realize{}这里被页面实例化,里面含有整个具体的支付逻辑,这里有小编提示的地方都会在注释后添加“~~~~~~~~~~~~~~~~~~~~~~~~~~~”,可以着重看一看
    3、代码中逻辑大致如下:首先会实例化realize,然后调用里面的init初始化,通过创建支付通道,检测产品正常等逻辑判断可以支付后,跳转restore()方法,在这个方法中先检测当前用户未关闭的订单。如果没有,直接再跳转到payment()方法,在这个方法中会请求服务器创建订单,拉起sdk的支付,向服务器发送票据进行验证,验证成功后关闭订单;如果上一步的restore()方法中检测到了未关闭的订单,就会对这个订单判断是是否支付成功的,如果未支付,就直接关闭订单,如果支付成功了,先从数据库中获取该用户最近的一次订单判断是否完成即使用购买的项目发放成功与否(因为这里小编确保了购买的项目订单只能一个一个完成),如果发放了,代表订单完成了但是还没有关闭,此时直接关闭即可,如果订单是未完成的,意味着票据验证成功但未发放购买项目就因为网络等问题服务器端执行失败了,这时重新发送请求至开发者服务器进行再次验证即可。
    4、第3条的逻辑分析可能有些混乱,大家多看看代码与文字分析对比一下,逻辑还是不难的
    5、注意一下代码中发送请求的接口,大家注意根据自己的更改一下
    
*/
import store from'../store/index.js'    //数据仓库,用来获取到当前的用户名,让订单与用户相关联
import { reqPost } from './index.js'    //用来发送post请求,向服务器获取订单号等

const IapTransactionState = {
	purchasing: "0", // 应用程序商店正在处理的交易.
	purchased: "1", // 成功处理的交易.
	failed: "2", // 一个失败的交易.
	restored: "3", // 恢复用户以前购买的内容的事务.
	deferred: "4" // 处于队列中的事务,但其最终状态为等待外部操作
};

class Iap {

	_channel = null; // 支付渠道

	_channelError = null; // 获取支付渠道失败的对象

	_productIds = []; // Apple 官网后台 配置的内部购买项目列表

	_ready = false; // 是否还有未处理的交易

	constructor({
		products
	}) {
		this._productIds = products;
	}

	/* 
	    初始化、获取支付渠道
	*/
	init() {
		return new Promise((resolve, reject) => {
			this.getChannels((channel) => {
				this._ready = true;
				resolve(channel);
			}, (err) => {
				reject(err);
			})
		})
	}

	/** 
	 * @description 向苹果服务器获取产品列表
	 * @param productIds 产品列表
	 */
	getProduct(productIds) {
		return new Promise((resolve, reject) => {
			this._channel.requestProduct(productIds || this._productIds, (res) => {
				resolve(res);
			}, (err) => {
				reject(err);
			})
		});
	}

	/**
	 * @description 发起支付请求
	 * @param orderInfo 订单信息
	 */
	requestPayment(orderInfo) {
		return new Promise((resolve, reject) => {
			uni.requestPayment({
				provider: 'appleiap',
				orderInfo: orderInfo,
				success: (res) => {
					resolve(res);
				},
				fail: (err) => {
					uni.hideLoading();
					reject(err);
				}
			});
		});
	}

	/**
	 * @description 获取苹果服务器已支付且未关闭的交易列表
	 * @param username 用户姓名
	 */
	restoreCompletedTransactions(username) {
		return new Promise((resolve, reject) => {
			this._channel.restoreCompletedTransactions({
				manualFinishTransaction: true,
				username,
			}, (res) => {
				resolve(res);
			}, (err) => {
				reject(err);
			})
		});
	}

	/**
	 * @description 关闭订单
	 * @param transaction 订单对象
	 */
	finishTransaction(transaction) {
		return new Promise((resolve, reject) => {
			this._channel.finishTransaction(transaction, (res) => {
				resolve(res);
			}, (err) => {
				uni.hideLoading();
				reject(err);
			});
		});
	}

	/**
	 * @description 获取支付渠道
	 * @param success 成功获取回调
	 * @param fail    失败获取回调
	 */
	getChannels(success, fail) {
		if (this._channel !== null) {
			success(this._channel)
			return
		}

		if (this._channelError !== null) {
			fail(this._channelError)
			return
		}

		uni.getProvider({
			service: 'payment',
			success: (res) => {
				this._channel = res.providers.find((channel) => {
					return (channel.id === 'appleiap')
				})

				if (this._channel) {
					success(this._channel)
				} else {
					this._channelError = {
						errMsg: 'paymentContext:fail iap service not found'
					}
					fail(this._channelError)
				}
			}
		});
	}

	get channel() {
		return this._channel;
	}
}

/* 实现支付 自定义逻辑   */
class realize {

	productItem = null; // 当前选择充值项id

	loading = false; // 是否允许提交充值

	//应用内购项目~~~~~~~~~~~~~~~这里写的要与你在项目app官网上配置的那些内购项目的产品id一致
	productList = ['xxxxxx1', 'xxxxx2', 'xxxxx3', 'xxxxxx4', 'xxxxx5', 'xxxxx6'];

	// 获取当前登录用户的用户名
	username = store.state.userInfo.username;

	// 调用官方案例
	_iap = new Iap({
		products: this.productList,
	});

	async init(price) {
		try {

			// 初始化,获取iap支付通道
			await this._iap.init();

			// 从苹果服务器获取产品列表
			this.productList = await this._iap.getProduct();

			//根据价格判断是哪个商品
			console.log(price);
			// 将price转换为整数
			const priceInt = parseInt(price);
			// 使用find方法查找符合条件的产品项
			const foundProduct = this.productList.find(product => product.price === priceInt);
			console.log(foundProduct)
			
			if (foundProduct) {
			    // 如果找到符合条件的产品项,则将其赋值给this.productItem
			    this.productItem = foundProduct;
			} else {
			    // 如果未找到符合条件的产品项,则输出错误信息
			    console.log('未定义价格错误');
			}
			console.log(this.productItem)

		} catch (e) {
			uni.showModal({
				title: "init",
				content: e.message,
				showCancel: false
			});
			console.log(e)
		} finally {
			uni.hideLoading();
		}

		if (this._iap._ready) {
			this.restore();
		}

	}

	async restore() {

			uni.showLoading({
				title: '正在检测未关闭的订单...'
			});

			try {
				console.log("本地用户名:" + this.username)
				// 从苹果服务器检查未关闭的订单,可选根据 username 过滤,和调用支付时透传的值一致
				const transactions = await this._iap.restoreCompletedTransactions({
					username: this.username
				});
				console.log(transactions)

				if (!transactions.length) {
					uni.showLoading({
						title: '正在创建新的订单...'
					});
					await this.payment()
					return;
				}

				// 开发者业务逻辑,从服务器获取当前用户未完成的订单列表,和本地的比较
				// 此处省略
				console.log("------有未关闭订单" + transactions)
				console.log(transactions)
				const statusInt = parseInt(transactions[0].transactionState);    //~~~~~~~~~~~~~~~~~小编这里确保了每次只会有一个订单因此这个返回的票据数组只有一个,所以直接取数组的第一个,如果你有多个返回的票据,这里需要自行更改,以适应自己的逻辑

				switch (statusInt) {	
					case 1:
						// 用户已付款但未关闭,在此处请求开发者服务器,在服务器端请求苹果服务器验证票据
						//获取当前用户充值的订单号和充值金额
						let resVerifyData = await reqPost("getVerifyData",);
						console.log(resVerifyData)
						if(resVerifyData['data']['close_order'] === true){		//金额都修改好了,可以直接关闭订单
							await this._iap.finishTransaction(transactions[0]);
							uni.showModal({
							    title: "success",
							    content: "关闭订单完成,请重新拉起订单...",
							    showCancel: false
							});
						}else{		//票据校验时间太长,数据没有修改。需要重新校验
							// 在此处请求开发者服务器,在服务器端请求苹果服务器验证票据
							const requestVerify = {
								orderId: resVerifyData['data']['order_no'],
								money: resVerifyData['data']['money'],
								transaction: transactions[0],
								transactionReceipt: transactions[0].transactionReceipt
							};
							console.log(requestVerify)
							let verifyRes = await reqPost("iosVerify",requestVerify);
							console.log(verifyRes)
							
							//判断校验结果
							if (verifyRes["data"]["code"] === 401) {
								// 验证成功后关闭订单
								await this._iap.finishTransaction(transactions[0]);
								uni.showModal({
								    title: "success",
								    content: verifyRes['data']['message'],
								    showCancel: false
								});
							} else {
								// uni.showToast('支付失败')
								uni.showModal({
								    title: "failed",
								    content: verifyRes['data']['message'],
								    showCancel: false
								});
							}
						}
						
						break;
					case 2:
						// 关闭未支付的订单
						console.log("正在关闭未支付的订单")
						await this._iap.finishTransaction(transactions[0]);
						uni.showModal({
						    title: "success",
						    content: "关闭未支付订单成功!请重新拉起支付...",
						    showCancel: false
						});
						break;
					default:
						break;
				}
			} catch (e) {
				console.log(e)
				uni.showModal({
					content: e.message,
					showCancel: false
				});
			} finally {
				uni.hideLoading();
			}
		}

		async payment() {

		// 请求苹果支付
		let transaction;
		
			console.log(this.loading)
			
			if (this.loading == true) {
				console.log(this.loading)
				return;
			}

			this.loading = true;
			console.log(this.loading)

			uni.showLoading({
				title: '支付处理中...'
			});

			
			try {

				// 从开发者服务器创建订单
				var orderId = '';
				const requestData = {
				    // data: {
				        money: this.productItem.price
				    // }
				};
				await reqPost("applePay",requestData)
				.then(res=>{
					console.log(res)
					orderId = res.data.order_no
				})
				console.log(orderId)
				console.log("--------请求获取订单号完成--------")
				
				transaction = await this._iap.requestPayment({
					productid: this.productItem.productid,
					manualFinishTransaction: true,
					orderId: orderId,
					username: this.username, //根据业务需求透传参数,关联用户和订单关系
				});
				console.log(transaction)
				console.log("--------请求支付完成--------")

				// 在此处请求开发者服务器,在服务器端请求苹果服务器验证票据 
				const requestVerify = {
					orderId: orderId,
					money: this.productItem.price,
					transaction: transaction,
					transactionReceipt: transaction.transactionReceipt
				};
				let verifyRes = await reqPost("iosVerify",requestVerify);
				console.log(verifyRes)

				if (verifyRes["data"]["code"] === 401) {
					// 验证成功后关闭订单
					await this._iap.finishTransaction(transaction);
					uni.showModal({
					    title: "success",
					    content: "购买成功,请返回刷新余额!",
					    showCancel: false
					});
				} else {
					// uni.showToast('支付失败')
					uni.showModal({
					    title: "failed",
					    content: verifyRes['data']['message'],
					    showCancel: false
					});
				}

				// 支付成功
			} catch (e) {
				console.log(e)
				this._iap.finishTransaction(transaction);

				if (e.errCode == 2) {
					uni.showModal({
					    title: "failed",
					    content: "取消支付",
					    showCancel: false
					});
					return false;
				}
				uni.showModal({
				    title: "failed",
				    content: e.message,
				    showCancel: false
				});
			} finally {
				this.loading = false;
				uni.hideLoading();
			}

		}


}

export {
	realize
}

3.2:服务器端的代码都有哪些:

/**
 * 后端有这四个方法
 *1、applePay()方法用来创建订单记录,将订单号返回给app端,前端只传过来一个money代表金额

 *2、iosVerify()验证app端传过来的票据,前端传票据,支付后返回的结果,订单id,充值金额
 *3、getVerifyData()从数据库中获取该用户最近的一笔订单的订单号和金额,前端无传值
 *4、iosVerifyTickets()ios验证票据,iosVerify方法调用的,无需修改
 *5、小编提示的地方都会在注释后添加“~~~~~~~~~~~~~~~~~~~~~~~~~~~”,可以着重看一看
 */  

  /**
	 * 苹果内购创建订单号
	 */
    public function 
    {
        if ($this->request->isPost()) {
            $params["user_id"] = $this->auth->id;
            $params["money"] =$this->request->post("money");
            xxxxxxxxx...// ~~~~~~~~~~~~~根据自己的表中的订单创建有哪些字段自行添加            $params["order_no"]=order_no();
            Db::startTrans();
            try {
                if(empty($params['user_id']) || !is_numeric($params['money'])){
                    throw new \think\Exception('参数错误!', 100006);
                }
                $params["after"] =$params["before"]+$params["money"];
                //添加
                xxxxxxxxxxxxxxxxx // ~~~~~~~~~~~~~~~~~~自行插入订单记录
                if(!$result){
                    throw new \think\Exception('操作异常,稍后重试!', 100006);
                }
                Db::commit();
            }catch (\think\Exception $e) {
                Db::rollback();
                $this->error($e->getMessage());
            }
            if ($result !== false) {
                $this->success("success",['order_no' => $params["order_no"]]);
            } else {
                $this->error("网络异常,请重试!");
            }
        }
        return $this->error('post请求');
    }
		/**
		* 苹果订单验证
		* 验证返回的状态码
		* 0     验证成功
		* 21000 App Store不能读取你提供的JSON对象
		* 21002 receipt-data域的数据有问题
		* 21003 receipt无法通过验证
		* 21004 提供的shared secret不匹配你账号中的shared secret
		* 21005 receipt服务器当前不可用
		* 21006 receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送
		* 21007 receipt是Sandbox receipt,但却发送至生产系统的验证服务
		* 21008 receipt是生产receipt,但却发送至Sandbox环境的验证服务
		*/
		public function iosVerify()
		{
			$params["user_id"] = $this->auth->id;	//用户id
			$params["money"] = $this->request->post("money");	//充值金额
			$transaction = $this->request->post("transaction/a");	//支付结果
	        $receipt = $this->request->post("transactionReceipt"); // 票据
			$orderId = $this->request->post("orderId"); // 当前交易的订单id
	        // 返回信息定义
	        $resultMsg = [
	            'code' => 400,
	            'message' => '支付验证失败',
				'result' => '',
	        ];
	        // 验证票据结果
	        $result = $this->iosVerifyTickets($receipt);
	        // 沙盒模式
	        if ($result['status'] == 21007) {
	            $result = $this->iosVerifyTickets($receipt, true);
	        }
			
			// // 设置超时时间为1秒
			// $timeout_seconds = 1;
			// // ~~~~~~~~~~~~~~~~~~~~~此处苹果服务器长时间不回复导致订单失败的情况。模拟延迟,使请求超时
			// sleep($timeout_seconds + 1);	
			// // 请求超时,将 $result 设置为空
			// $result = null;
			
	        if (!is_array($result)) {//大概率是超时
	            $resultMsg['code'] = 403;
				$resultMsg['message'] = '支付验证超时,请重新拉起支付验证本次结果...';
	        }
	        if ($result['status'] == 0) {//验证成功
				$resultMsg['result'] = $result;	//返回校验结果
	            //当订购一个套餐后再次订购此套餐可能会出现这种情况,非常规操作
	            if (empty($transaction)) {
	                $resultMsg['code'] = 402;
	            }else{
						$resultMsg['code'] = 401;
						$resultMsg['message'] = '支付验证成功';
						Db::startTrans(); // 开启事务
						try {
						    // 更新订单信息
							$res=Db::table('xxxxxxx')->where(['order_no'=>$orderId,'status'=>0])->find();//~~~~~~~~~~~~找到订单记录							if($res)
							{
							    Db::table('xxxxxxx')->where(['order_no'=>$orderId,'status'=>0])->update(["status"=>1,"updatetime"=>time()]);//~~~~~~~~~~~~~~~~~修改为已支付
							    Db::table('xxxxxxx').....//~~~~~~~~~~~~~~发放购买内容
							}
						    Db::commit(); // 提交事务
						} catch (Exception $e) {
							Db::rollback(); // 事务回退
						}
					// }
				}
	        } else {
	            $resultMsg['code'] = 400;
	        }
	        // return response()->json($resultMsg);
			$this->success("success",$resultMsg);
	    }
		/**
		 * 已支付但未关闭的订单从表中获取必要数据   order_no money
		 */
		public function getVerifyData()
		{
		    if ($this->request->isPost()) {
		        $params["user_id"] = $this->auth->id;
		        try {
		                    // ~~~~~~~~~~~~~~~~~~~~~获取最大ID对应的订单号和充值金额
		                    $result = Db::table("xxxxxxx")
		                        ->where('user_id', $params["user_id"])
		                        ->order('id', 'desc')
		                        ->limit(1)
		                        ->find();
		        
		                    if (!$result) {
		                        throw new \think\Exception('未找到相关订单信息!', 100006);
		                    }
							if($result['status'] == 1){	//~~~~~~~~~~数据库判断订单状态的字段,根据自己的修改。余额加上了,直接关闭订单
								$this->success("success", [
								    'close_order' => true // 设置关闭订单标志为true
								]);
							}
							if($result['status'] == 0){ //~~~~~~~~~~~~~~~~数据库判断订单状态的字段,根据自己的修改。余额没加上,返回订单号和充值金额重新进行校验
								$this->success("success", [
									'close_order' => false, // 设置关闭订单标志为false
								    'order_no' => $result['order_no'],
								    'money' => $result['money']
								]);
							}
							
							
		                } catch (\think\Exception $e) {
		                    $this->error($e->getMessage());
		                }
		    }
		    return $this->error('post请求');
		}
		/**
	     * ios验证票据
	     * @param string $receipt
	     * @param false $sandbox
	     * @return array|int|mixed
	     * @throws Exception
	     */
	    protected function iosVerifyTickets(string $receipt, bool $sandbox = false)
	    {
	        if ($sandbox) {
	            $url = 'https://sandbox.itunes.apple.com/verifyReceipt'; // 测试环境
	        } else {
	            $url = 'https://buy.itunes.apple.com/verifyReceipt'; // 正式环境
	        }
	        $params = json_encode(array("receipt-data" => $receipt));
	        $curl = curl_init($url);
	        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	        curl_setopt($curl, CURLOPT_POST, 1);
	        curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
	        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
	        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
	        $data = curl_exec($curl);
	        $errno = curl_errno($curl);
	        curl_close($curl);
	        $data = json_decode($data, true);
	        if (!is_array($data)) { // 开发过程中经常遇到curl 35错误码,或者28超时
	            return $errno;
	        }
	        return $data;
	    }
    

4、其他内容:

说明:
    由于当前这个app需要加的内容是余额充值和会员的购买,所以小编将上面的内容写成了两份 ,每份的产品id不一样,但逻辑是大致相同的,小伙伴可以根据自己情况进行开发。
    会员的购买小编使用了非续期订阅的方式,如果是自动续期的好像还和这种有些地方不同,有需要的小伙伴可以根据自己情况继续研究。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/640883.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

彩信JSON接口对接发送

随着通讯技术的飞速发展,传统的短信已经无法满足人们日益增长的沟通需求。在这样的背景下,群发彩信作为一种更为先进、更为丰富的信息传递方式,逐渐受到了企业和个人的青睐。那么,群发彩信应该怎么对接,又具体有哪些优…

经常碰到的20个等待事件

经常碰到的20个等待事件 oracle等待事件简介 DBA团队维护的部分应用运行在oracle数据库平台,为及时了解数据库的运行情况,需要建立涵盖各个维度的监控体系,包括实例状态、空间使用率、ORA错误等数十项监控指标。这其中有一个有效判断数据库…

Parasoft C++Test软件静态分析操作指南_软件质量度量

系列文章目录 Parasoft CTest软件安装指南 Parasoft CTest软件静态分析操作指南_编码规范/标准检查 Parasoft CTest软件静态分析操作指南_软件质量度量 Parasoft CTest软件静态分析_自动提取静态分析数据生成文档 Parasoft CTest软件单元测试_操作指南 Parasoft CTest软件单元…

Mqtt_Java_IDEA中编写“发布者”和“订阅者”

1Java创建项目 2导入依赖 将下面Mqtt的库名复制到 <dependencies> 下面 <dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse.paho.client.mqttv3</artifactId><version>1.2.5</version></d…

20212416 2023-2024-2 《移动平台开发与实践》第5次作业

百度地图应用 1.实验内容2.实验过程2.1 Android Studio配置2.1. 创建一个Android项目2.2 在项目中本地集成BaiduMap SDK 2.2 编写代码2.2.1 配置AndroidManifest.xml文件2.2.2 编写UI界面布局文件2.2.3 编写主函数代码2.2.4 运行结果 3.学习中遇到的问题及解决4.学习感悟与思考…

netdiscover一键收集子网内的所有信息(KALI工具系列六)

目录 1、KALI LINUX简介 2、netdiscover工具简介 3、在KALI中使用netdiscover 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、命令示例 4.1 扫描子网整个网段 4.2 指定网卡进行扫描 4.3 扫描网卡的公共网络 4.4 快速扫描网卡的公共lan地址 4.5 设置…

网络拓扑—DHCP服务配置

文章目录 DHCP服务搭建相关配置细节前提安装DHCP服务 DHCP服务搭建 相关配置细节前提 系统&#xff1a;Windows Server 2003 IP网段&#xff1a;10.0.0.0/24 三台机子&#xff1a; 普通PC机 DHCP服务器 路由器&#xff08;两块网卡&#xff0c;连接内外网&#xff09; //注…

Java进阶学习笔记6——继承的介绍

继承的学习目标&#xff1a; 认识继承&#xff1b; 继承的好处、应用场景 什么是继承&#xff1f; Java中提供了一个关键字extends&#xff0c;用这个关键字&#xff0c;可以让一个类和另外一个类建立父子关系。 继承的特点: 子类能继承父类的非私有成员&#xff08;成员变…

利用sql注入对某非法网站的渗透

本文仅用于技术讨论&#xff0c;切勿用于违法途径&#xff0c;且行且珍惜&#xff0c; 所有非经授权的渗透&#xff0c;都是违法行为 前言 这段时间一直在捣鼓sql注入&#xff0c;最近又通过一个sql注入点&#xff0c;成功进入某个非法网站的后台&#xff0c;拿到整个网站的…

mac版本Phpstudy本地环境安装Discuz教程【2024】

此方法适用于m1版本的mac版本Phpstudy本地环境安装Discuz&#xff0c;当然同样使用更高版本的mac端。网上各种安装教程参差不齐&#xff0c;根本解决不了小白的入门需求&#xff0c;以下是最新且直接明了的安装教程。 Phpstudy本地环境安装Discuz教程&#xff1a; 1、安装Phps…

渗透测试 一个很奇怪的支付漏洞

新手实战刷课网站、好玩又有趣&#xff01; 第一步 打开网站、任意账户名密码登陆发现验证码可重复利用 这时候我们可以试试admin账号、发现如果账号正确会提示账户已存在、反之回显账户密码错误 第二步 既然验证码可以重复利用&#xff1b;而且账号名有回显 这时候我们试…

安装harbor出现问题: Running 1/1 ✘ Network harbor_harbor Error

安装harbor出现问题&#xff1a; [] Running 1/1 ✘ Network harbor_harbor Error 0.2s failed to create network harbor_harbor: Error response from daemon: Fa…

数据结构和算法基础(一)

数据结构——基本概念 数据&#xff1a;数据是信息的载体&#xff0c;是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。数据是计算机程序加工的原料。 数据元素、数据项&#xff1a;数据元素是数据的基本单位&#xff0c;通常作为…

Ansible实战YAML语言完成apache的部署,配置,启动全过程

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f3dd;️Ansible专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年5月24日15点59分 目录 &#x1f4af;趣站推荐&#x1f4af; &#x1f38a;前言 ✨️YAML语言回顾 &#x1f386;1.编写YAML文…

嵌入式作业5

在函数main.c中初始化三种颜色的灯&#xff1a; gpio_init(LIGHT_BLUE, GPIO_OUTPUT, LIGHT_OFF); //蓝灯 gpio_init(LIGHT_GREEN, GPIO_OUTPUT, LIGHT_OFF); //绿灯 gpio_init(LIGHT_RED, GPIO_OUTPUT, LIGHT_OFF); //红灯 同时为了响应以上修改&#xff0c;进入isr。c…

低代码开发:拖拽式可视化构建工业物联网系统

什么是低代码&#xff1f; 低代码(Low Code)是一种可视化的软件开发方法&#xff0c;通过最少的手动编码可以更快地交付应用程序。低代码平台的图形用户界面和拖放功能可自动执行开发过程的各个方面&#xff0c;从而消除对传统计算机编程方法的依赖。 什么是低代码平台&#…

远程桌面连接不上远程服务器,如何有效的解决远程桌面连接不上远程服务器的问题

在解决远程桌面连接不上远程服务器的问题时&#xff0c;我们需要采取一种专业且系统的方法来排查和修复可能存在的故障。以下是一些建议和步骤&#xff0c;帮助用户更有效地解决此类问题。 首先&#xff0c;用户需要确认远程服务器的状态和网络连接是否正常。 这包括检查服务器…

一键批量提取TXT文档前N行,高效处理海量文本数据,省时省力新方案!

大量的文本信息充斥着我们的工作与生活。无论是研究资料、项目文档还是市场报告&#xff0c;TXT文本文档都是我们获取和整理信息的重要来源。然而&#xff0c;面对成百上千个TXT文档&#xff0c;如何快速提取所需的关键信息&#xff0c;提高工作效率&#xff0c;成为了许多人头…

014_C标准库函数之<stdio.h>

【背景】 今天这个主要说的是<stdio.h>头文件&#xff0c;大家众所周知&#xff0c;这个是我们学习C语言时第一个接触到的头文件了&#xff0c;那么为什么我不一开始就介绍这个头文件呢&#xff1f;我觉得有两个原因&#xff0c;如下&#xff1a; 1.一开始大家的编程思…

微信支付篇---用户支付订单

微信支付篇—用户支付订单 创建PayOrderForm.java Data ApiModel(value "订单付款的表单") public class PayOrderForm {ApiModelProperty(value "订单ID")Min(1)private Integer orderId;}编写WxController中的创建付款方法&#xff0c;用于创建支付订…