概述
当前版本:2.2.0.4
发版时间:2019年07月08
文档最后更新时间:2019年07月09日
常见问题请移步至「对接问题」
崩溃问题请移步至「崩溃问题」
版本更新:所有版本通用方式——替换SDK静态库, 删除旧版本SDK所有相关的`.framework``.bundle`文件,清除缓存,再导入新版SDK中的所有`.framework``.bundle`文件(.bundle文件注意保留开发者自定义资源),详细说明请移步至「升级指南」
如需SDK及相关文档下载请移步至「SDK资源下载」
一.准备工作
前置条件
- 闪验SDK支持Xcode 9.4.1,iOS8.0+及以上版本。
- 闪验SDK支持中国移动、联通、电信4G的取号能力。
- 闪验SDK支持网络环境为
a.纯数据网络
b.数据网络与wifi网络双开
- 对于双卡手机,闪验SDK取当前流量卡号
创建应用
提示:一个应用对应一个appid,多个应用(不同bundleID)需在闪验平台创建多个应用以对应多个appid
应用的创建流程及APPID/APPKEY的获取,请查看「账号创建」文档
快速体验Demo
将创建应用时获得的AppID、AppKey填入Demo工程中.pch文件,修改工程BundleID为创建应用时绑定的BundleID即可
开发环境搭建
闪验SDK提供两种集成方式供iOS开发者选择:
- 通过CocoaPods自动集成
- 手动集成
通过CocoaPods自动集成
在工程的Podfile
里面添加以下代码:
#以下三种版本选择方式示例
#集成最新版闪验SDK:
pod 'CL_ShanYanSDK'
#集成指定版本闪验SDK:
pod 'CL_ShanYanSDK', '2.2.0.4'
#集成指定版本闪验SDK,并在末位小版本范围更新:
pod 'CL_ShanYanSDK', '~> 2.2.0.4'
保存并执行pod install
,然后用后缀为.xcworkspace
的文件打开工程。
注意:
命令行下执行pod search CL_ShanYanSDK
,如显版本不是最新的,则先执行pod repo update
操作更新本地repo的内容
关于CocoaPods
的更多信息请查看 CocoaPods官方网站。
手动集成
- 导入framework: 将闪验SDK压缩包中framework文件夹下所有资源添加到工程中(注意勾选Copy items if needed)
- Xcode配置:
- OtherLinkerFlags中 添加**-ObjC**:xcode->BuildSetting->Other Linker Flags 添加 **-ObjC**
- 添加libc++.1.tbd: 在xcode->General->Linked Frameworks and Libraries中点击 **+** ,搜索并选择添加 **libc++.1.tbd**
- Swift工程需要额外添加-force_load:
- 在xcode->BuildSetting->Other Linker Flags 添加-force_load
- 在-force_load下方添加CL_ShanYanSDK.framework/CL_ShanYanSDK所在路径,具体操作可以将CL_ShanYanSDK.framework拖入空栏,在尾部拼接静态库名称CL_ShanYanSDK,将前缀绝对地址改成相对地址$(SRCROOT),最终为 "$(SRCROOT)/.../CL_ShanYanSDK.framework/CL_ShanYanSDK"形式
二.SDK使用说明
1.初始化
方法原型
/**初始化*/
+(void)initWithAppId:(NSString *)appId AppKey:(NSString *)appKey complete:(nullable CLComplete)complete;
参数描述
参数 | 是否必填 | 类型 | 说明 |
appId | 必填 | NSString | 闪验appID |
appKey | 必填 | NSString | 闪验appKey |
complete | 选填 | CLComplete | 初始化回调block,可以在此回调block中接收初始化情况,也可以不关心初始化结果 |
接口作用
初始化SDK :传入用户的appID、appKey,获取本机运营商,读取缓存,获取运营商配置,初始化SDK
使用场景
- 建议在app启动时调用
- 必须在一键登录前至少调用一次
- 只需调用一次,多次调用不会多次初始化,与一次调用效果一致
请求示例代码
ObjC:
- 导入闪验SDK头文件
#import <CL_ShanYanSDK/CL_ShanYanSDK.h>
- 在AppDelegate中的
didFinishLaunchingWithOptions
方法中添加初始化代码
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
//初始化
[CLShanYanSDKManager initWithAppId:cl_SDK_APPID AppKey:cl_SDK_APPKEY complete:nil];
...
}
Swift:
- 创建混编桥接头文件并导入闪验SDK头文件
#import <CL_ShanYanSDK/CL_ShanYanSDK.h>
- 在AppDelegate中的
didFinishLaunchingWithOptions
方法中添加初始化代码
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 建议先检测APP登录状态,未登录再使用闪验
...
//初始化
CLShanYanSDKManager.initWithAppId("your appID", appKey: "your appKey")
...
}
2.预取号
方法原型
/**
* 预取号(获取临时凭证)
* 建议在判断当前用户属于未登录状态时使用,已登录状态用户请不要调用该方法
*/
+(void)preGetPhonenumber:(nullable CLComplete)complete;
接口作用
电信、联通、移动预取号 :初始化成功后,如果当前为电信/联通/移动,将调用预取号,可以提前获知当前用户的手机网络环境是否符合一键登录的使用条件,成功后将得到用于一键登录使用的临时凭证,默认的凭证有效期60s(电信)/30min(联通)/60min(移动)。
使用场景
- 建议在执行一键登录的方法前,提前一段时间调用此方法,比如调一键登录的vc的viewdidload中,或者rootVC的viewdidload中,或者app启动后,此调用将有助于提高闪验拉起授权页的速度和成功率
- 不建议调用后立即调用拉起授权页方法(此方法是异步)
- 此方法需要1~2s的时间取得临时凭证,因此也不建议和拉起授权页方法一起串行调用
- 不建议频繁的多次调用和在拉起授权页后调用
- 建议在判断当前用户属于未登录状态时使用,已登录状态用户请不要调用该方法
请求示例代码
ObjC:
#import <CL_ShanYanSDK/CL_ShanYanSDK.h>
//开发者调拉起授权页的vc
@implementation CustomLoginViewController
- (void)viewDidLoad {
[super viewDidLoad];
if (YourAppLoginStatus == NO) {
//预取号
[CLShanYanSDKManager preGetPhonenumber:nil];
...
}
}
...
//拉起授权页
- (void)shanYanAuthPageLogin{
...
}
3.拉起授权页
在预取号成功后调用,预取号失败不建议调用。调用拉起授权页方法后将会调起运营商授权页面。该方法会拉起登录界面,已登录状态请勿调用 。
//闪验一键登录接口
+(void)quickAuthLoginWithConfigure:(CLUIConfigure *)clUIConfigure
openLoginAuthListener:(CLComplete)openLoginAuthListener
oneKeyLoginListener:(CLComplete)oneKeyLoginListener;
参数描述
参数 | 类型 | 说明 |
clUIConfigure 必填 | CLUIConfigure | 授权页控件属性配置对象 |
openLoginAuthListener 选填 | CLComplete | 拉起授权页的回调,拉起页面成功失败均触发 |
oneKeyLoginListener 必填 | CLComplete | 一键登录回调,用于接收一键登录的结果,点一键登录成功失败均触发,点自带的返回按钮也触发 |
使用场景
- 用户进行一键登录操作时,调用一键登录方法,如果初始化成功,SDK将会拉起授权页面,用户授权后,SDK将返回取号 token给到应用客户端。
- 可以在多处调用
- 需在调用预初始化方法之后调用
一键登录逻辑说明
- 存在调用预初始化时获取的临时凭证,调用一键登录方法将立即拉起授权页面
- openLoginAuthListener 拉起授权页监听回调,拉起成功或失败均触发
- 不存在临时凭证或临时凭证过期时(临时凭证有效期电信60s、联通30min、移动60min),调用一键登录方法,将有一个很短的时延,待取号成功后拉起授权页面
- 取号失败时,返回失败
请求示例代码
ObjC:
- 导入闪验SDK头文件
#import <CL_ShanYanSDK/CL_ShanYanSDK.h>
- 在需要使用一键登录的地方调用闪验一键登录接口
// 用户需要使用闪验一键登录时的方法
- (void)quickLoginBtnClick:(UIButton *)sender {
__weak typeof(self) weakself = self;
CGFloat screenScale = [UIScreen mainScreen].bounds.size.width/375.0;
CLUIConfigure * baseUIConfigure = [CLUIConfigure new];
baseUIConfigure.viewController = self;
baseUIConfigure.clLogoImage = [UIImage imageNamed:@"your_app_logo_image"];
//layout 布局
CLOrientationLayOut * clOrientationLayOutPortrait = [CLOrientationLayOut new];
clOrientationLayOutPortrait.clLayoutPhoneCenterY = @(0*screenScale);
clOrientationLayOutPortrait.clLayoutPhoneLeft = @(50*screenScale);
...
baseUIConfigure.clOrientationLayOutPortrait = clOrientationLayOutPortrait;
//开发者自己的loading(注意后面loading的隐藏时机)
[SVProgressHUD setContainerView:self.view];
[SVProgressHUD show];
//闪验一键登录接口(将拉起授权页)
[CLShanYanSDKManager quickAuthLoginWithConfigure:baseUIConfigure openLoginAuthListener:^(CLCompleteResult * _Nonnull completeResult) {
[SVProgressHUD hide];
if (completeResult.error) {
//拉起授权页失败
NSLog(@"openLoginAuthListener:%@",completeResult.error.userInfo);
}else{
//拉起授权页成功
NSLog(@"openLoginAuthListener:%@",completeResult.yy_modelToJSONObject);
}
} oneKeyLoginListener:^(CLCompleteResult * _Nonnull completeResult) {
__strong typeof(self) strongSelf = weakSelf;
[SVProgressHUD hide];
if (completeResult.error) {
//一键登录失败
NSLog(@"oneKeyLoginListener:%@",completeResult.error.description);
//提示:错误无需提示给用户,可以在用户无感知的状态下直接切换登录方式
if (completeResult.code == 1011){
//用户取消登录(点返回)
//处理建议:如无特殊需求可不做处理,仅作为交互状态回调,此时已经回到当前用户自己的页面
//点击sdk自带的返回,无论是否设置手动销毁,授权页面都会强制关闭
} else{
//处理建议:其他错误代码表示闪验通道无法继续,可以统一走开发者自己的其他登录方式,也可以对不同的错误单独处理
//1003 一键登录获取token失败
//1008 未开启移动网络
//1009 未检测到sim卡
//其他 其他错误//
//关闭授权页,如果授权页还未拉起,此调用不会关闭当前用户vc,即不执行任何代码
[CLShanYanSDKManager finishAuthControllerCompletion:nil];
}
}else{
//一键登录获取Token成功
NSLog(@"oneKeyLoginListener:%@",completeResult.yy_modelDescription);
//SDK成功获取到Token
/** token置换手机号
code
*/
}
}];
}
一键登录 quickAuthLoginWith:complete:
正确返回示例
completeResult->_data:
{
accessToken = 2ebf48fbe75a45449ed78ecca9c5d93c;
appId = xxxxxx;
device = ZGV2aWNlPWlQaG9uZTd8aXA9MTcyLjE2LjExLjZ8RElEPTBkOGY2NzViZjdkYjRjZDdmMWI1YmM5NDI3NWIyZTQ0fHV1aWQ9MzBjZGU2ZTktYzZmOS00MGNhLTk4MjEtMzI4NGM0ZDViZDNj;
randoms = "321bfcdb-bb34-4e3e-bbf7-def83d33bd76";
sign = "sNeHtWihz7B4iTY1Fch5+ztllDA=";
telecom = CTCC;
timestamp = 1558771442361;
version = "2.2.0";
}
字段 | 类型 | 含义 |
appId | String | 当前项目的appid |
accessToken | String | token,置换令牌,用来和后台置换手机号。一次有效,有效期3min |
telecom | String | 当前数据流量卡的运营商类型: CMCC 移动 CTCC 电信 CUCC 联通 |
timestamp | String | 网络时间 |
randoms | String | 随机数 |
version | String | 后台接口版本号 |
sign | String | 签名 |
device | String | 设备型号 |
ps:请注意返回字段中的accessToken、sign、device字段值为base64字符串,其中包含特殊字符,在客户APP与客户后台交互时应避免使用URL拼接字符串的传值方式,会导致特殊字符丢失,最终导致请求闪验后台的置换手机号接口报“请求非法,签名校验不通过”。
一键登录 quickAuthLoginWith:complete:
报错处理
completeResult->_error:
- 用户取消登录(授权页点击返回) 【处理建议:若无特殊需求可不做处理】
- 用户选择其他方式登录(点击授权页自带的其他方式登录): 【处理建议:可根据实际情况跳转其他登录方式 】
- 其他错误 【处理建议:使用闪验通道失败,可根据实际情况跳转其他登录方式 】
授权页销毁
注:sdk拉起授权页成功后,只允许点击一次一键登录按钮。一键登录按钮点击一次后,只有sdk自带的左上角返回按钮可以交互,效果为强制关闭授权页,其他页面元素均会被禁用,以防止多次点击导致多次回调而出现异常。
拉起授权页前 配置授权页面销毁机制属性
// 获取默认参数配置
CLUIConfigure * baseUIConfigure = [CLUIConfigure clDefaultUIConfigure];
baseUIConfigure.viewController = self;
// 是否需要手动销毁授权页面,默认自动销毁, YES->手动销毁
baseUIConfigure.manualDismiss = @(YES);
sdk获取token回调后销毁界面
//关闭页面
[CLShanYanSDKManager finishAuthControllerCompletion:^{
//如需关闭后present/push新页面,建议在completion回调中执行
//注:若未拉起授权页,调用此方法,block不会触发
//用户跳转短信验证
CustomSmsViewController * smsVc = [[CustomSmsViewController alloc]init];
CustomNavigationController * smsNav = [[CustomNavigationController alloc]initWithRootViewController:smsVc];
smsVc.navigationItem.title = @"短信验证";
[self presentViewController:smsNav animated:YES completion:nil];
}];
4.置换手机号
注:移动、联通、电信分别有自己的置换手机号url,需要根据相对应的运营商url置换手机号码。详情见下方 请求示例代码
使用场景
SDK成功返回token后,开发者需自行调用置换手机号接口,此接口需要开发者调用由服务端接入闪验服务端生成的接口,上线前的调试阶段可以直接使用示例代码中的接口
请求示例代码
ObjC:
[CLShanYanSDKManager quickAuthLoginWithConfigure:baseUIConfigure openLoginAuthListener:^(CLCompleteResult * _Nonnull completeResult) {
[SVProgressHUD hide];
if (completeResult.error) {
//拉起授权页失败
NSLog(@"openLoginAuthListener:%@",completeResult.error.userInfo);
}else{
//拉起授权页成功
NSLog(@"openLoginAuthListener:%@",completeResult.yy_modelToJSONObject);
}
} oneKeyLoginListener:^(CLCompleteResult * _Nonnull completeResult) {
[SVProgressHUD dismiss];
if (completeResult.error) {
...
}else{
//SDK成功获取到Token
NSString * telecom = [completeResult.data valueForKey:@"telecom"];
//urlStr:用户后台对接闪验后台后配置的API,以下为Demo提供的调试API及调用示例,在调试阶段可暂时调用此API,也可用此API验证后台API是否正确配置
NSString * urlStr = nil;
if ([telecom isEqualToString:@"CMCC"]) {
urlStr = [cl_SDK_URL stringByAppendingString:@"open/flashsdk/mobile-query-m"];
}else if ([telecom isEqualToString:@"CUCC"]) {
urlStr = [cl_SDK_URL stringByAppendingString:@"open/flashsdk/mobile-query-u"];
}else if ([telecom isEqualToString:@"CTCC"]) {
urlStr = [cl_SDK_URL stringByAppendingString:@"open/flashsdk/mobile-query-t"];
}else{
//失败
return;
}
if (urlStr) {
NSLog(@"tokenParamr:%@",completeResult.data);
[NetWorkManager POST_url:urlStr withParameter:completeResult.data complete:^(NSDictionary * _Nonnull responseObject, NSError * _Nonnull error) {
if (error) {
NSLog(@"%@",error);
}else{
NSInteger code = [[responseObject valueForKey:@"code"] integerValue];
if (code == 200000) {
NSString * mobileName = responseObject[@"data"][@"mobileName"];
NSString * mobileCode = [mobileName decryptUseDESKey:cl_SDK_APPKEY];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showSuccessWithStatus:[NSString stringWithFormat:@"免密登录成功,手机号:%@",mobileCode]];
});
NSLog(@"免密登录成功,手机号:%@",mobileCode);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showSuccessWithStatus:[NSString stringWithFormat:@"免密登录失败"]];
});
NSLog(@"免密登录失败:%@",responseObject);
}
}
}];
}
}
}];
Swift:
CLShanYanSDKManager.quickAuthLogin(with: clUIConfigure, openLoginAuthListener: { (completeResult) in
...
}) { (completeResult) in
if completeResult.error != nil {
...
}else{
//SDK成功获取到Token
NSLog("quickAuthLogin Success:%@",completeResult.data ?? "")
//urlStr:用户后台对接闪验后台后配置的API,以下为Demo提供的调试API及调用示例,在调试阶段可暂时调用此API,也可用此API验证后台API是否正确配置
var urlStr : String?
let APIString = "https://api.253.com/"
if let telecom = completeResult.data?["telecom"] as! String?{
switch telecom {
case "CMCC":
urlStr = APIString.appendingFormat("open/flashsdk/mobile-query-m")
break
case "CUCC":
urlStr = APIString.appendingFormat("open/flashsdk/mobile-query-u")
break
case "CTCC":
urlStr = APIString.appendingFormat("open/flashsdk/mobile-query-t")
break
default:
break
}
}
if let urlStr = urlStr{
let dataDict = completeResult.data as! Parameters
Alamofire.request(urlStr, method:.post, parameters:dataDict, encoding:URLEncoding.default, headers:[:]).responseJSON(completionHandler: { (response) in
if response.result.isSuccess {
if let json = response.result.value{
let jsonDict = JSON(json)
if jsonDict["code"].intValue == 200000{
let mobileName = jsonDict["data"]["mobileName"].stringValue
let mobileCode = StringDecryptUseDES.decryptUseDESString(mobileName, key: "tDo3Ml2K")//appKey
DispatchQueue.main.async(execute: {
SVProgressHUD.showSuccess(withStatus: ("免密登录成功,手机号:\(mobileCode)"))
})
print(("免密登录成功,手机号:\(mobileCode)"))
return;
}
}
}
DispatchQueue.main.async(execute: {
SVProgressHUD.showInfo(withStatus: ("免密登录失败:\(response.description)"))
print(("免密登录失败:\(response.description)"))
})
})
}
}
}
}
token置换手机号 正确返回示例
{
"chargeStatus": 1,
"message": "成功",
"data": {
"fanqizha": "1",
"tag": "",
"tradeNo": "18112115031414011",
"mobileName": "OaLj8kkXPwFGp/eRk+3vQQ\u003d\u003d"
},
"code": "200000"
}
字段 | 类型 | 含义 |
code | string | 200000成功,其他失败 |
chargeStatus | string | 1:收费 0:不收费 |
tradeNo | string | 交易流水号 |
mobileName | string | DES加密的真实手机号,使用CBC模式,PKCS5Padding填充原则。CBC模式对应的iv偏移向量为:byte[] iv = { 1, 2, 3, 4, 5, 6, 7, 8 },key为appKey |
fanqizha/tag | string | 防羊毛识别标识,此功能需要在后台开通 fanqizha:int 1:白名单 2:黑名单 3:可信任度低 4:未找到 tag:标签属性,判断的依据信息,用于黑名单。样例:批量操作/系统环境异常/信用低/小号养号/号码风险模型评分中/可能存在作弊行为 |
5.手动关闭授权页
当开发者设置点击一键登录或者自定义控件不自动销毁授权页时,将需要自行调用此方法主动销毁授权页,建议在置换手机号成功后销毁。如在得到回调后未销毁授权页而,使用拉起授权页方法再次拉起授权页,此页面将无法响应任何按键(除了导航栏的返回按钮)。
- 关闭授权页时机
a.SDK拉起授权页方法 直接回调失败时
b.置换手机号有返回结果时
- 当前页面直接销毁
//方式1
[self.PresentedViewController dismissViewControllerAnimated:YES completion:nil];
//方式二
[CLShanYanSDKManager finishAuthControllerCompletion:^{
//如需关闭后present/push新页面,建议在completion回调中执行
//注:若未拉起授权页,调用此方法,block不会触发
//用户跳转短信验证
CustomSmsViewController * smsVc = [[CustomSmsViewController alloc]init];
CustomNavigationController * smsNav = [[CustomNavigationController alloc]initWithRootViewController:smsVc];
smsVc.navigationItem.title = @"短信验证";
[self presentViewController:smsNav animated:YES completion:nil];
}];
- 找到topVC进行dismiss
dispatch_async(dispatch_get_main_queue(), ^{
//建议使用授权页面配置对象传入的viewcontroller 调 dismiss
if (self.navigationController.viewControllers.lastObject.navigationController) {
[self.navigationController.viewControllers.lastObject dismissViewControllerAnimated:YES completion:nil];
} else {
UIViewController *topRootViewController = [[UIApplication sharedApplication] keyWindow].rootViewController;
// 在这里加一个这个样式的循环
while (topRootViewController.presentedViewController) {
// 这里固定写法
topRootViewController = topRootViewController.presentedViewController;
}
// 然后再进行present操作
[topRootViewController dismissViewControllerAnimated:YES completion:nil];
}
});
三.授权界面修改
设计规范
开发者不得通过任何技术手段,将授权页面的隐私栏、品牌露出内容隐藏、覆盖,对于接入闪验SDK并上线的应用,我方和运营商会对上线的应用授权页面做审查,如果有出现未按要求设计授权页面,将隐私栏、运营商品牌、授权登录按钮隐去不可见的设计,我方有权将应用的登录功能下线。
页面可调整属性
注:授权页基本控件均支持上、下、左、右、宽、高、水平中心、竖直中心布局设置,布局通过布局对象设置,布局定位更加方便快捷,建议使用最新布局对象进行设置。
//要拉起授权页的vc [必填项] (注:SDK不持有接入方VC)
UIViewController * viewController;
/**
*外部手动管理关闭界面
*BOOL,default is NO
*eg.@(YES)
*/
NSNumber * manualDismiss;
/**授权页-背景图片*/
UIImage *clBackgroundImg;
//导航栏
/**导航栏 是否隐藏 BOOL default is NO, 设置优先级高于clNavigationBackgroundClear eg.@(NO)*/
NSNumber * clNavigationBarHidden;
/**导航栏 背景透明 BOOL eg.@(YES)*/
NSNumber * clNavigationBackgroundClear;
/**导航栏标题*/
NSAttributedString * clNavigationAttributesTitleText;
/**导航栏右侧自定义按钮*/
UIBarButtonItem * clNavigationRightControl;
/**导航栏左侧自定义按钮*/
UIBarButtonItem * clNavigationLeftControl;
/**导航栏左侧返回按钮图片*/
UIImage * clNavigationBackBtnImage;
/**导航栏左侧自定义按钮隐藏设置,默认显示 BOOL eg.@(YES)*/
NSNumber * clNavigationBackBtnHidden;
/**导航栏分割线 是否隐藏 default is NO, BOOL eg.@(YES) */
NSNumber * clNavigationBottomLineHidden;
/**导航栏 文字颜色*/
UIColor * clNavigationTintColor;
/**导航栏 背景色 default is white*/
UIColor * clNavigationBarTintColor;
/**导航栏 背景图片*/
UIImage * clNavigationBackgroundImage;
/**导航栏 配合背景图片设置,用来控制在不同状态下导航栏的显示(横竖屏是否显示) UIBarMetrics eg.@(UIBarMetricsCompact)*/
NSNumber * clNavigationBarMetrics;
/**导航栏 导航栏底部分割线(图片)*/
UIImage * clNavigationShadowImage;
/**
*NavigationBar.barStyle:默认UIBarStyleBlack
*Info.plist: View controller-based status bar appearance = YES
*导航栏隐藏时:
*状态栏黑白由 statusBarStyle 决定:
*UIStatusBarStyleLightContent:状态栏显示 黑
*UIStatusBarStyleLightContent:状态栏显示 白
*导航栏显示时:
*状态栏黑白由 navigation_barStyle 决定
*UIBarStyleDefault:状态栏显示 黑
*UIBarStyleBlack:状态栏显示 白
*
*eg. @(UIBarStyleBlack)
*/
NSNumber * clNavigationBarStyle;
//LOGO图片
/**LOGO图片*/
UIImage * clLogoImage;
/**LOGO圆角 CGFloat eg.@(2.0)*/
NSNumber * clLogoCornerRadius;
/**LOGO显隐 BOOL eg.@(NO)*/
NSNumber * clLogoHiden;
/**手机号显示控件*/
/**手机号颜色*/
UIColor * clPhoneNumberColor;
/**手机号字体*/
UIFont * clPhoneNumberFont;
/**手机号对齐方式 NSTextAlignment eg.@(NSTextAlignmentCenter)*/
NSNumber * clPhoneNumberTextAlignment;
/*一键登录按钮 控件
注: 一键登录授权按钮 不得隐藏
**/
/**按钮文字*/
NSString * clLoginBtnText;
/**按钮文字颜色*/
UIColor * clLoginBtnTextColor;
/**按钮背景颜色*/
UIColor * clLoginBtnBgColor;
/**按钮文字字体*/
UIFont * clLoginBtnTextFont;
/**按钮背景图片*/
UIImage * clLoginBtnNormalBgImage;
/**按钮边框颜色*/
UIColor * clLoginBtnBorderColor;
/**按钮偏圆角 CGFloat eg.@(5)*/
NSNumber * clLoginBtnCornerRadius;
/**按钮偏边框 CGFloat eg.@(2.0)*/
NSNumber * clLoginBtnBorderWidth;
/*隐私条款Privacy
注: 运营商隐私条款 不得隐藏
用户条款不限制
**/
/**隐私条款名称颜色:@[基础文字颜色UIColor*,条款颜色UIColor*] eg.@[[UIColor lightGrayColor],[UIColor greenColor]]*/
NSArray<UIColor*> *clAppPrivacyColor;
/**隐私条款文字字体*/
UIFont * clAppPrivacyTextFont;
/**隐私条款--APP名称简写 默认取CFBundledisplayname*/
NSString * clAppPrivacyAbbreviatedName;
/**隐私条款文字对齐方式 NSTextAlignment eg.@(NSTextAlignmentCenter)*/
NSNumber * clAppPrivacyTextAlignment;
/**运营商隐私条款书名号 默认NO 不显示 BOOL eg.@(YES)*/
NSNumber * clAppPrivacyPunctuationMarks;
/**隐私条款一:需同时设置Name和UrlString eg.@[@"条款一名称":@"条款一URL"]*/
NSArray<NSString*> * clAppPrivacyFirst;
/**隐私条款二:需同时设置Name和UrlString eg.@[@"条款一名称":@"条款一URL"]*/
NSArray<NSString*> * clAppPrivacySecond;
/**隐私协议WEB页面导航栏标题 NSAttributedString*/
NSAttributedString * clAppPrivacyWebAttributesTitle;
/**隐私协议WEB页面导航返回按钮图片*/
UIImage * clAppPrivacyWebBackBtnImage;
/**多行时行距 CGFloat eg.@(2.0)*/
NSNumber* clAppPrivacyLineSpacing;
/**是否需要sizeToFit,设置后与宽高约束的冲突请自行考虑 BOOL eg.@(YES)*/
NSNumber* clAppPrivacyNeedSizeToFit;
/*SLOGAN
注: 运营商品牌标签,不得隐藏
**/
/**slogan文字字体*/
UIFont * clSloganTextFont;
/**slogan文字颜色*/
UIColor * clSloganTextColor;
/**slogan文字对齐方式 NSTextAlignment eg.@(NSTextAlignmentCenter)*/
NSNumber * clSlogaTextAlignment;
/*CheckBox
*协议勾选框,默认选中且在协议前显示
*可在sdk_oauth.bundle中替换checkBox_unSelected、checkBox_selected图片
*也可以通过属性设置选中和未选择图片
**/
/**协议勾选框(默认显示,放置在协议之前)BOOL eg.@(YES)*/
NSNumber *clCheckBoxHidden;
/**协议勾选框默认值(默认不选中)BOOL eg.@(YES)*/
NSNumber *clCheckBoxValue;
/**协议勾选框 尺寸 CGSize eg.[NSValue valueWithCGSize:CGSizeMake(25, 25)]*/
NSValue *clCheckBoxSize;
/**协议勾选框 UIButton.image图片缩进 UIEdgeInset eg.[NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(2, 2, 2, 2)]*/
NSValue *clCheckBoxImageEdgeInsets;
/**协议勾选框 设置CheckBox顶部与隐私协议控件顶部对齐 YES或大于0生效 eg.@(YES)*/
NSNumber *clCheckBoxVerticalAlignmentToAppPrivacyTop;
/**协议勾选框 设置CheckBox顶部与隐私协议控件竖向中心对齐 YES或大于0生效 eg.@(YES)*/
NSNumber *clCheckBoxVerticalAlignmentToAppPrivacyCenterY;
/**协议勾选框 非选中状态图片*/
UIImage *clCheckBoxUncheckedImage;
/**协议勾选框 选中状态图片*/
UIImage *clCheckBoxCheckedImage;
/*Loading*/
/**Loading 大小 CGSize eg.[NSValue valueWithCGSize:CGSizeMake(50, 50)]*/
NSValue *clLoadingSize;
/**Loading 圆角 float eg.@(5) */
NSNumber *clLoadingCornerRadius;
/**Loading 背景色 UIColor eg.[UIColor colorWithRed:0.8 green:0.5 blue:0.8 alpha:0.8]; */
UIColor *clLoadingBackgroundColor;
/**UIActivityIndicatorViewStyle eg.@(UIActivityIndicatorViewStyleWhiteLarge)*/
NSNumber *clLoadingIndicatorStyle;
/**Loading Indicator渲染色 UIColor eg.[UIColor greenColor]; */
UIColor *clLoadingTintColor;
/**授权页自定义Loading
- containerView为loading的全屏蒙版view
- 请自行在containerView添加自定义loading
- 设置block后,上述loading属性将无效
*/
void(^loadingView)(UIView * containerView);
//添加自定义控件
/**可设置背景色及添加控件*/
void(^customAreaView)(UIView * customAreaView);
//竖屏布局配置对象 -->创建一个布局对象,设置好控件约束属性值,再设置到此属性中
/**竖屏:UIInterfaceOrientationPortrait|UIInterfaceOrientationPortraitUpsideDown
*由于运营商限制不同,外部自动旋转条件满足时,电信联通页面自动旋转,均使用此约束;移动强制竖屏
*eg. CLUIConfigure * baseUIConfigure = [CLUIConfigure new];
* CLOrientationLayOut * clOrientationLayOutPortrait = [CLOrientationLayOut new];
* clOrientationLayOutPortrait.clLayoutPhoneCenterY = @(0);
* clOrientationLayOutPortrait.clLayoutPhoneLeft = @(50*screenScale);
* ...
* baseUIConfigure.clOrientationLayOutPortrait = clOrientationLayOutPortrait;
*/
@property (nonatomic,strong) CLOrientationLayOut * clOrientationLayOutPortrait;
/*CLOrientationLayOut:
*横竖屏布局配置对象
*配置页面布局相关属性
*/
/**LOGO图片*/
// 约束均相对vc.view
NSNumber * clLayoutLogoLeft;
NSNumber * clLayoutLogoTop;
NSNumber * clLayoutLogoRight;
NSNumber * clLayoutLogoBottom;
NSNumber * clLayoutLogoWidth;
NSNumber * clLayoutLogoHeight;
NSNumber * clLayoutLogoCenterX;
NSNumber * clLayoutLogoCenterY;
/**手机号显示控件*/
//layout 约束均相对vc.view
NSNumber * clLayoutPhoneLeft;
NSNumber * clLayoutPhoneTop;
NSNumber * clLayoutPhoneRight;
NSNumber * clLayoutPhoneBottom;
NSNumber * clLayoutPhoneWidth;
NSNumber * clLayoutPhoneHeight;
NSNumber * clLayoutPhoneCenterX;
NSNumber * clLayoutPhoneCenterY;
/*一键登录按钮 控件
注: 一键登录授权按钮 不得隐藏
**/
//layout 约束均相对vc.view
NSNumber * clLayoutLoginBtnLeft;
NSNumber * clLayoutLoginBtnTop;
NSNumber * clLayoutLoginBtnRight;
NSNumber * clLayoutLoginBtnBottom;
NSNumber * clLayoutLoginBtnWidth;
NSNumber * clLayoutLoginBtnHeight;
NSNumber * clLayoutLoginBtnCenterX;
NSNumber * clLayoutLoginBtnCenterY;
/*隐私条款Privacy
注: 运营商隐私条款 不得隐藏, 用户条款不限制
**/
//layout 约束均相对vc.view
NSNumber * clLayoutAppPrivacyLeft;
NSNumber * clLayoutAppPrivacyTop;
NSNumber * clLayoutAppPrivacyRight;
NSNumber * clLayoutAppPrivacyBottom;
NSNumber * clLayoutAppPrivacyWidth;
NSNumber * clLayoutAppPrivacyHeight;
NSNumber * clLayoutAppPrivacyCenterX;
NSNumber * clLayoutAppPrivacyCenterY;
/*Slogan 运营商品牌标签:"认证服务由中国移动/联通/电信提供" label
注: 运营商品牌标签,不得隐藏
**/
//layout 约束均相对vc.view
NSNumber * clLayoutSloganLeft;
NSNumber * clLayoutSloganTop;
NSNumber * clLayoutSloganRight;
NSNumber * clLayoutSloganBottom;
NSNumber * clLayoutSloganWidth;
NSNumber * clLayoutSloganHeight;
NSNumber * clLayoutSloganCenterX;
NSNumber * clLayoutSloganCenterY;
添加自定义控件示例
ObjC:
// 快捷登录
- (void)quickLoginBtnClick:(UIButton *)sender {
...
CLUIConfigure * baseUIConfigure = [CLUIConfigure new];
baseUIConfigure.viewController = self;
CGFloat screenScale = [UIScreen mainScreen].bounds.size.width/375.0;
baseUIConfigure.customAreaView = ^(UIView * _Nonnull customAreaView) {
UIButton * button = [[UIButton alloc]init];
[button setTitle:@"其他方式登录" forState:(UIControlStateNormal)];
button.titleLabel.font = [UIFont systemFontOfSize:14];
[button setTitleColor:[UIColor grayColor] forState:(UIControlStateNormal)];
[button addTarget:self action:@selector(otherLoginWayBtnCliced:) forControlEvents:(UIControlEventTouchUpInside)];
[customAreaView addSubview:button];
[button mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(0);
make.top.mas_equalTo(280*screenScale);
make.height.mas_equalTo(40);
}];
};
...
}
//授权页 点击自定义控件绑定的方法
-(void)otherLoginWayBtnCliced:(UIButton *)sender{
//关闭页面
[CLShanYanSDKManager finishAuthControllerCompletion:^{
//如需关闭后present/push新页面,建议在completion回调中执行
}];
[SVProgressHUD showInfoWithStatus:@"用户使用其他方式进行注册登录"];
}
Swift:
@IBAction func quickLogin(_ sender: UIButton) {
...
let baseUIConfigure = CLUIConfigure()
//requried
baseUIConfigure.viewController = self
//Optional:
let screenScale = UIScreen.main.bounds.size.width/375.0;
baseUIConfigure.customAreaView = {
[unowned self] view in
let otherLoginButtonWay = UIButton()
otherLoginButtonWay.setTitle("其他方式登录", for: .normal)
otherLoginButtonWay.setTitleColor(UIColor.gray, for: .normal)
otherLoginButtonWay.titleLabel?.font = UIFont.systemFont(ofSize: 15)
view.addSubview(otherLoginButtonWay)
otherLoginButtonWay.snp.makeConstraints { (make) in
make.centerX.equalToSuperview()
make.top.equalTo(280*screenScale)
make.width.equalToSuperview()
make.height.equalTo(40)
}
otherLoginButtonWay.addTarget(self, action: #selector(self.otherLoginButtonWay(_:)), for: .touchUpInside)
}
...
}
//授权页 点击其他方式登录
@objc func otherLoginButtonWay(_ sender: UIButton) {
DispatchQueue.main.async(execute: {
SVProgressHUD.showSuccess(withStatus: ("用户选择其他方式登录"))
})
}
四.返回码对照
外层错误码
同一外层码可能对应不同的内层码
外层返回码 | 返回码描述 |
1000 | 一键登录成功,解析result,可得到网络请求参数 |
1011 | 用户取消免密登录(点击返回按钮) |
1001 | SDK初始化失败 |
1023 | 预取号/取号失败 |
1003 | 拉起授权页失败/一键登录失败/获取token失败 |
1008 | 未开启移动网络 |
1009 | 未检测/识别到sim卡 |
其他 | 其他错误 |
内层错误码
联通返回码
状态码 | 信息(msg) | 示例说明 |
100 | 成功 | |
101 | 鉴权失败 | 公钥或者apiKey不正确 |
103 | 成功(无数据返回) | |
104 | 系统繁忙 | |
100000 | 通用,未明确定义的其他错误 | |
100001 | 网络出错 | apiKey配置错误或者404错误 |
100002 | 参数错误 | 服务未初始化 |
100003 | 数据格式错误 | 服务器返回结果在客户端解析出错 |
100004 | 无结果 | 服务器返回null |
100005 | 数据不匹配 | 短信上行的认证结果与填入的不匹配计为认证失败 |
100006 | 包名不匹配 | 服务器返回的结果非本app计为失败 |
100007 | apikey为空 | 应用未填入apiKey密钥 |
100008 | 超时 | 取号或认证时间超过超时时间 |
100009 | 取消 | 用户取消操作,计为失败 |
100012 | 无公钥 | 应用未填入公钥 |
100016 | 公钥出错 | |
2001 | 取号/认证失败 | |
5001 | 序列号不存在 | seq过期或已使用(seq只能使用一次) |
6001 | 参数出错 | 配置信息填写错误 |
6002 | 应用信息错误 | |
6003 | SDK信息错误 | |
6005 | 接入信息错误 | |
6006 | 获取接入信息流控类型错误 | |
6008 | 流控超限 | |
6009 | 设备信息未注册 | |
6010 | 应用秘钥信息不匹配 | |
7004 | 公司或应用ID信息出错 | |
8001 | 无支付权限 | |
8002 | 余额不足 |
电信返回码
返回码 | 返回码描述 |
0 | 请求成功 |
-64 | permission-denied(无权限访问) |
-65 | API-request-rates-Exceed-Limitations(调用接口超限) |
-10001 | 取号失败 |
-10002 | 参数错误 |
-10003 | 解密失败 |
-10004 | ip受限 |
-10005 | 异网取号回调参数异常 |
-10006 | Mdn取号失败,且属于电信网络 |
-10007 | 重定向到异网取号 |
-10008 | 超过预设取号阈值 |
-10009 | 时间戳过期 |
-20005 | sign-invalid(签名错误) |
-7999 | 服务不可用(其他错误,默认返回值) |
-8000 | responseCode非200(网络错误,http状态码错误) |
-8001 | 请求网络异常 |
-8100 | 无网络连接(网络错误) |
-8101 | 获取失败(切换失败) |
-9999 | 网络故障(networkauth-fail) |
-30003 | topClass-invalid |
-8003 | 请求超时 |
移动返回码
返回码 | 返回码描述 |
103000 | 成功 |
102101 | 无网络 |
102102 | 网络异常 |
102103 | 未开启数据网络 |
102121 | 用户取消登录 |
102203 | 输入参数错误 |
102223 | 数据解析异常 |
102507 | 请求超时 |
102508 | 数据网络切换失败 |
200002 | 手机未安装sim卡 |
200005 | 用户未授权(READ_PHONE_STATE) |
200006 | 用户未授权(SEND_SMS) |
200007 | authType仅使用短信验证码认证 |
200008 | 1. authType参数为空;2. authType参数不合法; |
200009 | 应用合法性校验失败(包名包签名未填写正确) |
200010 | 预取号时imsi获取失败或者没有sim卡 |
200012 | 取号失败,跳短信验证码登录 |
200013 | 短信上行发送短信失败(短信上行) |
200014 | 手机号码格式错误(短验) |
200015 | 短信验证码格式错误 |
200016 | 更新KS失败 |
200017 | 非移动卡不支持短信上行 |
200018 | 不支持网关登录 |
200019 | 不支持短信验证码登录 |
200020 | 用户取消登录 |
200021 | 数据解析异常(服务器异常可重新尝试) |
200022 | 无网络状态 |
200023 | 请求超时 |
200024 | 数据网络切换失败 |
200025 | 未知错误一般出现在线程捕获异常,请配合异常打印分析 |
200026 | 输入参数错误 |
200027 | 预取号时未开启数据流量 |
200028 | 网络请求出错(根据日志分析) |
200029 | 请求出错,上次请求未完成 |
200030 | 没有初始化参数 |
200031 | 生成token失败 |
200032 | KS缓存不存在 |
200033 | 复用中间件获取Token失败 |
200034 | 预取号token失效 |
200035 | 协商ks失败 |
200036 | 预取号失败 |
200037 | 获取不到openid |
200038 | 电信重定向失败 |
200039 | 电信取号接口返回失败 |
200040 | UI资源加载异常 |
200042 | 授权页弹出异常 |
200060 | 用户点击“切换账号”且使用应用自己的短信验证码时返回 |
200062 | 联通号码不支持预取号 |
200063 | 电信号码不支持预取号 |
获取手机号码接口返回码 | |
返回码 | 返回码描述 |
103000 | 返回成功 |
103101 | 签名错误 |
103102 | 包名/包签名/bundle id错误 |
103106 | 输入手机号不合法(短验) |
103107 | 已存在相同的随机数(短验) |
103108 | 短信验证码错误 |
103109 | 短信验证码超时 |
103111 | wap网关IP错误 |
103112 | 错误的请求 |
103113 | token校验错误 |
103119 | appid不合法 |
103122 | btid不存在 |
103125 | 手机号格式错误(短验) |
103127 | 证书验证:版本过期 |
103133 | sourceid不合法 |
103138 | appid已存在 |
103139 | sourceid已存在 |
103202 | 缓存用户不存在或者验证短信输入失败次数过多 |
103203 | 缓存用户不存在(短信上行) |
103204 | 缓存随机数不存 |
103205 | 服务器异常 |
103207 | 发送短信失败 |
103211 | 其他错误 |
103214 | 访问缓存或数据库错误 |
103401 | 消息签名为空 |
103404 | 加密失败 |
103408 | 此sourceId注册已达上限99次 |
103409 | query参数为空 |
103412 | 无效的请求 |
103414 | 参数效验异常 |
103505 | 重放攻击 |
103511 | 源IP不合法 |
103810 | token校验失败,接口版本不一致 |
103811 | token为空 |
103901 | 短信验证码下发次数已达上限 (一分钟内最多5次,一天最多10次) |
103902 | 临时取号凭证scrip校验失败 |
103911 | 请求过于频繁(短信校验) |
103922 | 自动升级文件没找到 |
104201 | token已经被校验过或者超时失效 |
104202 | 短信验证失败过多 |
105001 | 联通网关取号失败 |
105002 | 移动网关取号失败 |
105003 | 电信网关取号失败 |
105005 | 短信上行发送信息为空 |
105007 | 手机号码格式错误(短信上行) |
105008 | 短信内容为空(短信上行) |
105010 | script失效或者非法 |
105011 | 参数加密的私钥失效或者非法 |
105012 | 不支持电信取号 |
105013 | 不支持联通取号 |
105018 | 用户权限不够 |
105019 | 应用未授权(未勾选能力) |
105302 | AppId不在白名单 |
五.已知问题
更多问题查询请移步至【常见问题】
1.ATS开关(Http与Https)
目前运营商个别接口为http请求,对于全局禁用Http的项目,需要设置Http白名单。以下为运营商http接口host名单:
*.cmpassport.com、id6.me、123.125.99.8:9001、ms.zzx9.cn、mdn.open.wo.cn、10.99.255.231,*为通配符,建议按以下方式配置Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>zzx9.cn</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>cmpassport.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>id6.me</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>wostore.cn</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>mdn.open.wo.cn</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
2.移动报 获取移动token失败 Code=1003 “(null)” UserInfo={desc=错误的请求签名, resultCode=103101}
【获取token失败、错误的请求签名、103101】
工程中的Info.plist中添加bundleId对应的key-value。搜索Xcode项目工程中的.strings国际化文件,如果app存在本地国际化文件,请在每个本地国际化文件中添加CFBundleIdentifier,各个语言版本文件中都需要添加。
3.联通预取号崩溃【预取号+联通+崩溃、NSDictionary + setObject、空指针】
联通SDK内部会读取校验info.plist文件中Bundle identifier、Bundle name、Bundle versions string, short、Bundle version字段,如未配置相关字段Value会导致以下崩溃