ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

微信小程序服务端开发实战:登录态与鉴权全解析

微信小程序服务端开发实战:登录态与鉴权全解析 简介适合微信小程序初学者与服务端开发者这是一份可直接运行的服务端开发示例演示了后端接口的基础写法与静态资源托管逻辑。资源包共11个文件以6个JavaScript源码文件为主另含依赖清单、转译配置、说明文档及测试文件压缩后仅8KB结构精简便于对照阅读。项目目录展示了从入口文件、核心配置到测试文本的完整项目骨架可帮助理解小程序服务端从接收请求到返回响应的基本流程。对于刚接触小程序后端开发的读者该示例提供了最小可运行版本能够帮助快速打通小程序前端与服务器之间的数据通道通过阅读源码还可以学习到如何创建网络服务、处理不同请求、设置响应头以及完成基本的参数解析这些都是在真实项目中频繁使用的技能。该资源已有1583人学习下载适合正在学习Node.js后端开发或进行小程序前后端联调的读者参考。1. 微信小程序服务端开发demo到底在解决什么问题经常有人拿到“微信小程序服务端开发demo源代码截图”以后在微信开发者工具里打开前端工程点击登录直接报错然后怀疑是源码有问题。实际绝大多数情况与代码无关而是只看到了半条链路——小程序前端负责展示和交互真正处理登录、数据校验、业务逻辑的是它背后的服务端。这个demo提供的就是这样一套最小可运行的HTTP服务把登录态交换、鉴权、数据读写串成闭环再用注释和截图把每个环节的输入输出标清楚。它适合第一次碰前后端联调的学生适合用uniapp做好前端却卡在服务端接口测试的人也适合想在微信小程序项目实例里摸清客户端和服务端如何通信的开发者。2. 服务端demo先立认知登录态链路与技术栈选型2.1 为什么小程序必须配一个独立服务端而不是直接请求微信接口wx.request可以请求普通的HTTPS接口但真实项目里没人会让小程序端直接去请求微信的jscode2session接口。原因很直接小程序前端代码打包下发之后是公开的任何人用微信开发者工具打开都能看到全部逻辑。如果把AppSecret写在小程序端的page或者utils目录里等于把账号凭证公开放在路边。因此所有涉及密钥的操作都必须收口到独立服务端小程序端只负责把wx.login产生的临时code传给自己的后端。这条链路的完整时序如下小程序端 wx.login() → 得到临时 code 小程序端 wx.request() → 把 code 发到你的服务端 POST /api/login 你的服务端 → 用 appid secret code 请求 https://api.weixin.qq.com/sns/jscode2session 微信接口 → 返回 openid用户唯一标识和 session_key 你的服务端 → 用 openid 查库或建用户签发自己的 token 你的服务端 → 把 token 返回给小程序端 小程序端 → 后续请求在 header 里带 Authorization: Bearer token这个时序里有三个关键约束。第一code只能用一次5分钟过期重复使用微信会返回40163第二openid是小程序与用户两个维度组合出来的唯一ID同一个用户在不同小程序里的openid不同第三session_key不能直接返回给前端它只用于服务端解密手机号这类敏感数据。把这三条记清楚调试接口时能少走一半弯路。2.2 开发demo选Node.js Express SQLite依据是什么服务端技术栈常见的有Node.js的Express/Koa、Java的Spring Boot、Python的FastAPI/Flask。对于“微信小程序服务端开发demo”这个交付形态我一般优先选Node.js Express SQLite理由有三个微信开发者工具和官方文档的生态偏向JavaScript前端接手时不用切换语言Express写一个带鉴权的REST接口只需几十行代码路由和中间件的概念在演示时也容易讲清楚SQLite是文件型数据库解压源码包就能跑不需要额外安装数据库服务端进程这正好契合“源代码截图”的交付体验——对方拿到手就能启动。对比项Node.js ExpressJava Spring BootPython FastAPI上手成本低前端可无缝接中高需要理解注解与容器低语法简洁启动速度秒级秒到十秒级秒级内存占用最低最高中等生态匹配度微信官方示例多为JS企业级规范成熟数据处理方便如果团队主栈是Java用Spring Boot配H2内存库也是同类思路接口契约比实现语言更重要。demo阶段不建议引入Redis做token存储、消息队列做异步任务它们属于规模扩大后再考虑的演进方向过早引入会让初学者分不清主次。一个能跑通、能截图、能讲明白的最小闭环比一个依赖复杂但看起来很“企业级”的半成品要有价值得多。2.3 package.json里的四个依赖刚好对应demo的四个职责package.json把依赖固定成下面这样每一条都对应服务端demo的一个明确职责。{ name: weapp-server-demo, version: 1.0.0, main: server.js, scripts: { start: node server.js }, dependencies: { express: ^4.19.2, axios: ^1.7.0, jsonwebtoken: ^9.0.2, better-sqlite3: ^11.3.0 } }express负责HTTP路由与中间件axios负责服务端主动请求微信的jscode2session接口jsonwebtoken负责签发和校验登录tokenbetter-sqlite3负责操作本地SQLite数据库文件。四个库没有多余的演示的时候可以顺着这张清单讲清楚整个服务端demo的骨架。版本号都使用主版本内的最新兼容版本npm install时会自动解析。3. 落地一个能跑的最小服务端建表、登录接口与curl自测3.1 极简目录结构与数据库初始化服务端代码遵循极简分层就够不需要上MVC。一个入口文件负责路由和中间件一个数据库初始化文件负责建表运行时自动生成SQLite文件整个服务端demo的目录就是demo-server/ ├── package.json ├── server.js # 入口路由 鉴权中间件 接口实现 ├── db.js # 初始化 SQLite 连接和表结构 └── data.sqlite # 运行后自动生成不需要手动创建db.js的初始化代码// db.js - 初始化 SQLite建表并导出实例 const Database require(better-sqlite3); const path require(path); const db new Database(path.join(__dirname, data.sqlite)); db.exec( CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, openid TEXT NOT NULL UNIQUE, nickname TEXT DEFAULT 微信用户, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS access_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, openid TEXT, action TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); ); module.exports db;better-sqlite3是同步API在demo的请求量级下完全够用代码写起来也比异步数据库驱动更直白。users表里openid必须加UNIQUE约束保证同一微信用户重复登录时不会产生脏数据created_at交给数据库默认值应用层少写一行代码。access_logs这张表不是给业务功能用的而是为了让截图演示时有据可查——每次接口调用插一条日志终端和数据库都能看到记录截图自然更有说服力。3.2 登录接口code换token的完整实现与关键参数server.js是服务端demo的核心代码分为配置区、登录接口、鉴权中间件、业务接口四块这里先看登录接口// server.js - 微信小程序服务端入口 const express require(express); const axios require(axios); const jwt require(jsonwebtoken); const db require(./db); const app express(); app.use(express.json()); // 以下配置替换成你自己小程序的信息SECRET 绝不能出现在小程序端代码里 const APPID wx你的appid; const SECRET 你的AppSecret; const JWT_KEY 自定义随机字符串至少32位; // 登录小程序端把 wx.login 拿到的 code 传过来 app.post(/api/login, async (req, res) { const { code } req.body; if (!code) { return res.status(400).json({ code: 400, msg: 缺少 code 参数 }); } // 用 code 向微信服务器换 openid 和 session_key const url https://api.weixin.qq.com/sns/jscode2session ?appid${APPID}secret${SECRET}js_code${code}grant_typeauthorization_code; let wxResp; try { wxResp await axios.get(url); } catch (err) { return res.status(502).json({ code: 502, msg: 微信接口不可达 }); } // 微信返回 errcode 说明换 token 失败常见 40029 是 code 无效或已使用 if (wxResp.data.errcode) { return res.status(401).json({ code: wxResp.data.errcode, msg: wxResp.data.errmsg }); } const { openid } wxResp.data; // openid 已存在则忽略不存在则插入新用户 db.prepare(INSERT OR IGNORE INTO users (openid) VALUES (?)).run(openid); const user db.prepare(SELECT * FROM users WHERE openid ?).get(openid); // 用 JWT 签发自定义 token有效期 7 天后续请求凭它识别用户 const token jwt.sign({ openid: user.openid }, JWT_KEY, { expiresIn: 7d }); res.json({ code: 0, data: { token, user } }); }); // 启动监听 0.0.0.0真机调试时手机才能通过局域网 IP 访问 app.listen(3000, 0.0.0.0, () { console.log(server running at http://0.0.0.0:3000); });逻辑说明code参数必填来自小程序端wx.login的返回值不能用假code做纯接口测试。INSERT OR IGNORE是SQLite的幂等写法避免了先SELECT判断再INSERT的竞态问题——两个请求同时带着新openid进来时不会重复建用户。jwt.sign默认使用HS256算法payload里只放openid不放手机号这类敏感字段。expiresIn设置7天是演示值正式上线建议调整为2小时短期token加refresh_token刷新机制。app.listen监听0.0.0.0而不是127.0.0.1是为了让真机调试时手机可以通过局域网IP访问到电脑上的Node进程。参数来源作用注意事项APPID公众平台→开发管理→开发设置标识你的小程序小程序端也会出现不算机密SECRET同一页面调用微信接口的凭证只能留在服务端泄露可被冒用JWT_KEY自己生成签名token的密钥demo写死生产放环境变量expiresIn自己定token有效期太短频繁重登太长增加盗用风险3.3 鉴权中间件与第二个业务接口只有一个登录接口的demo说服力不够加一个需要登录才能访问的用户信息接口顺便演示Express中间件最经典的用法// 鉴权中间件解析 Authorization 头里的 token失败直接返回 401 function auth(req, res, next) { const token req.headers.authorization req.headers.authorization.replace(Bearer , ); if (!token) { return res.status(401).json({ code: 401, msg: 未登录 }); } try { req.user jwt.verify(token, JWT_KEY); next(); } catch (e) { return res.status(401).json({ code: 401, msg: token 过期或无效 }); } } // 业务接口只有携带合法 token 才能拿到用户资料 app.get(/api/profile, auth, (req, res) { const user db.prepare(SELECT * FROM users WHERE openid ?).get(req.user.openid); res.json({ code: 0, data: user }); });逻辑说明auth函数放在路由路径之后、处理函数之前Express会先执行它再进入业务逻辑。jwt.verify抛异常说明token被篡改或已过期统一返回401而不区分具体原因避免向调用方泄露过多内部信息。解析出来的openid挂在req.user上处理函数直接取用不用二次解析token。学会这一种中间件模式后面加管理员接口、加统计接口都能复用同一套逻辑。3.4 本地启动与服务端接口测试依赖安装完成后启动看到进程监听日志说明服务端就绪npm install node server.js # 看到 server running at http://0.0.0.0:3000 说明进程正常用curl做一次服务端接口测试curl -X POST http://127.0.0.1:3000/api/login \ -H Content-Type: application/json \ -d {code:临时code}这里要提醒code无法凭空伪造必须是微信开发者工具里wx.login生成的真实code。推荐的做法是先在小程序端临时加一行console.log(code)把打印出来的值复制进curl或者直接在小程序端触发登录再看服务端终端打印的日志。返回40029说明code已经过期、被用过或者是从错误位置复制的。4. 小程序端联调、截图留档与高频报错排查4.1 request封装与baseURL的三种配置位置小程序端不能每个页面都直接写wx.request那样token注入和错误处理会重复十几遍。封装一个Promise版本的request函数是所有微信小程序项目实例的标准做法// utils/request.js - 在小程序端统一管理请求 const BASE_URL http://127.0.0.1:3000; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method, data, header: { Content-Type: application/json, Authorization: Bearer (wx.getStorageSync(token) || ) }, success: (res) { if (res.data.code 0) { resolve(res.data.data); } else if (res.data.code 401) { // token 过期清掉本地登录态并提示重新登录 wx.removeStorageSync(token); wx.showToast({ title: 登录已过期, icon: none }); reject(new Error(res.data.msg || 登录失效)); } else { reject(new Error(res.data.msg || 请求失败)); } }, fail: reject }); }); } module.exports { request, BASE_URL };逻辑说明统一判断服务端返回的code字段0视为成功401单独处理避免用户带着失效token在页面里反复点击都得到同一个异常。BASE_URL有三种配置位置——直接写在utils/request.js里、放在app.js的globalData里、或者独立一个config.js文件维护。demo用常量没有问题但要注意开发者工具里可以访问127.0.0.1真机不行真机必须改成电脑的局域网IP。如果前端是基于uni-app构建的微信小程序把wx.request换成uni.request其余封装思路完全一致。4.2 本地开发阶段的域名校验与豁免微信小程序生产环境要求request的域名必须是HTTPS并且要在公众平台后台配置合法域名。但本地开发调试时存在豁免开发者工具对http://127.0.0.1和http://localhost有默认放行可以直接请求如果请求的是局域网IP比如http://192.168.1.5:3000就需要在开发者工具右上角「详情」→「本地设置」里勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”。这个选项只影响开发者工具真机预览时仍会受到域名白名单限制需要走第4.3节的真机调试方案。提示勾选绕过域名校验只是开发提速手段线上版本必须配置HTTPS证书和合法请求域名否则正式版小程序会直接在wx.request阶段报错。4.3 真机调试必须处理的三件事真机预览时所有请求都失败通常不是代码问题而是网络链路问题按顺序检查三件事。第一电脑防火墙是否放行了3000端口macOS和Windows默认都会拦截来自局域网的新入连接放行Node进程或3000/tcp端口后重试。第二手机和电脑必须处于同一局域网公司办公网经常开启AP隔离即使连同一个Wi-Fi也无法互通。第三手机端访问的IP必须是电脑的实际局域网IP查看方法macOS用ifconfig、Windows用ipconfig找到类似192.168.1.5的地址把BASE_URL改成http://192.168.1.5:3000后重新编译。服务端启动时监听0.0.0.0的意义在这里体现只监听127.0.0.1时局域网内的手机连不上电脑上的Node进程。如果不想改IP也可以用微信开发者工具自带的“真机调试”功能它会建立一条调试通道手机端请求映射到开发者工具所在机器适合快速验证页面逻辑但要验证真实网络链路直接改局域网IP更接近上线后的行为。4.4 “源代码截图”演示时截图应该怎么截标题里的“截图”不是随便截两张小程序页面就完事截图的意义是证明这个demo真正跑通了。合格的演示截图要覆盖三端小程序端登录成功后的页面、服务端终端的请求日志、数据库里users表和access_logs表的新增记录三张图拼在一起能还原完整链路。服务端日志建议在登录接口和业务接口里加console.log打印请求路径、openid和耗时这些日志本身就能截图。数据库侧把查询结果显示出来再截不要只截命令行里CREATE TABLE的输出。小程序端打开开发者工具的Network面板过滤XHR/Fetch请求之后再截图请求URL、状态码、响应时间和响应体都出现在同一画面里这一张图的信息量比任何文字描述都大。4.5 六个高频报错的定位对照表demo阶段遇到的报错基本是配置问题对照下表直接定位现象可能原因处理方式request:fail 错误BASE_URL写错或服务端没有启动先用电脑浏览器访问该地址确认可通401 未登录token没写进header或已过期检查Authorization拼写重新登录40029 code无效code过期或已被使用每次登录重新调用wx.login40163 code已被使用同一code重复请求code是一次性的不可能重放真机访问超时防火墙拦截或IP不在同一网段放行端口并确认同一局域网errcode 40013APPID格式不完整回到公众平台复制完整的appid排查时先看服务端终端有没有收到请求没收到是网络层问题收到了再看返回的errcode。接口响应里的code是业务码HTTP状态码是传输层状态两者都对了才算链路正常。5. 把demo打磨到能验收的几个关键细节5.1 统一业务错误码让前端分支更简单demo里常见的写法是每个接口随意返回错误信息但一个能过审的工程需要统一错误码约定0成功400参数错误401未认证403无权限404接口不存在500服务端异常。前端request函数只要针对401做一次单独处理其余错误统一走兜底文案。错误码和HTTP状态码可以保持语义一致但业务判断只认code字段这样即使以后Nginx层返回502前端也能区分是业务错误还是基础设施错误不会被五花八门的响应结构搞乱。5.2 用一张统计接口让演示有数据在access_logs表基础上加一个汇总接口统计最近7天每天请求量返回形如[{date: 2025-01-13, count: 42}]的结构。实现不超过15行按日期分组查询access_logs再用Array.map把SQLite的行转成前端友好的字段名。这个接口让demo从“能登录”升级为“有数据可看”。演示时先后端操作几次再打开统计页面刷新数据变化直观可见比对着空表讲解更有说服力。记录日志时把请求路径一并存入action字段还能顺手统计哪个接口被调用最频繁。5.3 上线部署与验收核对清单验收不能凭感觉按下面的清单逐项过一遍核对项验证方式通过标准本地启动npm install后node server.js一条命令启动无报错功能链路前端登录→拿token→访问profile全流程无故障异常链路清空token后访问受保护接口前端提示登录已过期数据落库重启服务端后登录老用户openid不重复创建演示截图界面、服务端日志、数据库记录三端截图齐全线上配置部署云服务器并配置HTTPS证书公众平台后台已添加合法请求域名最后一项涉及的具体操作是把Node服务部署到带公网IP的服务器上用Nginx做HTTPS终止证书可以通过免费证书服务签发然后把HTTPS域名添加到公众平台后台的request合法域名列表。做完这一步这个微信小程序服务端开发demo就不再是hello world级别的演示而是一套可以被复用和继续迭代的最小工程骨架——下次在其上扩展的商品列表、订单模块都沿用同一套登录、鉴权、日志和错误码约定。本文还有配套的精品资源点击获取
返回列表