224 lines
6.7 KiB
Markdown
224 lines
6.7 KiB
Markdown
# 文件上传完整流程
|
||
|
||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||
|
||
本章从配置到使用,完整说明文件上传功能的实现方式,包括后端接口、前端组件、安全机制和最佳实践。
|
||
|
||
## 上传架构
|
||
|
||
```
|
||
前端 Vue3 后端 Django 文件系统
|
||
┌──────────┐ ┌──────────────┐ ┌──────────┐
|
||
│ Upload │ multipart │ 安全校验 │ 写入 │ uploads/ │
|
||
│ 组件 │ ─────────> │ ─ 扩展名白名单│ ───────> │ temp/ │
|
||
│ │ │ ─ MIME 校验 │ │ 日期/ │
|
||
│ │ │ ─ 大小校验 │ │ xxx.jpg│
|
||
└──────────┘ └──────────────┘ └──────────┘
|
||
│
|
||
▼
|
||
┌──────────────┐
|
||
│ 返回 fileUrl │
|
||
│ 前端存入字段 │
|
||
└──────────────┘
|
||
```
|
||
|
||
## 环境变量配置
|
||
|
||
在 `config/env.py` 中配置:
|
||
|
||
| 变量 | 默认值 | 说明 |
|
||
|:----|:----|:----|
|
||
| `DJANGO_UPLOAD_DIR` | `{项目根目录}/public/uploads` | 上传文件存储目录 |
|
||
| `DJANGO_FILE_URL` | `http://file.django.elevue` | 文件访问域名 |
|
||
| `DJANGO_TEMP_PATH` | `{DJANGO_UPLOAD_DIR}/temp` | 临时文件目录 |
|
||
|
||
## 上传接口
|
||
|
||
系统提供统一的文件上传入口,支持图片、文档、压缩包等各类文件:
|
||
|
||
### 上传文件
|
||
|
||
```
|
||
POST /upload/uploadFile
|
||
Content-Type: multipart/form-data
|
||
Authorization: Bearer <token>
|
||
|
||
file: <文件>
|
||
```
|
||
|
||
支持格式:
|
||
|
||
```
|
||
图片:.jpeg, .jpg, .png, .gif, .bmp
|
||
文档:.pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .txt
|
||
```
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "上传成功",
|
||
"data": {
|
||
"originalName": "photo.jpg",
|
||
"fileExtension": "jpg",
|
||
"fileType": "image/jpeg",
|
||
"fileSize": 306995,
|
||
"fileName": "2025030614302512345.jpg",
|
||
"filePath": "/temp/20250306/2025030614302512345.jpg",
|
||
"fileUrl": "http://file.django.elevue/temp/20250306/2025030614302512345.jpg"
|
||
},
|
||
"ok": true
|
||
}
|
||
```
|
||
|
||
:::tip 富文本编辑器集成
|
||
富文本编辑器(如 WangEditor 等)的图片上传同样使用此接口。编辑器配置上传地址为 `/upload/uploadFile`,上传成功后从 `response.data.fileUrl` 获取图片 URL 即可。
|
||
:::
|
||
|
||
## 安全机制
|
||
|
||
### 扩展名白名单
|
||
|
||
只允许指定的扩展名格式上传,拒绝危险格式(如 `.exe`、`.bat`、`.sh`、`.js` 等)。
|
||
|
||
### MIME 类型校验
|
||
|
||
通过 `mimetypes` 模块推断文件的 MIME 类型,确保文件内容与扩展名一致。
|
||
|
||
### 临时目录隔离
|
||
|
||
上传的文件首先存储在 `temp` 临时目录,业务数据提交时才迁移到正式目录。未提交的临时文件可定期清理。
|
||
|
||
## 文件存储策略
|
||
|
||
### 目录结构
|
||
|
||
```
|
||
public/uploads/
|
||
├── temp/ # 临时文件目录
|
||
│ ├── 20260905/
|
||
│ │ ├── 2026090514302512345.jpg
|
||
│ │ └── 2026090515011278901.pdf
|
||
│ └── 20260906/
|
||
├── example/ # 正式文件目录(按业务分类)
|
||
│ ├── 20260905/
|
||
│ │ └── 2026090514302512345.jpg
|
||
│ └── 20260906/
|
||
└── article/
|
||
└── 20260906/
|
||
```
|
||
|
||
- 按日期分目录存储(`{日期}/`),避免单目录文件过多
|
||
- 文件名使用 `年月日时分秒 + 5位随机数` 重命名,避免中文文件名和冲突
|
||
- 保留原始扩展名
|
||
|
||
### 文件访问 URL
|
||
|
||
上传成功后返回的 `fileUrl` 是拼接了 `DJANGO_FILE_URL` 前缀的完整 URL:
|
||
|
||
```
|
||
http://file.django.elevue/temp/20250306/2025030614302512345.jpg
|
||
```
|
||
|
||
Service 层的 `get_file_url()` 会自动为文件字段补此前缀。
|
||
|
||
## 前端集成
|
||
|
||
### 文件上传组件
|
||
|
||
```vue
|
||
<template>
|
||
<el-upload
|
||
:action="uploadUrl"
|
||
:headers="headers"
|
||
:on-success="handleSuccess"
|
||
:before-upload="beforeUpload"
|
||
>
|
||
<el-button type="primary">上传文件</el-button>
|
||
</el-upload>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue';
|
||
import { useUserStore } from '@/store/modules/user';
|
||
|
||
const uploadUrl = '/api/upload/uploadFile';
|
||
const userStore = useUserStore();
|
||
const headers = {
|
||
Authorization: `Bearer ${userStore.token}`,
|
||
};
|
||
|
||
function beforeUpload(file) {
|
||
const isLt10M = file.size / 1024 / 1024 < 10;
|
||
if (!isLt10M) {
|
||
ElMessage.error('文件大小不能超过 10MB');
|
||
}
|
||
return isLt10M;
|
||
}
|
||
|
||
function handleSuccess(response) {
|
||
if (response.code === 0) {
|
||
// 保存 fileUrl 到业务字段
|
||
form.value.cover = response.data.fileUrl;
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
## 业务模块中的文件字段
|
||
|
||
在 Service 中使用 `save_file()` 处理文件迁移:
|
||
|
||
```python
|
||
# application/example/services.py
|
||
from utils.common import save_file, get_file_url
|
||
|
||
def add_example(request):
|
||
data, error = parse_request_body(request)
|
||
# ...
|
||
avatar = cleaned_data.get('avatar')
|
||
if avatar:
|
||
avatar = save_file(avatar, "example") # 迁移到正式目录
|
||
Example.objects.create(avatar=avatar, ...)
|
||
```
|
||
|
||
### 自动处理逻辑
|
||
|
||
1. **新增/更新时**:检测文件字段值是否为临时路径,如果是则迁移到正式目录
|
||
2. **列表/详情时**:通过 `get_file_url()` 自动为文件字段补全 `DJANGO_FILE_URL` 前缀
|
||
|
||
## 富文本中的图片
|
||
|
||
使用 `save_content()` 处理富文本中的图片:
|
||
|
||
```python
|
||
# utils/common.py
|
||
def save_content(content, title, directory):
|
||
"""提取富文本中的图片并迁移,替换 URL"""
|
||
image_urls = re.findall('img src="(.*?)"', content, re.S)
|
||
for url in image_urls:
|
||
image = save_file(url, directory)
|
||
if image:
|
||
content = content.replace(url, "[IMG_URL]" + image)
|
||
return content
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
### 上传报演示环境无权限
|
||
|
||
**原因**:演示模式下写操作被拦截。
|
||
|
||
**解决**:将 `.env` 中的 `DJANGO_DEMO` 设为 `False`。
|
||
|
||
### 图片上传成功但不显示
|
||
|
||
**原因**:`DJANGO_FILE_URL` 配置错误或前端未拼接域名。
|
||
|
||
**解决**:检查 `config/env.py` 中的 `DJANGO_FILE_URL` 配置,确保前端正确拼接。
|
||
|
||
## 总结
|
||
|
||
文件上传功能通过扩展名白名单 + MIME 类型校验 + 临时目录隔离等安全机制保障安全性。文件按日期分目录存储,使用时间戳 + 随机数重命名。统一的 `uploadFile` 接口支持图片和各类文件上传。Service 层通过 `save_file()` 和 `save_content()` 处理文件迁移和 URL 拼接。
|