从零搭建文件上传系统:FastAPI 后端 + Streamlit 前端

【学习记录】从零搭建文件上传系统: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.postfiles 参数构造符合 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

测试流程

  1. 点击 Browse files 选择任意文件(例如 report.pdf)。
  2. 查看显示的文件名、大小和类型。
  3. 点击 上传到服务器
  4. 成功后会看到绿色提示和后端返回的 JSON,例如:
json 复制代码
{
  "status": "success",
  "message": "文件上传成功",
  "original_filename": "report.pdf",
  "saved_filename": "a1b2c3d4e5f6789...pdf",
  "saved_path": "./uploaded_files/a1b2c3d4e5f6789...pdf",
  "file_size": 123456
}
  1. 检查服务器上的 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 系统、文档管理平台或数据处理流水线中。希望本文对你有帮助,欢迎在实际项目中尝试并改进!

相关推荐
易知微EasyV数据可视化12 小时前
定制化3D组件开发:打造差异化孪生体验
前端·人工智能·经验分享·数字孪生
前端炒粉13 小时前
Vue2 SSE 流式对话完整前端代码
前端·sse·流式输出
BIM云平台开发14 小时前
【App.vue里跟踪页面跳转和用户ID】
开发语言·前端·javascript
汪汪大队u14 小时前
Zabbix 6.0 部署踩坑记:从启动失败到 Web 成功访问
前端·zabbix
顺颂时绥_114 小时前
new Set 过滤数据实战
前端
赵大仁14 小时前
Next.js + Vercel AI SDK 实战:30 分钟搭出流式 Chat 页面
前端·ai·实战·react·next.js·vercel
不瘦80斤不改名14 小时前
全家桶、乐高积木与类型引擎:重新认识 Django、Flask 与 FastAPI
django·flask·fastapi
云空14 小时前
《Three.js 完整版3D魔方:带贴纸+中心固定+自动求解复原》
前端·javascript·3d·three.js
酸梅果茶14 小时前
【7】lightning_lm项目-LIO 前端 -IVox 局部地图
前端·slam
gis开发之家15 小时前
《Vue3 从入门到大神33篇》Vue3 源码详解(三):响应式核心思想——从 Object.defineProperty 到 Proxy
前端·javascript·vue.js·vue3·vue3源码