Skip to content

第七站练习答案 ​

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

python
from django.test import RequestFactory, SimpleTestCase
from django.http import HttpResponse
from tickets.middleware import RequestIdMiddleware


class RequestIdMiddlewareTests(SimpleTestCase):
    def test_replaces_untrusted_request_id(self):
        request = RequestFactory().get("/", HTTP_X_REQUEST_ID="x" * 1000)
        response = RequestIdMiddleware(lambda req: HttpResponse())(request)
        self.assertLessEqual(len(response["X-Request-ID"]), 80)

    def test_preserves_valid_request_id(self):
        request = RequestFactory().get("/", HTTP_X_REQUEST_ID="trace-123")
        response = RequestIdMiddleware(lambda req: HttpResponse())(request)
        self.assertEqual(response["X-Request-ID"], "trace-123")

运行:

bash
python manage.py test tickets.tests.RequestIdMiddlewareTests

专题二核对 ​

python
from django.core import mail
from django.test import TestCase, override_settings

@override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend")
class ClaimEmailTests(TestCase):
    def test_committed_claim_sends_once(self):
        claim_ticket(self.ticket.pk, self.user)
        self.assertEqual(len(mail.outbox), 1)

核心认领测试应在断开 signal 后仍通过;signal 只做可丢弃的衍生行为。会话策略分别定义自然过期、显式登出、密码变更与多设备强制撤销的时限和审计证据。

恢复策略 ​

数据库事务与缓存/队列无法由 atomic() 一起提交。高价值变更把待发布事件写入同一数据库事务(outbox),独立发布器重试;消费者按事件 ID 幂等。短 TTL 只是兜底,不是完整一致性协议。

深水边界 ​

如果调用方 ID 会写入日志,要限制字符集与长度,防止日志注入和高基数。跨服务继续传播时还应区分可信 trace context 与任意业务 Header。

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

为用户登出/撤权设计缓存失效矩阵,列出按用户、角色、对象和租户缓存时必须清理的键。

继续第八站

加码题解:缓存、信号与邮件副作用 ​

python
from django.db import transaction

def save_with_event(obj, event):
    with transaction.atomic():
        obj.save()
        transaction.on_commit(lambda: publish(event))

关键推理 ​

事件只在数据库提交后发布,避免下游读取到会回滚的状态。

可替代方案 ​

高可靠场景使用 outbox 表,由可重试 worker 投递并记录状态。

典型错误 ​

在 model.save 或 signal 中直接发网络请求,导致事务变慢且失败语义混乱。

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

用心记录每一步成长