🃏预约系统后端实录:卡片服务与异步任务的设计与实现

2026-4-29|2026-4-29
WU
WU
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 记录:
关键设计决策:
  1. store_id 从请求体传入AdminUser 模型没有 store_id 字段(管理员可跨门店操作),所以开卡时 store_id 放在 CardOpenRequest 里,而非从 JWT 中取。
  1. 余额不能为负adjust_card 操作新增 CardBalanceError(错误码 20013),在服务层校验,而不是依赖数据库约束。
  1. 续费激活 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 文档 + 性能优化。

📎 参考资料

💬
你在构建订阅/课时卡类系统时,是怎么设计卡的状态机的?有没有遇到过"冻结期间到期"这种边界情况?欢迎在评论区交流!
Claude Code 结对编程实战:FastAPI 约课平台后端 8 周全记录AI 结对编程实战:4周搭建健身房约课系统后端
Loading...