Skip to content

模块手册 · 认证与用户域(auth-user) ​

收录 3 个模块的权威开发手册(源自各模块 CLAUDE.md,随仓库同步)。

本文目录 ​

    1. oauth
    1. user
    1. 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路径角色关键点
SmsSendUseCaseapplication/usecase/sms_send.py匿名发送短信码(Mock SMS fallback)
SmsVerifyUseCaseapplication/usecase/sms_verify.py匿名校验短信码(不签发 token)
SmsLoginUseCaseapplication/usecase/sms_login.py匿名短信码登录 + 自动注册 + 签发双 token + DeviceBinding upsert
CaptchaUseCaseapplication/usecase/captcha.py匿名图形验证码生成/校验
BindPhoneUseCaseapplication/usecase/bind_phone.py已登录绑定手机号(实名场景)
UnbindPhoneUseCaseapplication/usecase/unbind.py已登录解绑手机号
RotateTokenUseCaseapplication/usecase/rotate.py已登录refresh_token 换发新 access_token
CallbackUseCaseapplication/usecase/callback.py已登录三方登录回调(微信/Apple)
LogoutUseCaseapplication/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.pyRS256(ADR-0004),私钥/公钥分离
短信适配infrastructure/adapters/sms_sender_factory.pyV2_USE_TENCENT_SMS=1 切换真发;否则 Mock
限流infrastructure/adapters/rate_limiter_redis.pyRedis 滑窗;V2_USE_REDIS=1
双仓储infrastructure/persistence/repository_factory.pyV2_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) ​

  1. Mock SMS 怎么开启? — 默认自动 Mock;设 V2_USE_TENCENT_SMS=1 走真发。dev 不需要任何配置。
  2. 为什么是双 token? — access(短期,RS256)+ refresh(长期,DeviceBinding 锚定)。多设备上限由 DeviceBinding 控制。
  3. 顶层归一化为什么重要? — OATH-M-006 教训:ExceptionHandler 注册顺序错误导致 OAuthError 漏归一化,统一以 HTTP 200 + code 40101xxx 返回。
  4. 如何接入三方登录? — application/usecase/callback.py 抽象;具体 Provider(微信/Apple)在 infrastructure/adapters/ 下扩展。

相关文件清单 ​

  • docs/api-docs/v1/oauth/ — OpenAPI/接口文档
  • docs/internal-wiki/oauth-deep-dive/ — JWT 解剖 / 多设备会话 / Cookbook
  • docs/internal-wiki/architecture/oauth-implementation.md — 实施总览
  • docs/internal-wiki/decisions/ — RS256 决策

变更记录 (Changelog) ​

日期 (Asia/Shanghai)版本变更
2026-09-16 11:37:500.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}(V1 POST /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 / resetOnboardingonboardingDone 状态切换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.py11 action E2E(含错误分支)354 行
tests/unit/application/test_user_usecases.pyuse case 单元
tests/unit/domain/test_user_onboarding_service.pyonboarding 纯函数(画像/目标/方案/安全边界)

质量门禁与全仓一致:覆盖率 ≥ 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-161.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 等业务模块复用的"地基"层,专注于四件事:

  1. Tenancy 上下文 — 多租户 schema 隔离(ADR-0003)
  2. Celery 异步任务 — 审计日志 / 短信发送 / 验证码清理
  3. Celery 应用工厂 — Redis broker + structlog 集成
  4. 可观测性(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_logadmin P0+ use case高敏感动作(rollback/close 等)落 audit log
sms_sendoauth + 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:CzAuditBatchWriteraudit 异步批量写入(包装而非替换 — 缓冲层)eager(默认 dev/test,读路径等价直写)/ batched(V2_AUDIT_BATCH=1 生产:阈值 50 条或 1s flush);逐条失败隔离 + audit_log_write_total{result} 计数;关机经 main.py shutdown 钩子 aclose() 终刷
metrics.pyPrometheus 指标注册表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.pySentry 可选接入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) ​

  1. 新增共享任务放哪里? — src/app/shared/tasks/<name>.py,并在 celery_app.py:include 注册。
  2. 业务模块如何发异步? — 通过 cz_get_celery_app() 获取实例后 .send_task() 或 @shared_task 装饰器。
  3. Tenancy 怎么传递到 SQLAlchemy session? — 由 tenant_context 注入 search_path,Alembic env.py 异步循环保证 schema 存在。
  4. audit 批量写入为什么默认 eager? — 存量集成测试断言 read-after-write(records_by_action 请求返回后立即可读);eager 保读路径语义不变,生产以 V2_AUDIT_BATCH=1 启用真批量。读路径 list_all() 会穿透队列(审计闭环不漏)。
  5. 新增业务指标怎么埋点? — 在 metrics.py 注册 Counter/Histogram,use case 里 @cz_track_result(<counter>)(成功/失败双计数)或直接 .inc();测试用 cz_sample_value(name, labels) 增量断言。

变更记录 (Changelog) ​

日期 (Asia/Shanghai)版本变更
2026-09-160.2.0MIS Phase 4:新增 observability/ 四模块 + system_codes.py;audit 批量写入接线 main.py(eager 默认)+ /metrics 端点 + 慢操作上报
2026-09-16 11:37:500.1.0/init-project 首次沉淀模块 CLAUDE.md(含面包屑 + 三子能力说明 + FAQ)