当前位置: 首页 > news >正文

OC 技术 苹果内购

一直觉得自己写的不是技术,而是情怀,一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的,希望我的这条路能让你们少走弯路,希望我能帮你们抹去知识的蒙尘,希望我能帮你们理清知识的脉络,希望未来技术之巅上有你们也有我。

OC 技术 苹果内购视频解说

苹果内购(视频讲解的封装)代码下载地址

前言

之前做过内购的需求,然后把整个过程记录下来,以防将来忘记之后回忆起来。

内购流程

在这里插入图片描述

内购代码逻辑

在这里插入图片描述

代码解析

下面的图片是整个苹果内购封装好的方法,可以直接拖过去用的,如果需要修改的就只有ValidationVoucherModel这个类,需要更改为对应公司的接口,里面的代码看多几篇就熟悉了。下面的图片里面,所有类的封装文件都是为IPAPurchase这个类服务的,再开发调用主要就是使用IPAPurchase就可以了
在这里插入图片描述
下面我会根据交易的整个流程解析代码的逻辑

启动监听

首先App启动的时候需要使用单例,初始化内购的监听方法,两个作用:1.每当app进行内购支付的时候,支付成功失败都会走代理的方法。2.app每次启动都回去调用该代理看看有没有没有走完内购的流程的订单。没有就会继续走一遍完成它

class AppDelegate: UIResponder, UIApplicationDelegate {var window: UIWindow?func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {//创建内购监听IPAPurchase.manager()?.startManager()return true}
}

结束监听

app回到后台就移除监听

func applicationDidEnterBackground(_ application: UIApplication) {IPAPurchase.manager()?.stopManager()
}

购买商品的方法

在商品购买的按键方法里面,通过单例直接调用购买的封装方法就可以了

IPAPurchase.manager()?.buyProduct(withProductID: "201912040101", currentBaseUrl: "http://192.168.1.20:8082/", currentAccountID: String(User.default.accountVO.id), currentOrderID: String(), currentVC: self, payResult: { (isSuccess, result, errorMsg) inprint("result = \(String(describing: result))")if isSuccess {print("购买成功")}else{print("购买失败 - \(String(describing: errorMsg))")}
})

接下来下面的代码解析就是根据把支付的方法一个一个点进去深入解析内购的流程,解析整个代码逻辑,拆解代码详细解析

发起购买的内部方法

#pragma mark -- 发起购买的方法
-(void)buyProductWithProductID:(NSString *)productID CurrentBaseUrl:(NSString *)baseUrl CurrentAccountID:(NSString *)accountID currentOrderID:(NSString *)orderID currentVC:(UIViewController *)vc payResult:(PayResult)payResult{//保存产品IDself.productId = productID;//保存baseUrlself.baseUrl = baseUrl;//保存用户IDself.accountID = accountID;//保存订单IDself.orderID = orderID;//保存当前控制器self.vc = vc;//结束上次未完成的交易[self removeAllUncompleteTransactionsBeforeNewPurchase];//绑定闭包self.payResultBlock = payResult;//提示框购买中//如果产品ID为空if (!self.productId.length) {//显示产品ID为空UIAlertController*alet=[UIAlertController alertControllerWithTitle:nil message:@"没有相应的产品" preferredStyle:UIAlertControllerStyleAlert];UIAlertAction*sure=[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil];[alet addAction:sure];[vc presentViewController:alet animated:YES completion:nil];}//检测是否允许内购if ([SKPaymentQueue canMakePayments]) {//向苹果发起内购产品列表[self requestProductInfo:self.productId];}else{//请打开应用程序付费购买功能UIAlertController*alet=[UIAlertController alertControllerWithTitle:nil message:@"请打开应用程序付费购买功能" preferredStyle:UIAlertControllerStyleAlert];UIAlertAction*sure=[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil];[alet addAction:sure];[vc presentViewController:alet animated:YES completion:nil];}}

检测未完成的订单

在购买之前,先看看有没有上一个没有完成的订单,如果有就结束上一次未完成的订单。

//结束上次未完成的交易
[self removeAllUncompleteTransactionsBeforeNewPurchase];#pragma mark -- 结束上次未完成的交易
-(void)removeAllUncompleteTransactionsBeforeNewPurchase{//查看数组中是否有未完成的订单NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;if (transactions.count >= 1) {for (NSInteger count = transactions.count; count > 0; count--) {SKPaymentTransaction* transaction = [transactions objectAtIndex:count-1];if (transaction.transactionState == SKPaymentTransactionStatePurchased||transaction.transactionState == SKPaymentTransactionStateRestored) {[[SKPaymentQueue defaultQueue]finishTransaction:transaction];}}}else{NSLog(@"没有历史未消耗订单");}
}

检测是否允许内购

结束上一次未完成的订单后,检测是否允许内购,向苹果发起获取内购产品列表

//检测是否允许内购
if ([SKPaymentQueue canMakePayments]) {//向苹果发起内购产品列表[self requestProductInfo:self.productId];
}else{//请打开应用程序付费购买功能UIAlertController*alet=[UIAlertController alertControllerWithTitle:nil message:@"请打开应用程序付费购买功能" preferredStyle:UIAlertControllerStyleAlert];UIAlertAction*sure=[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil];[alet addAction:sure];[vc presentViewController:alet animated:YES completion:nil];
}

发起请求

获取代码的列表发送请求的详细代码就是主要是多请求对象准守一下代理

#pragma mark -- 发起购买请求   检索产品  去苹果开发网站查看这个商品是否存在  是否存在通过代理SKProductsRequestDelegate  返回结果
-(void)requestProductInfo:(NSString *)productID{NSArray * productArray = [[NSArray alloc]initWithObjects:productID,nil];NSSet * IDSet = [NSSet setWithArray:productArray];//把产品ID封装成一个SKProductsRequest请求对象request = [[SKProductsRequest alloc] initWithProductIdentifiers:IDSet];//对响应c方法request.delegate = self;//发送商品请求[request start];}

获取商品列表

发起请求之后,产品列表是通过代理的形式返回的,拿到商品id之后就发起支付

#pragma mark -- SKProductsRequestDelegate 查询成功后的回调
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{//把所有的产品检索回来NSArray *myProduct = response.products;    if (myProduct.count == 0) {//提示框,没有商品if (self.payResultBlock) {self.payResultBlock(NO, nil, @"无法获取产品信息,购买失败");}return;}//用于保存产品对象(其中产品ID就在里面)SKProduct * product = nil;//打印所有列表的相关信息for(SKProduct * pro in myProduct){NSLog(@"SKProduct 描述信息%@", [pro description]);NSLog(@"产品标题 %@" , pro.localizedTitle);NSLog(@"产品描述信息: %@" , pro.localizedDescription);NSLog(@"价格: %@" , pro.price);NSLog(@"Product id: %@" , pro.productIdentifier);if ([pro.productIdentifier isEqualToString:self.productId]) {product = pro;break;}}//如果产品不为空if (product) {//把产品添加到支付对象里面SKMutablePayment * payment = [SKMutablePayment paymentWithProduct:product];//使用苹果提供的属性,将平台订单号复制给这个属性作为透传参数payment.applicationUsername = self.orderID;//保存用户ID  目的是防止漏单后,用户登录App登录了别的用户,然后App有漏单,一启动走补单流程,就充错人[[NSUserDefaults standardUserDefaults] setObject:self.accountID forKey:@"unlock_iap_userId"];//调用购买产品接口  购买成功之后会去到updatedTransactions回调[[SKPaymentQueue defaultQueue] addPayment:payment];}else{NSLog(@"没有此商品信息");}
}

支付结果代理回调

支付成功失败都会通过代理的形式在枚举中反应。

#pragma mark -- 监听结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{//当用户购买的操作有结果时,就会触发下面的回调函数,for (SKPaymentTransaction * transaction in transactions) {//根据返回的状态进行处理switch (transaction.transactionState) {//交易完成case SKPaymentTransactionStatePurchased:{[self completeTransaction:transaction];}break;//交易失败case SKPaymentTransactionStateFailed:{[self failedTransaction:transaction];}break;//已经购买过该商品case SKPaymentTransactionStateRestored:{[self restoreTransaction:transaction];}break;//正在购买中...case SKPaymentTransactionStatePurchasing:{NSLog(@"正在购买中...");//[[SKPaymentQueue defaultQueue]finishTransaction:transaction];(会蹦)}break;//最终状态未确定case SKPaymentTransactionStateDeferred:{NSLog(@"最终状态未确定");}break;default:break;}}
}

然后调用成交交易的方法

#pragma mark -- 交易完成的回调
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{//获取交易成功后的购买凭证[self getAndSaveReceipt:transaction];  
}

保存凭证,发送服务器验证

获取交易成功后的购买凭证
1.获取到凭证之后首先保存起来
2.使用后台提供的接口给后台去苹果服务器去进行验证

#pragma mark -- 获取购买凭证  并保存到Tmp文件里面去
-(void)getAndSaveReceipt:(SKPaymentTransaction *)transaction{//存储  向服务器验证凭证前的 凭证 订单号 用户ID 交易时间(简单:向服务器验证前保存一下)NSMutableDictionary *dict = [self saveServeCheskWithSaveReceipt:transaction];//向服务器发送订单ID 凭证验证  购买  然后发货,发货的动作其实就是服务器在订单的某一个属性里面修改状态。[self sendAppStoreRequestBuyWithReceipt:dict[@"receipt_key"] userId:dict[@"user_id"] paltFormOrder:dict[@"order"] trans:transaction];
}

下面的方法就是保存整个内购的过程

#pragma mark -- 存储  向服务器验证凭证前的 凭证 订单号 用户ID 交易时间(简单:向服务器验证前保存一下)
-(NSMutableDictionary *)saveServeCheskWithSaveReceipt:(SKPaymentTransaction *)transaction{//初始化字典NSMutableDictionary * dic = [[NSMutableDictionary alloc]init];//保存凭证到字典里面//获取交易凭证NSURL * receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl];NSString * base64String = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];NSLog(@"base64String - %@",base64String);[dic setValue: base64String forKey:@"receipt_key"];//保存订单号到字典里面//透传参数  把刚刚购买前 transaction.payment.applicationUsername   里面存储的订单号那回来/*为什么不从上面的self.order获取你,如果这样获取就不会出现空的问题?这样想是错的。如果这样想的话,这个applicationUsername的参数就没有意义了。这个参数的设定是为了下面的坑而设定的。如果用户付款的之后,由于用户网络或者其他各种原因的问题,在没有拿到凭证之前(也就是没有进来updatedTransactions这个回调方法)把App退出。并且删了。这时候,用户重新下载App下.重新登录。由于订单没有结束(因为没有执行到)[[SKPaymentQueue defaultQueue]finishTransaction:transaction];这个方法。所以下次启动Appc注册监听之后,App马上会执行updatedTransactions的方法。方法的transaction对象会返回凭证给你,而applicationUsername属性里面存的就是你刚刚删除前的订单号。如果没有用applicationUsername设置对应凭证的订单号。就算给了凭证给你,你也不知道是哪张订单。到时候向公司服务器是验证不了的。*/NSString *order = @"";if (transaction.payment.applicationUsername != nil) {order = transaction.payment.applicationUsername;}else{order = self.orderID;}//如果这个返回为nilNSLog(@"后台订单号 -- %@",order);[dic setValue: order forKey:@"order"];//保存交易时间到字典里面[dic setValue:[self getCurrentZoneTime] forKey:@"time"];NSString * userId;//NSUserDefaults 值得注意的是对相同的key赋值约等于一次覆盖/*我觉得这里有问题。我决得。保存userID应该放在[[SKPaymentQueue defaultQueue] addPayment:payment];前面保存userid,你放在这里的话要订单凭证成功返回才保存,如果用户付钱后,拿到凭证前删除的话就GG了,反正NSUserDefaults是覆盖的形式的。这个问题有待验证。因为你不存不成功,杀死APP后下次进来就没有UserID了。为了确保成功。应该在用户付钱前就要保存*///  //下面userid不为0的情况是用户成功交易,一直留在app才能走到这里,这时候就拿上保存用户ID。
//  if (self.accountID) {//如果用户ID不为空
//    userId = self.accountID;
//    //把用户ID存起来
//    [[NSUserDefaults standardUserDefaults] setObject:userId forKey:@"unlock_iap_userId"];
//  }else{//如果用户ID为空  用户进来userid为空的原因是,用户交易未完成的情况下,下次重新打开App上会执行updatedTransactions的方法。这时候userid未空的。为空的话就拿会App关掉前保存的userid
//    //从沙盒中获取用户ID
//    userId = [[NSUserDefaults standardUserDefaults] objectForKey:@"unlock_iap_userId"];
//  }//从沙盒中获取用户IDuserId = [[NSUserDefaults standardUserDefaults] objectForKey:@"unlock_iap_userId"];//保存用户ID到字典里面[dic setValue: userId forKey:@"user_id"];//命名plist文件 生成唯一的字符串NSString *fileName = [NSString UUID];//沙盒Tmp 保存方式NSString *savedPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper iapReceiptPath], fileName];//这个存储成功与否其实无关紧要BOOL ifWriteSuccess = [dic writeToFile:savedPath atomically:YES];if (ifWriteSuccess){NSLog(@"购买凭据存储成功!");}else{NSLog(@"购买凭据存储失败");}return dic;}

使用后台提供的接口给后台去苹果服务器去进行验证

#pragma mark -- 去服务器验证购买
-(void)sendAppStoreRequestBuyWithReceipt:(NSString *)receipt userId:(NSString *)userId paltFormOrder:(NSString * )order trans:(SKPaymentTransaction *)transaction{ValidationVoucherModel *model = [[ValidationVoucherModel alloc] init];[model getValidationVoucherRequestCurrentBaseUrl:self.baseUrl CurrentAccountID:userId currentOrderID:order currentReceipt:receipt DataSuccess:^(id _Nullable result) {NSLog(@"result - %@",result);BOOL changeOrder = result[@"data"][@"changeOrder"];if (changeOrder  == YES) {//删除  向服务器验证凭证后的 凭证 订单号 用户ID 交易时间(简单:验证成功后可以把凭证删除)[self delectedConsumptionOfGoodsWithReceipt:receipt];//存储  向服务器验证凭证前的 凭证 订单号 用户ID 交易时间(简单:向服务器验证前保存一下)[self saveServeCheskWithSaveReceipt:transaction];self.payResultBlock(YES, @"已经发货", @"");//结束订单[[SKPaymentQueue defaultQueue]finishTransaction:transaction];}else{NSLog(@"未发货");}} failedBlock:^(NSError * _Nonnull error) {NSLog(@"error - %@",error);}];}

删除本地凭证

#pragma mark -- 删除  向服务器验证凭证后的 凭证 订单号 用户ID 交易时间(简单:验证成功后可以把凭证删除)
-(void)delectedConsumptionOfGoodsWithReceipt:(NSString * )receipt{NSFileManager *fileManager = [NSFileManager defaultManager];NSError * error;if ([fileManager fileExistsAtPath:[SandBoxHelper iapReceiptPath]]) {NSArray * cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];if (error == nil) {for (NSString * name in cacheFileNameArray) {NSString * filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];NSFileManager *fileManager = [NSFileManager defaultManager];NSError * error;NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];NSString * localReceipt = [dic objectForKey:@"receipt_key"];//通过凭证进行对比if ([receipt isEqualToString:localReceipt]) {BOOL ifRemove = [fileManager removeItemAtPath:filePath error:&error];if (ifRemove) {NSLog(@"验证凭证成功后 移除成功");}else{NSLog(@"验证凭证成功后 移除失败");}}else{NSLog(@"本地无与之匹配的订单");}}}}
}

结束交易(关键代码)

//结束订单
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];

经验

1.关于支付丢单的问题

问题1:账号A: 订单A;支付成功了(拿到凭证),应该拿着(凭证,账号id,订单号)上存到后台验证,这个时候把App退出,重新启动,账号A登录,但是这个时候 订单A丢失了缓存没了,如何解决该丢单的问题。

解答:支付是可以把订单保存到payment.applicationUsername = self.orderID;的属性中,登录原来的账号,在未完成支付(即调用:[[SKPaymentQueue defaultQueue]finishTransaction:transaction];支付会仍然继续拿到刚支付的凭证)原来的账号,原来的订单id,原来的凭证就可以继续上存后台验证登录。
其实可以把订单id做一个本地保存,安全一定,防止payment.applicationUsername = self.orderID;获取回来的订单id为空。

问题2:账号A: 订单A;支付成功了(拿到凭证),应该拿着(凭证,账号id,订单号)上存到后台验证,这个时候把App删除,重新下载,账号A登录,但是这个时候 订单A丢失了(即使保存在本地,app删除也会把订单号删除),如何解决该丢单的问题

解答:支付是可以把订单保存到payment.applicationUsername = self.orderID;的属性中,登录原来的账号,在未完成支付(即调用:[[SKPaymentQueue defaultQueue]finishTransaction:transaction];支付会仍然继续拿到刚支付的凭证)原来的账号,原来的订单id,原来的凭证就可以继续上存后台验证登录。

问题3:账号A: 订单A;支付成功了(拿到凭证),应该拿着(凭证,账号id,订单号)上存到后台验证,这个时候把App退出,重新启动,账号B登录,但是这个时候刚刚账号A付款会变成账号B吗,上存后台服务器成功后

解答:会

问题3:账号A: 订单A;支付成功了(拿到凭证),应该拿着(凭证,账号id,订单号)上存到后台验证,这个时候把App退出,换另一台手机登录账号A,账号A还能够继续完成支付吗?

解答: 我没有似过,分两种情况:如果把之前的手机苹果id退出,在另一台手机登录苹果id,app登录账号A,如果能拿到凭证就能继续完成支付,如果手机苹果id登录拿不到凭证就无法继续完成支付。

相关文章:

OC 技术 苹果内购

一直觉得自己写的不是技术&#xff0c;而是情怀&#xff0c;一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的&#xff0c;希望我的这条路能让你们少走弯路&#xff0c;希望我能帮你们抹去知识的蒙尘&#xff0c;希望我能帮你们理清知识的脉络&#xff0…...

云原生周刊:Kubernetes v1.30 一瞥 | 2024.3.25

开源项目推荐 Retina Retina 是一个与云无关的开源 Kubernetes 网络可观测平台&#xff0c;它提供了一个用于监控应用程序运行状况、网络运行状况和安全性的集中中心。它为集群网络管理员、集群安全管理员和 DevOps 工程师提供可操作的见解&#xff0c;帮助他们了解 DevOps、…...

2016年认证杯SPSSPRO杯数学建模D题(第一阶段)NBA是否有必要设立四分线解题全过程文档及程序

2016年认证杯SPSSPRO杯数学建模 D题 NBA是否有必要设立四分线 原题再现 NBA 联盟从 1946 年成立到今天&#xff0c;一路上经历过无数次规则上的变迁。有顺应民意、皆大欢喜的&#xff0c;比如 1973 年在技术统计中增加了抢断和盖帽数据&#xff1b;有应运而生、力挽狂澜的&am…...

EdgeGallery开发指南

API接口 简介 EdgeGallery支持第三方业务系统通过北向接口网关调用EdgeGallery的业务接口。调用流程如下图所示&#xff08;融合前端edgegallery-fe包含融合前端界面以及北向接口网关功能&#xff0c;通过浏览器访问时打开的是融合前端的界面&#xff0c;通过IP:Port/urlPref…...

ubuntu arm qt 读取execl xls表格数据

一&#xff0c;ubuntu linux pc编译读取xls的库 1&#xff0c;安装libxls(读取xls文件 电脑版) 确保你已经安装了基本的编译工具&#xff0c;如gcc和make。如果没有安装&#xff0c;可以使用以下命令安装&#xff1a; sudo apt-update sudo apt-get install build-essentia…...

STM32 使用gcc编译介绍

文章目录 前言1. keil5下的默认编译工具链用的是哪个2. Arm编译工具链和GCC编译工具链有什么区别吗&#xff1f;3. Gcc交叉编译工具链的命名规范4. 怎么下载gcc-arm编译工具链参考资料 前言 我们在STM32上进行开发时&#xff0c;一般都是基于Keil5进行编译下载&#xff0c;Kei…...

FPGA之组合逻辑与时序逻辑

数字逻辑电路根据逻辑功能的不同&#xff0c;可以分成两大类&#xff1a;组合逻辑电路和时序逻辑电路&#xff0c;这两种电路结构是FPGA编程常用到的&#xff0c;掌握这两种电路结构是学习FPGA的基本要求。 1.组合逻辑电路 组合逻辑电路概念&#xff1a;任意时刻的输出仅仅取决…...

git clone没有权限的解决方法

一般情况 git clone时没有权限&#xff0c;一般是因为在代码库平台上没有配置本地电脑的id_rsa.pub 只要配置上&#xff0c;一般就可以正常下载了。 非一般情况 但是也有即使配置了id_rsa.pub后&#xff0c;仍然无法clone代码的情况。如下 原因 这种情况是因为ssh客户端…...

Redis 的内存回收策略

Redis的内存回收策略用于处理过期数据和内存溢出情况&#xff0c;确保系统稳定性和性能。作为一个高性能的键值存储系统&#xff0c;它通过内存回收策略来维护内存的高效使用 主要包括过期删除策略和内存淘汰策略。 过期删除策略&#xff1a; Redis的过期删除策略是通过设置…...

小程序富文本图片宽度自适应

解决这个问题 创建一个util.js文件,图片的最大宽度设置为100%就行了 function formatRichText(html) {let newContent html.replace(/\<img/gi, <img style"max-width:100%;height:auto;display:block;");return newContent; }module.exports {formatRichT…...

安装redis时候修改过的配置文件

只要是石头&#xff0c;到哪里都不会发光的 bind 绑定主机某个网卡对应的IP地址&#xff0c;如果某个主机有两个网卡A和B&#xff0c;那么绑定了A&#xff0c;通过B连接就会无法访问protected-mode 保护模式 Yes为只能本地访问port 启动的端口号pidfile pid存放的位置&#xff…...

Stable Diffusion 本地部署教程

Stable Diffusion是一种用于构建和部署机器学习模型的开源工具。以下是在本地环境中部署 Stable Diffusion 的基本步骤: 步骤 1: 准备环境 确保你的系统中已经安装了以下软件和工具: Python(建议使用 Python 3.x)pip(Python 包管理工具)Docker(可选,用于容器化部署)…...

sql如何增加数据

在MySQL中增加数据主要是通过INSERT INTO SQL语句来实现的。以下是对插入语句的详细介绍以及举例说明&#xff1a; 1、插入语句的基本格式&#xff1a; 1INSERT INTO table_name (column1, column2, ..., columnN) 2VALUES (value1, value2, ..., valueN); table_name&#x…...

智慧交通(代码实现案例)

1.项目简介 目标: 了解智慧交通项目的架构知道智慧交通项目中的模块能够完成智慧交通项目的环境搭建 该项目是智慧交通项目&#xff0c;通过该项目掌握计算机视觉的方法在交通领域的相关应用&#xff0c;包括车道线检测的方法&#xff0c;多目标车辆追踪及流量统计方法&#…...

LeetCode 面试经典150题 205.同构字符串

题目&#xff1a; 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符&#xff0c;同时不改变字符的顺序。不同字符不能映射到同一个字…...

存内计算:释放潜能的黑科技

什么是存内计算&#xff1f; 存内计算技术是一种新型的计算架构&#xff0c;它将存储器和计算单元融合在一起&#xff0c;以实现高效的数据处理。存内计算技术的优势在于能够消除数据搬运的延迟和功耗&#xff0c;从而提高计算效率和能效比。目前&#xff0c;存内计算技术正处…...

CentOS Stream 8系统配置阿里云YUM源

Linux运维工具-ywtool 目录 一.系统环境二.修改yum文件2.1 CentOS-Stream-AppStream.repo2.2 CentOS-Stream-BaseOS.repo2.3 CentOS-Stream-Extras.repo 三.只有一个配置文件四.其他知识4.1 如果想要启用其他源,修改文件配置:enabled14.2 国内源链接 一.系统环境 CentOS Strea…...

MySQL Explain 优化参数详细介绍

Explain 是什么? Explain命令用于分析SQL查询的执行计划&#xff0c;帮助优化查询语句和索引选择。 Explain是MySQL提供的一个非常有用的工具&#xff0c;它能够帮助数据库管理员和开发者理解SQL查询是如何被数据库执行的。通过在SELECT语句前加上EXPLAIN关键字&#xff0c;…...

代码随想录Day58:每日温度、下一个更大元素 I

每日温度 class Solution { public:vector<int> dailyTemperatures(vector<int>& temperatures) {stack<int> st;vector<int> result(temperatures.size(), 0);for(int i 0; i < temperatures.size(); i){while(!st.empty() && tempe…...

冒泡排序 快速排序 归并排序 其他排序

书接上回.. 目录 2.3 交换排序 2.3.1冒泡排序 2.3.2 快速排序 快速排序的优化: 快速排序非递归 2.4 归并排序 基本思想 归并排序非递归 海量数据的排序问题 排序算法时间空间复杂度和稳定性总结 四. 其他非基于比较排序 (了解) 2.3 交换排序 基本思想&#xff1a;…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...