Skip to content

14 · 文件上传与对象存储 ​

用户传头像、传文档、传大文件——后端要接得住、存得对、出得去。本篇讲清上传链路、大文件分片、对象存储选型。

速览卡 ​

项内容
一句话定位文件从客户端到存储再到下载的完整链路,以及"别把文件放应用服务器"
核心内容上传链路设计、文件校验、大文件分片、对象存储(S3 兼容)、下载与防盗链
读完能回答文件应该存哪?大文件怎么传不卡?怎么校验文件是真的、不超限、不带毒?
相关板块C09 · 服务端语言上手 | C16 · 服务端安全纵深 | C12 · 错误处理日志配置

一、文件该存哪 ​

方案适用问题
应用服务器本地盘临时、单机多实例不一致、重启丢、扩容难
对象存储(S3/OSS/COS)生产推荐弹性、便宜、自带 CDN
数据库 BLOB极少量小文件撑爆 DB,别用

核心原则:文件放对象存储,数据库只存元数据(URL、大小、类型、上传时间)。应用服务器是无状态服务,文件不应粘在上面。


二、上传链路 ​

2.1 两种方式 ​

方式流程适用
服务端中转客户端 → 后端 → 对象存储需要校验/限流/权限控制
直传(预签名 URL)客户端拿签名 URL 直传对象存储大文件、省后端带宽

大文件推荐预签名直传:后端只签发带权限的 URL,文件不经过应用服务器,省流量、省压力。

python
from fastapi import UploadFile
import boto3

s3 = boto3.client("s3", endpoint_url=os.environ["S3_ENDPOINT"])


@app.post("/upload")
async def upload(file: UploadFile):
    data = await file.read()
    key = f"uploads/{uuid4().hex}-{file.filename}"
    s3.put_object(Bucket=os.environ["BUCKET"], Key=key, Body=data)
    return {"url": f"/files/{key}"}

2.2 必须做的校验 ​

python
MAX_SIZE = 10 * 1024 * 1024


@app.post("/upload")
async def upload(file: UploadFile):
    if file.size > MAX_SIZE:
        raise HTTPException(413, "文件过大")
    if file.content_type not in {"image/png", "image/jpeg"}:
        raise HTTPException(415, "不支持的类型")
校验项说明
大小限制超限 413
类型校验按 content_type 和实际内容魔数双重校验
文件名丢弃客户端文件名,自己生成(防路径穿越)
病毒/危险内容必要时接扫描服务

⚠️ 不要信任 file.filename:客户端可传 ../../etc/passwd。服务端一律用自己的随机 key。


三、大文件分片上传 ​

超过几十 MB 时,单次上传容易超时/中断。方案:

组件说明
前端分片切成 e.g. 5MB 一片,逐片上传
后端/存储合并S3 multipart、OSS 分片
断点续传记录已传分片,失败重传
秒传先算文件哈希,命中即跳过

简化套路:客户端上传每片(带 upload_id + 片号),全部传完调"合并"接口;存进度到 Redis/DB。


四、下载与访问控制 ​

python
@app.get("/files/{key}")
def download(key: str):
    url = s3.generate_presigned_url(
        "get_object",
        Params={"Bucket": BUCKET, "Key": key},
        ExpiresIn=300,
    )
    return {"url": url}
场景方案
公开文件(头像)直接走 CDN 公开 URL
私有文件预签名 URL(带过期),或经后端鉴权转发
防盗链对象存储/CDN 配 Referer、签名

自检清单 ​

  • [ ] 能说清"文件放对象存储、DB 只存元数据"的原则
  • [ ] 会做大小、类型(含魔数)、文件名校验
  • [ ] 知道大文件用分片/直传,而不是单次上传
  • [ ] 会用预签名 URL 控制私有文件访问

常见误区 ​

  • 误区:文件存数据库最稳。 撑爆 DB、备份变大,几乎永远是错方案。
  • 误区:信任客户端文件名。 路径穿越、覆盖攻击都从这里来,自己生成 key。
  • 误区:只校验后缀。 改个扩展名就绕过,要按内容魔数校验。
  • 误区:大文件单次传。 超时、中断、无法续传,要分片。

动手练习 ​

练习 A:头像上传 ​

用本地 MinIO(S3 兼容)实现头像上传 + 校验 + 预签名下载。

练习 B:文件名校验 ​

故意传一个 ../../evil.png,确认服务端生成的是自己的随机 key。

练习 C:分片思路 ​

设计(不实现)一个大文件分片上传的状态机:列出前端需要传哪些参数、服务端怎么记录进度、失败怎么续传。


相关文档 ​

本区 ​

其他域 ​