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登录拿不到凭证就无法继续完成支付。

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

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

相关文章

【Linux】-Linux下的编辑器Vim的模式命令大全及其自主配置方法

目录 1.简单了解vim 2.vim的模式 2.1命令模式 2.2插入模式 2.3底行模式 3.vim各模式下的命令集 3.1正常&#xff08;命令模式下&#xff09; 3.1.1光标定位命令 3.1.2 复制粘贴 3.1.3 删除 3.1.4 撤销 3.1.5大小写转换 3.1.6替换 「R」&#xff1a;替换光标所到之处的字符&…

使用llamafile 构建本地大模型运用

安装 https://github.com/Mozilla-Ocho/llamafile 下载 大模型文件&#xff0c;选择列表中任意一个 wget https://huggingface.co/jartine/llava-v1.5-7B-GGUF/resolve/main/llava-v1.5-7b-q4.llamafile?downloadtrue https://github.com/Mozilla-Ocho/llamafile?tabre…

Element UI中日期选择日(date-picker)等其他选择器下拉显示错位、位置错误解决

省流版 给选择器加上唯一key&#xff08;下面的想看就看&#xff09; 问题复现 需求是用一个下拉切换时间维度的选择&#xff0c;分别为年度、季度、月度&#xff0c;但是开发的时候发现&#xff0c;当切换的时候&#xff0c;视图可正常切换&#xff0c;但点击选择时却发现选…

基于nginx 动态 URL反向代理的实现

背景&#xff1a; 我们在项目中在这样一个场景&#xff0c;用户需要使用固定的软件资源&#xff0c;这些资源是以服务器或者以容器形式存在的。 资源以webAPI方式在内网向外提供接口&#xff0c;资源分类多种类型&#xff0c;每种类型的资源程序和Wapi参数都一样。这些资源部属…

STL —— string(3)

目录 1. 使用 1.1 c_str() 1.2 find() & rfind() 1.3 substr() 1.4 打印网址的协议域名等 1.5 find_first_of() 2. string() 模拟实现 2.1 构造函数的模拟实现 2.2 operator[] 和 iterator 的模拟实现 2.3 push_back() & append() & 的模拟实现 2.4 ins…

sqlite3嵌入式开发板命令行方式使用

如何在编译嵌入式版本的sqlite3&#xff0c;请看我上一篇文章 sqlite3 交叉编译-CSDN博客 一、sqlite3命令行方式使用 假如我将编译好的嵌入式的sqlite3放置在如下路径&#xff1a; 进入bin目录进行操作 1.运行sqlite3 运行sqlite3有两种方式 1&#xff09;直接在内存里面…

零拷贝技术、常见实现方案、Kafka中的零拷贝技术的使用、Kafka为什么这么快

目录 1. 普通拷贝 2. 数据拷贝基础过程 2.1 仅CPU方式 2.2 CPU&DMA方式 3.普通模式数据交互 4. 零拷贝技术 4.1 出现原因 4.2 解决思路 4.2.1 mmap方式 4.2.2 sendfile方式 4.2.3 sendfileDMA收集 4.2.4 splice方式 5. Kafka中使用到的零拷贝技术 参考链接 本…

Intellij IDEA构建Android开发环境

Intellij IDEA创建项目时没有Android的选项 进设置&#xff08;Intellij IDEA - Settings - Plugins &#xff09; 再次创建项目可以看到Android的选项 解决Android导入项目时Gradle下载速度慢/超时/失败

OpenHarmony内核编程实战

在正式开始之前&#xff0c;对于刚接触OpenHarmony的伙伴们&#xff0c;面对大篇幅的源码可能无从下手&#xff0c;不知道怎么去编码写程序&#xff0c;下面用一个简单的例子带伙伴们入门。 ▍任务 编写程序&#xff0c;让开发板在串口调试工具中输出”Hello&#xff0c;Open…

java Web会议信息管理系统 用eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 jsp 会议信息管理系统是一套完善的web设计系统&#xff0c;对理解JSP java SERLVET mvc编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0&am…

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记11:数字电位器MCP4017

系列文章目录 嵌入式|蓝桥杯STM32G431&#xff08;HAL库开发&#xff09;——CT117E学习笔记01&#xff1a;赛事介绍与硬件平台 嵌入式|蓝桥杯STM32G431&#xff08;HAL库开发&#xff09;——CT117E学习笔记02&#xff1a;开发环境安装 嵌入式|蓝桥杯STM32G431&#xff08;…

一周是一年的2%

今天读到阮一峰的293期周刊&#xff0c;其中有句话很让我震动——“一周是一年的2%”。 过去的时间里&#xff0c;我都没有在意时间的流逝&#xff0c;过的好的时候就觉得一周过的好快&#xff0c;周三一过这周也就过去了&#xff0c;过的不好的时候就感觉很漫长。 确实&…

C# wpf 嵌入hwnd窗口

WPF Hwnd窗口互操作系列 第一章 嵌入Hwnd窗口&#xff08;本章&#xff09; 第二章 嵌入WinForm控件 第三章 嵌入WPF控件 文章目录 WPF Hwnd窗口互操作系列前言一、如何实现1、继承HwndHost2、实现抽象方法3、xaml中使用HwndHost控件 二、具体实现1、Win32窗口2、HwndSource窗…

【Python】搭建 Python 环境

目 录 一.安装 Python二.安装 PyCharm 要想能够进行 Python 开发&#xff0c;就需要搭建好 Python 的环境 需要安装的环境主要是两个部分&#xff1a; 运行环境: Python开发环境: PyCharm 一.安装 Python (1) 找到官方网站 (2) 找到下载页面 选择 “Download for Windows”…

PHPCMS v9城市分站插件

PHPCMS自带的有多站点功能&#xff0c;但是用过的朋友都知道&#xff0c;自带的多站点功能有很多的不方便之处&#xff0c;例如站点栏目没法公用&#xff0c;每个站点都需要创建模型、每个站点都需要单独添加内容&#xff0c;还有站点必须静态化。如果你内容很多这些功能当然无…

Unity VisionOS开发流程

Unity开发环境 Unity Pro, Unity Enterprise and Unity Industry 国际版 Mac Unity Editor(Apple silicon) visionOS Build Support (experimental) 实验版 Unity 2022.3.11f1 NOTE: 国际版与国服版Pro账通用&#xff0c;需要激活Pro的许可证。官方模板v0.6.2,非Pro版本会打…

安科瑞智慧安全用电综合解决方案

概述 智慧用电管理云平台是智慧城市建设的延伸成果&#xff0c;将电力物联网技术与云平台的大数据分析功能相结合&#xff0c;实现用电信息的可视化管理&#xff0c;可帮助用户实现安全用电&#xff0c;节约用电&#xff0c;可靠用电。平台支持web&#xff0c;app&#xff0c;微…

【回眸】Tessy 单元测试软件使用指南(三)怎么打桩和指针赋值和测试

目录 前言 Tessy 如何进行打桩操作 普通桩 高级桩 手写桩 Tessy单元测试之指针相关测试注意事项 有类型的指针&#xff08;非函数指针&#xff09;&#xff1a; 有类型的函数指针&#xff1a; void 类型的指针&#xff1a; 结语 前言 进行单元测试之后&#xff0c;但凡…

zookeeper面试题

文章目录 ZooKeeper 是什么&#xff1f;ZooKeeper 提供什么&#xff1f;1. 文件系统2. 通知机制 ZooKeeper 文件系统四种类型的 znode1. PERSISTENT (持久化目录节点)2. PERSISTENT_SEQUENTIAL (持久化顺序编号目录节点)3. EPHEMERAL (临时目录节点)4. EPHEMERAL_SEQUENTIAL (临…

C语言------指针(2)

前面已经向大家介绍了指针的一些基本内容&#xff0c;接下来&#xff0c;就在再我来先大家讲解一下指针的其他内容。 1. 数组名的理解 int arr[10] { 1,2,3,4,5,6,7,8,9,10 }; 在学习数组的过程中&#xff0c;我们肯定会写过以上代码&#xff0c;我们知道 int 是该数组的数…