iOS开发--iOS 指纹识别常见问题汇总

本文详细介绍了如何在iOS应用中使用TouchID进行身份验证,包括基本知识、常见问题及解决方法等。提供了LAContext类的使用示例代码,帮助开发者更好地理解和实现TouchID功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.基本知识

点开这个LocalAuthentication.framework,发现里面主要有这么几个东西


- LAContext.h
- LAError.h
- LAPublicDefines.h
- LocalAuthentication.h

LocalAuthentication.h

这个没什么可讲的吧,代码就两行,一行导入LAContext.h,一行导入LAError.h,这个LocalAuthentication类是暴露出来方便开发者调用的类。

LAPublicDefines.h

先从简单的开始讲吧,首先是LAPublicDefines.h,从名字上来看是公共宏定义类,里面包含了许多定义好的宏,这些宏会在LAContext.h
得到使用。

//
//  LAPublicDefines.h
//  LocalAuthentication
//
//  Copyright (c) 2014 Apple. All rights reserved.
//

#ifndef LocalAuthentication_LAPublicDefines_h
#define LocalAuthentication_LAPublicDefines_h

// Policies
#define kLAPolicyDeviceOwnerAuthenticationWithBiometrics    1
#define kLAPolicyDeviceOwnerAuthentication                  2

// Options
#define kLAOptionUserFallback                               1
#define kLAOptionAuthenticationReason                       2

// Credential types
#define kLACredentialTypePasscode                          -1
#define kLACredentialTypePassphrase                        -2
#define kLACredentialCTKPIN                                -3

// Error codes
#define kLAErrorAuthenticationFailed                       -1
#define kLAErrorUserCancel                                 -2
#define kLAErrorUserFallback                               -3
#define kLAErrorSystemCancel                               -4
#define kLAErrorPasscodeNotSet                             -5
#define kLAErrorTouchIDNotAvailable                        -6
#define kLAErrorTouchIDNotEnrolled                         -7
#define kLAErrorTouchIDLockout                             -8
#define kLAErrorAppCancel                                  -9
#define kLAErrorInvalidContext                            -10

// Error domain
#define kLAErrorDomain        "com.apple.LocalAuthentication"

#endif

LAError.h

这个类其实也不用赘述,就是一个枚举,里面写的是错误的类型,其实就是把上面的kLAError宏写进这个枚举了,具体代码注释写的很清晰,大概翻译了一下

typedef NS_ENUM(NSInteger, LAError)
{
    LAErrorAuthenticationFailed,     // 验证信息出错,就是说你指纹不对
    LAErrorUserCancel               // 用户取消了验证
    LAErrorUserFallback             // 用户点击了手动输入密码的按钮,所以被取消了
    LAErrorSystemCancel             // 被系统取消,就是说你现在进入别的应用了,不在刚刚那个页面,所以没法验证
    LAErrorPasscodeNotSet           // 用户没有设置TouchID
    LAErrorTouchIDNotAvailable      // 用户设备不支持TouchID
    LAErrorTouchIDNotEnrolled       // 用户没有设置手指指纹
    LAErrorTouchIDLockout           // 用户错误次数太多,现在被锁住了
    LAErrorAppCancel                // 在验证中被其他app中断
    LAErrorInvalidContext           // 请求验证出错
} NS_ENUM_AVAILABLE(10_10, 8_0);

LAContext.h

重头戏来了,想在自己的项目中使用TouchID,就要用到LAContext这个类里面的方法首先映入眼帘的是一个NS_ENUM枚举LAPolicy。

typedef NS_ENUM(NSInteger, LAPolicy)
{
    LAPolicyDeviceOwnerAuthenticationWithBiometrics NS_ENUM_AVAILABLE(NA, 8_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0) = kLAPolicyDeviceOwnerAuthenticationWithBiometrics,
    LAPolicyDeviceOwnerAuthentication NS_ENUM_AVAILABLE(10_11, 9_0) = kLAPolicyDeviceOwnerAuthentication

} NS_ENUM_AVAILABLE(10_10, 8_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0);

第一个枚举LAPolicyDeviceOwnerAuthenticationWithBiometrics就是说,用的是手指指纹去验证的;NS_ENUM_AVAILABLE(NA, 8_0)iOS8 可用
第二个枚举LAPolicyDeviceOwnerAuthentication少了WithBiometrics则是使用TouchID或者密码验证,默认是错误两次指纹或者锁定后,弹出输入密码界面;NS_ENUM_AVAILABLE(10_11, 9_0)iOS 9可用

首先暴露出来的几个方法,注意这里都是实例方法,所以需要创建一个实例对象去才能调用,使用LAContext *context = [LAContext alloc] init];创建一个LAContext对象。

canEvaluatePolicy:error:方法用来检查当前设备是否可用touchID,返回一个BOOL值
evaluatePolicy:localizedReason:reply:调用验证方法,注意这里的三个参数:
第一个参数policy是要使用上面那个LAPolicy的枚举
第二个参数localizedReason是NSString类型的验证理由
第三个参数reply则是一个回调Block,block内有一个BOOL类型的success判断是否成功验证,还有一个用于判断错误信息的NSError类型的error
invalidate方法用来废止这个context


- (BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError * __autoreleasing *)error __attribute__((swift_error(none)));

- (void)evaluatePolicy:(LAPolicy)policy
       localizedReason:(NSString *)localizedReason
                 reply:(void(^)(BOOL success, NSError * __nullable error))reply;

- (void)invalidate;

枚举LACredentialType,LAAccessControlOperation,这个东西和下面的几个方法我查了很久也没弄明白用在哪,苹果官方文档也看的不太懂,枚举中只有一个LACredentialTypeApplicationPassword。
不过通过这个NS_ENUM_AVAILABLE(10_11, 9_0)还有方法后面的NS_AVAILABLE(10_11, 9_0)知道这个枚举和这两个方法只能在OS X 10.11和iOS 9.0以上版本使用,所以可能是比较新的东西,后面苹果还会对他扩充吧。
下面是方法的说明:

  // 目前额外加密就一种就是应用密码
  // 输入进去将会是 UTF-8 的字符串
  typedef NS_ENUM(NSInteger, LACredentialType)
  {
      LACredentialTypeApplicationPassword = 0,
  } NS_ENUM_AVAILABLE(10_11, 9_0);
// 以下方法据我理解应该是:可以在验证Touch ID之后额外加密?
  // 设置解锁额外加密凭证
  - (BOOL)setCredential:(nullable NSData *)credential
          type:(LACredentialType)type NS_AVAILABLE(10_11, 9_0);
  // 判断加密凭证是否设置成功
  - (BOOL)isCredentialSet:(LACredentialType)type NS_AVAILABLE(10_11, 9_0);
  // 通过Touch ID来验证加密凭证是否通过
  - (void)evaluateAccessControl:(SecAccessControlRef)accessControl
              operation:(LAAccessControlOperation)operation
        localizedReason:(NSString *)localizedReason
                  reply:(void(^)(BOOL success, NSError * __nullable error))reply
                  NS_AVAILABLE(10_11, 9_0);
  typedef NS_ENUM(NSInteger, LAAccessControlOperation)
  {
      // 创建额外加密
      LAAccessControlOperationCreateItem,
      // 使用额外加密
      LAAccessControlOperationUseItem,
      // 创建额外加密key
      LAAccessControlOperationCreateKey,
      // 使用额外加密key签名
      LAAccessControlOperationUseKeySign
  } NS_ENUM_AVAILABLE(10_11, 9_0);

属性的话,这里有5个

@property (nonatomic, nullable, copy) NSString *localizedFallbackTitle;

@property (nonatomic, nullable, copy) NSString *localizedCancelTitle NS_AVAILABLE(10_12, 10_0);

@property (nonatomic, nullable) NSNumber *maxBiometryFailures NS_DEPRECATED_IOS(8_3, 9_0) __WATCHOS_UNAVAILABLE __TVOS_UNAVAILABLE;

@property (nonatomic, nullable, readonly) NSData *evaluatedPolicyDomainState NS_AVAILABLE(10_11, 9_0) __WATCHOS_UNAVAILABLE __TVOS_UNAVAILABLE;

@property (nonatomic) NSTimeInterval touchIDAuthenticationAllowableReuseDuration NS_AVAILABLE(NA, 9_0) __WATCHOS_UNAVAILABLE __TVOS_UNAVAILABLE;

localizedFallbackTitle可以设置验证TouchID时弹出Alert的输入密码按钮的标题
localizedCancelTitle可以设置验证TouchID时弹出Alert的取消按钮的标题(iOS10才有)
maxBiometryFailures 最大指纹尝试错误次数。 这个属性我们可以看到他后面写了NS_DEPRECATED_IOS(8_3, 9_0),说明这个属性在iOS 8.3被引入,在iOS 9.0被废弃,所以如果系统版本高于9.0是无法使用的。
evalueatedPolicyDomainState这个跟可以检测你的指纹数据库的变化,增加或者删除指纹这个属性会做出相应的反应
touchIDAuthenticationAllowableReuseDuration这个属性应该是类似于支付宝的指纹开启应用,如果你打开他解锁之后,按Home键返回桌面,再次进入支付宝是不需要录入指纹的。因为这个属性可以设置一个时间间隔,在时间间隔内是不需要再次录入。默认是0秒,最长可以设置5分钟。

二.常见问题

1. 指纹识别的版本问题

  1. iOS 9 之前是没有LAErrorTouchIDLockout锁定这个选项的,默认错误5次后;第6次验证是自动弹出输入密码界面;

  2. iOS 9 之后锁定指纹识别之后,如果需要立即弹出输入密码界面需要使用LAPolicyDeviceOwnerAuthentication这个属性重新发起验证

  3. 如果输入了锁屏密码,指纹解密锁定会默认解除

2. 指纹识别的LAPolicy

  1. 第一个枚举LAPolicyDeviceOwnerAuthenticationWithBiometrics就是说,用的是手指指纹去验证的;NS_ENUM_AVAILABLE(NA, 8_0)iOS8 可用
  2. 第二个枚举LAPolicyDeviceOwnerAuthentication少了WithBiometrics则是使用TouchID或者密码验证,默认是错误两次指纹或者锁定后,弹出输入密码界面;NS_ENUM_AVAILABLE(10_11, 9_0)
    iOS 9可用

3. 指纹识别LAContext的方法

  1. canEvaluatePolicy:error:方法用来检查当前设备是否可用touchID,返回一个BOOL值;不会弹验证指纹密码框
  2. evaluatePolicy:localizedReason:reply:调用验证方法,会弹验证指纹密码框

4. feedback按钮显示

  1. 默认第一次识别只有取消按钮
  2. 错误一次之后,会显示 feedBack 按钮
  3. 如果不想显示 feedback 按钮;可以设置 feedBackTitle = @""

5. CancelTitle按钮显示

该属性, iOS 10 才可以进行设置,iOS 以前是不可以进行设置的

7.弹窗显示级别问题

指纹识别的弹窗的级别非常之高,高到离谱,经过验证应用程序内部没有比指纹识别的window的级别更高的UIWindowLevel,也就说了他是系统级的弹窗。需要注意的是,如果指纹弹窗显示和消失应用程序会调用:

  - (void)applicationWillResignActive:(UIApplication *)application;
  - (void)applicationDidBecomeActive:(UIApplication *)application;

所以应用程序内部无法获取。不知道越狱之后的手机能否获取到,如果能获取到,那就不可描述了,所以推荐各位看官没什么刚需不要越狱。

8.检测指纹库中指纹是否发生改变

苹果官方文档解释如下

This property returns a value only when the canEvaluatePolicy(:error:) method succeeds for a biometric policy or the evaluatePolicy(:localizedReason:reply:) method is called and a successful Touch ID authentication is performed. Otherwise, nil is returned.
The returned data is an opaque structure. It can be used to compare with other values returned by this property to determine whether the database of authorized fingerprints has been updated. However, the nature of the change cannot be determined from this data.

总结来说:

  1. 当你增加或者删除指纹时候,你在使用使用canEvaluatePolicy(_:error:)或者evaluatePolicy(_:localizedReason:reply:)方法验证;成功后evaluatedPolicyDomainState属性会返回一个 NSData 对象;否则返回 nil;
  2. 但是返回的evaluatedPolicyDomainState属性并不能说明发生了什么样子的改变;只是告诉你发生了改变

9.支持机型判断

从设备和系统判断是否是支持TouchID,有点取巧
.h 文件

//
//  NSString+QDTouchID.h
//  Ewallet
//
//  Created by 陈博文 on 16/10/25.
//  Copyright © 2016年 UCSMY. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NSString (QDTouchID)
+ (BOOL)judueIPhonePlatformSupportTouchID;
@end

. m 文件

//
//  NSString+QDTouchID.m
//  Ewallet
//
//  Created by 陈博文 on 16/10/25.
//  Copyright © 2016年 UCSMY. All rights reserved.
//

#import "NSString+QDTouchID.h"
#include <sys/sysctl.h>

#define IOS8_OR_LATER    ( [[[UIDevice currentDevice] systemVersion] compare:@"8.0"] != NSOrderedAscending )
#define IS_Phone UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPhone

@implementation NSString (QDTouchID)

//是否是iOS8.0以上的系统

//是否是5s以上的设备支持
+ (NSString *)platform
{
    size_t size;
    sysctlbyname("hw.machine", NULL, &size, NULL, 0);
    char *machine = malloc(size);
    sysctlbyname("hw.machine", machine, &size, NULL, 0);
    NSString *platform = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding];
    free(machine);
    return platform;
}

// 判断是否支持TouchID,只判断手机端,ipad端我们不支持

+ (BOOL)judueIPhonePlatformSupportTouchID
{
    /*
     if ([platform isEqualToString:@"iPhone1,1"])   return @"iPhone1G GSM";
     if ([platform isEqualToString:@"iPhone1,2"])   return @"iPhone3G GSM";
     if ([platform isEqualToString:@"iPhone2,1"])   return @"iPhone3GS GSM";
     if ([platform isEqualToString:@"iPhone3,1"])   return @"iPhone4 GSM";
     if ([platform isEqualToString:@"iPhone3,3"])   return @"iPhone4 CDMA";
     if ([platform isEqualToString:@"iPhone4,1"])   return @"iPhone4S";
     if ([platform isEqualToString:@"iPhone5,1"])   return @"iPhone5";
     if ([platform isEqualToString:@"iPhone5,2"])   return @"iPhone5";
     if ([platform isEqualToString:@"iPhone5,3"])   return @"iPhone 5c (A1456/A1532)";
     if ([platform isEqualToString:@"iPhone5,4"])   return @"iPhone 5c (A1507/A1516/A1526/A1529)";
     if ([platform isEqualToString:@"iPhone6,1"])   return @"iPhone 5s (A1453/A1533)";
     if ([platform isEqualToString:@"iPhone6,2"])   return @"iPhone 5s (A1457/A1518/A1528/A1530)";
     */


    if(IS_Phone)
    {
        if([self platform].length > 6 )
        {

            NSString * numberPlatformStr = [[self platform] substringWithRange:NSMakeRange(6, 1)];
            NSInteger numberPlatform = [numberPlatformStr integerValue];
            // 是否是5s以上的设备
            if(numberPlatform > 5)
            {
                return YES;
            }
            else
            {
                return NO;
            }

        }
        else
        {
            return NO;
        }
    }
    else
    {
        // 我们不支持iPad 设备
        return NO;
    }

}

@end

一、两种鉴定方式

LAPolicyDeviceOwnerAuthenticationWithBiometrics : 生物指纹识别。验证弹框有两个按钮,第一个是取消按钮,第二个按钮可以自定义标题名称(输入密码)。只有在第一次指纹验证失败后才会出现第二个按钮,这种鉴定方式的第二个按钮的功能自定义。前三次指纹验证失败,指纹验证框不再弹出。再次重新进入验证,还有两次验证机会,如果还是验证失败,TOUCH ID 被锁住不再继续弹出指纹验证框。以后的每次验证都将会弹出设备密码输入框直至输入正确的设备密码方可解除TOUCH ID锁。


LAPolicyDeviceOwnerAuthentication: 生物指纹识别或系统密码验证。如果TOUCH ID 可用,且已经录入指纹,则优先调用指纹验证。其次是调用系统密码验证,如果没有开启设备密码,则不可以使用这种验证方式。指纹识别验证失败三次将弹出设备密码输入框,如果不进行密码输入。再次进来还可以有两次机会验证指纹,如果都失败则TOUCH ID被锁住,以后每次进来验证都是调用系统的设备密码直至输入正确的设备密码方可解除TOUCH ID锁。

支持系统和机型:

iOS系统的指纹识别功能最低支持的机型为iPhone 5s,最低支持系统为iOS 8,虽然安装iOS 7系统的5s机型可以使用系统提供的指纹解锁功能,但由于API并未开放,所以理论上第三方软件不可使用。

#import "ViewController.h"

#import <SVProgressHUD.h>

#import <LocalAuthentication/LocalAuthentication.h>

#import <LocalAuthentication/LAError.h>

#import "NSString+QDTouchID.h"



@interface ViewController ()


@end


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];


}

#pragma mark - 支付宝

- (IBAction)authrizeAction:(id)sender {


    

    //这个 demo 是以目前的支付宝为例子写的

    

    if ([NSString judueIPhonePlatformSupportTouchID])

    {

        [self startTouchIDWithPolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics];


    }

    else

    {

        NSLog(@"您的设置硬件暂时不支持指纹识别");

    }

    

}


- (void)startTouchIDWithPolicy:(LAPolicy )policy{

    

    

    [SVProgressHUD show];

    [SVProgressHUD setDefaultStyle:SVProgressHUDStyleDark];

    LAContext *context = [[LAContext alloc]init];//使用 new 不会给一些属性初始化赋值

    

    context.localizedFallbackTitle = @"输入密码";//@""可以不 feedBack 钮显示

    //LAPolicyDeviceOwnerAuthenticationWithBiometrics

    [context evaluatePolicy:policy localizedReason:@"请验证已有指纹" reply:^(BOOL success, NSError * _Nullable error) {

        

        [SVProgressHUD dismiss];

        //SVProgressHUD dismiss 需要 0.15才会消失;所以dismiss 进行下一步操作;但是0.3是适当延长时间;留点余量

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

            

            if (success)

            {

                NSLog(@"纹识别成功");

                // 纹识别成功,回主线程更新UI

                dispatch_async(dispatch_get_main_queue(), ^{

                    //成功操作--马上调用纯指纹验证方法

                    

                    if (policy == LAPolicyDeviceOwnerAuthentication)

                    {

                        [self startTouchIDWithPolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics];

                    }

                    else

                    {

 

                    }

                    

                });

            }

            

            if (error) {

                //纹识别失败,回主线程更新UI

                NSLog(@"纹识别成功");

                dispatch_async(dispatch_get_main_queue(), ^{

                    //败操作

                    [self handelWithError:error];

                    

                });

            }

        });

    }];


}




/**

 处理错误数据


 @param error 错误信息

 */

- (void)handelWithError:(NSError *)error{


    if (error) {

        

        NSLog(@"%@",error.domain);

        NSLog(@"%zd",error.code);

        NSLog(@"%@",error.userInfo[@"NSLocalizedDescription"]);

        

        LAError errorCode = error.code;

        switch (errorCode) {

                

            case LAErrorTouchIDLockout: {

                //touchID 锁定--ios9才可以

                

                [self handleLockOutTypeAliPay];

                

                

                break;

            }

        }

    }

}




/**

 支付宝处理锁定

 */

- (void)handleLockOutTypeAliPay{

    

    //启验证--调用非全指纹指纹验证

    [self startTouchIDWithPolicy:LAPolicyDeviceOwnerAuthentication];

    

}



#pragma mark - 最基本使用的 Demo


- (IBAction)authrizeAction1:(id)sender {

    

    if([NSString judueIPhonePlatformSupportTouchID]){

        

        [self BaseDemo];

        

    }else{

        

        NSLog(@"您的设置硬件不支持指纹识别");

    }

    

    

}


- (void)BaseDemo{

    

    

    [SVProgressHUD show];

    

    LAContext *context = [[LAContext alloc]init];//使用 new 不会给一些属性初始化赋值

    

    context.localizedFallbackTitle = @"输入密码";//@""可以不 feedBack 钮显示

    //LAPolicyDeviceOwnerAuthenticationWithBiometrics

    [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"请验证已有指纹" reply:^(BOOL success, NSError * _Nullable error) {

        

        [SVProgressHUD dismiss];

        //SVProgressHUD dismiss 需要 0.15才会消失;所以dismiss 进行下一步操作;但是0.3是适当延长时间;留点余量

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

            

            if (success)

            {

                NSLog(@"纹识别成功");

                // 纹识别成功,回主线程更新UI

                dispatch_async(dispatch_get_main_queue(), ^{

                    //成功操作

                });

            }

            

            if (error) {

                //纹识别失败,回主线程更新UI

                dispatch_async(dispatch_get_main_queue(), ^{

                    //败操作

                    LAError errorCode = error.code;

                    switch (errorCode) {

                       

                        case LAErrorAuthenticationFailed:

                        {

                            NSLog(@"授权失"); // -1 连续三次指纹识别错误

                        }

                            break;

                        case LAErrorUserCancel// Authentication was canceled by user (e.g. tapped Cancel button)

                        {

                            NSLog(@"户取消验证Touch ID"); // -2 TouchID对话框中点击了取消按钮

                        }

                            break;

                        case LAErrorUserFallback// Authentication was canceled, because the user tapped the fallback button (Enter Password)

                        {

                            NSLog(@"户选择输入密码,切换主线程处理"); // -3 TouchID对话框中点击了输入密码按钮

                        }

                            break;

                        case LAErrorSystemCancel// Authentication was canceled by system (e.g. another application went to foreground)

                        {

                            NSLog(@"取消授权,如其他应用切入"); // -4 TouchID对话框被系统取消,例如按下Home或者电源键

                        }

                            break;

                        case LAErrorPasscodeNotSet// Authentication could not start, because passcode is not set on the device.

                            

                        {

                            NSLog(@"设备系统未设置密码"); // -5

                        }

                            break;

                        case LAErrorTouchIDNotAvailable// Authentication could not start, because Touch ID is not available on the device

                        {

                            NSLog(@"设备未设置Touch ID"); // -6

                        }

                            break;

                        case LAErrorTouchIDNotEnrolled// Authentication could not start, because Touch ID has no enrolled fingers

                        {

                            NSLog(@"户未录入指纹"); // -7

                        }

                            break;

                        case LAErrorTouchIDLockout//Authentication was not successful, because there were too many failed Touch ID attempts and Touch ID is now locked. Passcode is required to unlock Touch ID, e.g. evaluating LAPolicyDeviceOwnerAuthenticationWithBiometrics will ask for passcode as a prerequisite 户连续多次进行Touch ID验证失败,Touch ID锁,需要用户输入密码解锁,先Touch ID验证密码

                        {

                            NSLog(@"Touch ID锁,需要用户输入密码解锁"); // -8 连续五次指纹识别错误,TouchID功能被锁定,下一次需要输入系统密码

                        }

                            break;

                        case LAErrorAppCancel// Authentication was canceled by application (e.g. invalidate was called while authentication was in progress) 如突然来了电话,电话应用进入前台,APP被挂起啦");

                        {

                            NSLog(@"户不能控制情况下APP被挂起"); // -9

                        }

                            break;

                        case LAErrorInvalidContext// LAContext passed to this call has been previously invalidated.

                        {

                            NSLog(@"LAContext传递给这个调用之前已经失效"); // -10

                        }

                            break;

                    }


                });

            }

        });

    }];

    


}


@end


demo

TouchIDDemo



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值