Files
djangoadmin-wiki/DjangoAdmin_Django_NaiveVue/zh/volume4/tools/upload-guide.md
T
2026-09-12 23:12:15 +08:00

224 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 文件上传完整流程
<!-- 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 拼接。