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; // 当前选择充值项idloading = 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.productItemthis.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.rhkb.cn/news/336555.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nacos 2.x 系列【12】配置加密插件

文章目录 1. 前言2. 安装插件2.1 编译2.2 客户端2.3 服务端 3. 测试 1. 前言 为保证用户敏感配置数据的安全,Nacos提供了配置加密的新特性。降低了用户使用的风险,也不需要再对配置进行单独的加密处理。 前提条件: 版本:老版本暂时不兼容&…

网络工程基础 不同网段下的设备实现通信

交换机可以实现同一个网段下的不同设备直接通信 路由器可以实现不同的网段下的设备进行通信 路由器查看路由表命令 display ip routing-table 华为路由器配置静态路由命令: ip route-static 目的网络地址 子网掩码 下一跳地址 电脑判断不同网段的ip会把请求转给网…

30【Aseprite 作图】桌子——拆解

1 桌子只要画左上方,竖着5,斜着3个1,斜着两个2,斜着2个3,斜着一个5,斜着一个很长的 然后左右翻转 再上下翻转 在桌子腿部分,竖着三个直线,左右都是斜线;这是横着水平线不…

网络模型—BIO、NIO、IO多路复用、信号驱动IO、异步IO

一、用户空间和内核空间 以Linux系统为例,ubuntu和CentOS是Linux的两种比较常见的发行版,任何Linux发行版,其系统内核都是Linux。我们在发行版上操作应用,如Redis、Mysql等其实是无法直接执行访问计算机硬件(如cpu,内存…

RestTemplet 自定义消息转换器总结

一、请求流程 在RestTemplet 请求中,请求发送一个 HTTP 请求时,RestTemplet 会根据请求中的内容类型(Content-Type)选择合适的 HttpMessageConverter 来处理请求体的数据。同样地,当服务器返回一个 HTTP 响应时&#…

每天写两道(二)LRU缓存、数组中最大的第k个元素

146.LRU 缓存 . - 力扣(LeetCode) 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存…

【教程】Linux 安装 kkFileView 文档在线预览项目 及优化

【教程】Linux 安装 kkFileView 文档在线预览项目 官网 kkFileView - 在线文件预览 (keking.cn) 安装包 可以直接下载成品 也可以下载source 源码 自己编译 kkFileView 发行版 - Gitee.com 打开IDEA 然后先clear 再install 然后在 file-online-preview\server\target 目录…

canfd与can2.0关系

canfd是can2.0的升级版, 支持canfd的设备就支持can2.0,但can2.0的设备不支持canfd 参考 是选CAN接口卡还是CANFD接口卡_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Hh411K7Zn/?spm_id_from333.999.0.0 哪些STM32有CANFD外设 STM32G0, STM…

pycharm连接阿里云服务器过程记录

因为不想用自己的电脑安装anaconda环境,所以去查了一下怎么用服务器跑代码,试着用pycharm连接阿里云服务器,参考了很多博客,自己简单配置了一下,记录一下目前完成的流程. 主要是:阿里云服务器的远程登录和安装anaconda,以及怎么用pycharm连接阿里云服务器上的解释器. 小白刚开始…

Unity开发——XLua热更新之Hotfix配置(包含xlua获取与导入)

一、Git上获取xlua 最新的xlua包,下载地址链接:https://github.com/Tencent/xLua 二、Unity添加xlua 解压xlua压缩包后,将xlua里的Assets里的文件直接复制进Unity的Assets文件夹下。 成功导入后,unity工具栏会出现xlua选项。 …

【国产中颖】SH79F9202U单片机驱动LCD段码液晶学习笔记

1. 引言 因新公司之前液晶数显表产品单片机一直用的是 C51单片机(SH79F9202U9),本人之前没有接触过这款单片机,为了维护老产品不得不重新研究研究这款单片机。 10位ADC LCD的增强型8051微控制器 SH79F9202是一种高速高效率8051可兼容单片机。在同样振…

QT7_视频知识点笔记_67_项目练习(页面以及对话框的切换,自定义数据类型,DB数据库类的自定义及使用)

视频项目:7----汽车销售管理系统(登录,品牌车管理,新车入库,销售统计图表)-----项目视频没有,代码也不全,更改项目练习:学生信息管理系统。 学生信息管理系统&#xff1…

【小技巧】Keil C51 报错“*** ERROR L107: ADDRESS SPACE OVERFLOW****

软件:Keil C51 C51V961版本 电脑:Win10 报错提示: compiling System.c... linking... *** ERROR L107: ADDRESS SPACE OVERFLOW SPACE: DATA SEGMENT: ?DT?LCD LENGTH: 0034H Program Size: data174.0 xdata17 code1205 Target not create…

VMware安装Ubuntu系统(超详细)

一.Ubuntu官网下载镜像 Ubuntu官网:Enterprise Open Source and Linux | Ubuntu 二.安装Ubuntu系统 选择文件->创建虚拟机新建虚拟机(ControlN),这里直接选择典型即可 选择稍后安装系统 选择linux Ubuntu 64位 填写虚拟机名称…

【机器学习】支持向量机(SVM)

一、概述 支持向量机(Support Vector Machine,简称SVM)是一种对数据进行二分类的广义线性分类器,是一种监督学习算法,其决策边界是对学习样本求解的最大边距超平面。 SVM使用铰链损失函数计算经验风险并在求解系统中…

什么叫USDT(泰达币)的前世今生!

一、引言 在数字货币的世界里,USDT(Tether USDT)以其独特的稳定机制,成为了连接传统金融市场与加密货币市场的桥梁。本文将带您了解USDT的诞生背景、发展历程、技术特点以及未来展望。 二、USDT的诞生背景 USDT是Tether公司推出…

关于 Spring 是什么

Spring 是什么 我们通常所说的 Spring 指的是 Spring Framework(Spring 框架),它是⼀个开源框架,有着活跃⽽庞⼤的社区,这就是它之所以能⻓久不衰的原因。Spring ⽀持⼴泛的应⽤场景,它可以让 Java 企业级的…

gitlab 创建 ssh 和 token

文章目录 一、创建ssh key二、将密钥内容复制到gitlab三、创建token 一、创建ssh key 打开控制台cmd,执行命令 ssh-keygen -t rsa -C xxxxx xxxxx是你自己的邮箱 C:\Users\xx\.ssh 目录下会创建一个名为id_rsa.pub的文件,用记事本打开,并…

Vue3解决“找不到模块“@/components/xxx.vue”或其相应的类型声明”

文章目录 前言背景问题描述解决方案总结 前言 在使用 Vue 3 开发项目时,遇到“找不到模块 ‘/components/xxx.vue’ 或其相应的类型声明”的错误是一个常见问题。这通常与 TypeScript 和模块解析相关的配置不当有关。本文将详细介绍如何解决此问题,确保…

XDebug配置极简教程,phpstorm实现http请求断点调试

写这篇的文章的初衷:网络上配置XDebug的文章有很多,XDebug也有官方的文档, PhpStorm也有官方的文档,为什么还要写那? 相信不少人,都有一种感觉,虽然教程很多,但是按教程走一遍,自己的确不能正常调试。 问题出在下面几个方面: 1. 对调试过程中,没有一定的认识,因此…