Django默认同步阻塞,PDF生成与邮件发送需用Celery+RabbitMQ拆分为链式异步任务;注意字体配置、二进制附件、路径传递及状态持久化。

为什么 Django 默认不能直接异步发 PDF + 邮件
Django 的请求响应周期是同步阻塞的,generate_pdf() 和 send_mail() 这类 I/O 密集操作一旦放在视图里执行,用户会卡住几秒到几十秒——尤其当 PDF 含图表、分页、水印、多语言字体时,CPU 和磁盘压力明显。RabbitMQ 本身不解决 Django 异步问题,它只是消息中转站;真正起作用的是用 celery(或 django-rq)把耗时任务从主线程剥离。
如何用 Celery + RabbitMQ 拆出 PDF 生成与邮件发送两个异步阶段
高维度报表往往需要先查库、聚合、渲染模板、再转 PDF,最后才附在邮件里发出去。硬塞进一个 task 容易超时或内存溢出,建议拆成链式任务(chord 或 chain):
-
generate_report_data.delay(user_id, report_type):只查数据、缓存 JSON 到 Redis,返回 task_id -
render_pdf_from_cache.delay(task_id):拉取缓存数据,调用weasyprint或pdfkit渲染,存入media/reports/并返回文件路径 -
send_report_email.delay(user_id, pdf_path):读取 PDF 文件、构造EmailMultiAlternatives、附件发送
注意:render_pdf_from_cache 必须显式设置 soft_time_limit=120,避免因字体加载失败或 SVG 渲染卡死;PDF 文件路径不能用 settings.MEDIA_ROOT 拼接绝对路径传给下游 task——Celery worker 可能运行在另一台机器,应传相对路径或 URL,并由 receiver 主动拼接。
RabbitMQ 连接配置和常见 ConnectionResetError 原因
Celery 默认使用 amqp://guest:guest@localhost//,但生产环境必须改掉默认密码和 host。关键配置项有三个:BROKER_URL(旧版)、CELERY_BROKER_URL(新版)、CELERY_RESULT_BACKEND(若用 RabbitMQ 做结果后端需额外开启 rpc)。
ConnectionResetError 多半不是网络问题,而是:
- RabbitMQ 服务没开
management插件,导致 Celery 启动时探活失败 - Docker 部署时,Celery worker 容器没和 rabbitmq 容器在同一个 network,
localhost解析成容器自身而非宿主 - Broker 连接数超限,默认最大连接数是 1024,高并发任务下发时容易触发
AMQPConnectionError
验证方式:在 worker 启动后执行 celery -A myproject inspect ping,看到 pong 才算通。
PDF 中文字体失效和邮件附件乱码的实际修复点
Linux 环境下 weasyprint 渲染中文 PDF 丢字,根本原因是缺字体文件且未在 CSS 中指定 fallback。不能只靠 @font-face 引入 ttf,要确认:
- worker 容器或服务器已安装
fonts-wqy-zenhei或fonts-noto-cjk包 - CSS 中写死
font-family: "WenQuanYi Zen Hei", "Noto Sans CJK SC", sans-serif; -
weasyprint初始化时加参数presentational_hints=True,否则部分内联样式不生效
邮件附件乱码通常是因为 PDF 文件以文本模式打开再 attach:with open(pdf_path, 'r') as f: ❌,必须用二进制模式:with open(pdf_path, 'rb') as f: ✅,且 email.attach('report.pdf', f.read(), 'application/pdf') 的 MIME 类型不能写成 text/plain。
复杂点在于 PDF 渲染和邮件发送之间没有原子性保障——PDF 成功生成但邮件发送失败,用户收不到通知。得加一层状态表(如 ReportTaskLog),记录每个环节 timestamp 和 error,供后台重试或人工干预。这个表本身不能只靠 Celery 的 retry 机制兜底,因为 PDF 文件可能已被清理。



















