注册全链路
202. 注册模块:校验→TDEE→AuthService 全链路
日期: 2026-05-20 关联开发任务: 阶段 2 任务 2.1.2 ~ 2.1.4 注册表单校验/TDEE计算/AuthService 关联设计文档: 005.接口设计文档 §3.1、004.数据库设计文档 §4.1、001.概要设计文档 离线优先架构
一、问题/需求
注册按钮点击后需要依次完成:表单校验 → 唯一性检查 → BMR/TDEE 计算 → 密码哈希 → 数据库写入。不能把校验逻辑放在 QML 中(违反分层架构),需要 C++ 侧提供可调用的校验器和计算器,再由 AuthService 统一编排。
二、思路
- 校验独立:
RegistrationValidator作为纯 QObject,暴露Q_INVOKABLE validate(QVariantMap)方法,返回{valid, errors} - 计算独立:
TdeeCalculator封装 Mifflin-St Jeor 公式,提供calculateBmr/calculateTdee/applyDietGoal三个方法 - 编排在 Service:
AuthService通过依赖注入持有UserRepository,内部创建Validator和Calculator,registerUser()按序调用 - 暴露到 QML:Validator 和 Calculator 通过
qmlRegisterType注册,AuthService 通过setContextProperty注入
三、逻辑推导
3.1 依赖注入链
main.cpp
├── DatabaseManager::initialize()
├── new UserRepository(&app)
├── new AuthService(userRepo, &app)
└── engine.rootContext()->setContextProperty("authService", authService)
Service 层通过构造参数接收 Repository 指针,不 new 自己的依赖。
3.2 Mifflin-St Jeor 公式
男性: BMR = 10×W + 6.25×H - 5×A + 5
女性: BMR = 10×W + 6.25×H - 5×A - 161
TDEE = BMR × 1.55 (默认中等活动系数)
减脂目标: TDEE × 0.80
增肌目标: TDEE × 1.15
维持目标: TDEE × 1.00
3.3 校验规则与错误码
| 字段 | 规则 | 错误码 |
|---|---|---|
| nickname | 非空 | 2001 |
| height | 50-250 cm | 2001 |
| weight | 20-500 kg | 2001 |
| age | 1-150 岁 | 2001 |
| gender | 必须选择 (1 or 2) | 2001 |
| dietGoal | 必须选择 (0-2) | 2001 |
| login_id | 唯一性检查 | 4002 |
四、实施方案
文件清单
src/core/registration_validator.h/.cpp ← 表单校验
src/core/tdee_calculator.h/.cpp ← TDEE 计算
src/core/auth_service.h/.cpp ← 注册流程编排
src/app/main.cpp ← 依赖注入 + QML 类型注册
AuthService::registerUser() 流程
1. validator_->validate(profile) → valid?
2. userRepo_->existsByLoginId(loginId) → 已被占用?
3. tdeeCalc_->calculateBmr(...) → BMR
4. tdeeCalc_->calculateTdee(bmr) → TDEE
5. tdeeCalc_->applyDietGoal(tdee, goal) → 目标 TDEE
6. hashPassword(password) → SHA-256 (临时)
7. userRepo_->save(user) → INSERT INTO users
8. emit userRegistered(userId)
五、踩坑记录
| 现象 | 根因 | 修复 |
|---|---|---|
| 55 个 CREATE INDEX 全部报”no such table” | database_manager.cpp 的 startsWith("--") 跳过逻辑把含注释头的 CREATE TABLE 整块吞掉 | 移除 startsWith 跳过,改为过滤纯注释块 |
| 1 ok, 55 errors | 同上 + SQLite 下 CREATE INDEX 要求表必须存在 | 修复后 78 ok, 0 errors |
| 密码哈希用啥? | Argon2id 未集成(阶段 2.3 任务) | 临时用 QCryptographicHash::Sha256,标注 TODO |
六、验证
| 验证项 | 结果 |
|---|---|
| 表单校验:空昵称 → “请输入您的昵称” | ✅ |
| 表单校验:身高 49 → “请输入有效身高” | ✅ |
| 全部填写正确 → success:true | ✅ |
| 重复注册相同 login_id → “该账号已被注册” | ✅ |
| BMR 计算正确性(男 175/70/25 → 1642.5) | ✅ |
| 数据库写入 → smartdiet.db 可查 | ✅ |
七、补充:为什么没有”API 对接”
开发计划 008 中 2.1 包含「API 对接」子任务(原始 2h),指调用 POST /api/v1/auth/register。本阶段未实现。
原因:1.3 决定的离线优先架构——阶段 2-7 全部操作走本地 SQLite,不依赖远程服务器。AuthService::registerUser() 就是”API”——只是调用的是进程内 Repository 而非 HTTP。
后期规划:阶段 8 在 AuthService 中加一行 syncManager.enqueue(...) 即可推送到云端,接口不变。