Django 图片和视频上传应由 HTTP server 还是 media server 处理?
建议由 Django 管理产品数据、上传授权和媒体关联,文件交给对象存储或媒体服务接收。视频转码、缩略图生成等耗时操作放到后台任务中。产品先保存为草稿,等媒体就绪后再发布。
小文件、低并发场景下,通过 Django 上传也可行。是否采用直传,取决于文件大小、上传并发、带宽和部署资源。
HTTP server 和 media server 的区别
“不能通过 HTTP server 上传媒体文件”这个前提并不准确。对象存储和第三方媒体服务通常也通过 HTTP 接收上传。这里需要区分的是文件内容是否经过 Django 应用进程。
经过 Django:
浏览器 → Django → 对象存储
直接上传:
浏览器 → 对象存储或媒体服务
浏览器 → Django:申请上传、查询状态、确认关联
采用直传时,Django 只处理少量元数据,视频内容直接传给存储服务。后端仍可协调业务流程,无须转发整个视频。
先建草稿,再创建上传会话
产品管理页面可以采用以下流程:
- 创建产品草稿,返回
product_id。此时允许video为空,草稿不对外展示。 - 为该产品创建上传会话。Django 检查操作权限,生成唯一的对象 key,并保存上传记录。
- 返回短期有效的上传凭证,浏览器使用凭证直接上传文件。
- 服务端确认文件存在,完成必要校验和媒体处理,将上传记录标记为可关联。
- 在数据库事务中写入媒体引用。发布产品时,再检查必需的视频和图片是否已经就绪。
接口可以设计为:
POST /products/ 创建草稿
POST /products/{id}/uploads/ 创建上传会话,返回上传凭证
POST /uploads/{id}/complete/ 通知后端检查上传结果
GET /uploads/{id}/ 查询上传及处理状态
POST /products/{id}/publish/ 校验并发布产品
这些是业务接口示例,并非 Django 自带 API。创建草稿和上传会话也可以合并为一次请求。
上传记录至少应保存:
- 所属用户、目标产品和媒体用途,例如视频或产品图片。
- 服务端生成的对象 key,或媒体服务返回的资源 ID。
- 上传状态、会话有效期,以及校验或处理结果。
即使最后的关联操作失败,也能根据这些记录找到文件所属的上传会话和目标产品,继续处理。
如果业务要求先上传再创建产品,可以先让上传会话归属于用户。创建产品时提交 upload_id,在同一个数据库事务内完成产品创建和媒体关联。未使用的上传记录到期后清理。
通过重试和清理恢复失败操作
数据库与外部存储通常没有共同的事务。transaction.atomic() 可以保护数据库修改,但无法撤销已经完成的外部上传。因此,需要保存中间状态,并为每一步定义重试行为。
| 失败位置 | 处理方式 |
|---|---|
| 草稿创建成功,但响应丢失 | 使用幂等键重试,返回同一个草稿 |
| 文件上传失败或中断 | 按存储服务能力重试,必要时使用分片上传 |
| 文件已上传,但完成通知丢失 | 重试通知,或由存储事件、定时任务补查 |
| 关联数据库失败 | 保留上传记录和文件,重试关联 |
| 数据库已提交,但响应丢失 | 再次确认时返回已有结果 |
| 用户放弃操作 | 会话过期后清理未关联文件和废弃草稿 |
幂等键需要持久化记录,并设置唯一约束,例如让“用户 + 请求键”的组合唯一。仅由前端生成随机字符串,无法防止重复创建。
请求超时后不要立刻删除文件。超时只表示客户端没有拿到结果,数据库可能已经提交成功。
清理任务也必须与关联操作协调:先在数据库事务内把可清理记录的状态改为 deleting,再删除外部文件。关联接口应拒绝关联处于该状态的记录。外部文件删除失败后重试,避免清理任务与关联请求同时操作同一个资源。
Django 示例:在短事务中完成关联
示例假设:
Product有owner、status和可为空的video。Upload有owner、product、唯一的object_key、state和expires_at。verified表示服务端已完成校验及所需处理,文件已放入客户端不能覆盖的正式位置。expires_at是上传会话允许完成关联的截止时间。- 数据库支持
select_for_update()行锁。
代码只展示关联服务。模型定义、上传授权,以及异常到 HTTP 响应的转换,需要按项目实现:
from django.db import transaction
from django.utils import timezone
from .models import Product, Upload
@transaction.atomic
def attach_video(*, product_id, upload_id, user_id):
product = Product.objects.select_for_update().get(
pk=product_id,
owner_id=user_id,
)
upload = Upload.objects.select_for_update().get(
pk=upload_id,
product_id=product.pk,
owner_id=user_id,
)
# 已成功处理的重复请求直接返回。
if upload.state == "attached":
if product.video.name != upload.object_key:
raise ValueError("该上传记录已被使用,当前视频已变化")
return product
if product.status != "draft":
raise ValueError("当前产品状态不允许关联视频")
if upload.state != "verified":
raise ValueError("文件尚未通过校验或处理")
if upload.expires_at <= timezone.now():
raise ValueError("上传会话已过期")
if product.video:
raise ValueError("产品已有视频,请使用替换流程")
product.video = upload.object_key
product.save(update_fields=["video"])
upload.state = "attached"
upload.save(update_fields=["state"])
return product
事务内只执行数据库操作。远程文件查询、下载校验、转码和复制都应放在事务之外。其他会修改相同记录的流程,包括清理任务,也应采用一致的加锁顺序。
FileField 在数据库中保存文件名或存储路径,文件内容由配置的 storage 管理。文件直传到同一个 storage 后,可以将对应的 key 写入字段;上面的赋值不会重新上传文件,upload_to 也不会在这种赋值方式下自动添加路径。Django FileField 文档
如果第三方媒体服务使用独立资源 ID,通常更适合单独建立 MediaAsset 模型,保存资源 ID、处理状态等信息。临时签名 URL 会过期,不适合作为媒体的永久标识。
产品图片可以复用这套流程:关联时创建 ProductImage 记录,并给对应的上传记录增加唯一约束,防止重试产生重复图片。
前端上传文件,后端处理业务恢复
用户选择的文件在浏览器中,因此采用直传后,前端仍需申请凭证并执行上传。这部分可以封装为上传组件或 SDK,供页面调用“选择文件、查看进度、获取状态”等操作。
后端负责判断文件是否可用、是否允许关联,以及失败后如何继续。不能仅凭前端传来的 URL 或“上传成功”就发布产品。
完成通知接口可以触发后台校验并返回 202 Accepted,前端通过查询状态了解进度。如果有存储事件或媒体服务 webhook,后端也可以自动推进流程。回调需要验签、核对资源归属,并允许重复处理。
数据库提交后如果需要投递后台任务,可以使用 transaction.on_commit(),避免任务提前读取尚未提交的数据。它不保证消息一定投递成功;需要可靠投递时,可以在同一事务内保存待处理任务记录,再由 worker 扫描执行。Django 事务文档
容量与直传实现中的注意事项
Django 不再转发视频内容后,主要处理授权、状态查询和数据库更新。媒体下载可以交给存储服务或 CDN,转码由独立 worker 处理。实际容量仍需结合部署和负载评估。
实现直传时,还需处理以下细节:
- 上传凭证只允许写入指定对象,并设置合理的有效期。按服务能力限制文件大小和类型。浏览器直传通常还需要配置 CORS。
- 对象 key 由服务端生成,同时验证上传记录所属用户。关联接口接收
upload_id,避免直接接受客户端指定的任意 URL。 - 确认文件存在、大小符合预期,只完成了部分校验。实际格式、安全性或可播放性的验证应交给后台处理。
- 不要假设签名 URL 只能使用一次。例如,S3 向同一个 key 再次上传会覆盖已有对象。可以先上传到临时位置,校验后生成客户端无权覆盖的正式对象;支持版本绑定的服务也可以记录并使用具体版本。S3 预签名上传文档
- 替换媒体时,先确认新资源可用并提交新关联,再异步清理旧资源。Django 删除模型记录时不会自动删除关联文件,需要自行安排清理。Django 文件删除说明
如果目前只需上传少量小图片,通过 Django 接收会更省事。可以先限制上传大小、控制并发并观察资源使用,等视频较大或上传压力明显时再采用直传。两种上传方式都需要可重试的关联流程,以及未使用文件的清理机制。
备注:内容仅供参考。
