FinClip为企业提供小程序生态圈技术产品,开发者可在FinClip小程序开发帮助中心找到相关FinClip小程序指引

# 小程序登录常见问题

请注意

针对微信登录,您也可以使用 FinClip 提供的关联微信小程序登录功能,降低开发成本,快速复用微信登录能力。点击这里了解能力

在微信小程序下,小程序登录功能一般会通过 OpenIDUnionID 作为唯一标识,与小程序服务的账号体系进行关联打通,完成用户账户体系的构建与设计。

对于从微信小程序环境迁移到 FinClip 下的小程序,就会遇到如下问题:

  1. 登录流程如何快速复用?怎样与小程序服务的账号体系关联?
  2. 如何让微信小程序中的用户数据与 FinClip 的数据打通?双方的账号体系如何关联?

遇到上述问题时,您可通过以下方案尝试解决:

# 1. 登录方案

# 方案一:服务端改造+小程序改造

适用场景:平台方通过 FinClip 构建自有生态,FinClip 环境中运行的都是第三方的小程序(即平台方无法修改小程序代码)。

  • 第一步:在 App 中初始化SDK时,Config上配置userId、channel信息;
  • 第二步:小程序集成统一登录插件,从login接口获取的code、appId、apiServer等。
  • 第三步:调用request接口,将 code 、appId、apiServer发送给开发者的服务器;
  • 第四步:开发者服务器进行适配,通过code和apiServer,从apiServer对应小程序管理平台开放接口解析出 OpenIDUnionID,返回自身账户体系的内容即可。

# 方案二:服务端改造(唤起微信授权)

使用场景:平台方通过 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

  1. 自定义login接口
  2. 自定义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};
    }
}
  1. 注册自定义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;
  }
  1. 实现自定义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 端:

  1. 在原生App中注册自定义的login api
[[FATClient sharedClient]registerExtensionApi:@"login" handle:^(id param, FATExtensionApiCallback callback) {
        NSDictionary *resDic = @{};
        //回调给小程序用户信息的结果
        if (resDic) {
            callback(FATExtensionCodeSuccess,resDic);
        }else{
            callback(FATExtensionCodeFailure,resDic);
        }
    }];
  1. 小程序中调用声明的getUserProfile,得到用户信息
wx.login({
      success: (res) => {
        
      }
    })

# 2.3 获取用户信息

小程序中获取用户信息有两个接口:getUserInfo、getUserProfile。以及(open-type为getUserInfo类型)button组件。

  1. 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下,需要实现两个代理:IUserInfoHandlerIUserProfileHandler

具体见文档:iuserinfohandleriuserprofilehandler

  1. 小程序中调用获取用户信息接口
wx.getUserProfile({
  success: (res) => {
    console.log('getUserProfile success', res)
  },
  fail: (res) => {
    console.log('getUserProfile fail', res.errMsg)
  }
})

# 3. 常见问题

微信官方公告 (opens new window),自 2021 年 12 月 27 日后不再向开发者输出用户昵称与头像信息,您可能需要自行处理获取用户头像与昵称的相关逻辑。

常见的处理逻辑如下:

  1. 在小程序或小游戏中,不显示用户的头像与昵称,使用其他字段内容代替;
  2. 在用户进入小程序或小游戏后,随机给用户生成头像与昵称;
  3. 在用户进入小程序或小游戏后,引导用户自行配置头像,昵称等内容信息。
© FinClip with ❤ , Since 2017