8.4 KiB
8.4 KiB
Phase 1:凭据槽位数据层 - Context
Gathered: 2026-05-07
Status: Ready for planning
Source: 用户在 /gsd-plan-phase 1 调用时提供的内联约束(等同 PRD 快速通道)
本 phase 仅负责数据库层 + Django Admin 入口:
- 落地
CredentialSlot单例 Django 模型 + 数据迁移 - 注册 Django Admin(SimpleUI 主题 + 中英 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 里)
模型层(CRED-01)
- 单例语义实现方式:
pk=1固定主键 +get_or_create(pk=1)模式- 在模型
save()钩子中强制self.pk = 1,配合get_or_create(pk=1)让首次访问拿到一条空记录 - 不使用"单字段唯一约束"备选方案,因为该方案要求始终有非空字段,灵活性不如 pk=1
- 在模型
- 字段(最小集,沿用 ParadiseUser / 其它现有模型的命名习惯):
app_id:CharField,max_length 合理(128 足够覆盖常见服务商 ID 长度),允许空字符串(运营初次访问时尚未填写)access_token:CharField,max_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):grepmask|脱敏|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中文字面量(如"凭据槽位"还是"通用凭据")
<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看自定义模型 + 字段命名 + Metaqy_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>
## 具体要点(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" |
- at-rest 加密 access_token 字段:当前明文存 DB,依赖 PostgreSQL 访问控制;如未来需要应用层加密,开新 phase 评估
django-encrypted-model-fields等方案 - 审计日志(谁在什么时候改了 access_token):Django Admin 自带
LogEntry已经记录基本信息,本 phase 不做专门审计表 - 管理端 / 客户端 REST 接口:分别在 Phase 2 / Phase 3
- 阿里云日志脱敏过滤器:Phase 3 处理;Phase 1 仅保证 DB / Admin 不暴露明文;如果
aiapp现有日志中已经有"打印 model 实例"的代码路径,最多在本 phase 给__str__/__repr__加上 access_token 脱敏,但不展开做全链路日志改造
Phase: 01-credential-data-layer Context gathered: 2026-05-07 via inline PRD(用户在 /gsd-plan-phase 1 调用时提供完整约束)