Appearance
模块手册 · 认证与用户域(auth-user)
收录 3 个模块的权威开发手册(源自各模块
CLAUDE.md,随仓库同步)。
本文目录
- oauth
- user
- shared
1. oauth 模块
源:
src/app/oauth/CLAUDE.md(随仓库同步)
src/app/oauth/
OAuth 双 token 登录认证域 — DDD 4 层架构,9 个 use case,P0 阶段完成。 业务码区间
40101xxx(顶层宽容归一化 → HTTP 200 + code)。
模块职责
OAuth 域负责 C 端用户的短信验证码登录与设备绑定/会话续期全链路,覆盖 9 个 use case:
| use case | 路径 | 角色 | 关键点 |
|---|---|---|---|
SmsSendUseCase | application/usecase/sms_send.py | 匿名 | 发送短信码(Mock SMS fallback) |
SmsVerifyUseCase | application/usecase/sms_verify.py | 匿名 | 校验短信码(不签发 token) |
SmsLoginUseCase | application/usecase/sms_login.py | 匿名 | 短信码登录 + 自动注册 + 签发双 token + DeviceBinding upsert |
CaptchaUseCase | application/usecase/captcha.py | 匿名 | 图形验证码生成/校验 |
BindPhoneUseCase | application/usecase/bind_phone.py | 已登录 | 绑定手机号(实名场景) |
UnbindPhoneUseCase | application/usecase/unbind.py | 已登录 | 解绑手机号 |
RotateTokenUseCase | application/usecase/rotate.py | 已登录 | refresh_token 换发新 access_token |
CallbackUseCase | application/usecase/callback.py | 已登录 | 三方登录回调(微信/Apple) |
LogoutUseCase | application/usecase/logout.py | 已登录 | 注销 + 清 DeviceBinding |
合计 9 use case(P0 完整交付)。
DDD 4 层结构
src/app/oauth/
├── domain/ # 零 web 依赖(已被 scripts/check_layers.py 卡控)
│ ├── entities/
│ │ ├── captcha.py # Captcha + 校验状态机
│ │ ├── sms_code.py # SmsCode + VerifyResult 状态机
│ │ └── device_binding.py # DeviceBinding(多设备会话上限)
│ ├── value_objects/
│ │ ├── phone.py # Phone(E.164 校验 + 脱敏)
│ │ ├── client_id.py # ClientId + DEFAULT_CLIENT_ID
│ │ └── captcha_id.py # CaptchaId
│ └── exceptions.py # OAuthErrorCode 40101xxx + OAuthError
│
├── application/ # use case 编排 + 端口契约
│ ├── ports/
│ │ ├── repositories.py # SmsCodeRepo / DeviceBindingRepo / UserRepo ...
│ │ └── services.py # TokenServicePort / EventPublisherPort / SmsSenderPort
│ ├── factories/
│ │ └── create_oauth_usecases.py # 装配入口(与 pickRepo 工厂协同)
│ └── usecase/ # 9 use case
│ ├── sms_send.py / sms_verify.py / sms_login.py
│ ├── captcha.py
│ ├── bind_phone.py / unbind.py
│ ├── rotate.py
│ ├── callback.py
│ └── logout.py
│
├── infrastructure/ # 适配器 + 双仓储
│ ├── persistence/
│ │ ├── inmemory_repositories.py # 内存实现(dev / 单测)
│ │ ├── sqlmodel_repositories.py # SQLModel + PostgreSQL(prod)
│ │ ├── redis_repositories.py # Redis(短信码 / 限流 / 设备绑定)
│ │ ├── repository_factory.py # pickRepo 工厂(V2_USE_PRISMA / DATABASE_URL)
│ │ ├── db.py # 异步 session 工厂
│ │ └── sqlmodel_models/ # orm.py + user_extensions.py
│ └── adapters/
│ ├── token_service.py # RS256 JWT 双 token(ADR-0004)
│ ├── jwks.py # JWKS 公钥端点
│ ├── captcha_generator.py # 图形验证码
│ ├── sms_sender_factory.py # 自动 fallback Mock ↔ Tencent
│ ├── mock_sms_sender.py # dev mock
│ ├── tencent_sms_sender.py # 腾讯云 SMS
│ ├── rate_limiter.py # 内存实现(dev)
│ └── rate_limiter_redis.py # Redis 实现(prod)
│
└── interface/ # FastAPI 路由 + 顶层归一化
├── api/
│ └── oauth.py # OAuth 公开路由(/api/v1/oauth/*)
├── normalizer.py # 顶层宽容归一化(HTTP 200 + code 40101xxx)
└── handlers.py # ExceptionHandler 注册顺序(OATH-M-006 教训修复)关键依赖与配置
| 类别 | 文件 | 说明 |
|---|---|---|
| JWT 算法 | infrastructure/adapters/token_service.py | RS256(ADR-0004),私钥/公钥分离 |
| 短信适配 | infrastructure/adapters/sms_sender_factory.py | V2_USE_TENCENT_SMS=1 切换真发;否则 Mock |
| 限流 | infrastructure/adapters/rate_limiter_redis.py | Redis 滑窗;V2_USE_REDIS=1 |
| 双仓储 | infrastructure/persistence/repository_factory.py | V2_USE_PRISMA=1 + DATABASE_URL |
| JWKS 端点 | infrastructure/adapters/jwks.py | /.well-known/jwks.json |
数据模型
Captcha— captcha_id + base64 image + 答案(一次性)SmsCode— phone + code + 有效期 + 重发冷却 + 尝试次数DeviceBinding— user_id + device_id + last_seen + revoke_token(多设备上限 N)User(跨域)— 由application/ports/services.py: UserRepositoryPort抽象
错误码(40101xxx)
完整列表见 docs/internal-wiki/architecture/error-codes.md 与 src/app/oauth/domain/exceptions.py:OAuthErrorCode。
测试与质量
tests/oauth/integration/test_e2e.py— 短信登录 E2E(mock SMS)tests/unit/domain/test_captcha_state_machine.py— 状态机单测tests/unit/domain/test_sms_code_state_machine.py— 状态机单测tests/unit/application/test_oauth_p1_usecases.py— use case 单元tests/integration/test_admin_oauth_integration.py— 与 admin 共生集成- 守卫:
scripts/check_layers.py卡 domain 零依赖;scripts/check_exception_handlers.py卡注册顺序
常见问题 (FAQ)
- Mock SMS 怎么开启? — 默认自动 Mock;设
V2_USE_TENCENT_SMS=1走真发。dev 不需要任何配置。 - 为什么是双 token? — access(短期,RS256)+ refresh(长期,DeviceBinding 锚定)。多设备上限由 DeviceBinding 控制。
- 顶层归一化为什么重要? — OATH-M-006 教训:ExceptionHandler 注册顺序错误导致 OAuthError 漏归一化,统一以 HTTP 200 + code 40101xxx 返回。
- 如何接入三方登录? —
application/usecase/callback.py抽象;具体 Provider(微信/Apple)在 infrastructure/adapters/ 下扩展。
相关文件清单
docs/api-docs/v1/oauth/— OpenAPI/接口文档docs/internal-wiki/oauth-deep-dive/— JWT 解剖 / 多设备会话 / Cookbookdocs/internal-wiki/architecture/oauth-implementation.md— 实施总览docs/internal-wiki/decisions/— RS256 决策
变更记录 (Changelog)
| 日期 (Asia/Shanghai) | 版本 | 变更 |
|---|---|---|
| 2026-09-16 11:37:50 | 0.1.0 | /init-project 首次沉淀模块 CLAUDE.md(含面包屑 + DDD 4 层 + 9 use case + FAQ) |
| 2026-09-15 22:00:00 | — | P0 完成(与根 0.2.0 一致)— 内容仅在 docs/api-docs/v1/oauth/ |
2. user 模块
源:
src/app/user/CLAUDE.md(随仓库同步)
src/app/user/
user 模块 — V1 user 11 action 迁移(V2 DDD 4 层 + InMemory + 行为兼容),V2.0.14 落地。
模块职责
user 承接 C 端用户域(对齐 V1 server/src/modules/user/),路由风格 = action 协议(非 admin 的 REST 风格):
- 入口:
POST /api/v1/user+body {action, payload}(V1POST /api/user路径平移,协议 100% 兼容) - 11 action:
completeOnboarding/resetOnboarding/bindApps/bindInviter/checkReportQuota/redeemMember/submitOnboarding/onboardingSuggestion/adoptPlanOption/precheckDeleteAccount/deleteAccount - 鉴权:全部需已登录(
Authorization: Bearer <access_token>— access token 解码桥接 oauth token 服务) - 错误码区段:40301xxx(与 OAuth 40101xxx / admin 40201xxx 三层隔离)
- HTTP 状态:非 admin「默认 200」口径 —— 默认 400 / 401 / 404(V1 行为 100% 兼容)
DDD 4 层结构
src/app/user/
├── domain/ # 零 web 依赖
│ ├── entities/user.py # User 实体 + UserDTO(25 字段,camelCase 输出契约)
│ ├── services/
│ │ ├── onboarding_service.py # 纯函数:画像/目标/方案/安全边界(V1 onboarding 逐条对拍)
│ │ └── growth_level.py # 成长等级派生(total_points_earned 驱动)
│ └── exceptions.py # UserErrorCode (40301xxx) + cz_user_error_to_http_status
│
├── application/
│ ├── ports/
│ │ ├── repositories.py # UserRepositoryPort / TeamRepositoryPort / ReportQuotaPort /
│ │ │ # BodyCompositionRepositoryPort / AccountDeletionPort / UserCachePort
│ │ └── services.py # PasswordHasherPort / AccessTokenDecoderPort
│ ├── usecase/ # 11 use case(4 文件)
│ │ ├── onboarding.py # complete / reset / submit / suggestion / adoptPlanOption(5)
│ │ ├── account_binding.py # bindApps / bindInviter(2 — 邀请双方奖励)
│ │ ├── membership.py # checkReportQuota / redeemMember(2)
│ │ └── account_deletion.py # precheck / deleteAccount(2 — 个保法 + 苹果 5.1.1(v))
│ └── factories/create_user_usecases.py # 聚合工厂:注入 port → use_cases dict
│
├── infrastructure/
│ ├── adapters/access_token_adapter.py # SimpleUserAccessTokenAdapter(oauth token 服务桥接)
│ └── persistence/inmemory_user_repositories.py # 全 InMemory 实现 + seed_dev_fixtures
│
└── interface/
├── api/user_actions.py # 11 action 分发 + pydantic payload 校验(V1 zod 语义)
└── handlers.py # cz_register_user_exception_handlers对外接口(11 action 一览)
详细字段表见 API Docs → user/actions。
| action | 说明 | 关键错误码 |
|---|---|---|
completeOnboarding / resetOnboarding | onboardingDone 状态切换 | 40301004 |
bindApps | 绑定 phone/email/password/username(全局唯一防重) | 40301005 / 40301001 |
bindInviter | 邀请裂变(邀请人 +7 天 +50 分封顶 90 天;被邀人 +3 天 +20 分) | 40301008 / 40301009 |
checkReportQuota | 报告免费周配额(会员不限 quota=-1 / 免费每周 1 次) | 40301004 |
redeemMember | 积分兑换会员(7 天=100 分 / 30 天=300 分,白名单) | 40301007 / 40301006 |
submitOnboarding | 存资料 + 分型 + 画像(不标 done);写 BodyComp 失败不阻塞 | 40301013 |
onboardingSuggestion | 健康目标 + 2~3 套方案(供选不自动采纳) | 40301010 |
adoptPlanOption | 采纳方案(服务端二次校验防伪造 deficit — M4-15 修复) | 40301011 |
precheckDeleteAccount | 注销前置检查(积分/会员 blockers + 数据概览) | 40301004 |
deleteAccount | 注销(precheck 兜底 + 级联清理,不可逆) | 40301012 |
关键设计约束
响应序列化(V1 契约)
- route 层
cz_serialize():dataclass 字段 snake_case → camelCase;datetime → ISO - 信封
{code: "0", msg: "ok", data}(V2 全局) - payload 校验用手动 pydantic(
cz__validate→UserError(PARAM_INVALID, "<字段路径>: <原因>"),对齐 V1 zod parse 语义)
缓存失效锚点
V1 Cache.del(user:me:{userId}) 等价物 = UserCachePort.cz_del(user_id) —— 全部写 action 落库后必调(InMemory 无 IO 也要调,保持行为一致)。
事务语义(InMemory 阶段)
bindInviter:V1 单事务 → V2「前置校验 + 顺序编排」模拟(cap 超限在 team.create 之前拦截)deleteAccount:v2 子集级联(body compositions + team + user);生产 Prisma 阶段补全 V1 22 表清单
邀请奖励常量(V1 逐字对齐)
CZ_INVITER_BONUS_DAYS=7 / CZ_INVITEE_BONUS_DAYS=3 / CZ_INVITER_CAP_DAYS=90 / CZ_INVITER_BONUS_POINTS=50 / CZ_INVITEE_BONUS_POINTS=20;套餐 REDEEM_PACKAGES=((7,100),(30,300))。
测试与质量
| 文件 | 范围 |
|---|---|
tests/integration/test_user_actions.py | 11 action E2E(含错误分支)354 行 |
tests/unit/application/test_user_usecases.py | use case 单元 |
tests/unit/domain/test_user_onboarding_service.py | onboarding 纯函数(画像/目标/方案/安全边界) |
质量门禁与全仓一致:覆盖率 ≥ 80% / Ruff + mypy strict / DDD 4 层守卫。
FAQ
Q1: 为什么 user 模块用 action 协议而不是 REST 风格? A: V1(server/src/modules/user/user.routes.ts)是 action 风格,金标准 = V1 行为 100% 兼容 → 小程序客户端契约不变是硬约束(协议由 nginx 灰度按 path 切流定位 V1/V2)。admin 是新建后台无历史包袱,才用 REST。
Q2: 为什么错误码从 40301xxx 开始? A: 三层隔离约定:OAuth 40101xxx / admin 40201xxx / user 40301xxx(聚合错位,永不冲突)。设备线延续:honor 40401xxx / zepp 40402xxx / device 40403xxx。
Q3: adoptPlanOption 为什么不信前端传的 deficit? A: M4-15 修复 —— 未成年 / BMI<18.5 / C 类用户有安全边界(不得强加热量缺口)。服务端重新生成 suggestion 取该方案值(PLAN_OPTION_INVALID 兜底方案已不适用)。
相关文件
src/app/user/ ← 本文件所在模块
├── domain/{entities/user.py, services/{onboarding_service,growth_level}.py, exceptions.py}
├── application/{usecase/ 4 文件 = 11 use case, ports/, factories/create_user_usecases.py}
├── infrastructure/{adapters/access_token_adapter.py, persistence/inmemory_user_repositories.py}
└── interface/{api/user_actions.py, handlers.py}
tests/integration/test_user_actions.py
tests/unit/{application/test_user_usecases.py, domain/test_user_onboarding_service.py}
docs/api-docs/v1/user/actions.md ← 11 action 字段表(对外契约)变更记录 (Changelog)
| 日期 (Asia/Shanghai) | 版本 | 变更 |
|---|---|---|
| 2026-09-16 | 1.0.0 | 模块 CLAUDE.md 首版 — 11 action + DDD 4 层 + 40301xxx + V1 兼容约束 |
| 2026-09-16 | — | V2.0.14:V1 user 模块 11 action 迁移落地(commit bbd0d1e) |
| 2026-09-17 | — | zone.py 心率区间三算法迁移(V1 zone.util.ts 逐行对拍 — hrr/mhr/lthr ×5 区 + Tanaka;domain/services/zone.py + 63 用例;V1 中该 util 为 Sigma 回滚后孤儿,纯函数先行移植) |
3. shared 模块
源:
src/app/shared/CLAUDE.md(随仓库同步)
src/app/shared/
跨模块共享层 — Tenancy 上下文 + Celery 异步任务 + 基础设施复用。 不含业务 use case,仅承载横向能力与异步副作用。
模块职责
shared 是被 oauth / admin 等业务模块复用的"地基"层,专注于四件事:
- Tenancy 上下文 — 多租户 schema 隔离(ADR-0003)
- Celery 异步任务 — 审计日志 / 短信发送 / 验证码清理
- Celery 应用工厂 — Redis broker + structlog 集成
- 可观测性(Phase 4 §7.6) — audit 批量写入缓冲 + Prometheus 指标 + 慢操作上报 + Sentry 可选接入
任何业务 use case 都不应直接落在 shared;它只提供"轮子",不提供"业务"。
目录结构
src/app/shared/
├── celery_app.py # Celery 应用工厂(cz_create_celery_app / cz_get_celery_app)
├── system_codes.py # 系统级业务码区段(5xxxxxxx — 慢操作 / 未捕获异常)
├── observability/ # 可观测性横切能力(MIS Phase 4 §7.6)
│ ├── audit_batch.py # CzAuditBatchWriter — audit 异步批量写入(缓冲层)
│ ├── metrics.py # Prometheus 指标注册表 + cz_track_result 装饰器
│ ├── middleware.py # HTTP 耗时直方图 + 慢操作上报(SLOW_REQUEST_MS)
│ └── sentry.py # Sentry 可选接入(SENTRY_DSN 存在才 init)
├── tenancy/
│ ├── __init__.py
│ └── tenant_context.py # TenantContext(async contextvar)
└── tasks/
├── __init__.py
├── audit_log.py # 审计日志写入(admin 高敏感动作触发)
├── sms_send.py # 短信发送(异步解耦,OAuth/Admin 复用)
└── captcha_cleanup.py # 验证码过期清理(定时任务)四大子能力
1. Tenancy 上下文(tenancy/)
tenant_context.py:TenantContext— async contextvar 持有当前请求的 schema / tenant_id- 与 ADR-0003(多租户 schema 隔离)配套
- Alembic 迁移链
alembic/env.py异步 + 多租户 schema 循环依赖此 context
2. Celery 异步任务(tasks/)
| 任务 | 触发方 | 说明 |
|---|---|---|
audit_log | admin P0+ use case | 高敏感动作(rollback/close 等)落 audit log |
sms_send | oauth + admin | 真发腾讯 SMS(带结构化日志) |
captcha_cleanup | 定时 beat | 清理过期验证码(Redis TTL 兜底) |
3. Celery 应用工厂(celery_app.py)
cz_create_celery_app(name="qm-serverv2")— 创建 Celery 实例(Redis broker + result backend + 任务基类配置)cz_get_celery_app()— 单例获取工厂(避免 import 循环)- 集成
structlog信号(task_prerun/task_postrun/task_failure) - 自动 include:
src.app.shared.tasks.{audit_log, sms_send, captcha_cleanup}
4. 可观测性(observability/ — MIS Phase 4 §7.6)
| 模块 | 能力 | 关键点 |
|---|---|---|
audit_batch.py:CzAuditBatchWriter | audit 异步批量写入(包装而非替换 — 缓冲层) | eager(默认 dev/test,读路径等价直写)/ batched(V2_AUDIT_BATCH=1 生产:阈值 50 条或 1s flush);逐条失败隔离 + audit_log_write_total{result} 计数;关机经 main.py shutdown 钩子 aclose() 终刷 |
metrics.py | Prometheus 指标注册表 | admin_user_ban_total / admin_order_refund_amount_yuan_total / admin_withdrawal_payout_total / http_request_duration_seconds / slow_operations_total / audit_log_write_total;cz_track_result 装饰器零侵入埋点(domain 层禁依赖 prometheus) |
middleware.py | 慢操作上报 | 每请求观测耗时直方图(path_template = 路由模板,404 归并 __unmatched__);> SLOW_REQUEST_MS(默认 1000)→ structlog 警告(系统码 50001002)+ slow_operations_total + Sentry |
sentry.py | Sentry 可选接入 | env SENTRY_DSN 存在且 SDK 已装(uv sync --extra observability)才 init;无 DSN 零副作用;上报失败静默 |
system_codes.py | 系统级业务码(5xxxxxxx 区段) | SYSTEM_INTERNAL=50001001 / SYSTEM_SLOW_OPERATION=50001002 / SYSTEM_AUDIT_WRITE_FAILED=50001003 / SYSTEM_DEPENDENCY_UNAVAILABLE=50001004(admin-mis-design.md §6.1 区段) |
接线点:src/main.py(中间件注册 + /metrics 端点 + audit 缓冲层装配 + shutdown flush)。 压测基线报告:docs/internal-wiki/operations/perf-baseline-v2.0.12.md(scripts/locustfile.py 场景)。
关键约束
- 零业务依赖:shared 不允许 import
src/app/oauth/或src/app/admin/的 use case - Celery 命名空间前缀:使用
cz_前缀避免与 cz_ 前缀 agent 冲突(见celery_app.py注释) - 不动现有 oauth 代码:见
celery_app.py顶部⚠️警告 - 可观测性依赖方向:
shared.observability用 Protocol 结构化契约(CzAuditWritePort)包装 admin 端口 — 不反向 import admin;domain 层零 prometheus 依赖(check_layers 卡控)
相关测试
tests/shared/test_celery.py— Celery 应用工厂单测(broker/backend/任务注册)tests/unit/shared/test_tenant_context.py— TenantContext 上下文隔离tests/unit/shared/test_observability_{audit_batch,metrics,middleware,sentry}.py— Phase 4 可观测性单测(26 例)tests/integration/test_observability_endpoints.py—/metrics+ 中间件端到端(真装配)
常见问题 (FAQ)
- 新增共享任务放哪里? —
src/app/shared/tasks/<name>.py,并在celery_app.py:include注册。 - 业务模块如何发异步? — 通过
cz_get_celery_app()获取实例后.send_task()或@shared_task装饰器。 - Tenancy 怎么传递到 SQLAlchemy session? — 由
tenant_context注入search_path,Alembic env.py 异步循环保证 schema 存在。 - audit 批量写入为什么默认 eager? — 存量集成测试断言 read-after-write(
records_by_action请求返回后立即可读);eager 保读路径语义不变,生产以V2_AUDIT_BATCH=1启用真批量。读路径list_all()会穿透队列(审计闭环不漏)。 - 新增业务指标怎么埋点? — 在
metrics.py注册 Counter/Histogram,use case 里@cz_track_result(<counter>)(成功/失败双计数)或直接.inc();测试用cz_sample_value(name, labels)增量断言。
变更记录 (Changelog)
| 日期 (Asia/Shanghai) | 版本 | 变更 |
|---|---|---|
| 2026-09-16 | 0.2.0 | MIS Phase 4:新增 observability/ 四模块 + system_codes.py;audit 批量写入接线 main.py(eager 默认)+ /metrics 端点 + 慢操作上报 |
| 2026-09-16 11:37:50 | 0.1.0 | /init-project 首次沉淀模块 CLAUDE.md(含面包屑 + 三子能力说明 + FAQ) |