Python Web框架-FastAPI 上传文件 作者:马育民 • 2026-05-09 16:46 • 阅读:10004 # 介绍 FastAPI 文件上传核心是用`multipart/form-data`、依赖`python-multipart`,提供 **bytes(小文件)**与**UploadFile(大文件/异步)**两种接收方式,支持单文件、多文件、表单混合上传,并可做类型/大小/后缀校验。 ### bytes上传方式(不推荐) 会把 **整个文件一次性全部加载到内存**,上传 100MB、500MB、1GB 文件,直接塞进内存,容易引发服务器崩溃。**同步阻塞**,会废掉 FastAPI 异步性能。**不推荐** # UploadFile 基于`SpooledTemporaryFile`,文件小,暂存在内存。文件超过阈值,自动写入系统临时磁盘文件。是临时文件流式处理,可以分块一点点读取,不用一次性全加载,不占满内存、支持大文件、功能更强。 ### 属性与方法 | 属性 | 说明 | |---|---| | `filename` | 原始文件名(如`photo.jpg`) | | `content_type` | MIME类型(如`image/jpeg`) | | `file` | 底层`SpooledTemporaryFile`对象,一般用不上 | | 异步方法 | 说明 | |---|---| | `read(size)` | 读取文件内容(size可选) | | `write(data)` | 写入数据 | | `seek(offset)` | 移动文件指针 | | `close()` | 关闭文件 | # 环境准备 ```bash pip install python-multipart ``` **解释:** - `python-multipart`:解析 `multipart/form-data` 表单数据,文件上传必需。 # 例子 项目结构 ``` upload_project/ ├── main.py # 接口代码 ├── uploads/ # 小文件目录 └── large_files/ # 大文件目录 ``` ### 上传小文件 简单保存 ```python from fastapi import FastAPI, File, UploadFile app = FastAPI() @app.post("/upload/uploadfile") async def upload_uploadfile(file: UploadFile = File(...)): # 1. 元数据 print("文件名:", file.filename) print("MIME类型:", file.content_type) # 2. 异步保存 contents = await file.read() # 异步读 with open(file.filename, "wb") as f: f.write(contents) await file.close() # 关闭临时文件 return {"filename": file.filename, "content_type": file.content_type} ``` **解释:** - `File(...)` 中的 `...`:是 Python 内置常量,表示 **无默认值**、**必传参数**。等价于没有默认值,前端必须传文件,否则报错。 - `File(...)`:FastAPI 专用函数,表示这个参数来自 `form-data` 文件上传,不是普通参数,**标记这个字段是上传文件** ### 客户端测试 ##### 1. curl ```bash # 单文件 curl -X POST "http://localhost:8000/upload/uploadfile" -F "file=@/path/photo.jpg" # 多文件 curl -X POST "http://localhost:8000/upload/multi" -F "files=@/path/1.jpg" -F "files=@/path/2.png" ``` ##### 2. Python requests ```python import requests url = "http://localhost:8000/upload/uploadfile" files = {"file": open("photo.jpg", "rb")} res = requests.post(url, files=files) print(res.json()) ``` ### 上传大文件(推荐) 不一次性加载整个文件到内存,适合超大文件 ``` @app.post("/stream-save") async def stream_save(file: UploadFile = File(...)): with open(file.filename, "wb") as f: # 每次读1MB,循环写入 while chunk := await file.read(1024 * 1024): f.write(chunk) await file.close() return {"msg": "大文件流式保存完成"} ``` ### 多文件上传 用`List[UploadFile]`接收多个文件。 ```python from typing import List @app.post("/upload/multi") async def upload_multi(files: List[UploadFile] = File(...)): saved = [] for file in files: path = f"uploads/{file.filename}" with open(path, "wb") as f: # 每次读1MB,循环写入 while chunk := await file.read(1024 * 1024): f.write(chunk) await file.close() saved.append({"filename": file.filename, "size": await file.read()}) return {"saved_files": saved} ``` --- ### 文件+表单混合上传 同时接收文件与普通表单字段。 ```python from fastapi import Form @app.post("/upload/form") async def upload_form( file: UploadFile = File(...), username: str = Form(...), desc: str = Form(None) ): return { "username": username, "desc": desc, "file": file.filename } ``` # 高级校验 ### 校验文件大小 ``` from typing import Annotated from fastapi import FastAPI, UploadFile, File app = FastAPI() # 企业标准写法:接口层精准限制 5MB @app.post("/upload") async def upload( file: Annotated[UploadFile, File(max_size=5 * 1024 * 1024)] ): # 保存文件(流式,不占内存) with open(file.filename, "wb") as f: f.write(await file.read()) return {"msg": "上传成功"} ``` ### 类型/后缀校验 ```python ALLOWED_EXT = {"jpg", "jpeg", "png"} ALLOWED_MIME = {"image/jpeg", "image/png"} @app.post("/upload/type") async def upload_type(file: UploadFile = File(...)): # 校验后缀 ext = file.filename.split(".")[-1].lower() if ext not in ALLOWED_EXT: raise HTTPException(status_code=400, detail="仅支持jpg/png") # 校验MIME if file.content_type not in ALLOWED_MIME: raise HTTPException(status_code=400, detail="文件类型错误") return {"msg": "类型合法"} ``` # 常见问题与最佳实践 1. **422错误**:未安装`python-multipart`或客户端未用`multipart/form-data`。 2. **大文件内存溢出**:优先用`UploadFile`+流式读写,避免`bytes`。 3. **安全风险**: - 校验文件名(防止路径遍历,如`../etc/passwd`)。 - 限制文件大小、类型、后缀。 - 保存文件前生成随机文件名(如`uuid4()`)。 原文出处:http://www.malaoshi.top/show_1GW3HRKjwGmB.html