Skip to content

第六站练习答案 ​

返回纵览 | 专题一 | 专题二

python
from django.core.exceptions import ValidationError

try:
    ticket.full_clean()
except ValidationError as exc:
    return JsonResponse(
        {"error": {"code": "validation_error", "fields": exc.message_dict}},
        status=400,
    )
python
from django.test import TestCase
from django.urls import reverse


class TicketApiTests(TestCase):
    def test_response_uses_public_field_whitelist(self):
        self.client.force_login(self.user)
        response = self.client.get(reverse("tickets:api-collection"))
        item = response.json()["results"][0]
        self.assertEqual(
            set(item), {"id", "title", "status", "priority", "updated_at"}
        )

运行 python manage.py test tickets.tests.TicketApiTests。精确字段集合会在意外暴露 assignee 邮箱、内部标记或其他新增字段时失败。

专题二核对 ​

python
from django.core import signing

cursor = signing.dumps({"v": 1, "created": row.created_at.isoformat(), "id": row.pk}, salt="ticket-page")
payload = signing.loads(cursor, salt="ticket-page", max_age=3600)

篡改、过期或字段不完整统一映射为稳定 400。409 响应可用 {"error":{"code":"version_conflict","retryable":true}};v2 增字段时保留单独的 v1 序列化器和字段集合测试,不让查询模型直接决定公开合同。

深水边界 ​

展示性示例直接调用 full_clean(),但高并发下仍以数据库约束和事务结果为准。校验通过到保存之间存在竞争窗口。

开放练习(不提供答案) ​

设计 Idempotency-Key 的存储、请求摘要、响应重放和过期策略;说明两个并发相同键请求如何只创建一个工单。

继续第七站

加码题解:API 契约、分页与版本 ​

python
def error(code, message, details=None):
    return {'error': {
        'code': code,
        'message': message,
        'details': details or {},
    }}

关键推理 ​

错误结构稳定,客户端可以按 code 分支,而不是解析人类文案。

可替代方案 ​

对长列表使用 cursor/seek 分页,对一次性导出使用异步任务并返回 job id。

典型错误 ​

offset 深页没有稳定排序,或把数据库异常堆栈直接返回客户端。

复盘时请把状态码/事务/权限/资源清理中的至少一项写成自动化测试,而不是只截图成功页面。

用心记录每一步成长