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

news2024/11/5 22:41:27
 前言:
    公司的项目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不一样,但逻辑是大致相同的,小伙伴可以根据自己情况进行开发。
    会员的购买小编使用了非续期订阅的方式,如果是自动续期的好像还和这种有些地方不同,有需要的小伙伴可以根据自己情况继续研究。

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nature Communications | 柔性半导体器件的大规模集成(柔性半导体器件/柔性电子)

韩国中央大学Sung Kyu Park、 韩国成均馆大学Jong-Woong Kim和Yong-Hoon Kim团队,在《Nature Communications》上发布了一篇题为“Full integration of highly stretchable inorganic transistors and circuits within molecular-tailored elastic substrates on a large scal…

QGraphicsView实现简易地图16『爆炸效果』

前文链接:QGraphicsView实现简易地图15『测量面积』 一种简单的爆炸波扩散效果 动态演示效果: 静态展示图片: 核心代码: #pragma once #include "../AbstractGeoItem.h" #include "DataStruct/GeoData.h"…

C++ STL 函数对象:隐藏的陷阱,如何避免状态带来的麻烦?

STL 函数对象:无状态即无压力 一、简介二、函数对象三、避免在函数对象中保存状态3.1、函数对象3.2、lambda 表达式 四、选择合适的更高层次的结构五、总结 一、简介 在使用 C 标准模板库 (STL) 时,函数对象 (Function Object) 是一种强大的工具&#x…

我的文章分类合集目录

文章目录 Java相关基础常规问题类Docker类RabbitMQ类分库分表 网络工程相关路由交换、Cisco Packet TracerIP地址 前端相关数据库 Java相关 基础 Java开发规范、项目开发流程 SpringBoot整合MyBatis实现增删改查(简单,详细) SpringBoot整合MybatisPlus(详细&#…

C++ vector类

目录 0.前言 1.vector介绍 2.vector使用 2.1 构造函数(Constructor) 2.1.1. 默认构造函数 (Default Constructor) 2.1.2 填充构造函数 (Fill Constructor) 2.1.3 范围构造函数 (Range Constructor) 2.1.4 拷贝构造函数 (Copy Constructor) 2.2 迭代器(Iterator) 2.2.…

基于移动多媒体信源与信道编码调研

前言 移动多媒体是指在移动通信环境下,通过无线网络传输的音频、视频、图像等多种媒体信息。移动多媒体的特点是数据量大、传输速率高、服务质量要求高,因此对信源编码和信道编码的性能提出了更高的要求。 本文对进3年的移动多媒体信源与信道编码的研究…

信息系统项目管理师0130:工具与技术(8项目整合管理—8.7监控项目工作—8.7.2工具与技术)

点击查看专栏目录 文章目录 8.7.2 工具与技术8.7.2 工具与技术 专家判断监控项目工作过程中,应征求具备如下领域相关专业知识或接受过相关培训的个人或小组的意见,涉及的领域包括:挣值分析;数据的解释和情境化;持续时间和成本的估算技术;趋势分析;关于项目所在的行业以及…

爬虫案例-亚马逊反爬流程分析梳理(验证码突破)(x-amz-captcha)

总体概览:核心主要是需要突破该网站的验证码,成功后会返回我们需要的参数后再去请求一个中间页(类似在后台注册一个session),最后需要注意一下 IP 是不能随意切换的 主要难点: 1、梳理整体反爬流程 2、验证…

家政预约小程序01用户注册

目录 1 创建数据源2 创建应用3 创建页面4 用户注册5 角色选择6 设置首页总结 学习低代码的时候,使用官方模板搭建无疑是一个很好的途径。但是低代码工具更新比较频繁,基本每两周就要迭代一个版本。随着官方版本的迭代,官方模板安装好之后会有…

联想端游联运SDK接入指南

1. 接入流程 本文档主要介绍了 联想PC游戏SDK接入流程、联想游戏提供的功能、接入注意事项等。 1.1. 接入方式 1. 联想游戏SDK2.1版本支持“账号防沉迷支付”接入方式; a. 联想提供账号注册、登录等能力 b. 联想提供防沉迷服务 c. 联想提供游戏内支付 1.2. 对…

使用LoRA进行高效微调:基本原理

Using LoRA for efficient fine-tuning: Fundamental principles — ROCm Blogs (amd.com) [2106.09685] LoRA: Low-Rank Adaptation of Large Language Models (arxiv.org) Parametrizations Tutorial — PyTorch Tutorials 2.3.0cu121 documentation 大型语言模型&#xf…

Boyer-Moore投票算法

摩尔投票法,又称为博耶-摩尔多数投票算法,是一种用于在一组数据中寻找多数元素(出现次数超过一半的元素)的算法。该算法的效率非常高,时间复杂度为O(n),空间复杂度为O(1),适合处理大数据量的情况。 步骤 首先定义两个…

JSONP原理及应用实例

JSONP是什么 JSONP&#xff08;JSON with Padding&#xff09;是一种跨域数据请求技术&#xff0c;它允许网页在不受同源策略限制的情况下从其他域中请求数据。JSONP的原理是利用 <script> 标签的跨域特性&#xff0c;通过 <script> 标签&#xff0c;指向包含 JSO…

通过继承React.Component创建React组件-5

在React中&#xff0c;V16版本之前有三种方式创建组件&#xff08;createClass() 被删除了)&#xff0c;之后只有两种方式创建组件。这两种方式的组件创建方式效果基本相同&#xff0c;但还是有一些区别&#xff0c;这两种方法在体如下&#xff1a; 本节先了解下用extnds Reac…

vue+elemntui 加减表单框功能样式

<el-form ref"form" :model"form" :rules"rules" label-width"80px"><el-form-item label"配置时间" prop"currentAllocationDate"><div v-for"(item,key) in timeList"><el-date…

ROCm上来自Transformers的双向编码器表示(BERT)

14.8. 来自Transformers的双向编码器表示&#xff08;BERT&#xff09; — 动手学深度学习 2.0.0 documentation (d2l.ai) 代码 import torch from torch import nn from d2l import torch as d2l#save def get_tokens_and_segments(tokens_a, tokens_bNone):""&qu…

Cortex-M3的SysTick 定时器

目录 概述 1 SysTick 定时器 1.1 SysTick 定时器功能介绍 1.2 SysTick 定时器功能实现 1.3 SysTick在系统中的作用 2 SysTick应用的实例 2.1 建立异常服务例程 2.2 使能异常 2.3 闹钟功能 2.4 重定位向量表 2.5 消灭二次触发 3 SysTick在FreeRTOS中的应用 3.1 STM…

(完全解决)Python字典dict如何由键key索引转化为点.dot索引

文章目录 背景解决方案基础版升级版 背景 For example, instead of writing mydict[‘val’], I’d like to write mydict.val. 解决方案 基础版 I’ve always kept this around in a util file. You can use it as a mixin on your own classes too. class dotdict(dict)…

如何进行异地多地兼容组网设置?

跨地区工作、远程办公和异地合作已成为常态。由于网络限制和安全性要求&#xff0c;远程连接仍然是一个具有挑战性的问题。为了解决这一难题&#xff0c;各行各业都在寻找一种能在异地多地兼容的组网设置方案。本文将着重介绍基于【天联】的组网解决方案&#xff0c;探讨其操作…

SpringBoot——整合Thymeleaf模板

目录 模板引擎 新建一个SpringBoot项目 pom.xml application.properties Book BookController bookList.html ​编辑 项目总结 模板引擎 模板引擎是为了用户界面与业务数据分离而产生的&#xff0c;可以生成特定格式的页面在Java中&#xff0c;主要的模板引擎有JSP&…