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

uniapp+php服务端实现苹果iap内购的消耗性项目和非续期订阅项目,前后端代码加逻辑分析
前言:公司的项目app在上架苹果商店时发现人家要求里面的部分购买项目必须使用iap购买的方式,使用原本的微信支付方式审核不给通过,无奈只能重新研究这个东西。做起来还是有点麻烦,主要是网上的文章很少,不能直接硬抄。…...

【代码随想录】【算法训练营】【第11天】 [20]有效的括号 [1047]删除字符串中的所有相邻重复项 [150]逆波兰表达式求值
前言 思路及算法思维,指路 代码随想录。 题目来自 LeetCode。 day 11,周六,又开始变的困难了~ 题目详情 [20] 有效的括号 题目描述 20 有效的括号 解题思路 前提:括号匹配 思路:利用栈的后入先出特性…...
vue实现图片懒加载
在src中创建一个directives文件夹在里面创建一个lazy.js文件 在main.js中引入 import lazy from ./directives/lazy app.directive(lazy, lazy) 在app.vue中 <script setup lang"ts"> import { RouterLink, RouterView } from vue-router import HelloWorl…...

Python | Leetcode Python题解之第101题对称二叉树
题目: 题解: class Solution:# 在【100. 相同的树】的基础上稍加改动def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:if p is None or q is None:return p is qreturn p.val q.val and self.isSameTree(p.left, q.ri…...
周报5.20~5.26
学习内容: 主要了解了Qt的信号和槽、ui页面布局、各类常见控件的使用、绘图事件以及文件操作的相关知识,并且完成相关案例的设计。练习代码了解了多层感知机、激活函数、多项式回归、高维线性回归、暂退法、分布偏移、深度学习计算等相关知识与代码案例…...

RDP方式连接服务器上传文件方法
随笔 目录 1. RDP 连接服务器 2. 为避免rdp 访问界面文字不清晰 3. 本地上传文件到服务器 1. RDP 连接服务器 # mstsc 连接服务器step1: 输入mstscstep2: 输入 IP, username, passwd 2. 为避免rdp 访问界面文字不清晰 解决方法: 3. 本地上传文件到服务器 step…...

网络信息安全
目录 引言 网络信息安全的基本概念 定义 主要目标 网络信息安全的范围 主要威胁 恶意软件 黑客攻击 拒绝服务攻击 社交工程 内部威胁 常用技术和防护措施 加密技术 防火墙 入侵检测和防御系统 访问控制 多因素认证 安全审计和监控 安全培训和意识提升 未来发…...

java中的对象
文章目录 一、对象的创建过程1.1 检查加载1.2 分配内存1.3 内存空间初始化1.4 设置对象头1.5 对象初始化 二、对象内存布局2.1 布局解析2.2 JOL使用 三、对象的访问定位3.1 句柄访问3.2 直接指针 四、对象的分配策略4.1 栈上分配4.2 优先分配到Eden区4.3 大对象直接进入老年代4…...
【MySQL精通之路】MySQL-环境变量
本节列出了MySQL直接或间接使用的环境变量。 其中大部分也可以在本手册的其他地方找到。 命令行上的选项优先于选项文件和环境变量中指定的值,选项文件中的值优先于环境变量中的值。 在许多情况下,最好使用配置文件而不是环境变量来修改MySQL的行为。…...
Day42 最后一块石头的重量Ⅱ + 目标和 + 一和零
1049 最后一块石头的重量Ⅱ 题目链接:1049.最后一块石头的重量Ⅱ 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和…...

01.爬虫---初识网络爬虫
01.初识网络爬虫 1.什么是网络爬虫2.网络爬虫的类型3.网络爬虫的工作原理4.网络爬虫的应用场景5.网络爬虫的挑战与应对策略6.爬虫的合法性总结 1.什么是网络爬虫 网络爬虫,亦称网络蜘蛛或网络机器人,是一种能够自动地、系统地浏览和收集互联网上信息的程…...

集合、Collection接口特点和常用方法
1、集合介绍 对于保存多个数据使用的是数组,那么数组有不足的地方。比如, 长度开始时必须指定,而且一旦制定,不能更改。 保存的必须为同一类型的元素。 使用数组进行增加/删除元素的示意代码,也就是比较麻烦。 为…...
12. Web开发:介绍Web开发的基本概念,Servlet和JSP的使用,MVC设计模式的应用等。
Web开发的轻松入门之旅 想象一下,Web开发就像是搭建一个在线的小家,你既是设计师,又是建筑师,还是管家。我们一步步来探索这个过程,保证简单易懂,就像搭积木一样有趣! Web开发基础认知 Web开…...

文件系统--inode
文章目录 概述认识磁盘了解磁盘的存储结构对磁盘的存储结构进行逻辑抽象 操作系统对磁盘的使用宏观认识细节认识再谈目录再谈文件的增删 概述 文件有很多,但是被打开的文件很少,这些没有被打开的文件在磁盘中,这就叫做磁盘文件。每次先打开一…...

数据清洗(ETL)案例实操
文章目录 数据清洗(ETL)概述案例需求和分析代码实现和结果分析 数据清洗(ETL)概述 “ETL,是英文Extract-Transform-Load的缩写,用来描述将数据从来源端经过抽取(Extract)、转换&…...
Zookeeper 面试题(一)
1. ZooKeeper 适合哪些应用场景? ZooKeeper 是一个高性能、高可靠的分布式协调系统,它在分布式系统和大数据领域中有着广泛的应用。以下是 ZooKeeper 适合的一些应用场景: 数据发布/订阅:ZooKeeper 可以作为配置中心,…...
怎么安装django特定版本
要安装Django的特定版本,你可以使用Python的包管理工具pip。以下是在命令行中安装Django特定版本的步骤: 确保你的计算机上已经安装了Python和pip。如果没有安装,你可以从Python官方网站下载并安装最新版本的Python,pip通常会随Py…...
关于Broken pipe异常的一点学习记录
什么是Broken pipe? pipe,管道,管道里面自然就是数据,通过指从文件或网络套接字读取的数据。当一个进程试图向一个已关闭的管道(pipe)写数据或者从一个已关闭的通道读数据时就会出现中断,也就是Broken pi…...

第十一课,end关键字、简单while循环嵌套、初识for循环
一,end关键字 end关键字用于在print输出的内容后面声明结束的字符,我们之前学过并且十分了解print是默认输出内容之后跟着换行的,如果我们不希望换行而希望使用其它字符来代替换行,就可以用end关键字来实现 特殊的,en…...

spring boot 集成mongodb
引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId><version>2.2.0.RELEASE</version></dependency>配置db: spring:data:mongodb:host: 127.0.…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...

DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...