签入版本
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
# 文件上传完整流程
|
||||
|
||||
<!-- 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 拼接。
|
||||
Reference in New Issue
Block a user