type
Post
status
Published
date
Apr 29, 2026
slug
booking-app-card-async-tasks
summary
用 AI 辅助开发预约系统后端,记录 Week 6 卡片生命周期管理和 Week 7 Celery 异步任务的完整实现,从设计思路到测试策略,86 个单元测试全部通过。
tags
开发
AI
工具
category
技术分享
icon
password
前言:这是一个用 AI(Claude Code)辅助开发的健身预约 SaaS 系统后端。整个项目采用 FastAPI + SQLAlchemy 2.0 + MySQL + Celery 技术栈,按周迭代推进。本文记录 Week 6(卡片服务)和 Week 7(异步任务)的完整实现过程——从业务建模、状态机设计,到测试策略、踩坑总结。如果你正在构建类似的订阅/课时卡系统,这些经验值得参考。
📝 主旨内容
Week 6:卡片生命周期管理
课时卡不只是一个数字,它是有状态的合约——激活、冻结、到期,每次变更都要留痕。
健身预约系统的核心资产是"课时卡"。学员买卡、用卡、退费,商家开卡、续费、冻结……这些操作背后需要一套严谨的状态机和完整的操作日志。
卡的四种状态:
状态 | 含义 | 可转移到 |
ACTIVE | 正常可用 | FROZEN / EXPIRED / USED_UP |
FROZEN | 冻结(学员出差/受伤) | ACTIVE |
USED_UP | 课时耗尽 | ACTIVE(续费后) |
EXPIRED | 已过期 | — |
核心操作与 CardLog 记录:
关键设计决策:
store_id从请求体传入:AdminUser模型没有store_id字段(管理员可跨门店操作),所以开卡时store_id放在CardOpenRequest里,而非从 JWT 中取。
- 余额不能为负:
adjust_card操作新增CardBalanceError(错误码 20013),在服务层校验,而不是依赖数据库约束。
- 续费激活 USED_UP 卡:如果一张卡课时耗尽后续费加课时,状态自动从
USED_UP恢复为ACTIVE。
本周新增 21 个单元测试,覆盖所有正常路径和异常路径(非法状态转移、余额不足、商户越权等)。
Week 7:Celery 异步任务架构
业务逻辑和 Celery 任务分离——核心函数接受db: Session,可以直接单测;Celery wrapper 只负责创建 Session 和异常处理。
异步任务分三类:
① 定时 Cron 任务
Celery beat 配置:
mark_no_show 每小时触发,expire_cards 每天触发。② 微信订阅消息通知
三种通知:预约成功、上课提醒、取消通知。统一提取
_load_notification_context 加载上下文,失败自动 retry 3 次(间隔 60s):WX_MOCK=true 时,send_subscribe_message 只打日志,不真实调用微信接口,开发期无需真实 AppID。③ 数据导出任务
结果保存到
EXPORT_DIR(默认 /tmp/booking_exports/),文件名含商户ID和Celery任务ID,生产环境可替换为 OSS 上传。测试策略:如何对异步任务写单测
不要测 Celery 基础设施,测业务逻辑。
对所有 Celery 任务,我们的测试原则是:只测
_xxx 内部函数,不测 @celery_app.task 装饰器。通知任务测试则 mock 掉微信调用:
本周新增 12 个单元测试,项目累计 86 个测试全部通过。
🤗 总结归纳
两周下来,卡片服务和异步任务模块全部落地。核心收获有三点:①状态机要在服务层显式管理,不要隐式依赖数据库约束;②Celery 任务的可测性关键在于把业务逻辑和 Session 生命周期分离;③
WX_MOCK 模式让通知功能在开发期完全可用,不被第三方依赖阻塞。下一步是 Week 8:集成测试 + API 文档 + 性能优化。📎 参考资料
你在构建订阅/课时卡类系统时,是怎么设计卡的状态机的?有没有遇到过"冻结期间到期"这种边界情况?欢迎在评论区交流!

