FastAPI 静态文件
2026/5/7 23:03:42 网站建设 项目流程

FastAPI 静态文件学习笔记

一、基本用法 —StaticFiles

1. 挂载静态文件目录

fromfastapiimportFastAPIfromfastapi.staticfilesimportStaticFiles app=FastAPI()# 将 ./static 目录挂载到 /static 路径app.mount("/static",StaticFiles(directory="static"),name="static")

目录结构

project/ ├── main.py └── static/ ├── css/ │ └── style.css ├── js/ │ └── app.js └── images/ └── logo.png

访问方式

http://localhost:8000/static/css/style.css http://localhost:8000/static/js/app.js http://localhost:8000/static/images/logo.png

2. 参数说明

参数类型默认值说明
directorystr/Path必填静态文件目录路径
htmlboolFalse是否启用 HTML 模式
check_dirboolTrue启动时检查目录是否存在

二、HTML 模式

1. 启用 HTML 模式

app.mount("/",StaticFiles(directory="static",html=True),name="static")

HTML 模式行为

请求路径文件存在情况响应
/index.html存在返回index.html
/aboutabout.html存在返回about.html
/about/about/index.html存在返回about/index.html
/style.cssstyle.css存在返回style.css
/missing无匹配文件404

2. 典型用途 — 托管 SPA 前端

# Vue / React 构建产物app.mount("/",StaticFiles(directory="dist",html=True),name="spa")
dist/ ├── index.html ├── assets/ │ ├── index.abc123.js │ └── index.def456.css └── favicon.ico

注意:SPA 的前端路由(如/about/user/1)需要服务端统一返回index.htmlhtml=True只处理.html文件匹配,不支持 SPA fallback。完整 SPA 托管方案见第五节。


三、挂载位置与路由优先级

1.app.mount的特性

mount创建的是一个子应用,会匹配该路径下的所有请求,不再经过主应用的路由匹配:

app=FastAPI()@app.get("/static/data")# ← 永远不会被访问到asyncdefget_data():return{"data":"hello"}app.mount("/static",StaticFiles(directory="static"),name="static")# /static/* 的所有请求都被 StaticFiles 拦截

2. 正确的挂载顺序

app=FastAPI()# ① 先注册 API 路由@app.get("/api/data")asyncdefget_data():return{"data":"hello"}# ② 最后挂载静态文件(避免覆盖 API 路由)app.mount("/static",StaticFiles(directory="static"),name="static")

3. 挂载多个静态目录

app.mount("/static",StaticFiles(directory="static"),name="static")app.mount("/uploads",StaticFiles(directory="uploads"),name="uploads")app.mount("/assets",StaticFiles(directory="assets"),name="assets")

四、在 Jinja2 模板中引用静态文件

fromfastapiimportFastAPI,Requestfromfastapi.staticfilesimportStaticFilesfromfastapi.templatingimportJinja2Templates app=FastAPI()app.mount("/static",StaticFiles(directory="static"),name="static")templates=Jinja2Templates(directory="templates")@app.get("/page")asyncdefpage(request:Request):returntemplates.TemplateResponse("page.html",{"request":request})

模板中引用

<!-- 使用 url_for 动态生成路径 --><linkrel="stylesheet"href="{{ url_for('static', path='/css/style.css') }}"><scriptsrc="{{ url_for('static', path='/js/app.js') }}"></script><imgsrc="{{ url_for('static', path='/images/logo.png') }}"alt="logo">

url_for的第一个参数是mount时的name,第二个参数path是文件在静态目录中的相对路径。


五、SPA 前端托管完整方案

Vue / React 等单页应用需要所有未匹配路径返回index.html

方案一:中间件 fallback

fromfastapiimportFastAPI,Requestfromfastapi.staticfilesimportStaticFilesfromfastapi.responsesimportFileResponse app=FastAPI()# API 路由@app.get("/api/data")asyncdefget_data():return{"data":"hello"}# 静态资源(js/css/images)app.mount("/assets",StaticFiles(directory="dist/assets"),name="assets")# SPA fallback:未匹配的路由返回 index.html@app.get("/{full_path:path}")asyncdefserve_spa(request:Request,full_path:str):returnFileResponse("dist/index.html")

方案二:自定义静态文件中间件

importosfromfastapiimportFastAPI,Requestfromfastapi.responsesimportFileResponse,Responsefromstarlette.middleware.baseimportBaseHTTPMiddleware app=FastAPI()DIST_DIR="dist"classSPAMiddleware(BaseHTTPMiddleware):asyncdefdispatch(self,request:Request,call_next):response=awaitcall_next(request)# 如果路由返回 404 且是 HTML 请求,返回 index.htmlifresponse.status_code==404:accept=request.headers.get("accept","")if"text/html"inaccept:index_path=os.path.join(DIST_DIR,"index.html")ifos.path.exists(index_path):returnFileResponse(index_path)returnresponse app.add_middleware(SPAMiddleware)app.mount("/assets",StaticFiles(directory=f"{DIST_DIR}/assets"),name="assets")

方案三:纯静态挂载(最简单)

app=FastAPI()# API 路由@app.get("/api/data")asyncdefget_data():return{"data":"hello"}# 静态文件(html=True 自动处理 index.html)app.mount("/",StaticFiles(directory="dist",html=True),name="spa")

局限html=True只在请求路径对应.html文件存在时返回,不支持 SPA 的动态路由(如/user/123)。


六、文件上传与静态文件服务结合

importosimportuuidfromfastapiimportFastAPI,UploadFile,Filefromfastapi.staticfilesimportStaticFiles app=FastAPI()UPLOAD_DIR="uploads"os.makedirs(UPLOAD_DIR,exist_ok=True)@app.post("/upload")asyncdefupload_file(file:UploadFile=File(...)):# 生成唯一文件名ext=os.path.splitext(file.filename)[1]filename=f"{uuid.uuid4().hex}{ext}"filepath=os.path.join(UPLOAD_DIR,filename)# 保存文件withopen(filepath,"wb")asf:content=awaitfile.read()f.write(content)return{"filename":filename,"url":f"/uploads/{filename}",}# 挂载上传目录app.mount("/uploads",StaticFiles(directory=UPLOAD_DIR),name="uploads")

七、生产环境注意事项

1. Nginx 反向代理直接服务静态文件

生产环境建议由 Nginx 直接处理静态文件,性能更好:

server { listen 80; server_name example.com; # Nginx 直接服务静态文件 location /static/ { alias /var/www/static/; expires 30d; add_header Cache-Control "public, immutable"; } # API 请求转发给 FastAPI location /api/ { proxy_pass http://127.0.0.1:8000; } }

2. 缓存控制

fromstarlette.responsesimportResponsefromfastapi.staticfilesimportStaticFilesclassCachedStaticFiles(StaticFiles):asyncdeflookup_path(self,path:str):full_path,stat_result=awaitsuper().lookup_path(path)ifstat_result:# 根据文件扩展名设置缓存ifpath.endswith((".js",".css",".woff2",".png",".jpg")):self.headers["Cache-Control"]="public, max-age=31536000, immutable"else:self.headers["Cache-Control"]="public, max-age=3600"returnfull_path,stat_result app.mount("/static",CachedStaticFiles(directory="static"),name="static")

3. 目录安全

风险防护措施
目录遍历攻击StaticFiles默认禁止..路径
敏感文件泄露不要将配置文件、.env放在静态目录
上传恶意文件限制文件类型、重命名文件、隔离上传目录
大文件消耗带宽Nginx 层限制请求体大小

八、StaticFilesvs 其他方案

方案适用场景性能灵活性
StaticFiles开发环境、小型项目
Nginx 直接服务生产环境
CDN全球分发、高流量最高最高
云存储(OSS/S3)用户上传文件、海量存储

推荐

开发环境 → StaticFiles(零配置) 生产环境 → Nginx 直接服务静态文件 + CDN 加速 用户上传 → 云存储(OSS / S3)+ CDN

九、完整示例

fromfastapiimportFastAPI,Request,UploadFile,Filefromfastapi.staticfilesimportStaticFilesfromfastapi.responsesimportFileResponseimportos,uuid app=FastAPI()# ---- 目录准备 ----os.makedirs("static/css",exist_ok=True)os.makedirs("static/js",exist_ok=True)os.makedirs("uploads",exist_ok=True)# ---- API 路由 ----@app.get("/api/hello")asyncdefhello():return{"message":"Hello, FastAPI!"}@app.post("/api/upload")asyncdefupload(file:UploadFile=File(...)):ext=os.path.splitext(file.filename)[1]filename=f"{uuid.uuid4().hex}{ext}"withopen(f"uploads/{filename}","wb")asf:f.write(awaitfile.read())return{"url":f"/uploads/{filename}"}# ---- 静态文件挂载 ----app.mount("/static",StaticFiles(directory="static"),name="static")app.mount("/uploads",StaticFiles(directory="uploads"),name="uploads")# ---- SPA fallback(放最后)----@app.get("/{full_path:path}")asyncdefspa_fallback(request:Request,full_path:str):returnFileResponse("static/index.html")

十、注意事项

  1. mount必须放在路由注册之后:否则会拦截所有匹配路径的请求,导致 API 路由不可达。
  2. 目录必须存在:默认check_dir=True,目录不存在会抛异常。启动前确保目录已创建。
  3. name参数的作用:用于url_for反向生成 URL,建议始终设置。
  4. 开发 vs 生产StaticFiles适合开发,生产环境推荐 Nginx / CDN。
  5. 路径以/结尾:访问目录路径时,StaticFiles会自动查找index.html(需html=True)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询