如果你正在开发一个需要多端运行的uniapp项目(微信小程序、App、H5、支付宝小程序等),用户登录与身份认证往往是第一个要面对的基础设施。各平台都有自己的登录规范,如果为每个端单独写一套逻辑,维护成本会急剧上升。DCloud官方推出的uni-id就是专门解决这个问题的——它提供了一套统一、插件化的用户体系方案,配合uniCloud可以快速实现一键登录、token管理、用户角色权限等功能。
这篇文章会带你从头开始,在uniapp项目中集成uni-id,实现微信小程序端的“一键登录”功能,同时让这套逻辑可以无缝移植到App或其他小程序平台。我们会涵盖完整的配置过程、云函数编写以及前端调用代码。看完后你应该可以自己搭建一套可商用的用户系统。
前提准备:你需要什么
开始之前,确保你的开发环境满足以下要求:
- 安装最新版的 HBuilderX(推荐3.6以上版本),我们会用到它的uniCloud管理功能。
- 拥有一个已实名认证的微信小程序账号(用于获取AppID和AppSecret)。
- 在uniCloud服务空间创建好对应的阿里云或腾讯云服务空间(免费空间即可用于开发和测试)。
- 创建好一个空白uniapp项目(选择“uniapp项目”模板,Vue3或Vue2都支持,本文以Vue3组合式API为例)。
这里有一个容易被忽略的点:如果只是做微信小程序端,uniCloud服务空间需要与小程序账号绑定,具体来说就是需要在微信公众平台中把uniCloud的云环境ID配置到小程序的后台。这个步骤在uni-id的官方文档中有说明,但很多初次接触的开发者会卡在这里。我们后面在配置时会提到。
第一步:在项目中启用uni-id并完成基础配置
在HBuilderX中打开你的项目,找到 uniCloud 目录(如果没有就右键项目根目录选择“创建uniCloud云开发环境”)。接着我们需要引入uni-id的云端部分。
在 uniCloud/cloudfunctions 目录下新建一个云函数,名称可以叫 user-center,这个云函数将承载所有的用户操作(登录、注册、修改信息等)。进入该云函数目录,右键选择“使用公共模块或插件”,搜索“uni-id”并添加。添加成功后,目录下会自动生成 package.json 和 node_modules。
然后我们需要在配置文件中填入微信小程序的AppID和AppSecret。配置文件路径为:uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json。如果没有这个文件,可以手动创建。基础配置如下:
{
"passwordSecret": "你的一串随机字符串用于密码加密",
"tokenSecret": "另一串随机字符串用于JWT签名",
"tokenExpiresIn": 7200,
"tokenExpiresThreshold": 600,
"mp-weixin": {
"oauth": {
"weixin": {
"appid": "你的小程序AppID",
"appsecret": "你的小程序AppSecret"
}
}
},
"app": {
"oauth": {
"weixin": {
"appid": "App端微信开放平台AppID(如果有)",
"appsecret": "App端微信开放平台AppSecret"
}
}
}
}
这里的 passwordSecret 和 tokenSecret 需要替换成你自定义的高强度随机字符串,它们用于加密密码和生成JWT Token。特别注意:不要把这两个secret直接硬编码在客户端代码中,它们只存在于云函数端。
还有一个关键点:微信小程序的一键登录(使用手机号)需要用到 getPhoneNumber 接口,这个接口需要你将uniCloud的云环境ID配置到微信小程序后台的“开发-云开发-环境ID”设置中。如果不配,后续调用会报错。配置方法:登录微信公众平台 -> 开发管理 -> 开发设置 -> 云开发环境ID,填入你的uniCloud服务空间ID。
第二步:编写user-center云函数
uni-id的核心逻辑都封装在 uni-id 公共模块中,我们只需要在云函数中调用它提供的方法即可。打开 user-center/index.js,编写如下代码:
'use strict';
const uniID = require('uni-id-common');
const db = uniCloud.database();
exports.main = async (event, context) => {
// 初始化uni-id
const uniIDIns = uniID.createInstance({ context });
// event.action 用来区分操作类型:login, logout, updateUser等
const { action, params } = event;
switch (action) {
case 'loginByWeixinPhone': {
// 微信小程序一键登录
const res = await uniIDIns.loginByWeixinPhone(params);
return res;
}
case 'loginByWeixin': {
// 微信扫码登录(App端或小程序静默登录)
const res = await uniIDIns.loginByWeixin(params);
return res;
}
case 'logout': {
// 退出登录,传入用户token
const res = await uniIDIns.logout(params.uniIdToken);
return res;
}
case 'updateUser': {
// 修改用户信息
const res = await uniIDIns.updateUser({
uid: params.uid,
nickname: params.nickname,
avatar: params.avatar
});
return res;
}
case 'getUserInfo': {
// 获取当前登录用户信息
const tokenRes = await uniIDIns.checkToken(params.uniIdToken);
if (tokenRes.code !== 0) {
return tokenRes;
}
const userInfo = await db.collection('uni-id-users')
.doc(tokenRes.uid)
.field('nickname, avatar, mobile, role')
.get();
return {
code: 0,
userInfo: userInfo.data[0]
};
}
default:
return { code: -1, message: '未知操作' };
}
};
这里定义了多个action,用于处理不同的用户请求。loginByWeixinPhone 是微信小程序一键获取手机号登录的核心方法,它会自动创建或更新用户记录。其他如 loginByWeixin 用于静默登录(获取openid但不获取手机号),logout 用于使token失效。
需要注意的是,所有需要校验用户身份的接口都必须传入 uniIdToken,这个token是登录成功后返回的JWT字符串,客户端需要持久化存储(比如存入storage)。
第三步:前端登录页面实现
现在回到uniapp的前端页面。我们新建一个页面 pages/login/login.vue,核心逻辑是:点击按钮触发微信的一键登录,获取手机号临时code后,调用云函数完成登录,并保存token。
这里以Vue3组合式API的写法为例,但选项式API同样适用。
<template>
<view class="page">
<view class="logo-area">
<image src="/static/logo.png" mode="aspectFit" class="logo"></image>
<text class="app-name">你的应用名称</text>
</view>
<view class="login-area">
<button
class="login-btn"
type="primary"
open-type="getPhoneNumber"
@getphonenumber="handleGetPhoneNumber"
:loading="loading"
:disabled="loading"
>
一键登录 / 注册
</button>
<text class="tip">点击按钮即同意《用户协议》和《隐私政策》</text>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
const loading = ref(false);
// 用于存储云函数返回的数据
const uniIdToken = ref('');
// 处理微信手机号授权回调
const handleGetPhoneNumber = async (e) => {
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
uni.showToast({ title: '授权已取消', icon: 'none' });
return;
}
// e.detail.code 是微信返回的动态令牌,用来换取手机号
const { code } = e.detail;
if (!code) {
uni.showToast({ title: '获取code失败', icon: 'none' });
return;
}
loading.value = true;
try {
// 调用我们之前写好的云函数
const res = await uniCloud.callFunction({
name: 'user-center',
data: {
action: 'loginByWeixinPhone',
params: { code }
}
});
const result = res.result;
if (result.code === 0) {
// 登录成功,保存token和用户信息
uniIdToken.value = result.token;
uni.setStorageSync('uni_id_token', result.token);
uni.setStorageSync('user_info', result.userInfo || {});
uni.showToast({ title: '登录成功' });
// 跳转到首页或之前的页面
setTimeout(() => {
uni.switchTab({ url: '/pages/index/index' });
}, 800);
} else {
uni.showToast({ title: result.message || '登录失败', icon: 'none' });
}
} catch (err) {
uni.showToast({ title: '网络异常,请重试', icon: 'none' });
} finally {
loading.value = false;
}
};
</script>
关键在于 button 的 open-type="getPhoneNumber" 和对应的 @getphonenumber 事件。这个事件是微信小程序原生的能力,uniapp已经做了封装,可以直接使用。在App端或其他端,我们可以使用不同的登录方式(比如微信开放平台登录),这时只需要把云函数的action换成 loginByWeixin 并传入对应的参数即可。
第四步:完善应用启动时的自动登录逻辑
用户首次登录后token会持久化到本地,下次打开应用时我们应该先检查token有效性,如果有效就自动完成登录,避免用户反复授权。
在项目的 App.vue 的 onLaunch 中(或使用组合式API的 onLaunch 钩子),添加如下逻辑:
<script setup>
import { onLaunch } from '@dcloudio/uni-app';
onLaunch(async () => {
const token = uni.getStorageSync('uni_id_token');
if (!token) return; // 未登录
try {
const res = await uniCloud.callFunction({
name: 'user-center',
data: {
action: 'getUserInfo',
params: { uniIdToken: token }
}
});
if (res.result && res.result.code === 0) {
// token有效,更新本地用户信息
uni.setStorageSync('user_info', res.result.userInfo);
console.log('自动登录成功');
} else {
// token失效,清除本地存储
uni.removeStorageSync('uni_id_token');
uni.removeStorageSync('user_info');
}
} catch (e) {
console.error('自动登录检查失败', e);
}
});
</script>
这样,应用启动时会拿着本地token去云端校验。如果token过期或无效,就清除本地数据,等待用户下次手动登录。这套机制与绝大多数商业App的行为一致。
第五步:处理跨端差异(举例App端)
上面的代码主要针对微信小程序端。如果你的应用还要发布到App端(iOS/Android),微信登录流程会有所不同:App端通常使用微信开放平台的 uni.login 获取code,然后调用云函数 loginByWeixin 接口完成登录。
我们可以在登录页面加一个条件编译,针对不同平台显示不同的按钮:
<!-- #ifdef MP-WEIXIN -->
<button open-type="getPhoneNumber" @getphonenumber="handleGetPhoneNumber">手机号一键登录</button>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<button @click="handleAppWechatLogin">微信登录</button>
<!-- #endif -->
然后在 handleAppWechatLogin 方法中:
const handleAppWechatLogin = async () => {
const [err, res] = await uni.login({ provider: 'weixin' });
if (err) return;
const { code } = res.authResult;
// 调用云函数
const loginRes = await uniCloud.callFunction({
name: 'user-center',
data: {
action: 'loginByWeixin',
params: { code }
}
});
// 处理结果与之前类似...
};
这样,同一个云函数通过不同的action适配了多端。用户数据的存储、token的管理逻辑完全共用,这就是uni-id带来的核心价值。
你可以关注的一些补充细节
在真实项目中,还有几个点值得你提前设计好:
- 用户协议与隐私政策:微信小程序审核强制要求在一键登录按钮下方展示协议勾选框,否则无法通过。可以使用uniapp的checkbox组件配合状态管理。
- token刷新机制:uni-id默认的token过期时间是2小时,可以在config中调整。如果希望做到无感刷新,可以在云函数请求拦截器中统一处理401状态,然后使用refreshToken换取新token。uni-id本身支持refreshToken,但需要在前端做一下封装。
- 角色权限管理:uni-id内置了角色(role)和权限(permission)系统,可以在用户表中配置,并通过云函数校验。对于需要管理员后台的应用非常实用。
- 手机号绑定与解绑:用户可能更换手机号,可以通过云函数更新mobile字段,uni-id也提供了对应的API。
另外,uni-id的官方文档内容较多,初次接触可能会觉得信息密度大。建议先把上面的流程跑通,然后再根据项目需求去查阅特定功能的配置。实践出真知,跑通一个端到端的登录流程后,你对整个uni-id体系的理解会清晰很多。

