# 文件上传完整流程 本章从配置到使用,完整说明文件上传功能的实现方式,包括后端接口、前端组件、安全机制和最佳实践。 ## 上传架构 ``` 前端 Vue3 后端 FastAPI 文件系统 ┌──────────┐ ┌──────────────┐ ┌──────────┐ │ Upload │ multipart │ 安全校验 │ 写入 │ uploads/ │ │ 组件 │ ─────────> │ ─ 扩展名白名单│ ───────> │ 2026/ │ │ │ │ ─ 魔术字节 │ │ 09/ │ │ │ │ ─ 大小限制 │ │ 05/ │ │ │ │ ─ 路径遍历 │ │ xxx.jpg│ └──────────┘ └──────────────┘ └──────────┘ │ ▼ ┌──────────────┐ │ 返回 fileUrl │ │ 前端存入字段 │ └──────────────┘ ``` ## 环境变量配置 | 变量 | 默认值 | 说明 | |:----|:----|:----| | `UPLOAD_ALLOWED_EXTS` | `.jpg,.jpeg,.png,.gif,...,.zip` | 允许上传的文件扩展名(逗号分隔,带点号) | | `UPLOAD_MAX_SIZE_MB` | `10` | 上传文件大小上限(MB) | | `FASTAPI_FILE_URL` | `http://file.fastapi.antdvue` | 文件访问域名 | | `FASTAPI_UPLOAD_DIR` | `uploads` | 上传文件存储目录 | ## 上传接口 系统提供统一的文件上传入口,支持图片、文档、压缩包等各类文件: ### 上传文件 ``` POST /api/v1/upload/uploadFile Content-Type: multipart/form-data Authorization: Bearer file: <文件> ``` 支持格式由 `UPLOAD_ALLOWED_EXTS` 配置控制,默认包含: ``` 图片:.jpg, .jpeg, .png, .gif, .bmp, .ico 文档:.pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .txt 音视频:.mp3, .mp4 压缩:.zip, .rar, .7z ``` 成功响应: ```json { "code": 0, "msg": "上传成功", "data": { "originalName": "photo.jpg", "fileExtension": "jpg", "fileType": "image/jpeg", "fileSize": 306995, "fileName": "2025030614302512345.jpg", "filePath": "/20250306/2025030614302512345.jpg", "fileUrl": "http://127.0.0.1:8031/api/file/20250306/2025030614302512345.jpg" }, "ok": true } ``` :::tip 富文本编辑器集成 富文本编辑器(如 TinyMCE、CKEditor、WangEditor 等)的图片上传同样使用此接口。编辑器配置上传地址为 `/api/v1/upload/uploadFile`,上传成功后从 `response.data.fileUrl` 获取图片 URL 即可。 ::: ## 安全机制 ### 扩展名白名单 只允许 `UPLOAD_ALLOWED_EXTS` 中配置的扩展名。配置的扩展名会与内置安全集合取交集,确保不会绕过安全校验。留空时回落到内置智能集合。 禁止上传的扩展名(硬编码黑名单,无论配置如何一律拒绝): ``` .exe, .bat, .cmd, .sh, .ps1, .php, .asp, .aspx, .jsp, .py, .rb, .js, .html, .htm .svg(无魔数签名,且存在存储型 XSS 风险) .webp(魔数仅 RIFF 前缀,无可靠二进制签名) ``` ### 魔术字节校验 不仅检查扩展名,还读取文件头部魔术字节验证真实类型: | 类型 | 魔术字节 | |:----|:----| | JPEG | `FF D8 FF` | | PNG | `89 50 4E 47` | | GIF | `47 49 46 38` | | PDF | `25 50 44 46` | | ZIP | `50 4B 03 04` | 防止攻击者通过修改扩展名上传恶意文件。 ### 路径遍历防护 文件名经过安全校验,拒绝包含 `..` 或路径分隔符的文件名: ```python def _is_safe_filename(filename: str) -> bool: normalized = os.path.normpath(filename) if '..' in normalized.split(os.sep): return False basename = os.path.basename(normalized) return basename == normalized ``` ### 大小限制 上传文件通过流式读取校验大小,超过 `UPLOAD_MAX_SIZE_MB` 限制立即中断,避免整文件读入内存: ```python async def _read_upload_limited(file, max_size_bytes): chunks = [] total = 0 while True: chunk = await file.read(65536) if not chunk: break total += len(chunk) if total > max_size_bytes: return None, R.failed(msg="文件大小超过限制") chunks.append(chunk) return b''.join(chunks), None ``` ## 文件存储策略 ### 目录结构 ``` uploads/ ├── 2026/ │ ├── 09/ │ │ ├── 05/ │ │ │ ├── 2026090514302512345.jpg │ │ │ ├── 2026090515011278901.pdf │ │ │ └── ... │ │ └── 06/ │ └── 10/ └── 2025/ ``` - 按日期分目录存储(`{UPLOAD_DIR}/{YYYYMMDD}/`),避免单目录文件过多 - 文件名使用 `年月日时分秒 + 5位随机数` 重命名,避免中文文件名和冲突 - 保留原始扩展名 ### 文件访问 URL 上传成功后返回的 `fileUrl` 是拼接了 `FASTAPI_FILE_URL` 前缀的完整 URL: ``` http://127.0.0.1:8031/api/file/20250306/2025030614302512345.jpg ``` Service 层的 `BaseService._enrich_file_urls()` 会自动为文件字段补此前缀。 ## 前端集成 ### 文件上传组件 ```vue ``` ### 图片预览 ```vue ``` ## 业务模块中的文件字段 在 Service 中声明 `file_fields`,基类自动处理文件迁移和 URL 补全: ```python class ArticleService(BaseService[Article]): # 文件字段:add/update 时自动迁移临时文件,列表/详情自动补全 URL file_fields = ('cover', 'attachment') ``` ### 自动处理逻辑 1. **新增/更新时**:检测文件字段值是否为临时路径,如果是则迁移到正式目录 2. **列表/详情时**:自动为文件字段补全 `FASTAPI_FILE_URL` 前缀 ## 富文本中的图片 在 Service 中声明 `rich_text_fields`,基类自动处理: ```python class ArticleService(BaseService[Article]): # 富文本字段:自动迁移嵌入图片 + XSS 清洗 rich_text_fields = ('content',) ``` ### 自动处理逻辑 1. **新增/更新时**:解析富文本中的 `` 标签 2. 将临时路径的图片迁移到正式目录 3. 替换富文本中的图片路径 4. 进行 XSS 清洗(使用 bleach 库) ## 常见问题 ### 上传报 413 Request Entity Too Large **原因**:文件大小超过 `UPLOAD_MAX_SIZE_MB` 限制。 **解决**:修改 `.env` 中的 `UPLOAD_MAX_SIZE_MB` 值。 ### 上传报 403 Forbidden **原因**:文件扩展名不在白名单中。 **解决**:检查 `UPLOAD_ALLOWED_EXTS` 配置,添加所需扩展名。 ### 图片上传成功但不显示 **原因**:`FASTAPI_FILE_URL` 配置错误或前端未拼接域名。 **解决**:检查 `.env` 中的 `FASTAPI_FILE_URL` 配置,确保前端正确拼接。 ### 上传报演示环境无权限 **原因**:演示模式下写操作被拦截。 **解决**:将 `.env` 中的 `FASTAPI_DEMO` 设为 `False`。 ## 总结 文件上传功能通过扩展名白名单 + 魔术字节校验 + 路径遍历防护 + 大小限制四层安全机制保障安全性。文件按日期分目录存储,使用时间戳 + 随机数重命名。统一的 `uploadFile` 接口支持图片和各类文件上传。Service 层通过 `file_fields` 和 `rich_text_fields` 声明即可自动处理文件迁移和 URL 补全。