docs(02): 从用户内联约束生成 Phase 2 CONTEXT.md(管理端 REST 接口 PRD 快速通道)

This commit is contained in:
pmc 2026-05-07 18:15:54 +08:00
parent 658963fd0d
commit 172ab321c1

View File

@ -0,0 +1,150 @@
# Phase 2管理端读写接口 - Context
**Gathered**: 2026-05-07
**Status**: Ready for planning
**Source**: 用户在 `/gsd-plan-phase 2` 调用时提供的内联约束PRD 快速通道,等同 Phase 1 同模式)
<domain>
## Phase 边界
本 phase 仅负责 **DRF 序列化器 + view + URL 路由 + 鉴权 + 修改记录**
- 在 `/api/v1/admin/credential-slot/` 暴露 GET脱敏返回+ PUT全字段覆写两个方法
- 复用现有 `RedisTokenAuthentication`admin token 体系key `admin_token:{token}`
- 响应必须经过 `StandardResponseMiddleware` 壳层
- Access Token 在 GET 响应中通过 `mask_token` 脱敏(仅末 4 位)
- PUT 响应也走脱敏(写入成功后用脱敏值返回,避免运营在 admin 工具里看到自己刚提交的明文回显)
- 接口自动暴露到 `/swagger/` + `/redoc/`drf-yasgschema 与实际行为一致
- 修改记录在 qy_lty + qy-lty-admin **两端互引条目**Phase 2 是首次跨项目接口契约落地需要前端记一条互引qy-lty-admin 那侧 Phase 1 plan 已定义 CRED-FE-01 API client 会消费这个接口契约)
**不负责**(留给后续 phase
- Phase 3客户端读取接口GET `/api/credential-slot/`user token 鉴权,明文返回)+ 阿里云日志脱敏
- 任何前端代码(在 qy-lty-admin 仓库的 CRED-FE-01 phase 里)
- DB 字段加密、审计日志、token 轮换等增强项
</domain>
<decisions>
## 实现决策(锁定)
### URL 与路由
- **路径**`/api/v1/admin/credential-slot/`trailing slash 沿用 Django 默认风格)
- **HTTP 方法**GET 和 PUT 在同一 URL不分两个 endpoint符合 RESTful 单例资源约定)
- **路由注册位置**:决策由 planner 基于 read_first 后选择
- 候选 A`aiapp/urls.py`(凭据槽位语义偏向 AI
- 候选 B仓库现有的 admin namespace `/api/v1/admin/` 在哪个 urls 文件汇总planner 必须 read_first 全仓 grep `/api/v1/admin/` 找到现有汇总点(推测在 `userapp/urls.py``qy_lty/urls.py`),照抄注册风格
- **不**新建 `credential` app凭据槽位不值得一个独立 app它就是 aiapp 的一个子能力)
### View 实现
- **不用** `ModelViewSet`(带不必要的 list / create / delete单例资源用不上
- **用** `RetrieveUpdateAPIView`DRF 提供,开箱支持 GET + PUT/PATCH或自定义 `APIView` + 手写 `get` / `put` 方法
- **推荐** 自定义 `APIView` —— 单例语义不走 `lookup_field`/`pk`,调 `get_or_create(pk=1)` 取那条唯一记录;用 `RetrieveUpdateAPIView` 反而需要重写 `get_object()`,绕一圈不值得
- View 类命名:`CredentialSlotAdminView`,放 `aiapp/views.py`(沿用现有 view 文件)
### Serializer
- **DRF ModelSerializer**`CredentialSlotSerializer`,放 `aiapp/serializers.py`如不存在则新建planner 检查 `aiapp/` 是否已有 serializers.py
- 字段:`app_id``access_token``updated_at`
- `updated_at` 在 GET 响应里 read_onlyauto_now 自动维护)
- **Access Token 脱敏在 view 层处理,不在 serializer 层**serializer 直接把明文交给 viewview 在返回响应前用 `mask_token` 替换理由PUT 写入时需要明文走 serializer.is_valid + save(),脱敏放 view 层避免序列化器既要明文又要脱敏的双重责任
- 写入校验:`app_id``access_token` 都允许空字符串(与模型 `blank=True` 一致),但**不允许 None**serializer 字段配 `allow_null=False`
### 鉴权
- **直接复用 `RedisTokenAuthentication`** —— Phase 2 researcher 必须 read_first 找到该类的具体位置(推测 `userapp/authentication.py`),并 read 现有 `/api/v1/admin/` namespace 下任一接口的鉴权配置(如 Bot 管理接口)作为照抄对象
- View 类上配 `authentication_classes = [RedisTokenAuthentication]` + `permission_classes = [IsAdminTokenAuthenticated]`(如果项目已有这种 admin-only permission否则用 `IsAuthenticated` + 在 view 里加 `request.user.is_staff` 检查)
- **关键**admin token 的语义是"该 token 来自 `admin_token:{token}` Redis key",普通 user token 来自 `token:{token}`planner 应在 read_first 阶段确认现有 admin 接口怎么区分这两类 token很可能 `RedisTokenAuthentication` 自身根据 key 前缀做了区分,且配套有一个 admin-only permission class
- 拒绝时返回 401无 token或 403user token 但非 admin错误响应必须经过 `StandardResponseMiddleware` 壳层
### Swagger / ReDoc
- 接口必须出现在 `/swagger/` + `/redoc/`drf-yasg 会自动扫描 DRF 视图
- View 类上加 `@swagger_auto_schema` 装饰器method-level给 GET / PUT 各写一份),声明 request body schema 和 response schema
- response schema 显式标注 `access_token` 字段语义为「末 4 位脱敏掩码」,避免前端误解为明文
### 跨项目联动(修改记录互引)
- **本 phase 同期写两端 `docs/修改记录.md`**
- `qy_lty/docs/修改记录.md` 顶部写一条:"新增 Phase 2 管理端 REST 接口",跨项目联动字段引用 `qy-lty-admin/docs/修改记录.md` 的对应条目
- `qy-lty-admin/docs/修改记录.md` 顶部写一条:"锁定 Phase 2 后端 API 契约(消费方文档化)",跨项目联动字段引用 qy_lty 的对应条目
- 这是 Phase 2 与 Phase 1 的关键差异Phase 1 是纯服务端模型层、不涉及 API 契约所以前端不需要互引Phase 2 暴露 REST 接口给前端消费,**必须**互引
### 兼容性 / 不引入新依赖
- 沿用 Django 4.2.13、DRF 3.x现版、Python 3.8
- drf-yasg 已在依赖里,复用即可
- 不引入新依赖
### Claude's Discretion
- 序列化器是否拆 `CredentialSlotReadSerializer`(脱敏返回) + `CredentialSlotWriteSerializer`(明文写入)两个类,还是用同一个 + view 层脱敏 —— planner 决定
- view 是放 `aiapp/views.py` 末尾,还是新建 `aiapp/views/credential_slot.py` 子文件 —— 取决于 `aiapp/views.py` 现有规模
- 错误响应的具体 message 文案(中文),如 `"凭据槽位需要管理员权限"` / `"未提供有效的管理员 token"`
- View 里如何确保 `get_or_create(pk=1)` 在并发请求下不出竞态(最简:用 `select_for_update` 或单例语义本身已经被 `pk=1 + save 钩子` 兜底,可以不加锁)
</decisions>
<canonical_refs>
## Canonical References
**下游 agent 必读**
### 项目宪法
- `qy_lty/CLAUDE.md` — 沟通语言(中文)+ 修改记录强制规则 + **跨项目互引强制**
- `qy_lty/.planning/PROJECT.md` — Milestone v1.0「本期 Milestone」段、关键约束响应壳层 / token 命名)
- `qy_lty/.planning/REQUIREMENTS.md` — Active 段 CRED-03 + CRED-04 完整描述
- `qy_lty/.planning/ROADMAP.md` — Phase 2 详情段Goal、Success Criteria 4 条)
- `qy_lty/.planning/phases/01-credential-data-layer/01-CONTEXT.md` — 上一 phase 决策pk=1 单例 + mask_token 工具的来源)
- `qy_lty/.planning/phases/01-credential-data-layer/01-VERIFICATION.md` — 上一 phase 验证证据mask_token 行为、CredentialSlot.get_solo() 用法)
### DRF / 鉴权 / 路由现成模式(必读)
- `qy_lty/aiapp/views.py` — 同 app 内现有 view 写法
- `qy_lty/aiapp/models.py``CredentialSlot` + `get_solo()` + `mask_token` 复用入口
- `qy_lty/userapp/authentication.py``RedisTokenAuthentication` 实现推测路径researcher 确认)
- `qy_lty/userapp/views.py` —— 现有 admin 接口写法(推测含 `/api/v1/admin/` 命名空间下的 view 模板)
- `qy_lty/qy_lty/urls.py``qy_lty/userapp/urls.py``/api/v1/admin/` 路由汇总点researcher 找出来)
- `qy_lty/common/middleware.py``StandardResponseMiddleware`(确认它如何处理 DRF Response 对象)
- `qy_lty/common/utils.py``mask_token` 工具Phase 1 落地)
### Swagger
- `qy_lty/qy_lty/urls.py` 或独立的 swagger 注册文件 — 看 drf-yasg 如何配 schema_view照搬 `@swagger_auto_schema` 风格
### 修改记录
- `qy_lty/docs/修改记录.md` — 头部「修改格式说明」+ Phase 1 已落地的两条条目可作模板
- `qy-lty-admin/docs/修改记录.md` — 头部「修改格式说明」(如有;如不存在 planner 应在 task 中提示用同款骨架格式新建)
</canonical_refs>
<specifics>
## 具体要点Success Criteria 显式化)
| # | 验证点 | 检查方式 |
|---|--------|----------|
| 1 | GET 携 admin token 返回脱敏壳层 | `curl -H "Authorization: Bearer <admin_token>" /api/v1/admin/credential-slot/` 返回 200 + JSON 含 `success: true / data.access_token: <末 4 位掩码>` |
| 2 | PUT 携 admin token 全字段覆写 | `curl -X PUT -H "Authorization: Bearer <admin_token>" -d '{"app_id": "x", "access_token": "y"}' .../credential-slot/` 返回 200 + DB 更新 + `updated_at` 刷新 |
| 3 | PUT 在空记录场景自动 get_or_create | DB 删除 pk=1 后调 PUT依旧成功创建 |
| 4 | 无 token 返回 401 + 标准壳层 | `curl /api/v1/admin/credential-slot/` 返回 401 + JSON 含 `success: false / code != 0 / message != ""` |
| 5 | 仅 user token非 admin返回 403 + 标准壳层 | 携 `token:{token}`(非 admin_token调用返回 403 + 标准壳层 |
| 6 | Swagger 暴露 | `/swagger/` HTML 页面含 `/api/v1/admin/credential-slot/` 路径条目,含 GET + PUT 两个方法及对应 schema |
| 7 | drf-yasg request/response schema 与实际一致 | `/swagger.json``app_id` / `access_token` / `updated_at` 字段定义 + `access_token` schema description 标注「末 4 位脱敏掩码」 |
| 8 | 修改记录两端互引 | `qy_lty/docs/修改记录.md``qy-lty-admin/docs/修改记录.md` 顶部各有一条 Phase 2 条目,跨项目联动字段相互引用 |
</specifics>
<deferred>
## 推迟事项(明确不在 Phase 2 范围)
- **客户端 GET `/api/credential-slot/`** — Phase 3
- **阿里云日志脱敏过滤器** — Phase 3
- **PUT 写入时旧 access_token 的审计日志** — 不在 v1.0 milestone 范畴
- **token 鉴权失败时尝试用其它 token 类型重试 / fallback** — 当前架构禁止此类降级,无 token 就 401
- **API 限流(防暴力 PUT** — 现有架构未上限流,不在本 phase 范畴
- **DB at-rest 加密** — 未来评估,不在 v1.0 范畴
</deferred>
---
*Phase: 02-admin-rest*
*Context gathered: 2026-05-07 via inline PRD用户在 /gsd-plan-phase 2 调用时提供完整约束)*