docs(01): 从用户内联约束生成 Phase 1 CONTEXT.md(PRD 快速通道)

This commit is contained in:
pmc 2026-05-07 16:57:31 +08:00
parent 47d24a46ef
commit ddc7360f60

View File

@ -0,0 +1,123 @@
# Phase 1凭据槽位数据层 - Context
**Gathered**: 2026-05-07
**Status**: Ready for planning
**Source**: 用户在 `/gsd-plan-phase 1` 调用时提供的内联约束(等同 PRD 快速通道)
<domain>
## Phase 边界
本 phase 仅负责**数据库层 + Django Admin 入口**
- 落地 `CredentialSlot` 单例 Django 模型 + 数据迁移
- 注册 Django AdminSimpleUI 主题 + 中英 i18n
- 单例语义DB / 模型 / Admin 三层都要禁止出现第二条记录
- Access Token 字段在 Admin 列表/查看态脱敏(仅显示末 4 位),编辑态明文供运营录入
**不负责**(留给后续 phase
- Phase 2管理端 REST 接口GET/PUT `/api/v1/admin/credential-slot/`
- Phase 3客户端 REST 接口 + 阿里云日志脱敏
- 任何前端工作(在独立项目 `../qy-lty-admin/` 自己的 milestone 里)
</domain>
<decisions>
## 实现决策(锁定)
### 模型层CRED-01
- 单例语义实现方式:**`pk=1` 固定主键 + `get_or_create(pk=1)` 模式**
- 在模型 `save()` 钩子中强制 `self.pk = 1`,配合 `get_or_create(pk=1)` 让首次访问拿到一条空记录
- 不使用"单字段唯一约束"备选方案,因为该方案要求始终有非空字段,灵活性不如 pk=1
- 字段(最小集,沿用 ParadiseUser / 其它现有模型的命名习惯):
- `app_id`CharFieldmax_length 合理128 足够覆盖常见服务商 ID 长度),允许空字符串(运营初次访问时尚未填写)
- `access_token`CharFieldmax_length 长512 留余量,覆盖 JWT 之类的长 token允许空字符串
- `updated_at`DateTimeField`auto_now=True`,每次保存自动刷新
- 推荐 app 归属:在现有 `aiapp` / `common` / `userapp` 三个 app 里选一个最贴合的;优先 `aiapp`(凭据语义偏向 AI 服务商接入),但若 `aiapp` 已有强语义模型可考虑 `common`。**最终 app 归属由 planner 在 read_first 阶段读完 `aiapp/models.py``common/` 后决定**
- 模型 `__str__` 返回类似 `f"凭据槽位 (updated {self.updated_at:%Y-%m-%d %H:%M})"` 的可读串
### 数据迁移
- 用 `python manage.py makemigrations <app>` 生成迁移文件(不要手写)
- 迁移生成后须能在 dev 环境跑 `python manage.py migrate` 通过
- 迁移文件命名沿用 Django 默认(`0001_initial.py` 等),不强求中文注释
### Django Admin 注册CRED-02
- 注册位置:与模型同 app 的 `admin.py`,沿用 SimpleUI + django-rosetta 中英双语注册风格(参考 `userapp/admin.py` / `aiapp/admin.py`
- 列表页 `list_display`:包含脱敏后的 `access_token_masked``app_id``updated_at`
- 列表/查看态 access_token 脱敏实现:在 `ModelAdmin` 上加自定义方法 `access_token_masked(self, obj)` 返回末 4 位掩码(如 `****abcd`);通过 `list_display` / `readonly_fields` 暴露
- 编辑表单字段:`app_id``access_token` 都明文(让运营录入 / 修改)
- **隐藏"新增"按钮**:重写 `has_add_permission(self, request)` 返回 `False if exists else True`(即记录已存在时禁止新增)
- **禁止删除**(避免运营误删后单例语义丢失):重写 `has_delete_permission(self, request)` 返回 `False`
- 中英 i18n模型 `Meta.verbose_name` / `verbose_name_plural``_()` 标记翻译admin 列表方法的 `short_description` 同样标记
### 兼容性 / 不引入新依赖
- 沿用 Django 4.2.13、Python 3.8(已在 PROJECT.md 「约束」段标注 Python 3.8 EOL但不在本 milestone 内升级)
- 不引入新第三方包(不使用 `django-encrypted-model-fields` 等加密库;如未来需要 at-rest 加密,开新 phase 评估)
- 沿用 `StandardResponseMiddleware` —— 本 phase 不直接产生 REST 响应,所以无关
### Claude's Discretion
下面是 planner / 执行者可以自行决定的细节,不算锁定:
- 模型放在 `aiapp/models.py` 还是新建 `aiapp/models/credential_slot.py` 子文件 —— 取决于 `aiapp` 现有 models 文件大小
- 是否把 `access_token_masked` 工具函数抽到 `common/utils.py` 复用Phase 3 阿里云日志脱敏可能也用得上) —— 推荐抽,但不是 Phase 1 强约束
- Admin 列表页的字段顺序、过滤器等 UX 细节
- `verbose_name` 中文字面量(如"凭据槽位"还是"通用凭据"
</decisions>
<canonical_refs>
## Canonical References
**下游 agent 必读**researcher / planner / executor 在生成或落地代码前都要读):
### 项目宪法
- `qy_lty/CLAUDE.md` — 沟通语言(中文)+ 修改记录强制规则 + 跨项目联动
- `qy_lty/.planning/PROJECT.md` — Milestone v1.0「本期 Milestone」段、关键约束、关键决策
- `qy_lty/.planning/REQUIREMENTS.md` — Active 段 CRED-01 + CRED-02 的完整描述
- `qy_lty/.planning/ROADMAP.md` — Phase 1 详情段Goal、Success Criteria 4 条)
### 模型 / Admin 现成模式必读pattern mapper 应该会自动列出来)
- `qy_lty/userapp/models.py``ParadiseUser` 看自定义模型 + 字段命名 + Meta
- `qy_lty/userapp/admin.py` — SimpleUI 主题下的 Admin 注册模式 + `list_display` / `readonly_fields` 写法
- `qy_lty/aiapp/models.py` — 同 app 内现有模型(决定 CredentialSlot 是否塞进 aiapp
- `qy_lty/aiapp/admin.py` — 同 app 内现有 Admin决定共存方式
### 修改记录
- `qy_lty/docs/修改记录.md` — 文件头部「修改格式说明」即本 phase 落地后必须遵循的写入格式
### 跨项目互引
- `qy-lty-admin/.planning/REQUIREMENTS.md` — CRED-FE-01~05本 phase 提交时需在前后端两份 `docs/修改记录.md` 互相引用条目
</canonical_refs>
<specifics>
## 具体要点Success Criteria 显式化)
| # | 验证点 | 检查方式 |
|---|--------|----------|
| 1 | DB / 模型层强制最多一条 | Django shell尝试 `CredentialSlot.objects.create()` 第二次必须抛异常 / 被 save() 钩子改写到 pk=1 覆盖现有 |
| 2 | 迁移落地 + schema 字段齐全 | `python manage.py migrate` 退出码 0`python manage.py dbshell``\d <表名>` 含 app_id / access_token / updated_at 三列 |
| 3 | `get_or_create(pk=1)` 首访拿到空记录 | shell 中 `obj, created = CredentialSlot.objects.get_or_create(pk=1)``created==True``obj.app_id == ''``obj.access_token == ''` |
| 4 | Admin 列表/查看态脱敏,编辑态明文 | 浏览器登录 admin → 看列表页 access_token 字段显示 `****<末4位>` 形态;点进编辑表单看 access_token 字段为完整明文 input |
| 5 | Admin 列表页**不显示**「新增」按钮 | 浏览器登录 admin → 列表页右上角无 "Add 凭据槽位" / "新增凭据槽位" 按钮 |
| 6 | Admin **禁止删除**(额外保险) | 浏览器登录 admin → 编辑页底部无 "Delete" 按钮;批量动作不含 "Delete selected" |
</specifics>
<deferred>
## 推迟事项(明确不在 Phase 1 范围)
- **at-rest 加密 access_token 字段**:当前明文存 DB依赖 PostgreSQL 访问控制;如未来需要应用层加密,开新 phase 评估 `django-encrypted-model-fields` 等方案
- **审计日志**(谁在什么时候改了 access_tokenDjango Admin 自带 `LogEntry` 已经记录基本信息,本 phase 不做专门审计表
- **管理端 / 客户端 REST 接口**:分别在 Phase 2 / Phase 3
- **阿里云日志脱敏过滤器**Phase 3 处理Phase 1 仅保证 DB / Admin 不暴露明文;如果 `aiapp` 现有日志中已经有"打印 model 实例"的代码路径,最多在本 phase 给 `__str__` / `__repr__` 加上 access_token 脱敏,但不展开做全链路日志改造
</deferred>
---
*Phase: 01-credential-data-layer*
*Context gathered: 2026-05-07 via inline PRD用户在 /gsd-plan-phase 1 调用时提供完整约束)*