羽毛球活动平台的全栈拆分:小程序、管理端与 API
本文目录
一个活动平台同时服务小程序用户、运营管理者和 API,不等于把三个页面塞进同一个前端工程。本文基于作者个人项目的仓库源码、后端测试和只读线上探测做工程复盘;源码中实现的行为、尚未部署的修复与线上可见状态分别说明,不涉及账户、用户数据或环境变量内容。
三端如何分工
项目采用 npm workspaces 管理独立应用:badminton-backend 是 NestJS API,使用 Prisma 访问 PostgreSQL,并连接 Redis;badminton-admin 是 React、Vite 和 Ant Design 管理台;badminton-miniapp 以 Taro 和 React 构建微信小程序。它们共享业务 API 契约,但各自有独立的构建工具、运行环境和发布物。
API 前缀为 /badminton/api。管理台的 Vite base 和 React Router basename 都指向 /badminton/admin/;小程序把 API 地址收敛在 TARO_APP_API 配置,并通过一个请求模块统一附加 Bearer token、解包业务响应和处理 401。这个边界便于替换客户端,却也要求部署时明确区分静态资源路径、前端路由回退和 API 反向代理:源码有路由,不代表生产 Web 服务器已经提供对应页面。
领域模型围绕用户、活动和报名展开。活动保存报名时间、人数上限、候补上限、分组方式和状态;报名通过 activityId + userId 唯一约束防止同一用户重复建多条记录,并以 PENDING、CONFIRMED、WAITLIST、CANCELLED 等状态表达生命周期。比赛、分组、场地、费用和转让再与活动/用户建立关系,而不是由某个前端页面独自维护全局状态。
最后一席不能靠“先查再写”
源码审阅发现报名路径曾先读取活动与现有报名人数,随后在事务外创建记录。两个并发请求可能同时读到最后一个空位,再各自写入;单条记录上的唯一约束只能阻止同一用户重复报名,不能保证不同用户不会超过活动容量。
当前本地修复把状态检查、容量统计、旧记录清理和新报名放进同一个 Serializable 事务。遇到 Prisma P2034 写冲突时,最多重新读取并尝试三次;通知发送放在事务提交后,避免回滚的报名也产生“报名成功”消息:
const result = await prisma.$transaction(
async (tx) => {
const activity = await tx.activity.findUnique({ where: { id: activityId } });
// 在同一事务里重新检查状态、名额并创建报名。
return tx.registration.create({ data: { activityId, userId } });
},
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
);
本地回归覆盖普通报名、满员候补、名额耗尽,以及模拟序列化冲突后重新读取容量;通知只在最终事务成功后发送。它证明代码分支与重试边界,不等于真实 PostgreSQL 多进程压力测试,因此仍要在隔离数据库用并发请求验证最多产生一个最后席位,并检查冲突后的客户端提示。
身份与权限要按当前状态判断
服务端在应用级注册 JWT 守卫,登录/回调路由通过显式 Public 标注开放;后台与部分组织者动作再叠加角色守卫。这个结构比依赖前端隐藏菜单可靠,但要确保角色不是永不过期的令牌快照:每次请求都应依据 token subject 读取当前账号状态与角色,账号封禁、角色降级后旧令牌要及时失效。客户端收到 401 时清理本地会话并回到登录页;管理台和小程序各自实现了这一体验。
后台 Axios 拦截器从浏览器本地存储读取 token,因此管理端必须避免任意脚本执行并正确设置内容安全策略;更高敏感度的站点可评估 HttpOnly Secure Cookie,同时配套 SameSite 与 CSRF 防护。小程序运行在平台容器里,不能照搬浏览器 Cookie 假设;请求域名也必须在小程序平台配置为合法 HTTPS 域名。跨端共享的是 API 语义,不是存储和威胁模型。
已验证与未验证
本地后端 Jest 回归与 TypeScript 构建类型检查均通过。本轮源码增加了当前用户角色/状态重查、无效 JWT subject 拒绝、Node 内置 UUID 生成,并对满员竞争采用 Serializable 事务;这些改动尚未发布到生产。生产只读复核确认 badminton-api.service 正常运行,4000 仅绑定回环地址,/badminton/api/activity 与 /venue 返回 200。管理台访问路径 /badminton/admin/ 当前仍返回 404,说明它的静态资源映射和路由回退还没上线;这不能被写成“管理端已可用”。真实微信登录、合法域名、真机体验和高并发报名均未在本轮验收。
上线前的检查顺序
- 为认证密钥设置强随机值,使用启动失败而不是默认密钥;密钥轮换要计划用户重新登录、备份和回退。
- 把角色降级、封禁、缺失用户和过期 token 纳入认证回归,确认敏感动作逐路由要求相应角色。
- 在隔离 PostgreSQL 上并发抢最后名额,并覆盖候补、取消、候补转正、重复报名与通知失败。
- 分别构建管理台和小程序;验证静态目录、SPA fallback、API 代理、平台域名白名单与 401 行为。
- 用小程序开发者工具和真实设备检查登录、弱网、刷新、活动列表和报名过程,不把 Web 预览当成真机验证。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。