- 新增 .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
15 KiB
phase, plan, subsystem, tags, requirements_completed, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | requirements_completed | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-admin-rest | 01 | aiapp + userapp(admin namespace 路由) |
|
|
|
|
|
|
|
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
# 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
# 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:
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+ DRFNotAuthenticated→ reachable ✓ - truth #5(user token → 403)→
_ensure_adminis_staff校验 → reachable ✓ - truth #6(swagger 路径条目 + access_token 脱敏 description)→
@swagger_auto_schema+_credential_slot_data_schemadescription → 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_token1 行;新增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)
# 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 矩阵
# 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 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:
6820fe7feat(02-01): 新增 CredentialSlotSerializer - FOUND:
192d0a1feat(02-01): 新增 CredentialSlotAdminView(GET 脱敏 / PUT 全字段覆写) - FOUND:
9d02021feat(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 隔离)