OC 技术 苹果内购

news2024/11/19 23:28:58

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

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) in
  print("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{
  
  //保存产品ID
  self.productId = productID;
  //保存baseUrl
  self.baseUrl = baseUrl;
  //保存用户ID
  self.accountID = accountID;
  //保存订单ID
  self.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;
  }
  //如果这个返回为nil
  NSLog(@"后台订单号 -- %@",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"];
//  }
  //从沙盒中获取用户ID
  userId = [[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.coloradmin.cn/o/1547604.html

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

相关文章

2022 年甘肃省职业院校技能大赛 高职组 网络系统管理竞赛 网络构建模块试题

2022 年甘肃省职业院校技能大赛 高职组网络系统管理竞赛 网络构建模块试题 目 录 考试说明… 3 任务描述… 3 任务清单… 3 &#xff08;一&#xff09;基础配置… 3 &#xff08;二&#xff09;有线网络配置… 4 &#xff08;三&#xff09;无线网络配置… 6 &#xff08;四&a…

惊艳亮相!新版飞桨星河社区邀您共赴星辰大海

亲爱的开发者们&#xff1a; 一场始于好奇、归于热爱的邂逅&#xff0c;让开发者们在飞桨星河社区相聚&#xff0c;相逢于活动、课程、比赛、项目……五年来&#xff0c;千万开发者的共同参与&#xff0c;让这个社区更富活力、更加温暖。 2018年&#xff0c;AI Studio 1.0版本上…

RHCE- 4-Web服务器(2)

基于https协议的静态网站 概念解释 超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息。 HTTP协议以明文方式发送内容&#xff0c;不提供任何方式的数据加密&#xff0c;如果攻击者截取了Web浏览器和网站服务器之间的传输报文&#xff0c;就可以直接读懂其中…

LVS负载均衡(load balance)

一 LVS LVS&#xff1a;Linux Virtaul Server&#xff0c;该软件的功能是实现 LB&#xff08;load balance&#xff09; 二LVS 的三种工作模式 1.NAT 模式&#xff08;NAT&#xff09; LVS 服务器同时充当一台 NAT 网关&#xff0c;拥有公有 IP &#xff0c;同时负责将针对此…

Apple Vision Pro应用合集

这里给大家分享一个网站&#xff0c;手机了最新的apple vision pro 上面运行的应用。 1、查找应用&#xff1a;用户可以浏览特色推荐的应用&#xff0c;或者通过随机挑选功能发现新的应用。 2、社区交流&#xff1a;提供社区功能&#xff0c;用户可以在这里交流使用体验、分享…

Chrome/Edge 使用 Markdown Viewer 查看 Markdown 格式文件

Chrome/Edge 使用 Markdown Viewer 查看 Markdown 格式文件 0. 引言1. 安装 Markdown Viewer 插件2. 使用 Markdown Viewer 阅读 Markdown 格式文件 0. 引言 大部分程序员都喜欢 Markdown 格式的文件&#xff0c;这时给一些没有在电脑上安装 Markdown 编辑器的同事分享资料时&…

SAP gui 组服务器 提示 Error service sapmsPRD unknown

/etc/hosts 追加IP地址和域名的配对关系 /etc/services 追加 sapms[sid] 3601/tcp

java数据结构与算法刷题-----LeetCode665. 非递减数列

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 解题思路&#xff1a;时间复杂度O( n n n)&#xff0c;空间复杂度…

在vscode终端terminal加大栈空间How to increase max stack size in c++ using vscode

参考:https://devpress.csdn.net/cloud/63268204fd0b112779162383.html Answer a question In many dynamic programming and graph problems it is required to do long depth recursion. I am currently using vscode and mingw in windows for my c programs. But in defau…

计算机视觉之三维重建(4)---三维重建基础与极几何

文章目录 一、三维重建基础1.1 问题引入1.2 线性解法1.3 非线性解法1.4 多视图几何的关键问题 二、极几何与基础矩阵2.1 极几何2.2 极几何特例2.3 本质矩阵2.4 本质矩阵的性质2.5 基础矩阵2.6 基础矩阵的性质 三、基础矩阵估计 一、三维重建基础 1.1 问题引入 1. 从单张图像恢…

MySQL Explain 字段详解

Explain 工具介绍 Explain 一般被称为解释器&#xff0c;通过 Explain 工具&#xff0c;我们能分析我们使用的查询语句或是结构的性能瓶颈&#xff0c;它提供 MySQL 如何执行语句的信息。 使用语法&#xff1a; explain [extended|partition] select在 select 关键字前加 ex…

【多模态融合】SuperFusion 激光雷达与相机多层次融合 远距离高清地图预测 ICRA 2024

前言 本文介绍激光雷达与相机进行多层次融合&#xff0c;包括数据级融合、特征级融合和BEV级融合。 融合后的BEV特征可以支持不同的任务头&#xff0c;包括语义分割、实例编码和方向预测&#xff0c;最后进行后处理生成高清地图预测&#xff0c;它是来自ICRA 2024的。 会讲解…

【C++ leetcode】双指针(专题完结)

15. 三数之和 题目 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的…

3.26学习总结

java 实例变量和局部变量 实例变量是记录这个类中对象的特点的每一个对象的实例变量都可以不同(例如名字,性别等),其中一个对象的实例变量改变不会影响其他的变量. 类变量是一种特殊的实例变量,他的特殊在于所有的对象的类变量都是相同的,当一个对象改变了类变量那么所有对象…

0101支付安全-支付模块-项目实战

文章目录 一、信息安全的基础-机密性1 相关概念2 对称加密和非对称加密 二、身份认证三 摘要算法四、数字签名五、数字证书结语 在支付过程中&#xff0c;设计多方的敏感信息&#xff0c;那么安全尤为重要。下面先简单介绍下&#xff0c;相关概念。 一、信息安全的基础-机密性 …

Java项目:76 Springboot学生读书笔记共享

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本文从管理员、用户的功能要求出发&#xff0c; 读书笔记共享平台系统中的功能模块主要是实现管理员&#xff1b;首页、个人中心、用户管理、…

Intellij IDEA构建Android开发环境

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

Ansys Speos | Light Expert Group探测器组使用技巧

附件下载 联系工作人员获取附件 概述 相机挡板的设计需要在光路的不同位置同步多个照度图&#xff0c;以尽量减少杂散光。2023R2 Speos提供了一种新的探测器&#xff0c;用于高阶杂散光分析&#xff0c;可以同时对多个探测器进行光线追迹。Light Expert工具可以即时过滤3D视…

docker安装elasticseachkibana

1.docker安装es 创建本机挂载目录&#xff0c;与容器上目录映射 /Users/wangpei/2024/mydata/elasticsearch conf下创建yml文件 echo "http.host : 0.0.0.0" >> /Users/wangpei/2024/mydata/elasticsearch/config/elasticsearch.yml 安装容器&#xff1a; d…

书籍推荐|meta分析R语言实践教程-Doing Meta-Analysis with R: A Hands-On Guide

“The problems are solved, not by giving new information, but by arranging what we have known since long.” – Ludwig Wittgenstein 推荐理由 《Doing Meta-Analysis with R: A Hands-On Guide》是由 Mathias Harrer, Pim Cuijpers, Toshi Furukawa, 和 David Ebert所…