Files
djangoadmin-wiki/DjangoAdmin_Flask_NaiveVue/zh/volume4/integration/upload-sync.md
T
2026-09-12 23:12:15 +08:00

194 lines
5.0 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.
# 文件上传联调
:::tip 说明
文件上传涉及前端 Upload 组件、后端上传接口和 URL 拼接三个环节,需要前后端协同配置。
:::
## 上传流程
```
1. 前端 Upload 组件选择文件
2. VAxios.uploadFile() 发送 multipart/form-data POST 请求到 /api/v1/upload/uploadFile
3. 后端校验文件(扩展名、大小、魔术字节)
4. 后端保存文件到临时目录,返回文件信息(fileUrl 等)
5. 前端将 fileUrl 存入业务字段
6. 业务数据提交时携带 fileUrl
7. 后端 save_file() 将临时文件迁移到正式目录
```
## 前端上传实现
### VAxios.uploadFile
`VAxios` 类内置 `uploadFile` 方法:
```typescript
// src/utils/http/axios/Axios.ts
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
const formData = new window.FormData();
const customFilename = params.name || 'file';
if (params.filename) {
formData.append(customFilename, params.file, params.filename);
} else {
formData.append(customFilename, params.file);
}
if (params.data) {
Object.keys(params.data).forEach((key) => {
const value = params.data![key];
if (Array.isArray(value)) {
value.forEach((item) => formData.append(`${key}[]`, item));
return;
}
formData.append(key, params.data![key]);
});
}
return this.axiosInstance.request<T>({
method: 'POST',
data: formData,
headers: {
'Content-type': ContentTypeEnum.FORM_DATA,
ignoreCancelToken: true,
},
...config,
});
}
```
### Upload 组件使用
```vue
<template>
<Upload v-model="formData.cover" :limit="1" accept="image/*" />
</template>
<script setup>
const formData = ref({
name: '',
cover: '', // 存储文件 URL
})
</script>
```
Upload 组件内部调用上传接口:
```typescript
const handleUpload = async (file) => {
const formData = new FormData();
formData.append('file', file);
const res = await uploadFile(formData);
// res.data.fileUrl = "http://127.0.0.1:8021/api/file/20250306/xxx.jpg"
emit('update:modelValue', res.data.fileUrl);
};
```
## 后端上传接口
```
POST /api/v1/upload/uploadFile
Content-Type: multipart/form-data
参数: file(文件字段)
```
返回:
```json
{
"code": 0,
"data": {
"originalName": "photo.jpg",
"fileExtension": "jpg",
"fileType": "image/jpeg",
"fileSize": 306995,
"fileName": "2025030614302512345.jpg",
"filePath": "/20250306/2025030614302512345.jpg",
"fileUrl": "http://127.0.0.1:8021/api/file/20250306/2025030614302512345.jpg"
},
"msg": "上传成功",
"ok": true
}
```
## URL 拼接
### 后端拼接
后端 `save_file()` 函数处理文件迁移后返回相对路径,`get_file_url()` 拼接完整 URL:
```python
def get_file_url(path):
if not path:
return ""
if path.find(FLASK_FILE_URL) != -1:
return path
return FLASK_FILE_URL + path
```
### 前端显示
前端显示图片时,需要拼接完整的 URL:
```typescript
const getFileUrl = (path) => {
if (!path) return '';
if (path.startsWith('http')) return path;
return import.meta.env.VITE_GLOB_FILE_URL + path;
};
```
## 临时文件迁移
业务数据提交时,后端 service 调用 `save_file()` 将临时文件迁移到正式目录:
```python
# 临时路径: {temp}/{日期}/{文件名}
# 正式路径: {upload_dir}/{分类}/{日期}/{文件名}
def save_file(url, directory):
disk_path = _url_to_disk_path(url)
if disk_path.startswith(FLASK_TEMP_PATH):
target_dir = os.path.join(FLASK_UPLOAD_DIR, directory, time.strftime('%Y%m%d'))
new_path = os.path.join(target_dir, os.path.basename(disk_path))
shutil.move(disk_path, new_path)
return _disk_to_url_path(new_path)
return _disk_to_url_path(disk_path)
```
## 配置一致性
确保以下配置一致:
| 配置项 | 位置 | 说明 |
|--------|------|------|
| `FLASK_FILE_URL` | 后端 `.env` | 文件访问域名 |
| `VITE_GLOB_FILE_URL` | 前端 `.env` | 文件访问域名 |
| Nginx `location` | Nginx 配置 | 文件代理路径 |
:::tip 温馨提示
开发环境和生产环境的文件访问域名不同,需要分别配置。`VITE_GLOB_FILE_URL` 通过 `useGlobSetting()` 获取,生产环境通常使用独立的文件服务器或 CDN。
:::
## 常见问题
### 图片不显示
```
1. 检查 VITE_GLOB_FILE_URL 配置是否正确
2. 检查 Nginx 是否配置了 /api/file/ 代理
3. 检查文件是否已从临时目录迁移到正式目录
```
### 上传失败
```
1. 检查文件扩展名是否在白名单中
2. 检查文件大小是否超过 UPLOAD_MAX_SIZE_MB 限制
3. 检查后端服务是否正常运行
```
## 总结
文件上传通过 `VAxios.uploadFile()` 发送 `multipart/form-data` 请求,后端保存到临时目录并返回 `fileUrl`。业务提交时后端 `save_file()` 迁移到正式目录。前端通过 `VITE_GLOB_FILE_URL` 拼接完整 URL 显示文件。