# 需求文档：小程序用户认证系统（miniapp-auth-system）

## 简介

本 SPEC 实现小程序用户认证系统，涵盖微信登录、用户申请审核、人员匹配、多店铺权限管理等完整认证链路。系统基于 P1（miniapp-db-foundation）已建立的 `auth` Schema 和 FDW 映射，在 `test_zqyy_app.auth` 中创建用户、申请、角色、绑定等业务表，并在 FastAPI 后端实现对应的 API 端点和权限中间件。

## 术语表

- **Auth_System**：小程序用户认证系统，负责微信登录、用户管理、申请审核、权限控制的完整后端服务
- **WeChat_Auth_Service**：微信认证服务模块，负责调用微信 `code2Session` 接口换取 `openid` 和 `session_key`
- **Application_Service**：用户申请服务模块，负责处理用户提交的入驻申请、状态流转和审核操作
- **Matching_Service**：人员匹配服务模块，负责根据球房ID和手机号/编号在助教表和员工表中查找候选匹配
- **Permission_Middleware**：权限中间件，基于用户的 `site_id` + `role` 拦截无权请求
- **JWT_Service**：JWT 令牌服务，负责签发和刷新 access_token / refresh_token（已有实现，本 SPEC 扩展）
- **site_code**：球房ID，格式为 2 字母 + 3 数字（如 `AB123`），与 `site_id` 一一映射
- **site_id**：门店标识符，类型为 `BIGINT`，用于多门店数据隔离
- **user_status**：用户状态枚举，取值为 `pending`（审核中）/ `approved`（已通过）/ `rejected`（已拒绝）/ `disabled`（已禁用）
- **binding_type**：绑定类型枚举，取值为 `assistant`（助教）/ `staff`（员工）/ `manager`（管理员）
- **FDW**：`postgres_fdw` 外部数据包装器，通过 `fdw_etl` Schema 读取 ETL 库数据
- **Migration_Script**：存放在 `db/zqyy_app/migrations/` 中的纯 SQL 迁移脚本，以日期前缀命名
- **BD_Manual**：数据库手册文档，存放在 `docs/database/` 中，记录表结构变更、兼容性影响、回滚策略和验证 SQL
- **DDL_Baseline**：DDL 基线文件，存放在 `docs/database/ddl/` 中，由 `gen_consolidated_ddl.py` 自动生成
- **Miniprogram_Auth_Pages**：小程序认证相关前端页面，包括登录页、申请表单页、审核等待页、无权限页
- **Dev_Login**：开发模式下的 mock 登录端点，绕过微信 code2Session 调用，用于联调测试

## 需求

### 需求 1：认证数据表创建

**用户故事：** 作为后端开发者，我需要在 `auth` Schema 中创建用户认证相关的数据表，以便支撑完整的认证和权限管理功能。

#### 验收标准

1. WHEN Migration_Script 执行完成, THE Auth_System SHALL 在 `auth` Schema 中创建 `users` 表，包含 `id`（SERIAL PK）、`wx_openid`（UNIQUE）、`wx_union_id`、`wx_avatar_url`、`nickname`、`phone`、`status`（默认 `pending`）、`created_at`、`updated_at` 字段
2. WHEN Migration_Script 执行完成, THE Auth_System SHALL 在 `auth` Schema 中创建 `user_applications` 表，包含 `id`（SERIAL PK）、`user_id`（FK → users）、`site_code`、`applied_role_text`、`employee_number`（可选）、`phone`、`status`（默认 `pending`）、`reviewer_id`、`review_note`、`created_at`、`reviewed_at` 字段
3. WHEN Migration_Script 执行完成, THE Auth_System SHALL 在 `auth` Schema 中创建 `site_code_mapping` 表，包含 `id`（SERIAL PK）、`site_code`（UNIQUE，格式 2 字母 + 3 数字）、`site_id`（BIGINT UNIQUE）、`site_name`、`tenant_id`、`created_at` 字段
4. WHEN Migration_Script 执行完成, THE Auth_System SHALL 在 `auth` Schema 中创建 `user_site_roles` 表，包含 `id`（SERIAL PK）、`user_id`（FK → users）、`site_id`（BIGINT）、`role_id`（FK → roles）、`created_at` 字段，并对 `(user_id, site_id, role_id)` 建立唯一约束
5. WHEN Migration_Script 执行完成, THE Auth_System SHALL 在 `auth` Schema 中创建 `user_assistant_binding` 表，包含 `id`（SERIAL PK）、`user_id`（FK → users）、`site_id`（BIGINT）、`assistant_id`（BIGINT，可选）、`staff_id`（BIGINT，可选）、`binding_type`、`created_at` 字段
6. WHEN Migration_Script 执行完成, THE Auth_System SHALL 在 `auth` Schema 中创建 `roles` 表，包含 `id`（SERIAL PK）、`code`（UNIQUE）、`name`、`description`、`created_at` 字段
7. WHEN Migration_Script 执行完成, THE Auth_System SHALL 在 `auth` Schema 中创建 `permissions` 表，包含 `id`（SERIAL PK）、`code`（UNIQUE）、`name`、`description`、`created_at` 字段
8. WHEN Migration_Script 执行完成, THE Auth_System SHALL 在 `auth` Schema 中创建 `role_permissions` 表，包含 `role_id`（FK → roles）、`permission_id`（FK → permissions）字段，并以 `(role_id, permission_id)` 为联合主键
9. THE Migration_Script SHALL 使用 `IF NOT EXISTS` / `OR REPLACE` 等幂等语法，确保重复执行不会报错
10. THE Migration_Script SHALL 在脚本中包含回滚语句（以注释形式）

### 需求 2：种子数据预置

**用户故事：** 作为系统管理员，我需要系统预置固定的权限列表和默认角色，以便审核时可直接分配。

#### 验收标准

1. WHEN 种子数据脚本执行完成, THE Auth_System SHALL 在 `auth.permissions` 表中插入 5 条固定权限记录：`view_tasks`、`view_board`、`view_board_finance`、`view_board_customer`、`view_board_coach`
2. WHEN 种子数据脚本执行完成, THE Auth_System SHALL 在 `auth.roles` 表中插入默认角色（至少包含 `coach`（助教）、`staff`（员工）、`site_admin`（店铺管理员）、`tenant_admin`（租户管理员））
3. WHEN 种子数据脚本执行完成, THE Auth_System SHALL 在 `auth.role_permissions` 表中为每个默认角色分配对应的权限组合
4. THE 种子数据脚本 SHALL 使用 `ON CONFLICT DO NOTHING` 语法，确保重复执行不会产生重复数据

### 需求 3：微信登录

**用户故事：** 作为球房工作人员，我需要通过微信登录小程序，以便快速进入系统。

#### 验收标准

1. WHEN 小程序端发送微信临时登录凭证（`code`）, THE WeChat_Auth_Service SHALL 调用微信 `code2Session` 接口换取 `openid` 和 `session_key`
2. WHEN `code2Session` 返回有效 `openid` 且该 `openid` 已存在于 `auth.users` 表中, THE Auth_System SHALL 返回该用户的 JWT 令牌对（access_token + refresh_token）和用户状态信息
3. WHEN `code2Session` 返回有效 `openid` 且该 `openid` 不存在于 `auth.users` 表中, THE Auth_System SHALL 创建新用户记录（status 为 `pending`），返回 JWT 令牌对和 `pending` 状态标识
4. IF `code2Session` 接口调用失败或返回错误码, THEN THE WeChat_Auth_Service SHALL 返回 HTTP 401 错误，包含具体的错误描述
5. WHEN 用户状态为 `disabled`, THE Auth_System SHALL 返回 HTTP 403 错误，拒绝登录

### 需求 4：用户申请提交

**用户故事：** 作为球房工作人员，我需要在首次登录后填写申请表单（球房ID、申请身份、手机号、编号、昵称），以便管理员审核我的身份。

#### 验收标准

1. WHEN 用户提交申请表单（包含 `site_code`、`applied_role_text`、`phone`，可选 `employee_number`）, THE Application_Service SHALL 在 `auth.user_applications` 表中创建一条 status 为 `pending` 的申请记录
2. WHEN 用户提交的 `site_code` 在 `auth.site_code_mapping` 中存在映射, THE Application_Service SHALL 将申请记录关联到对应的 `site_id`
3. WHEN 用户提交的 `site_code` 在 `auth.site_code_mapping` 中不存在映射, THE Application_Service SHALL 仍然接受申请，申请记录中保留 `site_code` 文本，管理端显示"未找到关联信息"
4. WHEN 用户提交申请时提供了 `nickname`, THE Auth_System SHALL 更新 `auth.users` 表中该用户的 `nickname` 字段
5. IF 用户提交的 `phone` 为空或格式无效（非 11 位数字）, THEN THE Application_Service SHALL 返回 HTTP 422 错误，包含具体的校验失败信息
6. WHEN 用户已有一条 `pending` 状态的申请, THE Application_Service SHALL 拒绝重复提交，返回 HTTP 409 错误

### 需求 5：人员匹配

**用户故事：** 作为系统，我需要根据球房ID和手机号自动建议用户与助教/员工的对应关系，以便管理员快速审核。

#### 验收标准

1. WHEN 管理员查看某条申请详情时, THE Matching_Service SHALL 根据申请中的 `site_id` 和 `phone` 在 `fdw_etl.v_dim_assistant` 中按 `site_id` + `mobile` 匹配助教记录
2. WHEN 管理员查看某条申请详情时, THE Matching_Service SHALL 根据申请中的 `site_id` 和 `phone` 在 `fdw_etl.v_dim_staff` 和 `fdw_etl.v_dim_staff_ex` 中按 `site_id` + `mobile` 匹配员工记录
3. WHEN 申请中包含 `employee_number`, THE Matching_Service SHALL 额外按 `job_num` 字段匹配员工记录
4. THE Matching_Service SHALL 将助教匹配结果和员工匹配结果合并为统一的候选列表返回，每条候选包含来源类型（`assistant` / `staff`）、姓名、手机号、编号
5. WHEN 助教表和员工表均无匹配结果, THE Matching_Service SHALL 返回空候选列表，管理端显示"未找到关联信息"
6. WHEN 申请的 `site_code` 无法映射到 `site_id`, THE Matching_Service SHALL 跳过匹配，返回空候选列表

### 需求 6：申请审核

**用户故事：** 作为租户管理员，我需要审核用户申请，将用户关联到对应的助教/员工，并分配身份权限。

#### 验收标准

1. WHEN 管理员批准申请并选择了候选匹配对象, THE Application_Service SHALL 将申请状态更新为 `approved`，在 `auth.user_assistant_binding` 中创建绑定记录，在 `auth.user_site_roles` 中分配角色
2. WHEN 管理员批准申请但无候选匹配（手动审核）, THE Application_Service SHALL 将申请状态更新为 `approved`，仅在 `auth.user_site_roles` 中分配角色，不创建绑定记录
3. WHEN 管理员拒绝申请, THE Application_Service SHALL 将申请状态更新为 `rejected`，记录 `review_note`（拒绝原因）
4. WHEN 申请审核通过后, THE Auth_System SHALL 将 `auth.users` 表中该用户的 `status` 更新为 `approved`
5. WHEN 审核操作完成, THE Application_Service SHALL 记录 `reviewer_id` 和 `reviewed_at` 时间戳
6. IF 审核目标申请的状态不是 `pending`, THEN THE Application_Service SHALL 返回 HTTP 409 错误，拒绝重复审核

### 需求 7：用户状态查询

**用户故事：** 作为用户，我需要看到自己的申请状态（审核中/通过/拒绝），以便了解审核进度。

#### 验收标准

1. WHEN 用户查询自身状态, THE Auth_System SHALL 返回用户的 `status`、所有申请记录列表（含每条申请的 `site_code`、`applied_role_text`、`status`、`review_note`）
2. WHEN 用户状态为 `approved`, THE Auth_System SHALL 同时返回用户已关联的店铺列表和对应角色

### 需求 8：多店铺支持与店铺切换

**用户故事：** 作为用户，我可以同时属于多个店铺（连锁场景），切换店铺后数据正确隔离。

#### 验收标准

1. THE Auth_System SHALL 允许一个用户通过多次申请关联到多个不同的 `site_id`，每个 `site_id` 独立分配角色
2. WHEN 用户切换当前店铺, THE JWT_Service SHALL 签发包含新 `site_id` 的 JWT 令牌对
3. WHEN 用户携带某 `site_id` 的 JWT 访问 API, THE Permission_Middleware SHALL 仅允许访问该 `site_id` 下用户拥有权限的资源

### 需求 9：权限中间件

**用户故事：** 作为系统，我需要权限中间件正确拦截无权请求，确保数据安全。

#### 验收标准

1. WHEN 用户携带有效 JWT 访问受保护端点, THE Permission_Middleware SHALL 从 JWT 中提取 `user_id` 和 `site_id`，查询 `auth.user_site_roles` 和 `auth.role_permissions` 获取用户在该店铺下的权限列表
2. WHEN 用户的权限列表不包含端点所需的权限 code, THE Permission_Middleware SHALL 返回 HTTP 403 错误
3. WHEN 用户的 `status` 不是 `approved`, THE Permission_Middleware SHALL 返回 HTTP 403 错误，拒绝访问受保护端点
4. WHEN JWT 令牌过期或无效, THE Permission_Middleware SHALL 返回 HTTP 401 错误

### 需求 10：JWT 令牌扩展

**用户故事：** 作为后端开发者，我需要扩展现有 JWT 服务以支持微信登录场景和多店铺切换。

#### 验收标准

1. THE JWT_Service SHALL 在 JWT payload 中包含 `user_id`（sub）、`site_id`、`roles`（角色 code 列表）、`type`（access/refresh）、`exp` 字段
2. WHEN 用户通过微信登录且状态为 `approved`, THE JWT_Service SHALL 使用用户默认店铺（第一个关联的 site_id）签发令牌
3. WHEN 用户通过微信登录且状态为 `pending`, THE JWT_Service SHALL 签发不含 `site_id` 和 `roles` 的受限令牌，仅允许访问申请提交和状态查询端点
4. WHEN 用户请求切换店铺, THE JWT_Service SHALL 验证用户在目标 `site_id` 下有角色绑定后签发新令牌

### 需求 11：迁移脚本管理

**用户故事：** 作为后端开发者，我需要所有数据库变更都有对应的迁移脚本，以便变更可追溯、可重放。

#### 验收标准

1. THE Migration_Script SHALL 将所有认证相关表的 DDL 存放在 `db/zqyy_app/migrations/` 目录中
2. THE Migration_Script SHALL 使用日期前缀命名（格式：`YYYY-MM-DD__<描述>.sql`）
3. THE Migration_Script SHALL 使用 UTF-8 编码，纯 SQL（非 ORM）
4. THE Migration_Script SHALL 在每个脚本中包含回滚语句（以注释形式）
5. THE Migration_Script SHALL 使用幂等语法（`IF NOT EXISTS`、`ON CONFLICT DO NOTHING`），确保重复执行不会报错

### 需求 12：DDL 测试库落库与文档同步

**用户故事：** 作为后端开发者，我需要所有 DDL 变更在测试库（`test_zqyy_app`）中实际执行验证，并同步更新数据库手册和 DDL 基线，确保文档与实际 Schema 一致。

#### 验收标准

1. WHEN 迁移脚本编写完成, THE Auth_System SHALL 在 `test_zqyy_app` 测试库中执行迁移脚本，验证无错误
2. WHEN 迁移脚本执行成功, THE Auth_System SHALL 创建或更新 `docs/database/BD_Manual_auth_tables.md` 数据库手册，包含变更说明、兼容性影响、回滚策略、验证 SQL（至少 3 条）
3. WHEN 迁移脚本执行成功, THE Auth_System SHALL 运行 `python scripts/ops/gen_consolidated_ddl.py` 重新生成 DDL 基线文件 `docs/database/ddl/zqyy_app__auth.sql`
4. WHEN 种子数据脚本执行成功, THE Auth_System SHALL 在数据库手册中记录种子数据内容（角色、权限、角色-权限映射）

### 需求 13：小程序认证前端页面

**用户故事：** 作为球房工作人员，我需要在小程序中看到登录页、申请表单页、审核状态页，以便完成从微信登录到正式使用的完整流程。

#### 验收标准

1. WHEN 用户首次打开小程序, THE Auth_System SHALL 展示登录页面，调用 `wx.login()` 获取 code 并发送到后端 `/api/xcx/login`
2. WHEN 后端返回 `user_status=pending` 且用户无 pending 申请, THE Auth_System SHALL 跳转到申请表单页面，包含球房ID（`site_code`）、申请身份、手机号、编号（选填）、昵称输入框
3. WHEN 用户提交申请表单, THE Auth_System SHALL 调用 `/api/xcx/apply` 提交申请，成功后跳转到审核等待页面
4. WHEN 用户状态为 `pending` 且已有 pending 申请, THE Auth_System SHALL 展示审核等待页面，显示"审核中"状态和申请信息摘要
5. WHEN 用户状态为 `rejected`, THE Auth_System SHALL 在审核等待页面显示拒绝原因，并提供"重新申请"按钮
6. WHEN 用户状态为 `approved`, THE Auth_System SHALL 跳转到小程序主页（任务列表）
7. WHEN 用户状态为 `disabled`, THE Auth_System SHALL 展示无权限页面，提示账号已被禁用
8. THE Auth_System SHALL 在小程序 `app.ts` 的 `onLaunch` 中实现自动登录逻辑，根据用户状态路由到对应页面
9. WHEN 用户拥有多个店铺, THE Auth_System SHALL 在主页提供店铺切换入口

### 需求 14：前后端联调验证

**用户故事：** 作为开发者，我需要在微信开发者工具中验证完整的认证流程（登录→申请→审核→进入主页），确保前后端接口对接正确。

#### 验收标准

1. THE Auth_System SHALL 提供联调验证脚本或文档，说明如何在微信开发者工具中测试完整认证流程
2. THE Auth_System SHALL 在后端提供开发模式下的 mock 登录端点（`POST /api/xcx/dev-login`），接受任意 openid 直接返回 JWT，绕过微信 code2Session 调用
3. WHEN 开发模式启用时, THE Auth_System SHALL 允许通过环境变量 `WX_DEV_MODE=true` 切换到 mock 模式
4. THE Auth_System SHALL 在 `apps/miniprogram/doc/` 中提供联调指南文档，包含微信开发者工具配置、后端启动步骤、测试账号说明
