# 文件上传联调 :::tip 说明 文件上传涉及前端 Upload 组件、后端上传接口和 URL 拼接三个环节,需要前后端协同配置。 ::: ## 上传流程 ``` 1. 前端 Upload 组件选择文件 2. VAxios.uploadFile() 发送 multipart/form-data POST 请求到 /api/upload/uploadFile 3. 后端校验文件(扩展名、大小、魔术字节) 4. 后端保存文件到临时目录,返回文件信息(fileUrl 等) 5. 前端将 fileUrl 存入业务字段 6. 业务数据提交时携带 fileUrl 7. 后端 save_file() 将临时文件迁移到正式目录 ``` ## 前端上传实现 ### VAxios.uploadFile `VAxios` 类内置 `uploadFile` 方法: ```typescript // src/utils/http/axios/Axios.ts uploadFile(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({ method: 'POST', data: formData, headers: { 'Content-type': ContentTypeEnum.FORM_DATA, ignoreCancelToken: true, }, ...config, }); } ``` ### Upload 组件使用 ```vue ``` 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:8031/api/file/20250306/xxx.jpg" emit('update:modelValue', res.data.fileUrl); }; ``` ## 后端上传接口 ``` POST /upload/uploadFile Content-Type: multipart/form-data Authorization: Bearer 参数: 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:8031/api/file/20250306/2025030614302512345.jpg" }, "msg": "上传成功", "ok": true } ``` ## URL 拼接 ### 后端拼接 后端 `save_file()` 函数处理文件迁移后返回相对路径,`get_file_url()` 拼接完整 URL: ```python # utils/common.py def get_file_url(path): if not path: return None return DJANGO_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 # utils/common.py # 临时路径: {temp}/{日期}/{文件名} # 正式路径: {upload_dir}/{分类}/{日期}/{文件名} def save_file(url, directory): if url.find(DJANGO_FILE_URL) != -1: if url.find("/temp") != -1: path = DJANGO_UPLOAD_DIR + "/" + directory + "/" + time.strftime('%Y%m%d') mkdir(path) old_path = url.replace(DJANGO_FILE_URL, DJANGO_UPLOAD_DIR) new_path = DJANGO_UPLOAD_DIR + "/" + directory + old_path.replace(DJANGO_TEMP_PATH, "") os.rename(old_path, new_path) return new_path.replace(DJANGO_UPLOAD_DIR, "") ``` ## 配置一致性 确保以下配置一致: | 配置项 | 位置 | 说明 | |--------|------|------| | `DJANGO_FILE_URL` | 后端 `config/env.py` | 文件访问域名 | | `VITE_GLOB_IMG_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()` 迁移到正式目录。前端通过 `DJANGO_FILE_URL` 拼接完整 URL 显示文件。