uniapp作为跨端开发框架,在App端使用原生渲染(基于Weex或纯原生),无法直接运行H5的JS SDK。要实现号码认证一键登录,需要将易盾的原生SDK封装为uniapp原生插件,通过 uni.requireNativePlugin 在JS层调用。

本文聚焦uniapp App端(Android + iOS)的插件封装和接入流程。

整体架构

uniapp JS层 (Vue页面)
    │  uni.requireNativePlugin('Yidun-QuickLogin')
    │
    ▼
原生插件层 (Android: Module | iOS: Framework)
    │  封装 QuickLogin / NTESQuickPass SDK
    │  将原生回调桥接为 uniapp 事件
    │
    ▼
易盾号码认证 SDKAndroid: quicklogin 3.6.0+
    │  iOS: NTESQuickPass 3.2.3+
    │
    ▼
运营商网关 → 返回 token + accessToken

核心思路:原生插件负责SDK初始化、预取号、授权页拉起、Token获取,通过事件回调将结果传递给JS层。

Android端插件封装

插件目录结构

nativeplugins/Yidun-QuickLogin/
├── package.json          # 插件描述
└── android/
    ├── build.gradle      # 依赖配置
    └── src/
        └── QuickLoginModule.java

package.json

{
  "name": "Yidun-QuickLogin",
  "id": "Yidun-QuickLogin",
  "version": "1.0.0",
  "description": "易盾号码认证一键登录uniapp插件",
  "_dp_type": "nativeplugin",
  "_dp_nativeplugin": {
    "android": {
      "hooksClass": "",
      "plugins": [
        {
          "type": "module",
          "name": "Yidun-QuickLogin",
          "class": "com.yidun.uni.QuickLoginModule"
        }
      ],
      "integrateType": "aar"
    }
  }
}

build.gradle(依赖易盾SDK)

dependencies {
    // 易盾号码认证SDK
    implementation 'io.github.yidun:quicklogin:3.6.0'
    // uniapp必需的依赖
    compileOnly 'com.alibaba:fastjson:1.2.83'
    provided files('libs/uniapp-v8-release.aar')
}

QuickLoginModule.java(核心封装)

package com.yidun.uni;

import com.alibaba.fastjson.JSONObject;
import com.netease.nis.quicklogin.QuickLogin;
import com.netease.nis.quicklogin.helper.UnifyUiConfig;
import com.netease.nis.quicklogin.listener.QuickLoginPreMobileListener;
import com.netease.nis.quicklogin.listener.QuickLoginTokenListener;

import io.dcloud.feature.uniapp.annotation.UniJSMethod;
import io.dcloud.feature.uniapp.bridge.UniJSCallback;
import io.dcloud.feature.uniapp.common.UniModule;

public class QuickLoginModule extends UniModule {

    private QuickLogin quickLogin;

    /**
     * 初始化SDK
     * @param options { businessId: string }
     */
    @UniJSMethod
    public void init(JSONObject options, UniJSCallback callback) {
        String businessId = options.getString("businessId");
        quickLogin = QuickLogin.getInstance();
        quickLogin.init(
            mUniSDKInstance.getContext(),
            businessId
        );
        // Android 6.0+ 动态申请 READ_PHONE_STATE(建议但非必须)
        callback.invoke(new JSONObject() {{
            put("code", 0);
            put("msg", "初始化成功");
        }});
    }

    /**
     * 预取号
     * 建议在 App.vue onLaunch 中提前调用
     */
    @UniJSMethod
    public void prefetchMobileNumber(UniJSCallback callback) {
        if (quickLogin == null) {
            callback.invoke(new JSONObject() {{
                put("code", -1);
                put("msg", "请先调用 init");
            }});
            return;
        }
        quickLogin.prefetchMobileNumber(new QuickLoginPreMobileListener() {
            @Override
            public void onGetMobileNumberSuccess(String ydToken, String mobileNumber) {
                callback.invoke(new JSONObject() {{
                    put("code", 0);
                    put("ydToken", ydToken);
                    put("mobileNumber", mobileNumber);
                    put("msg", "预取号成功");
                }});
            }
            @Override
            public void onGetMobileNumberError(String ydToken, int code, String msg) {
                callback.invoke(new JSONObject() {{
                    put("code", code);
                    put("ydToken", ydToken);
                    put("msg", msg);
                }});
            }
        });
    }

    /**
     * 设置授权页UI配置
     * @param options { navTitle, logoIcon, loginBtnText, ... }
     */
    @UniJSMethod
    public void setUiConfig(JSONObject options) {
        UnifyUiConfig.Builder builder = new UnifyUiConfig.Builder();
        if (options.containsKey("navTitle")) {
            builder.setNavigationTitle(options.getString("navTitle"));
        }
        if (options.containsKey("loginBtnText")) {
            builder.setLoginBtnText(options.getString("loginBtnText"));
        }
        if (options.containsKey("logoWidth")) {
            builder.setLogoWidth(options.getIntValue("logoWidth"));
        }
        if (options.containsKey("logoHeight")) {
            builder.setLogoHeight(options.getIntValue("logoHeight"));
        }
        // ... 更多UI配置项按需映射
        quickLogin.setUnifyUiConfig(builder.build(
            mUniSDKInstance.getContext()
        ));
    }

    /**
     * 拉起授权页并获取Token
     */
    @UniJSMethod
    public void onePass(UniJSCallback callback) {
        if (quickLogin == null) {
            callback.invoke(new JSONObject() {{
                put("code", -1);
                put("msg", "请先调用 init");
            }});
            return;
        }
        quickLogin.onePass(new QuickLoginTokenListener() {
            @Override
            public void onGetTokenSuccess(String ydToken, String accessCode) {
                quickLogin.quitActivity();
                callback.invoke(new JSONObject() {{
                    put("code", 0);
                    put("ydToken", ydToken);
                    put("accessCode", accessCode);
                    put("msg", "取号成功");
                }});
            }
            @Override
            public void onGetTokenError(String ydToken, int code, String msg) {
                quickLogin.quitActivity();
                callback.invoke(new JSONObject() {{
                    put("code", code);
                    put("ydToken", ydToken);
                    put("msg", msg);
                }});
            }
            @Override
            public void onCancelGetToken() {
                callback.invoke(new JSONObject() {{
                    put("code", -2);
                    put("msg", "用户取消登录");
                }});
            }
        });
    }

    /**
     * 本机校验
     * @param options { phoneNumber: string }
     */
    @UniJSMethod
    public void getToken(JSONObject options, UniJSCallback callback) {
        String phoneNumber = options.getString("phoneNumber");
        quickLogin.getToken(phoneNumber, new QuickLoginTokenListener() {
            @Override
            public void onGetTokenSuccess(String ydToken, String accessCode) {
                callback.invoke(new JSONObject() {{
                    put("code", 0);
                    put("ydToken", ydToken);
                    put("accessCode", accessCode);
                }});
            }
            @Override
            public void onGetTokenError(String ydToken, int code, String msg) {
                callback.invoke(new JSONObject() {{
                    put("code", code);
                    put("msg", msg);
                }});
            }
        });
    }
}

iOS端插件封装

iOS端封装为Framework,核心桥接逻辑类似。通过 NTESQuickLoginManager 封装预取号和一键登录方法,以 UniJSCallback 回传结果。

关键适配点:

  • iOS端需要在插件的 .podspec 或 Podfile 中依赖 NTESQuickPass
  • 授权页配置通过 NTESQuickLoginModel 对象映射,和Android端字段名对齐
  • iOS端 presentDirectionTypeauthWindowPop 需要做枚举值转换

uniapp JS层调用

页面代码示例

// pages/login.vue
<template>
  <view class="login-page">
    <button @click="handleOneClickLogin">本机号码一键登录</button>
  </view>
</template>

<script>
// #ifdef APP-PLUS
let quickLoginPlugin = null;

export default {
  data() {
    return {
      preFetched: false,
    };
  },

  onLoad() {
    this.initQuickLogin();
  },

  methods: {
    // 初始化(建议在 App.vue onLaunch 中调用)
    initQuickLogin() {
      // #ifdef APP-PLUS
      quickLoginPlugin = uni.requireNativePlugin('Yidun-QuickLogin');

      quickLoginPlugin.init({
        businessId: '你的业务ID',
      }, (res) => {
        console.log('初始化结果:', res);
        if (res.code === 0) {
          // 初始化成功后立即预取号
          this.doPrefetch();
        }
      });
      // #endif
    },

    // 预取号
    doPrefetch() {
      quickLoginPlugin.prefetchMobileNumber((res) => {
        if (res.code === 0) {
          this.preFetched = true;
          console.log('预取号成功, 掩码:', res.mobileNumber);
        } else {
          console.log('预取号失败:', res.msg);
          // 失败不阻断后续流程,走降级
        }
      });
    },

    // 点击登录
    handleOneClickLogin() {
      // 设置授权页样式(可选)
      quickLoginPlugin.setUiConfig({
        navTitle: '本机号码一键登录',
        loginBtnText: '一键登录',
        logoWidth: 80,
        logoHeight: 80,
      });

      // 拉起授权页
      quickLoginPlugin.onePass((res) => {
        if (res.code === 0) {
          // 成功拿到 token 和 accessCode,传给后端
          this.doServerVerify(res.ydToken, res.accessCode);
        } else if (res.code === -2) {
          // 用户取消
          console.log('用户取消登录');
        } else {
          // 取号失败,走降级(短信验证码)
          this.fallbackToSMS();
        }
      });
    },

    // 后端校验
    doServerVerify(ydToken, accessCode) {
      uni.request({
        url: 'https://your-api.com/api/login',
        method: 'POST',
        data: {
          token: ydToken,
          accessToken: accessCode,
        },
        success: (res) => {
          if (res.data.success) {
            uni.showToast({ title: '登录成功' });
            // 保存用户信息,跳转主页
          }
        },
      });
    },

    // 降级到短信验证码
    fallbackToSMS() {
      uni.navigateTo({ url: '/pages/sms-login/sms-login' });
    },
  },
};
// #endif
</script>

预取号时机优化

和原生接入一样,预取号是提升体验的关键。在uniapp中的最佳实践:

方案一:App.vue onLaunch 中预取号

// App.vue
export default {
  onLaunch() {
    // #ifdef APP-PLUS
    const plugin = uni.requireNativePlugin('Yidun-QuickLogin');
    plugin.init({ businessId: 'xxx' }, (res) => {
      if (res.code === 0) {
        // 存储预取号结果到全局
        plugin.prefetchMobileNumber((preRes) => {
          getApp().globalData.prefetchResult = preRes;
        });
      }
    });
    // #endif
  },
};

方案二:登录页 onLoad 中预取号 + 按钮点击时拉起

这是推荐方案——用户进入登录页时预取号,1-2秒后预取号完成,用户点击登录按钮时直接拉起授权页,几乎无等待感。

关键规则:预取号和拉起授权页不要串行调用。预取号需要1-2秒,应在用户进入页面的时机提前执行。

常见接入问题

在uniapp中接入时,可能出现以下几个特有问题:

1. 授权页生命周期冲突

uniapp的页面栈和原生Activity/Fragment可能存在生命周期冲突。授权页拉起后是一个独立的Activity,关闭时需要确保uniapp页面的状态恢复正确。建议在 onePass 的回调中处理页面跳转逻辑,不要在授权页显示期间触发uniapp的路由操作。

2. Android SDK版本冲突

易盾号码认证SDK(quicklogin:3.6.0)依赖了一些AndroidX库。如果uniapp项目本身使用了较低版本的support库,可能产生依赖冲突。解决办法:在 build.gradle 中统一使用AndroidX,并配置 android.useAndroidX=true

3. 混淆配置

发布时需要保留易盾SDK相关类,在 proguard-rules.pro 中添加:

-keep class com.netease.nis.quicklogin.** { *; }
-keep class com.netease.nis.basesdk.** { *; }
-keep class com.cmic.gen.sdk.** { *; }
-keep class com.unicom.online.account.** { *; }
-keep class cn.com.chinatelecom.account.** { *; }

4. iOS端URL Schemes配置

iOS端一键登录需要在 Info.plist 中配置URL Schemes(部分运营商授权页的返回机制依赖此配置)。如果iOS端拉起授权页后无法正常回调,检查此项配置。

常见问题(FAQ)

Q1:uniapp有现成的号码认证插件吗?

目前易盾官方尚未发布uniapp插件市场的标准插件。需要开发者按照本文方案自行封装原生插件,或联系易盾技术支持获取封装示例。

Q2:本机校验在uniapp中怎么调用?

调用 getToken({ phoneNumber: '手机号' }, callback) 即可。本机校验不需要拉起授权页,流程更简洁。

Q3:uniapp中三网分别测试要注意什么?

和原生接入一样,必须用移动、联通、电信三张SIM卡分别测试。uniapp的跨端特性不会改变底层运营商授权页的实现差异。

Q4:插件封装后能上架插件市场吗?

可以。将上述 nativeplugins/Yidun-QuickLogin/ 目录打包提交uniapp插件市场,其他开发者即可通过 uni.requireNativePlugin 直接使用。


了解易盾号码认证一键登录的完整产品能力与接入方案,可访问易盾号码认证产品页

Logo

网易易盾是国内领先的数字内容风控服务商,依托网易二十余年的先进技术和一线实践经验沉淀,为客户提供专业可靠的安全服务,涵盖内容安全、业务安全、应用安全、安全专家服务四大领域,全方位保障客户业务合规、稳健和安全运营。

更多推荐