个人健身预约系统:从业务建模到 Alpha 验收
#FastAPI #Vue #PostgreSQL #Docker #预约系统 #软件测试
记录一个面向个人健身教练的预约系统如何从网页版 Demo 演进到可公开测试的
Alpha:业务角色、预约与履约状态、敏感数据、日程可视化、验收数据和部署安全。
一、目标与边界
项目先交付移动优先的网页版,未来再迁移到微信小程序。首版不接入付费短信、在线支付和跨教练调度,登录使用账号密码;微信版本再切换到微信认证。
核心角色分为:
-
学员:注册、预约、取消、查看训练记录、上传饮食记录;
-
教练:发布时段、设置休息、代客预约、确认履约或爽约、维护学员档案;
-
管理员:审批教练、管理人员绑定、维护课程和全局配置;
-
多身份用户:不退出登录即可切换教练与管理员工作区。
技术栈最终采用 Vue 3、FastAPI、PostgreSQL、Docker Compose 和 nginx。相比 Flask,
FastAPI 的类型模型、请求校验和自动生成 OpenAPI 的能力更适合接口较多、准备迁移到小程序的项目;生产环境则关闭公开 API 文档。
二、核心实体关系
主要实体包括用户、角色分配、教练绑定、课程、开放时段、预约、休息时间、通知、审计日志、学员档案、课程笔记和饮食记录。
几个关键设计:
-
UserRoleAssignment独立于用户主角色,支持同一账号拥有多种身份; -
StudentCoachBinding明确学员当前绑定的教练,所有教练端查询都按绑定关系过滤; -
预约保存课程名称、价格、时长和地点快照,后续修改课程不会污染历史;
-
休息时间和预约都使用起止时间区间,创建时检查交叠;
-
取消预约和取消休息均保留状态、时间与原因,不做物理删除;
-
关键操作写入审计日志,通知携带业务目标 ID,可以下钻到具体课程详情。
手机号采用两份派生数据:
-
AES-GCM 加密值用于必要时解密和脱敏展示;
-
HMAC-SHA256 摘要用于精确登录查询。
数据库不保存明文手机号和明文密码,密码使用带随机盐的 scrypt。
三、预约、排班与可视化
学员既可以预约单次课程,也可以按“每隔 X 天”或“每隔 X 周”批量预约。服务端会:
-
锁定目标时段;
-
重新检查时段是否开放;
-
检查教练和学员的时间冲突;
-
校验至少提前两小时、最远三十天;
-
通过幂等键避免网络重试产生重复预约;
-
返回成功和跳过的日期。
教练可以按工作日批量开放时段,也可以周期性设置休息。休息与已有预约冲突时跳过;取消休息后保留历史记录,并恢复相关开放时段。
主页日程采用“日期列 × 每日时间轴”:
-
预约块按实际开始和结束时间定位;
-
履约、爽约、取消使用不同颜色;
-
教练休息使用斜纹块;
-
学员只能看到“教练休息”,教练能看到真实原因;
-
查询范围覆盖当日前一个月至后一个月;
-
日期切换带方向性的平滑动画;
-
点击预约块进入课程详情,退出后回到原标签页。
四、学员档案与历史记录
课程详情不仅展示预约信息,还作为学员档案的下钻入口:
-
基础资料和脱敏手机号;
-
当前健康注意事项;
-
每次课程的训练内容和课程笔记;
-
每日三餐的图片和文字记录。
饮食图片在服务端统一校验格式、限制大小、纠正 EXIF 方向,缩放到最长边不超过
1600 像素并转成 WebP,减少长期存储压力。
学员和教练都可以按课程、地点、日期关键词检索预约历史,并筛选预约、履约和爽约。所有记录列表和站内消息每页最多显示十条。
五、安全与部署
服务部署在 MyServer,通过 Docker Compose 运行 API、Web 和 PostgreSQL。nginx 负责
TLS 终止和反向代理,公网地址在笔记中统一记为 <VPS-IP>。
已启用的基础防护包括:
-
ORM 参数化查询与 Pydantic 长度、类型校验;
-
Vue 默认文本转义,避免服务端文本被当作 HTML 执行;
-
Content Security Policy、HSTS、Permissions Policy;
-
登录与 API 请求限流;
-
JWT 与手机号加密密钥的生产强校验;
-
/docs、/redoc和/openapi.json在生产环境不可访问; -
手机号脱敏和基于绑定关系的对象级权限检查。
nginx 与公网 HTTPS 部署思路和此前的《OpenClaw 公网访问:FRP + nginx HTTPS 配置实录》相似,但本项目直接以端口形式提供测试访问,没有在文章中保留公网 IP。
六、如何让验收数据可重复
开发过程中临时注册、取消和通知会迅速污染数据库。最终增加了一个显式确认的场景重置工具,它会清空旧模拟业务数据和测试上传文件,再生成固定验收集:
-
两名教练、两名正式学员和一名管理员;
-
待审批学员、待审批教练和代约访客;
-
已预约、已履约、显式爽约、自动爽约、学员取消、教练取消;
-
有效休息与已取消休息;
-
健康资料、课程笔记、饮食记录;
-
超过十条的消息,用于分页验证;
-
两组教练绑定,用于验证跨教练访问隔离。
生成后由验证器依次登录三类角色,检查路由、权限、数据状态、休息原因脱敏、课程笔记和自动爽约判定。另有真实 Chromium 巡检,在公网 HTTPS 页面执行导航、查询、筛选和详情下钻。
最终测试组合包括:
-
13 项后端业务、安全和并发测试;
-
前端 XSS 输出转义与分页测试;
-
PostgreSQL 双请求抢约测试;
-
三角色公网浏览器巡检;
-
Python 和 Node 依赖漏洞扫描;
-
数据库迁移与临时库恢复演练。
七、当前结构暴露出的下一步
Alpha 已经足够支持小范围测试,但当前模型也暴露出几个应在继续扩展前解决的问题:
-
将“确认出勤”和“课程完成”拆成两个状态,避免提前确认到场就把课程标为完成;
-
为周期预约和周期休息增加系列实体,支持“仅本次、本次及以后、全部”操作;
-
完善开放时段的关闭、重新开放和取消生命周期;
-
增加账号停用、恢复以及代约访客认领正式账号;
-
将历史查询和通知分页下沉到服务端;
-
将健康状态从覆盖字段升级为可追溯的变更历史;
-
正式上线前把本地存储的 Bearer Token 迁移到安全 Cookie,并补充 CSRF 防护。
这轮实践的关键收获不是完成了多少界面,而是把业务状态、对象级权限、可重复验收数据和真实浏览器验证放在同一条交付链路中。只有这些部分能够一起重建和验证,Demo 才真正接近一个可以继续演进的产品。
