docs(02-01): 收尾 Plan 02-01(CredentialSlotAdminView 已落地)
- 新增 .planning/phases/02-admin-rest/02-01-SUMMARY.md(含 frontmatter / decisions / metrics / 偏差 / Plan 02-02 端到端 verify hook) - STATE.md:当前位置 1→2、Plan 02 of 02→01 of 02、progress 75%、性能指标加 Plan 02-01 行、 累积决策追加 5 条 [Plan 02-01] 标签项、下一步切到 /gsd-execute-plan 02-02 - ROADMAP.md:Phase 2 plan 进度 0/2 → 1/2 - REQUIREMENTS.md:CRED-03 / CRED-04 标记 complete + traceability 表更新 - config.json:gsd-tools init 写入 workflow._auto_chain_active flag(不影响本期执行) Plan 02-01 三个 task commit: 6820fe7 / 192d0a1 / 9d02021
This commit is contained in:
parent
9d020218d2
commit
2dec1fd813
@ -97,8 +97,8 @@
|
||||
|
||||
- [x] **CRED-01** 单例 `CredentialSlot` Django 模型 + 迁移;DB 层强制最多一条记录(`pk=1` 固定主键或单字段唯一约束);含 `app_id`、`access_token`、`updated_at` 字段 ✓ Plan 01-01 完成(2026-05-07,commits a9c25eb / 30c7caf / a475fe4)
|
||||
- [x] **CRED-02** Django Admin 注册:列表态/查看态对 `access_token` 字段脱敏;新增/编辑态可见明文(运营录入需要);隐藏"新增"按钮(强制单例语义) ✓ Plan 01-02 完成(2026-05-07,commit 653f057;Task 2 由 orchestrator Django test client 程序化验收 10/10 PASS)
|
||||
- [ ] **CRED-03** 管理端 GET `/api/v1/admin/credential-slot/`:admin token 鉴权(`admin_token:{token}` Redis key 体系);返回 `{ app_id, access_token: <masked>, updated_at }`,Access Token 仅返回末 4 位脱敏掩码
|
||||
- [ ] **CRED-04** 管理端 PUT `/api/v1/admin/credential-slot/`:admin token 鉴权;接受 `{ app_id, access_token }` 全字段覆写更新;空记录场景自动 `get_or_create`;变更写入 `updated_at`
|
||||
- [x] **CRED-03** 管理端 GET `/api/v1/admin/credential-slot/`:admin token 鉴权(`admin_token:{token}` Redis key 体系);返回 `{ app_id, access_token: <masked>, updated_at }`,Access Token 仅返回末 4 位脱敏掩码
|
||||
- [x] **CRED-04** 管理端 PUT `/api/v1/admin/credential-slot/`:admin token 鉴权;接受 `{ app_id, access_token }` 全字段覆写更新;空记录场景自动 `get_or_create`;变更写入 `updated_at`
|
||||
- [ ] **CRED-05** 客户端 GET `/api/credential-slot/`:user token 鉴权(`token:{token}` Redis key 体系,复用 `RedisTokenAuthentication`);**明文**返回 `{ app_id, access_token, updated_at }`(手机端 / 设备端实际调用第三方服务需要)
|
||||
- [ ] **CRED-06** Access Token 日志过滤:阿里云日志格式化器 / 自定义日志过滤器中识别 `access_token` 字段并脱敏,覆盖 PUT 请求体、admin GET 响应体两条最易泄露路径
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
3. 不携带 admin token、或仅携带普通 user token 调用上述两个端点均被拒绝(401 / 403),错误响应同样符合 `StandardResponseMiddleware` 壳层
|
||||
4. 接口出现在 `/swagger/` 与 `/redoc/` 中,请求/响应 schema 与实际行为一致(drf-yasg 自动生成)
|
||||
**Plans:** 2 plans
|
||||
- [ ] 02-01-PLAN.md — CredentialSlot serializer + view(GET 脱敏 / PUT 覆写 + admin 二次校验)+ admin_urls 路由注册(CRED-03 + CRED-04)
|
||||
- [x] 02-01-PLAN.md — CredentialSlot serializer + view(GET 脱敏 / PUT 覆写 + admin 二次校验)+ admin_urls 路由注册(CRED-03 + CRED-04)
|
||||
- [ ] 02-02-PLAN.md — 端到端 curl + Django shell 验收 8 条 success criteria + qy_lty / qy-lty-admin 两端修改记录互引(CRED-03 + CRED-04)
|
||||
|
||||
### Phase 3: 客户端读取与日志脱敏
|
||||
|
||||
@ -2,21 +2,21 @@
|
||||
gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: 通用凭据槽位
|
||||
status: Phase 1 全部完成,等待启动 Phase 2 规划
|
||||
stopped_at: Phase 1 全部完成(Plan 01-01 数据层 + Plan 01-02 Admin/修改记录),ROADMAP Phase 1 4 条 success criteria 全部满足;下一步启动 `/gsd-plan-phase 2`(管理端 REST 接口)
|
||||
last_updated: "2026-05-07T10:34:22.576Z"
|
||||
status: Phase 2 Plan 02-01 完成,等待执行 Plan 02-02(端到端 verify + 修改记录两端互引)
|
||||
stopped_at: Plan 02-01 完成(CredentialSlotSerializer + CredentialSlotAdminView + URL 注册);下一步启动 Plan 02-02(端到端 verify + 修改记录两端互引)
|
||||
last_updated: "2026-05-07T14:57:06.337Z"
|
||||
last_activity: 2026-05-07
|
||||
progress:
|
||||
total_phases: 3
|
||||
completed_phases: 1
|
||||
total_plans: 4
|
||||
completed_plans: 2
|
||||
percent: 50
|
||||
completed_plans: 3
|
||||
percent: 75
|
||||
---
|
||||
|
||||
# Project State — QY LTY Backend
|
||||
|
||||
**最后更新**: 2026-05-07(Phase 1 Plan 01-02 完成,Admin 注册 + 修改记录归档落地;Phase 1 整体 Complete)
|
||||
**最后更新**: 2026-05-07(Phase 2 Plan 02-01 完成:CredentialSlotSerializer + CredentialSlotAdminView + URL 注册)
|
||||
|
||||
## 项目引用
|
||||
|
||||
@ -24,37 +24,38 @@ progress:
|
||||
|
||||
**核心价值**:设备端与手机端通过同一个 user_id 实时互通——`device_{user_id}` 分组语义必须始终成立。
|
||||
|
||||
**当前重点**:Milestone v1.0 通用凭据槽位(APP ID + Access Token)— Phase 1 已完成,等待启动 Phase 2(管理端 REST 接口,覆盖 CRED-03 + CRED-04)
|
||||
**当前重点**:Milestone v1.0 通用凭据槽位(APP ID + Access Token)— Phase 1 已完成;Phase 2 Plan 02-01(管理端 REST 接口,CRED-03 + CRED-04)已落地,等待 Plan 02-02 端到端 verify + 修改记录两端互引。
|
||||
|
||||
## 当前位置
|
||||
|
||||
```
|
||||
Phase: 1 of 3(凭据槽位数据层)— Complete
|
||||
Plan: 02 of 02(Admin 注册 + 修改记录)— Complete
|
||||
Status: Phase 1 全部完成,等待启动 Phase 2 规划
|
||||
Phase: 2 of 3(管理端 REST 接口)— In Progress
|
||||
Plan: 01 of 02(serializer + view + URL + Swagger)— Complete
|
||||
Status: Plan 02-01 完成,等待执行 Plan 02-02
|
||||
Last activity: 2026-05-07
|
||||
```
|
||||
|
||||
Progress: [██████████] 100%(Phase 1 内 plan 进度:2/2,已收尾)
|
||||
Progress: [████████░░] 75%(累计完成 plan:3/4 — Phase 1 全部 + Phase 2 Plan 02-01)
|
||||
|
||||
## 性能指标
|
||||
|
||||
**速度:**
|
||||
|
||||
- 已完成 plan 数:2
|
||||
- 平均耗时:~6.5 min(顺序执行模式)
|
||||
- 总执行时间:784 s(Plan 01-01: 184s + Plan 01-02: ~600s 含 checkpoint 验收)
|
||||
- 已完成 plan 数:3
|
||||
- 平均耗时:~333 s(顺序执行模式)
|
||||
- 总执行时间:1000 s(Plan 01-01: 184 s + Plan 01-02: ~600 s + Plan 02-01: 216 s)
|
||||
|
||||
**按 Phase:**
|
||||
|
||||
| Phase | Plans | Total | Avg/Plan |
|
||||
|-------|-------|-------|----------|
|
||||
| 1 | 2/2 | 784 s | 392 s |
|
||||
| Phase | Plans | Total | Avg/Plan |
|
||||
|-------|-------|--------|----------|
|
||||
| 1 | 2/2 | 784 s | 392 s |
|
||||
| 2 | 1/2 | 216 s | 216 s |
|
||||
|
||||
**最近趋势:**
|
||||
|
||||
- 最近 5 个 plan:01-01(184 s,3 task / 3 commit / 3 文件) / 01-02(~600 s,4 task / 2 commit / 2 文件 + 1 checkpoint 验收)
|
||||
- 趋势:第二个 plan 含 checkpoint:human-verify 验收阶段,耗时显著高于纯 auto plan,符合预期
|
||||
- 最近 5 个 plan:01-01(184 s,3 task)/ 01-02(~600 s,4 task + checkpoint 验收)/ 02-01(216 s,3 task / 3 commit / 3 文件)
|
||||
- 趋势:纯 auto plan(无 checkpoint)落地速度稳定在 200-220 s 区间;checkpoint 验收 plan 显著放大耗时
|
||||
|
||||
*每完成一个 plan 后更新*
|
||||
|
||||
@ -77,6 +78,11 @@ Progress: [██████████] 100%(Phase 1 内 plan 进度:2/2
|
||||
- **[Plan 01-02]** BotAdmin / ChatMessage 注册块的历史 class 名误用问题不修(不在 phase scope)
|
||||
- **[Plan 01-02]** 修改记录两条条目都在 Task 3 一次性写入「跨项目联动: 无」字段(INFO #2 调整),不留 Task 4 二次写入
|
||||
- **[Plan 01-02]** qy-lty-admin/docs/修改记录.md 不写互引条目;Phase 1 是纯服务端改动,CLAUDE.md 跨项目规则下纯单端不需要互引;Phase 2 暴露 REST 接口时再做前后端互引
|
||||
- **[Plan 02-01]** CredentialSlotAdminView 1:1 复刻 RTCChatHistoryAPIView 自定义 APIView 风格(不走 RetrieveUpdateAPIView,仓库零先例)
|
||||
- **[Plan 02-01]** permission_classes=[IsAuthenticated] + view 内 _ensure_admin 二次校验 is_staff(不发明 IsAdminTokenAuthenticated permission 类,沿用 AdminEmailLoginView/AdminLogoutView 模式)
|
||||
- **[Plan 02-01]** 脱敏放 view 层 _build_response_data helper:GET 与 PUT 响应都强制走脱敏,避免 PUT 直接 return success_response(data=serializer.data) 明文回显(Pitfall 3)
|
||||
- **[Plan 02-01]** drf-yasg request_body 用独立 CredentialSlotPutRequestSchema 类(与 AdminEmailLoginRequestSchema 模式一致),与实际写入校验的 CredentialSlotSerializer 解耦
|
||||
- **[Plan 02-01]** 不写 docs/修改记录.md(用户在 prompt 显式声明);由 Plan 02-02 Task 2 一次性补两端互引条目
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@ -104,17 +110,18 @@ Progress: [██████████] 100%(Phase 1 内 plan 进度:2/2
|
||||
## 下一步
|
||||
|
||||
```
|
||||
/gsd-plan-phase 2
|
||||
/gsd-execute-plan 02-02
|
||||
```
|
||||
|
||||
Phase 1 已完成(Plan 01-01 + Plan 01-02 全部交付):
|
||||
Phase 2 Plan 02-01 已完成(commits 6820fe7 / 192d0a1 / 9d02021):
|
||||
|
||||
- Plan 01-01:CredentialSlot 单例模型 + 0004 迁移 + mask_token 工具 + DB 探针(commits a9c25eb / 30c7caf / a475fe4 / 20036ee)
|
||||
- Plan 01-02:CredentialSlotAdmin 注册(脱敏 + 单例 + 禁删,commit 653f057)+ 修改记录两条 Phase 1 条目(commit ddbcb7d)
|
||||
- Task 1:`aiapp/serializers.py` 追加 `CredentialSlotSerializer`(commit 6820fe7)
|
||||
- Task 2:`aiapp/views.py` 追加 `CredentialSlotAdminView`(GET 脱敏 + PUT 全字段覆写 + _ensure_admin + _build_response_data)(commit 192d0a1)
|
||||
- Task 3:`userapp/admin_urls.py` 注册 `path('credential-slot/', ..., name='admin_credential_slot')`(commit 9d02021)
|
||||
|
||||
ROADMAP Phase 1 全部 4 条 success criteria + 2 条工程硬要求均满足,DB 探针 `pk=1, access_token='probe_secret_xxxx'` 已还原至稳定起点。
|
||||
URL `/api/v1/admin/credential-slot/` 已可被 reverse / Django check 通过;探针脱敏 `probe_secret_xxxx` → `*************xxxx` 验证 OK。CRED-03 / CRED-04 已在 REQUIREMENTS.md 标记 complete。
|
||||
|
||||
下一步启动 `/gsd-plan-phase 2` 规划管理端 REST 接口(CRED-03 GET 脱敏 + CRED-04 PUT 全字段覆写,admin token 鉴权)。
|
||||
下一步执行 Plan 02-02 端到端 verify(Django test client + curl)+ qy_lty 与 qy-lty-admin 两端 docs/修改记录.md 互引条目。
|
||||
|
||||
## 工作流配置
|
||||
|
||||
@ -144,10 +151,10 @@ CLAUDE.md 两条强制规则(任何 phase 都必须遵守):
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-05-07
|
||||
Stopped at: Phase 1 全部完成(Plan 01-01 数据层 + Plan 01-02 Admin/修改记录),ROADMAP Phase 1 4 条 success criteria 全部满足;下一步启动 `/gsd-plan-phase 2`(管理端 REST 接口)
|
||||
Resume file: 无 — Phase 1 收尾,等待 Phase 2 规划启动
|
||||
Last session: 2026-05-07T14:57:06.334Z
|
||||
Stopped at: Plan 02-01 完成(CredentialSlotSerializer + CredentialSlotAdminView + URL 注册);下一步启动 Plan 02-02(端到端 verify + 修改记录两端互引)
|
||||
Resume file: None
|
||||
|
||||
---
|
||||
|
||||
*由 /gsd-execute-phase 顺序执行器于 2026-05-07 更新(Plan 01-02 完成时点 = Phase 1 收尾)*
|
||||
*由 /gsd-execute-phase 顺序执行器于 2026-05-07 更新(Plan 02-01 完成时点)*
|
||||
|
||||
@ -35,7 +35,8 @@
|
||||
"plan_bounce": false,
|
||||
"plan_bounce_script": null,
|
||||
"plan_bounce_passes": 2,
|
||||
"auto_prune_state": false
|
||||
"auto_prune_state": false,
|
||||
"_auto_chain_active": false
|
||||
},
|
||||
"hooks": {
|
||||
"context_warnings": true
|
||||
|
||||
298
qy_lty/.planning/phases/02-admin-rest/02-01-SUMMARY.md
Normal file
298
qy_lty/.planning/phases/02-admin-rest/02-01-SUMMARY.md
Normal file
@ -0,0 +1,298 @@
|
||||
---
|
||||
phase: 02-admin-rest
|
||||
plan: 01
|
||||
subsystem: aiapp + userapp(admin namespace 路由)
|
||||
tags: [credential-slot, admin-api, drf, mask, swagger, rest]
|
||||
requirements_completed:
|
||||
- CRED-03
|
||||
- CRED-04
|
||||
dependency_graph:
|
||||
requires:
|
||||
- aiapp.models.CredentialSlot(Phase 1 / Plan 01-01 落地)
|
||||
- aiapp.models.CredentialSlot.get_solo()(Phase 1 / Plan 01-01 落地)
|
||||
- common.utils.mask_token(Phase 1 / Plan 01-01 落地)
|
||||
- common.responses.success_response / error_response(已存在)
|
||||
- common.swagger_utils.get_standardized_response_schema(已存在)
|
||||
- userapp.authentication.RedisTokenAuthentication(已存在)
|
||||
provides:
|
||||
- aiapp.serializers.CredentialSlotSerializer(DRF ModelSerializer,3 字段)
|
||||
- aiapp.views.CredentialSlotAdminView(APIView,GET/PUT 两端点)
|
||||
- URL: /api/v1/admin/credential-slot/(name='admin_credential_slot')
|
||||
affects:
|
||||
- 下一 plan:02-02-PLAN(端到端 verify + 修改记录两端互引)
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- DRF 自定义 APIView 单 URL 多方法(1:1 复刻 RTCChatHistoryAPIView)
|
||||
- permission_classes=[IsAuthenticated] + view 内 _ensure_admin 二次校验 is_staff
|
||||
(沿用 AdminEmailLoginView / AdminLogoutView 模式,不发明 IsAdminTokenAuthenticated)
|
||||
- 脱敏在 view 层 _build_response_data helper 完成,不在 serializer 层
|
||||
- GET 与 PUT 响应都走 _build_response_data,避免 PUT 明文回显
|
||||
- method-level @swagger_auto_schema 装饰器 + access_token 字段 description
|
||||
显式标注脱敏掩码语义
|
||||
key_files:
|
||||
created: []
|
||||
modified:
|
||||
- qy_lty/aiapp/serializers.py
|
||||
- qy_lty/aiapp/views.py
|
||||
- qy_lty/userapp/admin_urls.py
|
||||
decisions:
|
||||
- "View 1:1 复刻 RTCChatHistoryAPIView 风格:不走 RetrieveUpdateAPIView(仓库零先例)"
|
||||
- "permission_classes=[IsAuthenticated] + 视图内 _ensure_admin 二次校验:与 AdminEmailLoginView/AdminLogoutView 一致;不发明 IsAdminTokenAuthenticated permission 类"
|
||||
- "脱敏放 view 层不放 serializer:PUT 路径需明文走 is_valid + save,serializer 只做字段校验,避免双重责任"
|
||||
- "GET 与 PUT 响应都强制走 _build_response_data:避免 PUT 直接 return success_response(data=serializer.data) 导致刚提交的明文回显(Pitfall 3)"
|
||||
- "drf-yasg request body 用独立 CredentialSlotPutRequestSchema serializer 类(与 userapp/views.py:705-708 AdminEmailLoginRequestSchema 模式一致):与实际写入校验的 CredentialSlotSerializer 解耦"
|
||||
- "method-level @swagger_auto_schema:在 GET 与 PUT 各挂一份;access_token 响应字段 description 明示末 4 位脱敏掩码"
|
||||
metrics:
|
||||
duration_seconds: 216
|
||||
tasks_completed: 3
|
||||
files_modified: 3
|
||||
commits:
|
||||
- 6820fe7 feat(02-01): 新增 CredentialSlotSerializer
|
||||
- 192d0a1 feat(02-01): 新增 CredentialSlotAdminView(GET 脱敏 / PUT 全字段覆写)
|
||||
- 9d02021 feat(02-01): 注册 /api/v1/admin/credential-slot/ 路由
|
||||
completed_date: 2026-05-07
|
||||
---
|
||||
|
||||
# Phase 2 Plan 02-01:管理端 REST 接口(serializer + view + URL + Swagger)Summary
|
||||
|
||||
在 `/api/v1/admin/credential-slot/` 暴露 GET(脱敏读取)+ PUT(全字段覆写)两个 admin token 鉴权端点,全部沿用仓库现有 RTCChatHistoryAPIView / AdminEmailLoginView 模式,零新依赖。
|
||||
|
||||
## 一句话概述
|
||||
|
||||
新增 `CredentialSlotSerializer` + `CredentialSlotAdminView`(GET/PUT),在 `userapp/admin_urls.py` 注册到 `/api/v1/admin/credential-slot/`,view 层 `mask_token` 脱敏 access_token,覆盖 CRED-03 / CRED-04。
|
||||
|
||||
## 改动文件清单
|
||||
|
||||
| 文件 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `qy_lty/aiapp/serializers.py` | 修改 | import 区追加 `CredentialSlot`;文件末尾追加 `CredentialSlotSerializer`(ModelSerializer,3 字段) |
|
||||
| `qy_lty/aiapp/views.py` | 修改 | import 区追加 `CredentialSlot` / `CredentialSlotSerializer` / `mask_token` / `get_standardized_response_schema`;文件末尾追加 `CredentialSlotPutRequestSchema`(drf-yasg 请求体 schema)+ `_credential_slot_data_schema`(响应 data 子 schema)+ `CredentialSlotAdminView`(含 `_ensure_admin` / `_build_response_data` / GET / PUT 4 个方法) |
|
||||
| `qy_lty/userapp/admin_urls.py` | 修改 | 顶部 import `CredentialSlotAdminView`;urlpatterns 追加 `path('credential-slot/', ..., name='admin_credential_slot')` |
|
||||
|
||||
## 实际落地的关键产物
|
||||
|
||||
### Serializer
|
||||
|
||||
```python
|
||||
# qy_lty/aiapp/serializers.py
|
||||
class CredentialSlotSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = CredentialSlot
|
||||
fields = ['app_id', 'access_token', 'updated_at']
|
||||
read_only_fields = ['updated_at']
|
||||
extra_kwargs = {
|
||||
'app_id': {'allow_blank': True, 'allow_null': False, 'required': False},
|
||||
'access_token': {'allow_blank': True, 'allow_null': False, 'required': False},
|
||||
}
|
||||
```
|
||||
|
||||
### View
|
||||
|
||||
`qy_lty/aiapp/views.py` 文件末尾:
|
||||
|
||||
- `CredentialSlotPutRequestSchema(serializers.Serializer)` — drf-yasg 请求体 schema
|
||||
- `_credential_slot_data_schema = openapi.Schema(...)` — 响应 data 子 schema(access_token description 明示末 4 位脱敏掩码)
|
||||
- `CredentialSlotAdminView(APIView)`:
|
||||
- `authentication_classes = [RedisTokenAuthentication]`
|
||||
- `permission_classes = [IsAuthenticated]`
|
||||
- `_ensure_admin(request)` — `if not request.user.is_staff: return error_response(code=403, status_code=403)`
|
||||
- `_build_response_data(instance)` — 调 `mask_token(instance.access_token)` 覆盖明文
|
||||
- `get(request)` / `put(request)` — 各挂 `@swagger_auto_schema`
|
||||
|
||||
### URL
|
||||
|
||||
```python
|
||||
# qy_lty/userapp/admin_urls.py
|
||||
from aiapp.views import CredentialSlotAdminView
|
||||
urlpatterns = [
|
||||
...,
|
||||
path('credential-slot/', CredentialSlotAdminView.as_view(), name='admin_credential_slot'),
|
||||
]
|
||||
```
|
||||
|
||||
完整路径:`/api/v1/admin/credential-slot/`(拼接自 `qy_lty/urls.py:59` 的 `path('v1/admin/', include('userapp.admin_urls'))` + `path('credential-slot/', ...)`)。
|
||||
|
||||
### 调用 mask_token 的位置(脱敏调用点)
|
||||
|
||||
`qy_lty/aiapp/views.py:637`:
|
||||
|
||||
```python
|
||||
data['access_token'] = mask_token(instance.access_token)
|
||||
```
|
||||
|
||||
`_build_response_data` 是 GET 与 PUT 唯一的响应数据构造点,强制脱敏。
|
||||
|
||||
## Plan 内自验证据
|
||||
|
||||
| 验证点 | 命令 | 结果 |
|
||||
|--------|------|------|
|
||||
| import 链路 | `from aiapp.views import CredentialSlotAdminView; from aiapp.serializers import CredentialSlotSerializer` | OK 无 ImportError |
|
||||
| URL 解析 | `reverse('admin_credential_slot')` | `/api/v1/admin/credential-slot/` |
|
||||
| Django check | `python manage.py check` | 仅 1 条 W004(STATICFILES_DIRS)— 预先存在,与本 plan 无关 |
|
||||
| Serializer 字段 | 实例化 `pk=1` 后 `.data.keys()` | `['app_id', 'access_token', 'updated_at']` |
|
||||
| Serializer read_only_fields | `.fields['updated_at'].read_only` | `True` |
|
||||
| Serializer allow_null/allow_blank | `.fields['app_id'/'access_token']` | `allow_blank=True, allow_null=False` |
|
||||
| View 鉴权 / 权限链 | `CredentialSlotAdminView.authentication_classes / permission_classes` | `[RedisTokenAuthentication]` / `[IsAuthenticated]` |
|
||||
| View 4 个 method 完整性 | `hasattr(v, 'get'/'put'/'_ensure_admin'/'_build_response_data')` | 全部 True |
|
||||
| Swagger 装饰器 | `hasattr(get_method/put_method, '_swagger_auto_schema')` | 全部 True |
|
||||
| 探针脱敏 | `mask_token('probe_secret_xxxx')` | `*************xxxx`(13 stars + xxxx;len=17) |
|
||||
|
||||
### Goal-backward reachability self-check
|
||||
|
||||
- truth #1(GET 脱敏 200)→ `views.py CredentialSlotAdminView.get` + `serializers.py CredentialSlotSerializer` + `admin_urls.py path` → reachable ✓
|
||||
- truth #2(PUT 全字段覆写 + updated_at 刷新 + 响应脱敏)→ `views.py CredentialSlotAdminView.put`(`serializer.save` + `_build_response_data`)→ reachable ✓
|
||||
- truth #3(PUT 在空记录场景 get_or_create)→ `CredentialSlot.get_solo()`(Phase 1 已在)→ reachable ✓
|
||||
- truth #4(无 token → 401)→ `RedisTokenAuthentication` + DRF `NotAuthenticated` → reachable ✓
|
||||
- truth #5(user token → 403)→ `_ensure_admin` `is_staff` 校验 → reachable ✓
|
||||
- truth #6(swagger 路径条目 + access_token 脱敏 description)→ `@swagger_auto_schema` + `_credential_slot_data_schema` description → reachable ✓
|
||||
|
||||
端到端 curl 验收(含 admin token 签发 / user token 拒绝 / swagger.json 校验)由 Plan 02-02 完成。
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### 偏差 1:Plan import 行号偏移(Rule 1 等价 — 现实修正)
|
||||
|
||||
- **Found during:** Task 2
|
||||
- **Plan 假设:** `from drf_yasg.utils import swagger_auto_schema` 在第 13 行
|
||||
- **实际仓库状态:** 该 import 实际位于第 14 行(第 11 行已是 `from common.swagger_utils import swagger_schema`)
|
||||
- **Adjustment:** 不按行号定位,按字符串精确 Edit 替换 8 行 import 块(包含 `.models` / `.serializers` / `RedisTokenAuthentication` / `serializers` / `swagger_utils` / `responses`),追加 `mask_token` 1 行;新增 `get_standardized_response_schema` 通过扩展现有 `from common.swagger_utils import swagger_schema` 一行完成
|
||||
- **Reason:** Plan 行号是参考值,不是契约;本仓库 import 区结构与 plan 描述完全等价(仅个别行偏移),按精确 token 串替换更安全
|
||||
- **Files modified:** `qy_lty/aiapp/views.py`
|
||||
- **Commit:** `192d0a1`
|
||||
|
||||
### 偏差 2:Task 1 自动化校验命令补 Django setup(Rule 3 — 阻塞修复)
|
||||
|
||||
- **Found during:** Task 1 verify
|
||||
- **Issue:** Plan 提供的 `python -c "from aiapp.serializers import CredentialSlotSerializer..."` 命令在 windows 命令行下直接 import 会触发 `ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured`
|
||||
- **Fix:** 在 verify 命令内显式 `os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'qy_lty.settings'); django.setup()`;功能等价但兼容裸 python -c 调用
|
||||
- **Reason:** Plan 假设的 verify 命令行假设了某个隐式环境(如 `python manage.py shell -c`),裸 python 解释器需显式 setup;不影响功能正确性
|
||||
- **Files modified:** 无(仅 verify 命令)
|
||||
- **Commit:** 无(不涉及代码改动)
|
||||
|
||||
### 偏差 3:未写 docs/修改记录.md(per execution_context 强制约束)
|
||||
|
||||
- **Reason:** 用户在 `<sequential_execution>` 显式声明 "本 plan 不写 docs/修改记录.md(Plan 02-02 Task 2 一并写两端互引)"
|
||||
- **Status:** 故意不写,由 Plan 02-02 一次性补完两端互引条目(CLAUDE.md 跨项目联动规则)
|
||||
- **Risk:** 在 Plan 02-02 落地前,本仓库 `docs/修改记录.md` 不含 Phase 2 条目;可接受,因为 Plan 02-01 + 02-02 是同 session 同 phase 的连续动作
|
||||
|
||||
### 其余偏差
|
||||
|
||||
无。Plan 三个 task 的代码内容、acceptance criteria、anti-pattern 约束、reachability 验证全部按 Plan 1:1 落地。
|
||||
|
||||
## 留给 Plan 02-02 的端到端 verify hook
|
||||
|
||||
### Setup(Wave 0 — 签发测试 token)
|
||||
|
||||
```python
|
||||
# Django shell:python manage.py shell
|
||||
from userapp.models import ParadiseUser
|
||||
from userapp.utils import generate_token
|
||||
|
||||
# 找一个 staff 用户(或现成的 admin)
|
||||
admin_user = ParadiseUser.objects.filter(is_staff=True).first()
|
||||
admin_token = generate_token(admin_user.id, is_admin=True)
|
||||
print('ADMIN_TOKEN=', admin_token)
|
||||
|
||||
# 找一个普通用户
|
||||
normal_user = ParadiseUser.objects.filter(is_staff=False).first()
|
||||
user_token = generate_token(normal_user.id, is_admin=False)
|
||||
print('USER_TOKEN=', user_token)
|
||||
```
|
||||
|
||||
### Curl 矩阵
|
||||
|
||||
```bash
|
||||
# 1. 无 token → 401
|
||||
curl -i http://localhost:8000/api/v1/admin/credential-slot/
|
||||
|
||||
# 2. user token → 403 + "需要管理员权限"
|
||||
curl -i -H "Authorization: Bearer ${USER_TOKEN}" http://localhost:8000/api/v1/admin/credential-slot/
|
||||
|
||||
# 3. admin token GET → 200 + access_token 脱敏(probe_secret_xxxx → *************xxxx)
|
||||
curl -i -H "Authorization: Bearer ${ADMIN_TOKEN}" http://localhost:8000/api/v1/admin/credential-slot/
|
||||
|
||||
# 4. admin token PUT → 200 + DB 写入 + 响应同样脱敏
|
||||
curl -i -X PUT -H "Authorization: Bearer ${ADMIN_TOKEN}" -H "Content-Type: application/json" \
|
||||
-d '{"app_id":"new_app","access_token":"new_secret_token_5678"}' \
|
||||
http://localhost:8000/api/v1/admin/credential-slot/
|
||||
# 期望响应 data.access_token = "****************5678",updated_at 刷新
|
||||
|
||||
# 5. swagger.json 含 credential-slot 路径条目 + access_token 脱敏 description
|
||||
curl -s http://localhost:8000/swagger.json | python -c "import json,sys; d=json.load(sys.stdin); p='/api/v1/admin/credential-slot/'; assert p in d['paths'], p; assert 'GET' in [m.upper() for m in d['paths'][p].keys()]; assert 'PUT' in [m.upper() for m in d['paths'][p].keys()]; print('OK swagger')"
|
||||
```
|
||||
|
||||
### Django shell 程序化验收(test client)
|
||||
|
||||
```python
|
||||
# python manage.py shell
|
||||
from rest_framework.test import APIClient
|
||||
from userapp.models import ParadiseUser
|
||||
from userapp.utils import generate_token
|
||||
|
||||
c = APIClient()
|
||||
|
||||
# admin token GET
|
||||
admin_user = ParadiseUser.objects.filter(is_staff=True).first()
|
||||
admin_token = generate_token(admin_user.id, is_admin=True)
|
||||
c.credentials(HTTP_AUTHORIZATION=f'Bearer {admin_token}')
|
||||
resp = c.get('/api/v1/admin/credential-slot/')
|
||||
print(resp.status_code, resp.json())
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()['success'] == True
|
||||
assert '*' in resp.json()['data']['access_token'] # 脱敏特征
|
||||
|
||||
# admin token PUT
|
||||
resp2 = c.put('/api/v1/admin/credential-slot/',
|
||||
data={'app_id': 'put_app', 'access_token': 'put_secret_5678'},
|
||||
format='json')
|
||||
print(resp2.status_code, resp2.json())
|
||||
assert resp2.status_code == 200
|
||||
assert resp2.json()['data']['access_token'].endswith('5678') # 末 4 位
|
||||
assert '*' in resp2.json()['data']['access_token'] # 脱敏
|
||||
|
||||
# user token → 403
|
||||
normal_user = ParadiseUser.objects.filter(is_staff=False).first()
|
||||
user_token = generate_token(normal_user.id, is_admin=False)
|
||||
c.credentials(HTTP_AUTHORIZATION=f'Bearer {user_token}')
|
||||
resp3 = c.get('/api/v1/admin/credential-slot/')
|
||||
print(resp3.status_code, resp3.json())
|
||||
assert resp3.status_code == 403
|
||||
assert resp3.json()['success'] == False
|
||||
|
||||
# 无 token → 401
|
||||
c.credentials()
|
||||
resp4 = c.get('/api/v1/admin/credential-slot/')
|
||||
print(resp4.status_code, resp4.json())
|
||||
assert resp4.status_code == 401
|
||||
```
|
||||
|
||||
## Threat Flags
|
||||
|
||||
无。本 plan 改动严格落在 02-01-PLAN 的 `<threat_model>` 8 条已声明威胁内(T-02-01 ~ T-02-08),未引入新 trust boundary 或新攻击面。
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
### Files
|
||||
|
||||
- FOUND: `qy_lty/aiapp/serializers.py`(已修改,含 CredentialSlotSerializer)
|
||||
- FOUND: `qy_lty/aiapp/views.py`(已修改,含 CredentialSlotAdminView)
|
||||
- FOUND: `qy_lty/userapp/admin_urls.py`(已修改,含 admin_credential_slot URL)
|
||||
- FOUND: `.planning/phases/02-admin-rest/02-01-SUMMARY.md`(本文件)
|
||||
|
||||
### Commits
|
||||
|
||||
- FOUND: `6820fe7` feat(02-01): 新增 CredentialSlotSerializer
|
||||
- FOUND: `192d0a1` feat(02-01): 新增 CredentialSlotAdminView(GET 脱敏 / PUT 全字段覆写)
|
||||
- FOUND: `9d02021` feat(02-01): 注册 /api/v1/admin/credential-slot/ 路由
|
||||
|
||||
### Imports
|
||||
|
||||
- VERIFIED: `from aiapp.views import CredentialSlotAdminView; from aiapp.serializers import CredentialSlotSerializer` 同行无 ImportError
|
||||
- VERIFIED: `reverse('admin_credential_slot')` = `/api/v1/admin/credential-slot/`
|
||||
- VERIFIED: `mask_token('probe_secret_xxxx')` = `*************xxxx`
|
||||
|
||||
---
|
||||
|
||||
*Phase: 02-admin-rest / Plan: 01*
|
||||
*Executed: 2026-05-07 by gsd-executor(顺序执行模式,无 worktree 隔离)*
|
||||
Loading…
x
Reference in New Issue
Block a user