From 5269a08118388d00ca1627b57641c9bf6cd078ff Mon Sep 17 00:00:00 2001 From: pmc <740076875@qq.com> Date: Fri, 8 May 2026 10:13:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(03-01):=20=E5=9C=A8=20aiapp/views.py=20?= =?UTF-8?q?=E6=9C=AB=E5=B0=BE=E8=BF=BD=E5=8A=A0=20CredentialSlotClientView?= =?UTF-8?q?=20=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 _credential_slot_client_data_schema:客户端响应 schema,access_token description 标注「明文」 - 新增 CredentialSlotClientView(APIView):仅 GET,user/admin token 鉴权(RedisTokenAuthentication + IsAuthenticated) - 关键差异(vs Phase 2 admin view):不调 _ensure_admin / 不调 _build_response_data / 不调 mask_token / 不含 def put - 直接 success_response(data=serializer.data) 明文返回,供手机/设备端实际调用第三方服务 - imports 段未变(Phase 2 已就位 CredentialSlot/CredentialSlotSerializer/RedisTokenAuthentication 等) CRED-05 落地步骤 1/3 --- qy_lty/aiapp/views.py | 61 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/qy_lty/aiapp/views.py b/qy_lty/aiapp/views.py index 4707d8e..2b2c2f4 100644 --- a/qy_lty/aiapp/views.py +++ b/qy_lty/aiapp/views.py @@ -684,4 +684,63 @@ class CredentialSlotAdminView(APIView): # 重新读取 instance.access_token:serializer.save() 后 instance 已被同步刷新; # _build_response_data 内部会再次 dict(serializer.data) 拿最新 OrderedDict。 data = self._build_response_data(instance) - return success_response(data=data, message="凭据已更新") \ No newline at end of file + return success_response(data=data, message="凭据已更新") + + +# ====================================================================== +# Phase 3 — 通用凭据槽位客户端读取接口(CRED-05) +# 1:1 复刻 CredentialSlotAdminView 的 GET 部分,删 _ensure_admin / _build_response_data / PUT +# 关键差异:明文返回 access_token,不调 mask_token,不做 is_staff 二次校验 +# ====================================================================== + +# 客户端响应 data 子 schema:access_token 字段 description 显式标注「明文」 +_credential_slot_client_data_schema = openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + 'app_id': openapi.Schema( + type=openapi.TYPE_STRING, + description='第三方服务商分配的 APP ID(明文)', + ), + 'access_token': openapi.Schema( + type=openapi.TYPE_STRING, + description='明文 Access Token,供手机/设备端实际调用第三方服务(管理端同接口会脱敏返回末 4 位)', + ), + 'updated_at': openapi.Schema( + type=openapi.TYPE_STRING, + format='date-time', + description='最近一次更新时间(ISO 8601)', + ), + }, +) + + +class CredentialSlotClientView(APIView): + """通用凭据槽位客户端读取接口(user / admin token 鉴权,明文返回)。 + + GET: 返回明文 app_id + access_token,供手机/设备端实际调用第三方服务。 + + 与管理端 CredentialSlotAdminView 的关键差异(per CONTEXT.md D-Client-View): + - 不做 is_staff 二次校验:admin / user token 都允许(admin 用户是手机用户超集) + - 不脱敏:直接返回 serializer.data(明文返回) + - 仅 GET:客户端只读,不能写入 + """ + authentication_classes = [RedisTokenAuthentication] + permission_classes = [IsAuthenticated] + tags = ['通用凭据槽位(客户端)'] + + @swagger_auto_schema( + operation_description="读取通用凭据槽位(明文 access_token,供手机/设备端实际调用第三方服务)", + responses={ + 200: openapi.Response( + '读取成功', + get_standardized_response_schema(_credential_slot_client_data_schema), + ), + 401: openapi.Response('未提供有效 token', get_standardized_response_schema()), + }, + security=[{'Bearer': []}], + tags=['通用凭据槽位(客户端)'], + ) + def get(self, request): + instance = CredentialSlot.get_solo() + serializer = CredentialSlotSerializer(instance) + return success_response(data=serializer.data, message="读取成功")