# 小程序登录常见问题
请注意
针对微信登录,您也可以使用 FinClip 提供的关联微信小程序登录功能,降低开发成本,快速复用微信登录能力。点击这里了解能力
在微信小程序下,小程序登录功能一般会通过 OpenID
或 UnionID
作为唯一标识,与小程序服务的账号体系进行关联打通,完成用户账户体系的构建与设计。
对于从微信小程序环境迁移到 FinClip 下的小程序,就会遇到如下问题:
- 登录流程如何快速复用?怎样与小程序服务的账号体系关联?
- 如何让微信小程序中的用户数据与 FinClip 的数据打通?双方的账号体系如何关联?
遇到上述问题时,您可通过以下方案尝试解决:
# 1. 登录方案
# 方案一:服务端改造+小程序改造
适用场景:平台方通过 FinClip 构建自有生态,FinClip 环境中运行的都是第三方的小程序(即平台方无法修改小程序代码)。
- 第一步:在 App 中初始化SDK时,Config上配置userId、channel信息;
- 第二步:小程序集成统一登录插件,从login接口获取的code、appId、apiServer等。
- 第三步:调用request接口,将 code 、appId、apiServer发送给开发者的服务器;
- 第四步:开发者服务器进行适配,通过code和apiServer,从apiServer对应小程序管理平台开放接口解析出
OpenID
和UnionID
,返回自身账户体系的内容即可。
# 方案二:服务端改造(唤起微信授权)
使用场景:平台方通过 FinClip 完成自身功能的拆分,小程序都由平台方自己开发(即平台方可以修改小程序代码)。
- 第一步:SDK 中集成微信开发平台 SDK;
- 第二步:使用自定义 API 注入
wx.login
,唤起微信授权后即可取得返回 code,此时可能需要对 code 拼接唯一标识; - 第三步:小程序无需改动,按照在微信端的实现,此时会将 code 发送给开发者的服务器;
- 第四步:服务端根据 code 唯一标识,调用不同的授权接口取得
OpenID
,查询登录态返回;
限制:由于微信限制不同主体,不同开放平台下的 OpenID
是不一致的,此时小程序需要关联到同一主体,或者同一开放平台(此时唯一标识为 UnionID
)。
# 2. 方案实现代码示例
我们提供了对应的实现代码,供您参考。
# 2.1 方案一
首先,app集成SDK,初始化SDK时,配置userId和channel。
iOS
FATConfig *config = [FATConfig configWithStoreConfigs:storeArrayM];
config.currentUserId = @"xxxxx";
config.channel = @"finclip";
config.phone = @"12345678901";
FATUIConfig *uiConfig = [[FATUIConfig alloc] init];
BOOL flag = [[FATClient sharedClient] initWithConfig:config uiConfig:uiConfig error:nil];
Android
val config = FinAppConfig.Builder()
.setFinStoreConfigs(finStoreConfigs)
.setUserId("xxxxxx")
.setChannel("finclip")
.setPhone("12345678901")
.build()
FinAppClient.init(application, config, object : FinCallback<Any?> {
override fun onSuccess(result: Any?) {
}
override fun onError(code: Int, error: String) {
Log.d(TAG, "init code:$code error:$error")
}
override fun onProgress(status: Int, error: String?) {
}
})
然后,小程序在app.json中添加统一登录的插件
"plugins":{
"loginPlugins": {
"version": "1.0.5",
"provider": "2275269218722181"
}
}
实际中插件的版本和provider,因服务器不同可能会略有不同。
再然后,小程序中调用登录接口,以及将appId、code、apiServer传递给小程序页面后台。
wx.login({
success: (res) => {
console.log('获取到的登录code为:', res)
//此处添加调用小程序业务服务获取用户信息逻辑
const appId = res.appId;
const code = res.code;
const apiServer = res.apiServer;
// 将appId、code、apiServer,传递给小程序业务后台。
// 小程序业务后台,需要调用小程序管理平台的开放接口
wx.request({
url: url,
data: {
appId: appId,
code: code,
apiServer: apiServer
},
success:(res) {
console.log("openId:", res.data.openId);
console.log("unionId:", res.data.unionId);
}
})
}
})
而服务器端的open 接口说明如下:
地址
{apiserver}/api/v1/open/dev/mini-apps/unified-auth/login
请求类型
POST
请求参数
字段 | 类型 | 添加位置 | 描述 |
---|---|---|---|
Authorization | String | 请求的header中,格式:Bearer {personalAccessToken} | personalAccessToken 在开发中心的个人中心生成。请确保勾选了「小程序统一认证」的scope |
appId | String | 请求的body中 | 小程序id |
code | String | 请求的body中 | login接口返回的code |
返回内容
字段 | 类型 | 描述 |
---|---|---|
data | Map | 返回的数据,包含channel、openId、unionId、accessToken |
error | String | 错误提示 |
errcode | String | 错误码 |
请求示例:
POST {小程序服务器 apiserver}/api/v1/open/dev/mini-apps/unified-auth/login
Body application/json
{
"appId":"fc2183741419665349", "code":"1drac2l7scWpTtetZtF4tA02llZegeLbJSxTYTUG6MmjBA2L3xewOk7IjSqY72ZNDsvWKU0r2EdLKcLqS82g0EKnDgE0uSu1REmvFpPIF8Y="
}
Response
{
"error": "",
"errcode": "OK",
"data": {
"channel": "com.finogeeks.finosprite",
"openId": "2279547539610117",
"unionId": "5110c76fbc1c8ebc7429e906b83c1f7f",
"accessToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNlcnQtYnVpbHQtaW4iLCJ0eXAiOiJKV1QifQ.eyJvd25lciI6ImNvbS5maW5vZ2Vla3MuZmlub3Nwcml0ZSIsIm5hbWUiOiIyMjc5NTQ3NTM5NjEwMTE4IiwiY3JlYXRlZFRpbWUiOiIyMDI0LTA3LTE4IDEwOjE1OjQyIiwidXBkYXRlZFRpbWUiOiIyMDI0LTA3LTE4IDEwOjE1OjQyIiwiaWQiOiIyMjc5NTQ3NTM5NjEwMTE4IiwidHlwZSI6Im5vcm1hbC11c2VyIiwicGFzc3dvcmQiOiIiLCJwYXNzd29yZFNhbHQiOiIiLCJkaXNwbGF5TmFtZSI6IiIsImZpcnN0TmFtZSI6IiIsImxhc3ROYW1lIjoiIiwiYXZhdGFyIjoiIiwicGVybWFuZW50QXZhdGFyIjoiIiwiZW1haWwiOiIiLCJlbWFpbFZlcmlmaWVkIjpmYWxzZSwicGhvbmUiOiIiLCJsb2NhdGlvbiI6IiIsImFkZHJlc3MiOltdLCJhZmZpbGlhdGlvbiI6IiIsInRpdGxlIjoiIiwiaWRDYXJkVHlwZSI6IiIsImlkQ2FyZCI6IiIsImhvbWVwYWdlIjoiIiwiYmlvIjoiIiwicmVnaW9uIjoiIiwibGFuZ3VhZ2UiOiIiLCJnZW5kZXIiOiIiLCJiaXJ0aGRheSI6IiIsImVkdWNhdGlvbiI6IiIsInNjb3JlIjowLCJrYXJtYSI6MCwicmFua2luZyI6NCwiaXNEZWZhdWx0QXZhdGFyIjpmYWxzZSwiaXNPbmxpbmUiOmZhbHNlLCJpc0FkbWluIjpmYWxzZSwiaXNHbG9iYWxBZG1pbiI6ZmFsc2UsImlzRm9yYmlkZGVuIjpmYWxzZSwiaXNEZWxldGVkIjpmYWxzZSwic2lnbnVwQXBwbGljYXRpb24iOiJjb20uZmlub2dlZWtzLmZpbm9zcHJpdGVfZmMyMTgzNzQxNDE5NjY1MzQ5IiwiaGFzaCI6IiIsInByZUhhc2giOiIiLCJjcmVhdGVkSXAiOiIiLCJsYXN0U2lnbmluVGltZSI6IiIsImxhc3RTaWduaW5JcCI6IiIsImxkYXAiOiIiLCJwcm9wZXJ0aWVzIjp7fSwicm9sZXMiOltdLCJwZXJtaXNzaW9ucyI6W10sImxhc3RTaWduaW5Xcm9uZ1RpbWUiOiIiLCJzaWduaW5Xcm9uZ1RpbWVzIjowLCJ0b2tlblR5cGUiOiJhY2Nlc3MtdG9rZW4iLCJzY29wZSI6InByb2ZpbGUiLCJpc3MiOiJodHRwczovL2lhbS1tYW5hZ2VyOjgwMDAiLCJzdWIiOiIyMjc5NTQ3NTM5NjEwMTE4IiwiYXVkIjpbImU0ZDcwY2FmNDExZWY4YjU2YzRkIl0sImV4cCI6MTcyMTg3Mzc0MiwibmJmIjoxNzIxMjY4OTQyLCJpYXQiOjE3MjEyNjg5NDIsImp0aSI6ImFkbWluLzMxYzEzYzM0LThhZjUtNDYzYS05OGQwLTY0ZGEwNWY2NDViMiJ9.WCjVGoF55xeBQLXs7D0gHwHbif92_Zfc7pNtToAytiwtBpKfPAIzVUKh4w4xNtzCVNW0ov1ajNBNlP-qmDgd-jAgN_0HETPi3q_kYT4gc5Nh-iBCeXgHxXfBAl3is7J6999RAUVPyoM0T-CbU6A-opIGyXYYc1giCfta53rcMlM2PNYudquoCy9oyWjzriATWElQARP3lDHARJA_mRMoufVUgm92q5xq8Dw4gnpGKHSmweNhEqrjSj78jMz2_eeXMxfX1f2t-1asV4RahFVOZSSYLGcuFf44oK4GPMfzhOwwTIH4sqmNlsHIor8aT0DUFUU5HW3nMKDM3shl0zsnlgQT5-YdxhaYElLMjSOpY4pscwn6pdaGb9yYwkTommE_RxysmpXfA6TPuTg14_rNXQXxcJ1eDG4BjjHP9dp1Vk66AmUVnNbLZCheFioG1IsXIjaXnUUSxwyUCZRHL3GFrPbcwiyqmWwwgOLZfYetQTUO3MPJOY52eR0zS_-fWwGcHBf2SMZMb7Daz9w7u_fOL-FU1T57MvF97cEB3zx9o4GHIjd7K1gUX0UnvAvQsYUG2A6iJu9C2KziCDFH30q1rxCqrG35ZkwsKoYh1PHBu1ej-T1h9oRUFjzlO83N7LPR3KsQKCcgpkkchUje4pg2iD9-UDRoKsOuwn02qf1dlk0"
},
"traceid": "4fccb7b175cc401690235e4a400bb363"
}
# 2.2 方案二
# 2.1 APP接入微信登录
若您需要实现APP的微信账号授权登录,请参考微信开放平台的移动应用开发 (opens new window)和文档微信授权登录开发文档 (opens new window)进行实现,若不需要则直接从第2步开始即可。
# 2.2 APP实现自定义注入接口
首先,APP需要参考iOS SDK自定义注入接口和安卓 SDK自定义注入接口两份文档,将账号的授权登录能力注入至SDK中,让小程序能调用,详细说明如下。
Android端: 为了让小程序能够获取到小程序以外的APP数据,需要注册小程序自定义接口,自定义小程序接口具体说明请参照FinClip小程序开放平台-自定义小程序接口、FinClip小程序开放平台-在小程序进程中注册api。
- 自定义login接口
- 自定义Api类,指定api名称为“login”;
public class LoginApi extends AbsApi {
private final static String API_NAME_LOGIN = "login"; // 小程序基础库调用的api名称
@Override
public String[] apis() {
return new String[]{API_NAME_LOGIN};
}
}
- 注册自定义Api;
示例展示了用户授权提示Dialog,需要Activity对象作为Dialog的context参数, 所以需要在小程进程注册自定义Api。
if (FinAppClient.INSTANCE.isFinAppProcess(this)) {
// 小程序进程
// 小程序进程中注册api的方法能获取到小程序所在activity对象,可以用做创建对话框的context参数)
FinAppProcessClient.INSTANCE.setCallback(new FinAppProcessClient.Callback() {
@Override
public List<IApi> getRegisterExtensionApis(@NotNull Activity activity) {
ArrayList<IApi> apis = new ArrayList<>();
apis.add(new LoginApi());
return apis;
}
@Nullable
@Override
public List<IApi> getRegisterExtensionWebApis(@NotNull Activity activity) {
return null;
}
});
return;
}
- 实现自定义Api invoke()方法;
先展示授权提示Dialog(开发者请根据需求决定是否展示授权提示Dialog), 然后再从主进程(App进程)获取用户登录信息。
public class LoginApi extends AbsApi {
@Override
public void invoke(String event, JSONObject param, ICallback callback) {
if (event.equals(API_NAME_LOGIN)) {
showAuthDialog(callback);
}
}
/**
* 显示获取用户登录信息的授权提示对话框
*/
private void showAuthDialog(final ICallback callback) {
// 是否需要显示授权提示对话框请开发者按照产品需求自行处理
new AlertDialog.Builder(activity)
.setTitle("是否同意授权获取用户登录信息?")
.setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
callback.onFail();
}
})
.setPositiveButton("同意", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
loginMainProcess(callback);
}
})
.show();
}
/**
* 从主进程获取用户登录信息
*/
private void loginMainProcess(final ICallback callback) {
// 小程序进程调用主进程,在主进程获取用户信息后返回给小程序进程
FinAppProcessClient.INSTANCE.getAppletProcessApiManager()
.callInMainProcess(API_NAME_LOGIN, null, new FinCallback<String>() {
@Override
public void onSuccess(final String result) {
// 需要在主线程调用callback方法
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
callback.onSuccess(new JSONObject(result));
} catch (JSONException e) {
e.printStackTrace();
callback.onFail();
}
}
});
}
@Override
public void onError(int code, String error) {
callback.onFail();
}
@Override
public void onProgress(int status, String info) {
}
});
}
}
随后在主进程(通常是在Application里面,开发者也可以选择其他合适的位置), 返回用户登录信息给小程序进程。
// 在主进程设置"小程序进程调用主进程"的处理方法
// 开发者也可以选择在主进程其他合适的代码位置设置处理方法
FinAppClient.INSTANCE.getAppletApiManager()
.setAppletProcessCallHandler(new IAppletApiManager.AppletProcessCallHandler() {
@Override
public void onAppletProcessCall(@NotNull String name,
@Nullable String params,
@Nullable FinCallback<String> callback) {
if (callback != null) {
if (name.equals(LoginApi.API_NAME_LOGIN)) {
// 从主进程获取登录信息,返回给小程序进程
// 这里返回的是虚拟的用户登录信息,开发者请从APP里面自行获取用户登录信息
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("userId", "123");
} catch (JSONException e) {
e.printStackTrace();
}
callback.onSuccess(jsonObject.toString());
}
}
}
});
至此,小程序通过自定义Api登录的整个流程就已经完成了。
注意:如果产品需求不需要展示用户授权提示Dialog,建议在主进程(APP进程)注册自定义Api,从而省掉上述在小程序进程调用主进程(APP进程)方法获取数据的过程。
iOS 端:
- 在原生App中注册自定义的
login
api
[[FATClient sharedClient]registerExtensionApi:@"login" handle:^(id param, FATExtensionApiCallback callback) {
NSDictionary *resDic = @{};
//回调给小程序用户信息的结果
if (resDic) {
callback(FATExtensionCodeSuccess,resDic);
}else{
callback(FATExtensionCodeFailure,resDic);
}
}];
- 小程序中调用声明的getUserProfile,得到用户信息
wx.login({
success: (res) => {
}
})
# 2.3 获取用户信息
小程序中获取用户信息有两个接口:getUserInfo、getUserProfile。以及(open-type为getUserInfo
类型)button组件。
- app实现对应的代理方法,在代理方法中返回用户信息。
iOS
iOS下需要实现FATAppletButtonOpenTypeDelegate
中的如下两个代理方法:
- (BOOL)getUserInfoWithAppletInfo:(FATAppletInfo *)appletInfo bindGetUserInfo:(void (^)(NSDictionary *result))bindGetUserInfo;
- (BOOL)getUserProfileWithAppletInfo:(FATAppletInfo *)appletInfo bindGetUserProfile:(void (^)(NSDictionary *result))bindGetUserProfile;
具体见文档:buttonopentypedelegate
Android
Android下,需要实现两个代理:IUserInfoHandler
和 IUserProfileHandler
具体见文档:iuserinfohandler 和 iuserprofilehandler
- 小程序中调用获取用户信息接口
wx.getUserProfile({
success: (res) => {
console.log('getUserProfile success', res)
},
fail: (res) => {
console.log('getUserProfile fail', res.errMsg)
}
})
# 3. 常见问题
微信官方公告 (opens new window),自 2021 年 12 月 27 日后不再向开发者输出用户昵称与头像信息,您可能需要自行处理获取用户头像与昵称的相关逻辑。
常见的处理逻辑如下:
- 在小程序或小游戏中,不显示用户的头像与昵称,使用其他字段内容代替;
- 在用户进入小程序或小游戏后,随机给用户生成头像与昵称;
- 在用户进入小程序或小游戏后,引导用户自行配置头像,昵称等内容信息。