126 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`**researcher 已确认:`common/` 不是 Django app 无 `apps.py`、未在 `INSTALLED_APPS`,无法承载 Model`userapp/models.py` 已 471 行承重过大;`aiapp/models.py` 当前仅 51 行追加合适;语义"凭据"与 AI 服务商接入强相关)。模型追加到 `aiapp/models.py` 末尾,不新建子文件
- **单例模式直接复刻 `userapp.models.AffinitySetting`247-314 行)**:仓库已有 pk=1 单例范本含 `save()` 钩子重定向 + `get_solo()` 类方法。Planner 必须把这两个文件读进 read_first照抄结构不要重新发明
- **脱敏工具新建 `common/utils.py:mask_token(token, visible_tail=4)`**grep `mask|脱敏|redact` 在代码侧 0 命中,须新建。放 `common/utils.py` 让 Phase 3 阿里云日志 formatter 可直接复用
- 模型 `__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**沿用仓库实操约定 = 中文字面量**researcher 实测:本仓库 4 个 admin.py 全是中文字面量,`_()` 仅 7 处零散使用,且 `LANGUAGES` / `LOCALE_PATHS``settings.py` 已被注释掉。Phase 1 不为 `verbose_name` / `short_description` 引入 `gettext_lazy`,保持与 `userapp/admin.py` / `aiapp/admin.py` 一致。i18n 体系化清洗留给独立 milestone。
### 兼容性 / 不引入新依赖
- 沿用 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 调用时提供完整约束)*