8.7 KiB
小程序静默登录与服务端鉴权接入
日期:2026-10-15。范围:doodle-store 服务端;小程序 HTTP 适配、资料修改/注销、七牛头像签名和 Web 登录另行实现。
1. 本次接口
统一前缀为 /api/v1,开发文档为 /api-docs。
| 接口 | 身份要求 | 行为 |
|---|---|---|
POST /auth/wechat/login |
无本站 token,必须有真实微信 code | 验证微信身份、创建或恢复账户,签发默认 24 小时 JWT |
GET /me |
本站 Bearer JWT | 查询当前账户的本人摘要和当前 role |
GET /health |
公开 | HTTP 进程存活,不检查外部服务 |
GET /health/ready |
公开 | 检查 PostgreSQL 连通性,不代表微信或七牛可用 |
小程序通过 wx.login 获得 code 后提交:
POST /api/v1/auth/wechat/login
Content-Type: application/json
{
"code": "本次 wx.login 返回的 code",
"mode": "start"
}
start允许首次建号,已有用户仍沿用原 ID;默认 role=user、status=active、authVersion=1。resume只恢复已有用户,账户不存在时返回 401/ACCOUNT_NOT_FOUND,不自动重建。- 两个模式都先向微信验证 code,且都拒绝 disabled 用户;不能使用 start 重置禁用状态、角色或资料。
- 请求不接受 AppID、AppSecret、OpenID、UnionID、userId 或角色等额外字段;应用凭据固定取服务端配置。
成功结果为 {code:0,message:"ok",data:{accessToken,tokenType,expiresIn,expiresAt,user}}。user 只含 id/nickName/avatarUrl/avatarUrlExpiresAt/createdAt;GET /me 另含当前 role,不输出微信身份或凭证版本。
expiresIn/expiresAt 从实际 JWT claims 取得;后续请求使用 Authorization: Bearer <accessToken>。登录、本人接口的成功与失败响应均为 Cache-Control: no-store。
当前七牛签名未接入,头像 URL 和期限返回 NULL;数据库已有头像引用保留,不改成公开对象地址。资料修改、清资料、注销及账户版本主动递增服务不包含在本次接口中。
2. 鉴权与恢复
受保护接口默认执行:固定算法/issuer/audience 的 JWT 验证 → 查询当前 users → 核对 active 和 authVersion → 检查服务端 role。账户不存在、被禁用或版本失效时旧 token 立即被拒绝;角色变化无需等待 token 到期。
GET /me 的 userId 只取守卫上下文,不允许指定其他账户;即使 query 中出现 userId,也不会用于身份选择。管理 API 仍需 @Roles('admin') 和对应业务归属/写事务校验;本次没有新增公开管理员登录或任意用户 token 签发接口。
错误保留 HTTP 状态与 {code,message,requestId},仅可信业务异常增加有限 reason:
| HTTP | reason | 小程序下一步 |
|---|---|---|
| 401 | AUTH_REQUIRED / AUTH_INVALID / AUTH_EXPIRED | 按设计共用一次静默恢复;原业务最多重发一次 |
| 401 | AUTH_REVOKED | 停止后台恢复,用户主动继续时再验证微信 |
| 401 | ACCOUNT_NOT_FOUND | 清旧状态,停止后台建号;主动开始时才使用 start |
| 401 | ACCOUNT_DISABLED | 停止恢复;重新登录不解除禁用 |
| 400 | WECHAT_CODE_INVALID | 当前 code 不能重用;受总重试次数限制地获取新 code |
| 403 | WECHAT_LOGIN_BLOCKED | 微信风控拦截,停止自动重试 |
| 429 | RATE_LIMITED | 遵守 Retry-After,不能立即循环 |
| 503 | AUTH_UPSTREAM_UNAVAILABLE | 微信网络/配置/响应异常,退避;不传播微信 errmsg |
| 503 | DATABASE_UNAVAILABLE | 数据库暂不可用,不降级为只验 JWT 放行 |
微信调用固定 HTTPS 地址,5 秒超时,禁止重定向,不自动重试一次性 code。每分钟单 IP 最多 30 次登录兑换、可信 OpenID 最多 10 次;限流使用有界内存,进程重启会重置,当前用于单实例。来源 IP 按已配置的可信代理解析,不接受任意客户端自报的 X-Forwarded-For。
不记录 code、OpenID/UnionID、AppSecret、session_key、token 或原始微信错误。session_key 没有当前业务用途,因此不落库、不缓存、不下发。
3. 配置与启动
服务端只加载 apps/server/.env,以下配置现在都必须有效存在:
DATABASE_URL:本地 PostgreSQL 连接串,使用 public schema。WECHAT_MINI_APP_ID与WECHAT_MINI_APP_SECRET:对应当前小程序,不能与 JWT_SECRET 混用。JWT_SECRET:至少 32 字节的独立密钥;issuer/audience/期限继续沿用 Server 基础设置。
从 doodle-store 根目录运行 pnpm dev。Prisma 客户端在 dev/build/typecheck/test 前生成;实际查询按需建立连接,HTTP 健康检查不因数据库故障被误判为进程退出。应用关闭时释放连接池。
真实 code 必须由相同 AppID 的小程序现取,不能用文档占位值或在公网开放测试后门。微信成功登录还需要合法请求域名、HTTPS 与真机/体验版联调;自动测试替换微信 HTTP 边界,不代表已经验证真实微信账号登录。
4. 数据库与迁移
Prisma Schema 映射现有 13 张表,客户端仅服务端可用。0_v35_baseline 保留原始 SQL 的 CHECK、C 排序规则、部分唯一索引和初始配置;不能用 Prisma 自动生成的简化 DDL 覆盖这些约束。
已有数据库在完整核对后,使用 pnpm --filter @doodle/server exec prisma migrate resolve --applied 0_v35_baseline 登记基线;该操作新增 Prisma 迁移记录,不重建业务表。空库使用 pnpm --filter @doodle/server exec prisma migrate deploy,会执行基线及初始配置。
后续升级新增迁移文件,已登记的基线保持不变。sql/004~008 是旧库升级档案,不在最新基线之上重跑;日常开发不使用 db push 或 reset 改写已建好的库。
登录先在事务外验证微信,再以 OpenID 唯一约束和用户行锁处理账户。并发 start 使用 INSERT ON CONFLICT DO NOTHING,不覆盖既有状态;resume 缺用户不写入。普通登录仅维护活跃时间;UnionID 只补空,不清空、不自动合并;非空冲突记录不含身份值的警告。
5. 验证方式
pnpm check:格式、ESLint、类型、构建及无需真实数据库的测试;数据库专项测试在未提供隔离连接时跳过。pnpm test:db:从本地开发连接取得建库权限,创建随机命名的独立短期库,执行正式迁移,运行 HTTP + 真实 Prisma/PostgreSQL 测试,结束后删除短期库。
数据库专项脚本仅允许本机 doodle_dev 实例,测试代码还验证随机短期库名称;不会清空开发库,也不在开发库写入假用户。覆盖老 ID 延续、并发首次登录、大小写语义、resume 不建号、UnionID 保留、禁用/降权/撤销/删除、本人读取、参数注入、上游错误、限流及日志脱敏。
微信接口依据:code2Session。Prisma 接入参考:CommonJS 客户端生成、既有数据库基线。
6. 本次验证记录
2026-10-15:pnpm check 通过,20 项常规测试通过;数据库专项在常规运行时按设计跳过,已通过独立 pnpm test:db 执行,8 项检查全部通过。真实微信调用使用 HTTP 替身验证固定地址、凭据来源、错误映射和一次性 code 不重试,尚未进行小程序真实 code 联调。
构建产物使用本机真实配置启动,HTTP 存活和数据库就绪均 200,本人接口无凭证 401,空 code 登录请求 400;检查服务已关闭。本地基线已登记,Prisma migrate diff 无差异;13 张业务表及两条初始配置保留,开发库没有写入测试用户,所有临时测试/参考库已删除。真实密钥未进入可提交文件。