【学习记录】从零搭建文件上传系统:FastAPI 后端 + Streamlit 前端
在 Web 应用中,让用户上传文件并安全存储到服务器是一个常见需求。本文将带你从零开始,使用 FastAPI (高性能异步 Python 后端)和 Streamlit(纯 Python 前端框架)构建一个完整的文件上传与存储系统。全程只需 Python 代码,无需 HTML/CSS/JavaScript,非常适合快速原型开发。
📌 技术栈简介
| 组件 | 作用 | 特点 |
|---|---|---|
| FastAPI | 后端 API 服务 | 高性能、自动生成文档、原生异步支持 |
| Streamlit | 前端交互界面 | 纯 Python、无需前端知识、快速迭代 |
| Uvicorn | ASGI 服务器 | 运行 FastAPI 应用 |
| Requests | HTTP 客户端 | 前端调用后端 API |
🗂️ 项目结构
file_upload_system/
├── backend.py # FastAPI 后端
├── frontend.py # Streamlit 前端
├── uploaded_files/ # 文件存储目录(自动创建)
└── requirements.txt # 依赖清单
🔧 环境准备
确保 Python ≥ 3.8,安装依赖:
bash
pip install fastapi uvicorn streamlit requests python-multipart
或创建 requirements.txt 并一键安装:
txt
fastapi
uvicorn
streamlit
requests
python-multipart
bash
pip install -r requirements.txt
🖥️ 后端实现(FastAPI)
创建 backend.py,实现:
- 接收前端上传的文件
- 检查文件大小(限制 50 MB)
- 为文件生成唯一名称(使用 UUID,避免重名覆盖)
- 保存到本地目录
./uploaded_files - 返回上传成功的元信息
python
import os
import shutil
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import uuid
app = FastAPI(title="文档上传服务")
# 跨域配置(Streamlit 默认 8501 端口,FastAPI 8000 端口)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 开发环境允许所有源,生产环境需限制
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
UPLOAD_DIR = "./uploaded_files"
os.makedirs(UPLOAD_DIR, exist_ok=True)
@app.get("/")
async def root():
return {"message": "文档上传服务运行中"}
@app.post("/upload")
async def upload_document(file: UploadFile = File(...)):
MAX_SIZE = 50 * 1024 * 1024 # 50 MB
# 检查文件大小
file.file.seek(0, 2)
size = file.file.tell()
if size > MAX_SIZE:
raise HTTPException(status_code=413, detail="文件大小超过 50 MB 限制")
file.file.seek(0)
# 生成唯一文件名,保留原始扩展名
original_filename = file.filename
ext = os.path.splitext(original_filename)[1]
unique_filename = f"{uuid.uuid4().hex}{ext}"
save_path = os.path.join(UPLOAD_DIR, unique_filename)
# 保存文件
try:
with open(save_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
except Exception as e:
raise HTTPException(status_code=500, detail=f"文件保存失败: {str(e)}")
return JSONResponse(content={
"status": "success",
"message": "文件上传成功",
"original_filename": original_filename,
"saved_filename": unique_filename,
"saved_path": save_path,
"file_size": size
})
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
关键点说明:
CORSMiddleware解决跨域问题,让 Streamlit 前端能调用不同端口的后端。UploadFile类型自动处理 multipart 表单数据。uuid.uuid4().hex生成 32 位随机字符串,彻底避免文件名冲突。shutil.copyfileobj高效地将上传流写入磁盘,内存友好。- 文件大小检查通过
seek(0,2)移动到末尾获取总大小,然后重置指针。
🎨 前端实现(Streamlit)
创建 frontend.py,提供:
- 文件选择器
- 文件信息展示
- 上传按钮
- 显示后端返回的结果
python
import streamlit as st
import requests
API_URL = "http://localhost:8000/upload"
st.set_page_config(page_title="文档上传工具", page_icon="📄")
st.title("📄 文档上传到后端服务器")
uploaded_file = st.file_uploader("选择文件", type=None) # 允许所有类型
if uploaded_file is not None:
st.write(f"**文件名:** {uploaded_file.name}")
st.write(f"**文件大小:** {uploaded_file.size} 字节")
st.write(f"**文件类型:** {uploaded_file.type}")
if st.button("上传到服务器"):
files = {"file": (uploaded_file.name, uploaded_file.getvalue(), uploaded_file.type)}
try:
with st.spinner("上传中..."):
response = requests.post(API_URL, files=files)
if response.status_code == 200:
data = response.json()
st.success("✅ 上传成功!")
st.json(data)
else:
st.error(f"❌ 上传失败: {response.status_code} - {response.text}")
except Exception as e:
st.error(f"❌ 连接后端失败: {e}")
关键点说明:
st.file_uploader返回UploadedFile对象,可直接获取文件名、大小、二进制内容。requests.post的files参数构造符合multipart/form-data格式。st.spinner在等待响应时提供加载动画。st.json漂亮地显示后端返回的 JSON。
🚀 运行与测试
启动后端
在终端一执行:
bash
python backend.py
输出:
INFO: Uvicorn running on http://0.0.0.0:8000
启动前端
在终端二执行:
bash
streamlit run frontend.py
浏览器将自动打开 http://localhost:8501。
测试流程
- 点击 Browse files 选择任意文件(例如
report.pdf)。 - 查看显示的文件名、大小和类型。
- 点击 上传到服务器。
- 成功后会看到绿色提示和后端返回的 JSON,例如:
json
{
"status": "success",
"message": "文件上传成功",
"original_filename": "report.pdf",
"saved_filename": "a1b2c3d4e5f6789...pdf",
"saved_path": "./uploaded_files/a1b2c3d4e5f6789...pdf",
"file_size": 123456
}
- 检查服务器上的
uploaded_files目录,文件已保存。
🧪 验证上传是否成功
在服务器(或本地)执行:
bash
ls -lh uploaded_files/
会看到类似 a1b2c3d4e5f6789...pdf 的文件,大小与上传一致。

🎯 总结与扩展思考
通过本文,你学会了:
- ✅ 使用 FastAPI 编写文件上传 API(含大小限制、唯一命名)。
- ✅ 使用 Streamlit 构建简单的上传界面(纯 Python,无需前端知识)。
- ✅ 处理前后端跨域问题。
- ✅ 结合
shutil.copyfileobj高效保存文件。
可以在此基础上扩展的功能:
- 支持多文件并发上传(Streamlit 的
file_uploader可设置accept_multiple_files=True)。 - 添加上传进度条(可配合
requests的流式上传或使用tqdm)。 - 将文件存储到云存储(如阿里云 OSS、AWS S3、MinIO)。
- 增加文件类型白名单(例如只允许
.pdf,.jpg,.png)。 - 对上传的文件进行病毒扫描或内容校验。
- 将文件元信息存入数据库(如 SQLite、PostgreSQL)。
这套基础架构可以快速集成到更大的 RAG 系统、文档管理平台或数据处理流水线中。希望本文对你有帮助,欢迎在实际项目中尝试并改进!