Python Web框架-FastAPI 显示不在静态目录的图片

介绍

在企业后台、文件管理系统、用户素材平台等真实业务场景中,图片资源几乎都不能直接公开访问:

  1. 用户私有图片、工单截图、证件照片,必须登录鉴权后才能预览;
  2. 文件记录存储在数据库,通过 id 查询真实磁盘路径,无法直接挂载静态目录;
  3. 禁止通过静态地址裸访问,防止资源泄露、越权查看他人文件;
  4. 原图体积不一,既要保证浏览器流畅完整展示,又不能一次性加载全图占用服务内存;
  5. 兼容中文文件名、跨域前端读取文件名、浏览器缓存加速等业务需求。

FileResponse 优势

  1. 天然适配鉴权场景:接口前置登录、权限校验,拦截未授权访问,杜绝图片资源泄露;
  2. 内置大缓冲区,不会出现图片分段从上到下逐步加载的现象,本机/线上加载流畅;
  3. 自动计算 Content-Length 响应头,浏览器渲染策略更友好;
  4. 原生支持 HTTP Range 断点续传,大图片拖拽、中断重连体验更好;
  5. 底层自动隔离同步文件IO,不会阻塞其他接口请求;
  6. 代码简洁,无需手动编写文件读取迭代器,减少线上IO异常bug;
  7. 兼容中文文件名、跨域Header、浏览器缓存等企业常用需求。

实现

FileResponse 属于 FastAPI/Starlette 内置响应对象,无需额外安装核心库;代码中 os、urllib.parse 均为 Python 标准库,开箱即用。

from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession
from urllib.parse import quote
import os

# 项目内部模块,按需替换为自身业务代码
from db_connect import get_db
from file_service import get_image_by_id
from auth_depend import get_current_user  # 登录鉴权依赖

router = APIRouter(prefix="/file", tags=["文件资源"])

@router.get("/{file_id}/image", summary="鉴权后在线预览图片")
async def preview_image(
    file_id: str,
    db: AsyncSession = Depends(get_db),
    # 企业必备:登录校验,未登录直接401拦截
    current_user = Depends(get_current_user)
):
    # 1. 根据文件ID查询数据库图片记录
    image_info = await get_image_by_id(db, file_id)
    if not image_info:
        raise HTTPException(status_code=404, detail="图片不存在或该文件非图片类型")

    # 2. 企业权限校验:只能查看自己上传的图片,防止越权访问
    if image_info["upload_user_id"] != current_user.id:
        raise HTTPException(status_code=403, detail="无权限访问该图片资源")

    file_path = image_info["path"]
    content_type = image_info["content_type"]
    origin_filename = image_info["filename"]

    # 3. 兜底校验磁盘文件,避免数据库残留记录但文件已删除
    if not os.path.isfile(file_path):
        raise HTTPException(status_code=404, detail="图片文件已丢失")

    # 4. 兼容全浏览器中文文件名乱码问题
    url_encode_name = quote(origin_filename, encoding="utf-8")

    # 5. 返回图片预览响应
    return FileResponse(
        path=file_path,
        media_type=content_type,
        filename=origin_filename,
        # inline:浏览器直接展示图片;attachment:触发下载
        content_disposition_type="inline",
        headers={
            # 兼容老旧浏览器中文文件名
            "Content-Disposition": f"inline; filename*=UTF-8''{url_encode_name}",
            # 允许前端跨域读取文件名Header
            "Access-Control-Expose-Headers": "Content-Disposition",
            # 浏览器缓存24小时,重复访问大幅提速
            "Cache-Control": "public, max-age=86400"
        }
    )

path=file_path

作用:指定本地磁盘上图片的真实物理路径,告诉框架读取哪个文件。

  • 示例:/upload/2026/07/xxx.png
  • 要求:必须是服务器本地存在的文件,不能是OSS/网络流;
  • 内部逻辑:框架会基于这个路径读取二进制文件内容、自动获取文件大小。

media_type=content_type

作用:设置 HTTP 响应头 Content-Type,告诉浏览器当前资源是什么类型。

  • 常见图片值:
    • image/png、image/jpeg、image/gif、image/webp
  • 如果不填,框架会自动根据文件后缀猜测,但数据库里存好 content_type 主动传入更稳定,避免识别错误。
  • 浏览器靠这个字段判断:直接渲染图片,而不是当成二进制文件下载。

filename=origin_filename

作用:设置下载/预览时展示给用户的原始文件名。

  • 场景1:inline 预览图片时,浏览器标签、开发者工具会显示该文件名;
  • 场景2:如果改为 attachment 下载,弹出的下载框默认文件名就是这个;
  • 局限:老旧浏览器对中文文件名兼容性差,所以代码里额外手动拼接 filename*=UTF-8'' 做兼容兜底。

content_disposition_type=”inline”

两种可选值:

  1. inline:浏览器直接在页面展示图片(预览需求,当前业务使用)
  2. attachment:浏览器弹出下载窗口,把图片保存到本地

headers 自定义头部

额外补充业务需要的响应头:

  1. filename*=UTF-8''xxx:解决 IE、旧 Edge 中文文件名乱码;
  2. Access-Control-Expose-Headers:前端跨域时能读取到 Content-Disposition;
  3. Cache-Control:开启浏览器缓存,重复访问不用重新拉取图片。

精简总结

  • path:文件物理地址(读哪里)
  • media_type:文件MIME类型(告诉浏览器这是图片)
  • filename:对外展示/下载的原始文件名
  • content_disposition_type:预览还是下载
  • headers:自定义跨域、缓存、中文兼容头部

aiofiles库

场景1:不安装 aiofiles(本地开发、低并发内部系统)

无需执行任何安装命令,代码完全不用修改。

  1. 底层运行逻辑
    Starlette 自动使用 anyio.to_thread.run_sync,将同步文件读取操作丢到后台独立线程池执行,不会阻塞异步主事件循环;内置 256KB 大缓冲区读取文件,不会出现小块分片、图片分段刷新的问题。

  2. 适用场景
    本地调试、公司内部后台、同时在线访问图片人数少、并发量低的小型系统。

  3. 优缺点
    优点:零额外依赖、部署简单;
    缺点:高并发大量图片请求时,频繁线程切换会带来少量性能损耗。

场景2:安装 aiofiles(线上生产、高并发用户平台,推荐)

安装命令

pip install aiofiles
  1. 核心关键点
    安装后上面整套图片接口代码无需修改一行,框架内部自动检测并切换为纯异步文件读取逻辑。

  2. 底层运行逻辑
    框架使用 aiofiles 异步上下文打开文件,全程通过 await 分块读取,减少线程频繁切换开销;Linux 高版本内核可利用 io_uring 真正异步磁盘IO,并发吞吐能力更强。

  3. 适用场景
    对外公开平台、大量用户同时预览图片、项目同时存在异步文件上传业务。

  4. 补充说明
    Windows 系统无原生异步磁盘IO,aiofiles 底层依旧依托线程池,但封装更完善,并发稳定性优于原生 to_thread。

配套部署优化小技巧

  1. Uvicorn 启动参数调高并发,提升图片接口承载能力
    uvicorn main:app --limit-concurrency 200
    
  2. 超大原图(几十MB+)建议搭配对象存储+CDN,减轻服务器本地磁盘IO压力;
  3. 缓存头 Cache-Control 按需调整时长,静态素材可延长至7天;动态敏感证件类图片可改为 no-cache 禁止浏览器缓存。

总结

  1. 企业需要鉴权、按ID查询本地图片的场景,统一使用 FileResponse 作为标准方案;
  2. 本地开发、低并发项目可不用安装 aiofiles,代码直接运行;
  3. 线上对外服务、高并发场景建议安装 aiofiles,无代码侵入,提升并发性能;
  4. 自带登录权限校验逻辑,完美解决私有图片防越权、防裸泄露的业务安全需求。

原文出处:/show_1GW3e5qiebGo.html