1. 登录流程概述:
- 前端通过 wx.login() 获取临时登录凭证 code
- 将 code 发送到后端服务器
- 后端通过 code 换取用户的 openid 和 session_key
- 后端生成 JWT token 返回给前端
- 前端存储 token 用于后续请求的身份验证
2. 前端登录实现:
wx.getUserProfile是微信专门用来获取授权人信息的api,参数有2个:
-
1是的desc,告诉接口你拿登录信息做什么?
-
2.success是拿到登录信息以后要做什么,就是一个回调函数。
在回调函数里面做了2件事情:
-
1.向本地服务发送登录请求
-
2.获取到本地的token以后,缓存在本地
js
// 调用微信登录
wx.login({
success: (res) => {
if (res.code) {
// 获取用户信息
wx.getUserProfile({
desc: '用于完善会员资料',
success: (userInfo) => {
// 发送 code 和用户信息到后端
wx.request({
url: 'api/user/login',
method: 'POST',
data: {
code: res.code,
userInfo: userInfo.userInfo
},
success: (loginRes) => {
// 保存 token
wx.setStorageSync('token', loginRes.data.token)
}
})
}
})
}
}
})
代码拆开如下所示:
js
const { code } = await new Promise((resolve, reject) => {
wx.login({
success: resolve,
fail: reject
});
});
// 2. 获取用户信息
const userProfileRes = await new Promise((resolve, reject) => {
wx.getUserProfile({
desc: '用于完善会员资料',
success: resolve,
fail: reject
});
});
微信返回的数据如图所示:
js
{
userInfo: {
nickName: "用户昵称",
avatarUrl: "头像URL",
gender: 1, // 性别 0:未知、1:男、2:女
country: "国家",
province: "省份",
city: "城市",
language: "zh_CN" // 语言
},
rawData: "原始数据字符串",
signature: "签名",
encryptedData: "加密数据",
iv: "加密算法的初始向量"
}
wx.login会返回一个临时的code,这个 code 不能直接用来识别用户, 后端需要用这个 code 去微信服务器换取 openid 和 session_key, openid 才是用户的真实唯一标识。
也就是说在前端你用wx.login和wx.getUserProfile获取到的信息,都是用户公开信息,不能用来做用户的唯一标识,真正的唯一标识是openid,还是得有小程序的后端代码获取。
3. 后端处理流程:
从代码中可以看到,后端的登录处理主要包含以下步骤:
在前端wx.login里面你拿到了用户的临时code,然后你通过本地后台的'/user/login接口传给后端,后端拿到你的code以后就会调用微信服务,通过微信服务拿到sessionInfo里面的openid。
在你的数据库User表里面一定有一个字段是openid,用来搜索用户信息的。如果User里面有这个openId就说明它登录了,没有那就要后端把这个用户及它的openid存到数据库里面去。(用户信息你可以通过前端和code一起传过来)
js
// 调用微信登录服务
const wechatService = new (require('../services/wechat'))();
const sessionInfo = await wechatService.code2Session(code);
if (!sessionInfo || !sessionInfo.openid) {
throw new Error('获取微信用户信息失败');
}
console.log('微信登录成功,openId:', sessionInfo.openid);
// 查找或创建用户
let user = await User.findOne({ where: { openId: sessionInfo.openid } });
4. 身份认证机制:
微信用户信息创建更新完以后,我们需要获取token,这个件事也是在后端登录接口里面做的。
4.1.后端获取token
一般我们通过第三方库拿到jwt实例,然后再去认证token(身份令牌),比如在node服务里面,我们用的是jsonwebtoken
js
const jwt = require('jsonwebtoken');
- 使用 JWT(JSON Web Token)进行身份验证:
js
// 生成JWT token
const token = jwt.sign(
{
userId: user.userId, // 用户ID
openId: user.openId // 微信openId
},
// 第二个参数:密钥
process.env.JWT_SECRET || 'your_jwt_secret_key',
// 第三个参数:配置选项,7d表示: token有效期7天
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
);
JWT格式
js
// JWT格式:header.payload.signature
// 例如:
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
// eyJ1c2VySWQiOjEsIm9wZW5JZCI6Im94eHh4IiwiaWF0IjoxNTE2MjM5MDIyfQ.
// SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
小程序后端的login请求结束以后会把token返回出去
js
ctx.body = {
code: 200,
message: '登录成功',
data: {
token,
userInfo: {
userId: user.userId,
nickName: user.nickName,
avatarUrl: user.avatarUrl,
phoneNumber: user.phoneNumber,
openId: user.openId,
balance: user.balance || 0,
status: user.status,
createTime: user.createTime,
lastLoginTime: user.lastLoginTime
}
}
};
4.2前端拿到token以后需要做的事情
前端拿到token要做三件事,1.缓存token,2.发送请求的时候把token带上,3.后端api验证token
4.2.1缓存token
在前端里面我们用const app = getApp();拿到小程序的全局变量,这个getApp都做了什么事情
js
// app.js
App({
globalData: {
userInfo: null, // 用户信息
hasUserInfo: false, // 是否已登录
token: null, // 登录token
baseUrl: 'http://localhost:3000/api', // API基础地址
selectedCategoryId: null // 选中的分类ID
},
// 检查登录状态
checkLoginStatus() {
try {
const token = wx.getStorageSync('token');
const userInfo = wx.getStorageSync('userInfo');
if (token && userInfo) {
this.globalData.token = token;
this.globalData.userInfo = userInfo;
this.globalData.hasUserInfo = true;
return true;
}
return false;
} catch (error) {
return false;
}
},
// 更新登录状态
updateLoginStatus(token, userInfo) {
if (token && userInfo) {
this.globalData.token = token;
this.globalData.userInfo = userInfo;
this.globalData.hasUserInfo = true;
wx.setStorageSync('token', token);
wx.setStorageSync('userInfo', userInfo);
} else {
this.globalData.token = null;
this.globalData.userInfo = null;
this.globalData.hasUserInfo = false;
wx.removeStorageSync('token');
wx.removeStorageSync('userInfo');
}
}
})
最主要的就是
js
wx.setStorageSync('token', token);
wx.setStorageSync('userInfo', userInfo);
使用的时候就这样:
js
const app = getApp();
// 1. 获取全局数据
const { userInfo, token } = app.globalData;
// 2. 调用全局方法
app.checkLoginStatus();
app.updateLoginStatus(token, userInfo);
在代码里面一般这样用,在进入获取用户信息的时候用app.checkLoginStatus(),在登录的时候用:app.updateLoginStatus(token, completeUserInfo);
js
// 获取用户信息时
getUserInfo() {
// 从全局状态获取登录信息
const { hasUserInfo, userInfo } = app.globalData;
if (hasUserInfo && userInfo) {
// 使用全局数据更新页面
this.setData({
userInfo,
hasUserInfo: true
});
} else {
// 检查登录状态
const isLoggedIn = app.checkLoginStatus();
// ...
}
}
// 登录成功时
// 6. 更新全局状态
app.updateLoginStatus(token, completeUserInfo);
4.2.2请求带上token
缓存本地token能干嘛?就是在请求后端接口的时候,从缓存里面拿到token,然后放到请求里面去
js
const token = wx.getStorageSync('token');
wx.request({
url: 'api/xxx',
header: {
'Authorization': `Bearer ${token}`
}
})
一般情况下,我们会在小程序项目里面有个请求拦截文件,进行统一处理,所以你在请求api的时候一般看不到,如下:

4.2.3后端解析token,验证身份
当后端接收到请求以后,它会用jwt实例去解析token,token其实就是userId和openid的加密字符串。
js
// 当收到请求时,后端会:
try {
// 1. 从请求头获取 token
const token = ctx.header.authorization?.split(' ')[1];
// 2. 使用相同的密钥验证 token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 3. 如果验证成功,可以获取到用户信息
const userId = decoded.userId;
const openId = decoded.openId;
// 4. 验证失败会抛出异常
} catch (err) {
// token 无效或已过期
ctx.throw(401, '无效的token');
}
如果你想知道token里面到底是什么就用免费的JWT解析器解析一下看看

4.2.4.退出登录处理
前端微信删除token,然后后端会更新用户登录状态
js
// 前端清除本地存储的token
wx.removeStorageSync('token')
// 后端更新用户状态
await User.update({
updateTime: new Date()
}, {
where: { userId }
});
5.利用心跳包来维护登录状态
5.1概念
心跳包就是前端有个接口,定时的向后端发送请求,让服务器告诉我,我还活着没有
js
// 心跳包就像人的心跳一样,定期发送一个信号,表示"我还活着"
// 例如:每30秒发送一次请求
setInterval(() => {
wx.request({
url: '/api/heartbeat',
method: 'POST',
success: () => {
console.log('心跳包发送成功');
}
});
}, 30000); // 30秒
具体的实现如下:
前端
js
// 在小程序中实现心跳机制
const startHeartbeat = () => {
// 1. 首次连接
sendHeartbeat();
// 2. 定期发送
const intervalId = setInterval(() => {
sendHeartbeat();
}, 30000);
// 3. 保存定时器ID,以便可以停止
wx.setStorageSync('heartbeatInterval', intervalId);
};
const sendHeartbeat = async () => {
try {
const res = await wx.request({
url: 'api/user/heartbeat',
method: 'POST',
header: {
'Authorization': `Bearer ${wx.getStorageSync('token')}`
}
});
if (res.statusCode === 200) {
console.log('心跳成功');
} else {
console.log('心跳失败,可能需要重新登录');
}
} catch (error) {
console.error('心跳请求失败:', error);
}
};
// 在用户登录时启动心跳
onLogin() {
startHeartbeat();
}
// 在用户退出时停止心跳
onLogout() {
const intervalId = wx.getStorageSync('heartbeatInterval');
if (intervalId) {
clearInterval(intervalId);
}
}
后端
js
// 在Redis中记录用户在线状态
class UserController {
static async heartbeat(ctx) {
const userId = ctx.state.user.userId;
// 使用Redis记录用户在线状态
await redis.setex(
`user:online:${userId}`, // key
60, // 过期时间(秒)
'online' // value
);
ctx.body = {
code: 200,
message: 'Heartbeat received'
};
}
// 检查用户是否在线
static async checkUserOnline(userId) {
const status = await redis.get(`user:online:${userId}`);
return status === 'online';
}
}
这个机制的好处是:
- 实时性:可以及时发现用户离线
- 可靠性:通过定期检查确保连接状态
- 灵活性:可以根据需求调整心跳频率
- 安全性:可以及时清理过期的会话
缺点是:
- 需要额外的服务器资源
- 增加了网络流量
- 需要考虑移动设备的电量消耗
- 需要处理网络波动的情况
您觉得需要在项目中添加这个功能吗?我可以帮您实现。
这个登录认证流程的优点是:
- 使用微信官方的登录机制,安全可靠
- 采用 JWT 进行身份验证,无需保存会话状态
- token 有过期机制,增加安全性
- 支持自动创建新用户和更新用户信息
- 完整的错误处理和日志记录