pmc 2dec1fd813 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
2026-05-07 22:58:40 +08:00

15 KiB
Raw Blame History

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 + userappadmin namespace 路由)
credential-slot
admin-api
drf
mask
swagger
rest
CRED-03
CRED-04
requires provides affects
aiapp.models.CredentialSlotPhase 1 / Plan 01-01 落地)
aiapp.models.CredentialSlot.get_solo()Phase 1 / Plan 01-01 落地)
common.utils.mask_tokenPhase 1 / Plan 01-01 落地)
common.responses.success_response / error_response已存在
common.swagger_utils.get_standardized_response_schema已存在
userapp.authentication.RedisTokenAuthentication已存在
aiapp.serializers.CredentialSlotSerializerDRF ModelSerializer3 字段)
aiapp.views.CredentialSlotAdminViewAPIViewGET/PUT 两端点)
URL
/api/v1/admin/credential-slot/name='admin_credential_slot'
下一 plan02-02-PLAN端到端 verify + 修改记录两端互引)
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 显式标注脱敏掩码语义
created modified
qy_lty/aiapp/serializers.py
qy_lty/aiapp/views.py
qy_lty/userapp/admin_urls.py
View 1:1 复刻 RTCChatHistoryAPIView 风格:不走 RetrieveUpdateAPIView仓库零先例
permission_classes=[IsAuthenticated] + 视图内 _ensure_admin 二次校验:与 AdminEmailLoginView/AdminLogoutView 一致;不发明 IsAdminTokenAuthenticated permission 类
脱敏放 view 层不放 serializerPUT 路径需明文走 is_valid + saveserializer 只做字段校验,避免双重责任
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 位脱敏掩码
duration_seconds tasks_completed files_modified commits completed_date
216 3 3
6820fe7 feat(02-01)
新增 CredentialSlotSerializer
192d0a1 feat(02-01)
新增 CredentialSlotAdminViewGET 脱敏 / PUT 全字段覆写)
9d02021 feat(02-01)
注册 /api/v1/admin/credential-slot/ 路由
2026-05-07

Phase 2 Plan 02-01管理端 REST 接口serializer + view + URL + SwaggerSummary

/api/v1/admin/credential-slot/ 暴露 GET脱敏读取+ PUT全字段覆写两个 admin token 鉴权端点,全部沿用仓库现有 RTCChatHistoryAPIView / AdminEmailLoginView 模式,零新依赖。

一句话概述

新增 CredentialSlotSerializer + CredentialSlotAdminViewGET/PUTuserapp/admin_urls.py 注册到 /api/v1/admin/credential-slot/view 层 mask_token 脱敏 access_token覆盖 CRED-03 / CRED-04。

改动文件清单

文件 类型 描述
qy_lty/aiapp/serializers.py 修改 import 区追加 CredentialSlot;文件末尾追加 CredentialSlotSerializerModelSerializer3 字段)
qy_lty/aiapp/views.py 修改 import 区追加 CredentialSlot / CredentialSlotSerializer / mask_token / get_standardized_response_schema;文件末尾追加 CredentialSlotPutRequestSchemadrf-yasg 请求体 schema+ _credential_slot_data_schema(响应 data 子 schema+ CredentialSlotAdminView(含 _ensure_admin / _build_response_data / GET / PUT 4 个方法)
qy_lty/userapp/admin_urls.py 修改 顶部 import CredentialSlotAdminViewurlpatterns 追加 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 子 schemaaccess_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:59path('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 条 W004STATICFILES_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') *************xxxx13 stars + xxxxlen=17

Goal-backward reachability self-check

  • truth #1GET 脱敏 200views.py CredentialSlotAdminView.get + serializers.py CredentialSlotSerializer + admin_urls.py path → reachable ✓
  • truth #2PUT 全字段覆写 + updated_at 刷新 + 响应脱敏)→ views.py CredentialSlotAdminView.putserializer.save + _build_response_data)→ reachable ✓
  • truth #3PUT 在空记录场景 get_or_createCredentialSlot.get_solo()Phase 1 已在)→ reachable ✓
  • truth #4无 token → 401RedisTokenAuthentication + DRF NotAuthenticated → reachable ✓
  • truth #5user token → 403_ensure_admin is_staff 校验 → reachable ✓
  • truth #6swagger 路径条目 + 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

偏差 1Plan 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

偏差 2Task 1 自动化校验命令补 Django setupRule 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/修改记录.mdper execution_context 强制约束)

  • Reason: 用户在 <sequential_execution> 显式声明 "本 plan 不写 docs/修改记录.mdPlan 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

SetupWave 0 — 签发测试 token

# Django shellpython 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: 6820fe7 feat(02-01): 新增 CredentialSlotSerializer
  • FOUND: 192d0a1 feat(02-01): 新增 CredentialSlotAdminViewGET 脱敏 / 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 隔离)