输入关键词开始搜索

注册全链路

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 统一编排。


二、思路

  1. 校验独立RegistrationValidator 作为纯 QObject,暴露 Q_INVOKABLE validate(QVariantMap) 方法,返回 {valid, errors}
  2. 计算独立TdeeCalculator 封装 Mifflin-St Jeor 公式,提供 calculateBmr/calculateTdee/applyDietGoal 三个方法
  3. 编排在 ServiceAuthService 通过依赖注入持有 UserRepository,内部创建 ValidatorCalculatorregisterUser() 按序调用
  4. 暴露到 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
height50-250 cm2001
weight20-500 kg2001
age1-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(...) 即可推送到云端,接口不变。