这是一个非常标准的微信小程序登录 + 自定义登录态的全链路实现。
为了让你直接能用,我将分为 Node.js 后端 (Express) 和 小程序前端 两部分。
核心流程图解
前端:
wx.login()拿到临时code-> 发给后端。后端:拿
code+appid+appsecret去微信服务器换取openid和session_key。后端:生成自己的 Token (JWT),返回给前端。
前端:存 Token,以后请求带上它。
第一部分:Node.js 后端实现 (Express)
你需要安装以下依赖:
Bash
npm install express axios jsonwebtoken body-parser
server.js 代码:
JavaScript
const express = require('express');
const axios = require('axios');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
// ================= 配置区 =================
const WX_CONFIG = {
appid: '你的小程序APPID',
secret: '你的小程序APPSECRET',
};
const JWT_SECRET = 'your_jwt_secret_key'; // 用于签发 Token 的密钥
// ================= 登录接口 =================
app.post('/api/login', async (req, res) => {
const { code } = req.body;
if (!code) {
return res.status(400).json({ error: '缺少 code 参数' });
}
try {
// 1. 向微信服务器发送请求,换取 openid 和 session_key
const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${WX_CONFIG.appid}&secret=${WX_CONFIG.secret}&js_code=${code}&grant_type=authorization_code`;
const wxRes = await axios.get(url);
const { openid, session_key, errcode, errmsg } = wxRes.data;
// 检查微信返回的错误
if (errcode) {
console.error('微信 API 报错:', errmsg);
return res.status(500).json({ error: '微信登录失败', detail: errmsg });
}
// 2. 【核心业务逻辑】查询数据库,查找或注册用户
// 这里是模拟数据库操作
let user = await mockFindUserByOpenid(openid);
if (!user) {
user = await mockCreateUser(openid); // 如果是新用户,存入数据库
}
// 3. 生成自定义 Token (JWT)
// 注意:千万不要把 session_key 直接返回给前端,也不要放在 token 里,除非你需要解密手机号
const token = jwt.sign(
{ userId: user.id, openid: openid },
JWT_SECRET,
{ expiresIn: '7d' } // Token 有效期 7 天
);
// 4. 返回 Token 给前端
res.json({
code: 0,
msg: 'Login success',
data: {
token: token,
userInfo: user // 可选:返回一些用户信息
}
});
} catch (error) {
console.error(error);
res.status(500).json({ error: '服务器内部错误' });
}
});
// ================= 中间件:验证 Token =================
// 用于保护其他接口,确保用户已登录
const authMiddleware = (req, res, next) => {
const authHeader = req.headers['authorization'];
// 格式通常是: "Bearer <token>"
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user; // 把解密出来的用户信息挂载到 req 上
next();
});
};
// 测试受保护的接口
app.get('/api/profile', authMiddleware, (req, res) => {
res.json({ msg: '这是受保护的数据', currentUser: req.user });
});
// ================= 模拟数据库函数 =================
async function mockFindUserByOpenid(openid) {
// 实际开发中请使用 db.collection('users').findOne({ openid })
console.log(`正在查找用户: ${openid}`);
return null; // 模拟未找到,走注册流程
}
async function mockCreateUser(openid) {
console.log(`正在注册新用户: ${openid}`);
return { id: 1001, nickname: '微信用户', openid };
}
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
第二部分:小程序前端实现
建议封装一个 login.js 工具文件,或者直接写在 app.js 里。
utils/request.js (简单的请求封装,自动带 Token)
JavaScript
// 基础 URL
const BASE_URL = 'http://localhost:3000';
function request(url, method = 'GET', data = {}) {
const token = wx.getStorageSync('token'); // 从本地缓存拿 Token
return new Promise((resolve, reject) => {
wx.request({
url: BASE_URL + url,
method: method,
data: data,
header: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : '' // 自动携带 Token
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data);
} else if (res.statusCode === 401) {
// Token 过期,可以在这里触发重新登录逻辑
wx.showToast({ title: '登录过期', icon: 'none' });
reject(res);
} else {
reject(res);
}
},
fail: (err) => reject(err)
});
});
}
module.exports = { request };
pages/login/login.js (执行登录逻辑)
JavaScript
const { request } = require('../../utils/request');
Page({
// 点击登录按钮
handleLogin() {
wx.showLoading({ title: '登录中...' });
// 1. 调用微信登录接口
wx.login({
success: async (res) => {
if (res.code) {
try {
// 2. 把 code 发给后端
const result = await request('/api/login', 'POST', {
code: res.code
});
console.log('后端返回结果:', result);
if (result.code === 0) {
// 3. 登录成功,保存 Token 到本地
wx.setStorageSync('token', result.data.token);
wx.setStorageSync('userInfo', result.data.userInfo);
wx.showToast({ title: '登录成功' });
// 4. 跳转到首页或测试受保护接口
this.testProtectedApi();
}
} catch (error) {
console.error('登录业务失败', error);
wx.showToast({ title: '登录服务器报错', icon: 'none' });
} finally {
wx.hideLoading();
}
} else {
console.log('登录失败!' + res.errMsg);
}
}
});
},
// 测试请求受保护的接口
async testProtectedApi() {
try {
const res = await request('/api/profile', 'GET');
console.log('受保护数据:', res);
} catch (err) {
console.error(err);
}
}
});
面试/实战中的关键点 (FAQ)
为什么不能在前端直接请求微信的
jscode2session接口?安全问题: 微信明确禁止。因为这需要把
AppSecret暴露在前端代码中,一旦泄露,黑客可以冒充你的小程序身份。
session_key有什么用?它是用来解密开放数据(如用户手机号、运动步数)的密钥。
注意: 不要把它存到数据库里长期使用,它会过期。一般建议存在 Redis 里,或者用到的时候重新
wx.login获取。
Token 过期了怎么处理?
在前端封装的
request中拦截401状态码。一旦发现
401,静默调用wx.login重新走一遍登录流程(无感刷新),或者弹窗提示用户重新点击登录。