Habit 是一套面向家庭与儿童成长场景的多租户系统。它在 SaaS 底座上增加了家庭成员、儿童档案、习惯计划、每日记录、积分、奖励、惩罚、课程和智能陪伴等领域,因此请求除了租户身份,还必须确定当前家庭与儿童上下文。
物理项目与交付物
| 目录 | 技术与形态 | 主要职责 | 交付方式 |
|---|---|---|---|
platform |
Vue 3、Vite、Element Plus | 租户、平台账号、权限、全局设置和系统治理 | 平台静态管理站 |
tenant |
Vue 3、Vite、Element Plus | 租户员工、习惯模板、课程、奖励商品、标签和运营配置 | 租户静态管理站 |
pc |
Nuxt、Element Plus | 家长和儿童的桌面 Web 入口 | SSR 服务或静态产物 |
uniapp |
Vue 3、uni-app、Pinia | 家庭、儿童、训练、课程、积分与奖励的主要移动端 | H5、小程序或 App |
server |
PHP 8、ThinkPHP 8、Redis、OpenAI Client | 平台、租户和用户 API,训练与奖励规则,缓存和智能陪伴 | PHP-FPM 与任务进程 |
源码中同时存在旧版页面与 v2 页面、Logic 和数据结构。它们属于同一产品的迭代边界,不应在新功能中继续复制两套规则。
总体拓扑
flowchart TB Platform[platform
平台治理] --> PA[platformapi] Tenant[tenant
租户运营] --> TA[tenantapi] PC[pc
家庭桌面端] --> API[api] Uni[uniapp
家庭移动端] --> API PA --> TenantCtx[租户上下文] TA --> TenantCtx API --> FamilyCtx[用户 / 家庭 / 儿童上下文] TenantCtx --> Domain[公共领域服务] FamilyCtx --> Domain Domain --> Train[计划 / 阶段 / 模块
每日记录 / 归档] Domain --> Growth[积分 / 成就 / 奖励
惩罚 / 兑换] Domain --> Course[课程 / 剧集 / 学习记录] Domain --> Companion[成长档案 / 智能陪伴] Domain --> DB[(MySQL)] Domain --> Cache[(Redis
统计缓存)] Companion --> LLM[模型服务]
服务端应用边界
| 应用 | 调用方与职责 | 数据边界 |
|---|---|---|
platformapi |
平台运营,管理租户、平台权限和全局能力 | 可跨租户治理,跨租户查看需要明确审计 |
tenantapi |
租户管理员,管理员工、模板、课程、奖励商品和租户配置 | 只访问当前租户及授权部门范围 |
api |
家长、儿童与普通用户,执行训练、积分、奖励和课程操作 | 同时校验当前用户、租户和儿童关系 |
testapi |
测试或联调入口 | 不应在生产环境无保护开放 |
common |
模型、枚举、领域逻辑和公共服务 | 不直接暴露公网接口 |
平台租户、租户员工、家长用户和儿童不是同一种角色的不同等级。儿童通常不拥有独立完整管理权限,操作必须通过 UserChild 等关系确认监护或家庭归属。
两级上下文与身份校验
租户上下文
租户可以由域名、应用配置或登录会话确定。内容、模板、课程、奖励商品、支付配置和用户关系都应带入当前租户。服务端不能直接采用客户端提交的 tenant_id,否则修改请求参数就可能跨租户访问。
平台模板与租户自定义内容需要明确覆盖规则。例如奖励商品或训练模板可以来自平台,也可以由租户创建;查询时应标记来源,租户只能编辑自己的记录。
家庭与儿童上下文
同一用户可以关联多个儿童,同一儿童也可能由多个家庭成员共同照护。请求进入儿童业务前至少验证:
- 当前用户会话有效;
- 目标儿童通过
UserChild与当前用户关联; - 儿童所属租户与当前应用租户一致;
- 当前关系允许执行该动作,例如查看、记录、奖励或修改档案;
- 已选择的儿童对象写入请求上下文,后续 Logic 不再相信任意
child_id。
儿童 ID、租户 ID 和用户 ID 也要进入缓存 Key、队列负载、操作日志和导出任务。
训练领域
训练模块不是一张“打卡表”,源码已经拆出完整的计划结构:
| 层级 | 主要对象 | 含义 |
|---|---|---|
| 模板与标签 | ChildHabitPlanTemplates、Tag Map、规则分类与选项 |
租户可复用的计划蓝本和规则选项 |
| 计划 | ChildHabitPlans、Collections、Routines |
某个儿童真正执行的习惯、合集和日程 |
| 阶段 | Plan Stages、Stage Rules | 把长期习惯拆成阶段目标和生效规则 |
| 模块 | Plan Modules、Module Items、Topics | 将一次训练拆成记录项、题目或任务内容 |
| 要求 | Requirements、Requirement Rules | 描述次数、时长、数值、证明等完成条件 |
| 每日事实 | Daily Records、Fields、Values、Duration Logs、Check-ins | 保存当天实际完成情况和结构化记录 |
| 汇总与归档 | Daily Stats、Archive Records | 用于月历、趋势和阶段性成长档案 |
模板只用于创建计划,之后模板变更不应无条件改写儿童正在执行的计划。需要升级模板时,应生成新版本并由家长或租户确认迁移。
一次习惯完成怎样落库
- API 根据会话取得当前儿童和租户;
- 验证计划处于有效期、当天允许执行且记录项满足要求;
- 写每日记录、字段值、时长或打卡事实;
- 根据规则计算本次积分变化;
- 写
ChildAccountLog,并关联计划、合集和每日记录; - 更新习惯积分、积分中心和合集统计缓存;
- 检查成就、阶段完成和归档条件;
- 返回本次结果与最新汇总。
第 3 至第 5 步属于同一个事务边界。缓存更新失败可以重建,但每日事实和积分流水不能只写进 Redis。
积分账本与统计缓存
ChildAccountLog 是积分变化的主账,ChildAccountLogHabitPlan 和 ChildHabitPlanPointsLog 保存计划关联及聚合信息。每笔变化需要包含增加或扣减方向、金额、业务类型、业务对象、操作者、备注和创建时间。
Redis 中的 HabitPointsStatCache、PointsCenterStatCache 和 CollectionStatCache 用于加速当天、月份和合集统计。它们是可重建投影,不是最终事实。出现缓存丢失时,应能从账户流水和每日记录重新计算。
积分操作使用业务幂等键,例如“儿童 + 每日记录 + 规则 + 动作”。重复提交、客户端重试或队列重放不能重复加分。
奖励、惩罚与兑换
奖励领域包含成就规则与记录、等级、惩罚规则、惩罚成本、兑换规则、兑换记录、暂存记录和奖励商品。它们分别回答:何时获得、当前处于什么等级、违反规则如何扣减、积分能换什么以及兑换是否已经履约。
兑换过程应按以下边界执行:
1 | 校验商品与规则 -> 锁定儿童积分账户 -> 写扣分流水 |
兑换记录与扣分流水用业务号关联。实物或家庭承诺尚未履约时,不能只把它标记为完成;取消兑换也应写一笔反向流水,而不是删除原始扣分。
惩罚成本与习惯计划、规则和调整记录关联。计算逻辑需要保存当时的规则快照,避免修改规则后改变历史结果。
课程与智能陪伴
课程模块包含课程、分集、模块模板、模块内容、标签、收藏和观看历史。课程学习可以触发积分或成长记录,但应通过明确事件接入,不能让课程模块直接修改奖励表。
智能陪伴使用儿童成长档案、陪伴设置和上下文服务组织模型输入。模型只得到完成当前任务所需的最小信息;儿童真实姓名、联系方式、家庭隐私和原始流水不应整批发送给外部模型。生成建议是辅助内容,不应自动执行扣分、诊断或高风险决定。
兼容层与 v2 演进
源码中已有 v2/train、v2/reward 和 v2/user,覆盖每日计划、要求、积分、成就、惩罚成本、兑换、儿童档案和陪伴设置。这说明系统正在从早期单一打卡页面转向结构化训练与成长模型。
维护时应做到:
- 新规则只在领域服务实现一次,旧接口通过适配器调用;
- 明确旧接口的停用时间,不无限期双写新旧表;
- 为迁移后的积分余额、计划状态和兑换记录做逐项对账;
- 客户端按能力或版本切换接口,不根据页面名称猜测数据版本。
数据、缓存与外部服务
| 设施 | 保存内容 | 注意事项 |
|---|---|---|
| MySQL | 租户、家庭关系、儿童、计划、每日记录、积分、奖励、课程和支付事实 | 组合索引包含租户与儿童范围 |
| Redis | 统计缓存、会话、锁和短期状态 | 所有统计可从主账重建,并设置版本号 |
| 对象存储 | 头像、课程媒体、训练证明和生成内容 | 下载前校验用户与儿童关系 |
| 支付渠道 | 充值、课程或会员订单 | 回调验签、金额校验和幂等处理 |
| LLM | 陪伴建议和辅助生成 | 最小化儿童数据,设置超时和内容安全策略 |
部署与故障域
| 运行单元 | 扩缩容特点 | 主要故障影响 |
|---|---|---|
| 平台与租户后台 | 静态独立发布 | 运营暂不可用,不影响家庭继续记录 |
| PC 与 uni-app | 按渠道发布 | 单端故障不影响其他客户端 |
| HTTP API | 无状态扩容 | 训练、积分和课程操作失败 |
| 统计与异步任务 | 带锁运行,可重放 | 汇总或通知延迟,主账不丢失 |
| MySQL | 业务事实与积分账本 | 不可用时禁止离线猜测积分写入 |
| Redis | 会话和统计投影 | 可降级到数据库,但要限制重查询 |
| 模型服务 | 单独超时与熔断 | 陪伴建议不可用,不影响核心打卡与兑换 |
当前架构债务与改进顺序
- 儿童上下文收口:统一验证用户、租户和儿童关系,禁止各 Logic 只按
child_id查询; - 积分幂等与对账:给所有加减分来源定义业务号,建立主账与缓存的重建、核对命令;
- 训练状态机:明确计划、阶段、每日记录和归档的合法状态转换;
- 奖励履约:把申请、审核、履约、取消和退回形成闭环,保留完整反向流水;
- 版本收敛:让旧接口调用
v2领域服务,逐步停止新旧逻辑并行; - 儿童隐私:建立数据最小化、监护人授权、导出删除和模型调用审计;
- 可观测性:用请求 ID、儿童 ID、计划 ID 和积分业务号串联每日记录、流水与奖励。
积分获得、惩罚和奖励兑换之间的具体时序,可继续阅读积分与奖励闭环。