# 文件上传接口文档 ## 概述 通用文件上传接口,用于上传无法归类到具体业务的文件。 **基础路径**: `/api/v1/upload/` **认证要求**: 所有接口都需要 JWT Token 认证 **内容格式**: `multipart/form-data` (上传时) / `application/json` (响应) **注意事项**: - 不支持列表查询(list) - 不支持修改操作(PUT/PATCH) - 仅支持单个文件查询、上传、删除操作 --- ## 数据模型 ### UploadedFile | 字段 | 类型 | 说明 | |------|------|------| | id | integer | 文件ID | | path | string | 文件存储路径(随机文件名) | | file_url | string | 文件访问URL | | owner | integer | 上传者用户ID | | owner_username | string | 上传者用户名 | | is_deleted | boolean | 是否已删除(软删除标记) | | original_filename | string | 原始文件名 | | file_size | integer | 文件大小(字节) | | content_type | string | MIME类型(如 image/jpeg) | | created_at | datetime | 创建时间 | | updated_at | datetime | 更新时间 | --- ## 接口列表 ### 1. 上传文件 **请求** ``` POST /api/v1/upload/ Content-Type: multipart/form-data Authorization: Bearer ``` **请求参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | file | file | 是 | 要上传的文件(最大100MB) | **请求示例** ```bash curl -X POST http://localhost:8000/api/v1/upload/ \ -H "Authorization: Bearer YOUR_TOKEN" \ -F "file=@/path/to/your/file.pdf" ``` **成功响应** (201 Created) ```json { "id": 1, "path": "uploads/2025/11/17/a1b2c3d4e5f6...hex.pdf", "file_url": "/media/uploads/2025/11/17/a1b2c3d4e5f6...hex.pdf", "owner": 1, "owner_username": "admin", "is_deleted": false, "original_filename": "document.pdf", "file_size": 1048576, "content_type": "application/pdf", "created_at": "2025-11-17T10:30:00Z", "updated_at": "2025-11-17T10:30:00Z" } ``` **错误响应** (400 Bad Request) ```json { "file": ["未上传文件"] } ``` ```json { "file": ["文件大小不能超过100MB"] } ``` **安全特性**: - 文件名使用 UUID 随机化,防止文件名冲突和路径遍历攻击 - 原始文件名保存在数据库中,不影响存储安全 - 自动记录上传者信息 --- ### 2. 获取文件信息 **请求** ``` GET /api/v1/upload/{id}/ Authorization: Bearer ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | integer | 是 | 文件ID | **请求示例** ```bash curl -X GET http://localhost:8000/api/v1/upload/1/ \ -H "Authorization: Bearer YOUR_TOKEN" ``` **成功响应** (200 OK) ```json { "id": 1, "path": "uploads/2025/11/17/a1b2c3d4e5f6...hex.pdf", "file_url": "/media/uploads/2025/11/17/a1b2c3d4e5f6...hex.pdf", "owner": 1, "owner_username": "admin", "is_deleted": false, "original_filename": "document.pdf", "file_size": 1048576, "content_type": "application/pdf", "created_at": "2025-11-17T10:30:00Z", "updated_at": "2025-11-17T10:30:00Z" } ``` **错误响应** (404 Not Found) ```json { "detail": "未找到" } ``` **注意**: 已软删除的文件无法通过此接口查询 --- ### 3. 软删除文件 **请求** ``` DELETE /api/v1/upload/{id}/ Authorization: Bearer ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | integer | 是 | 文件ID | **请求示例** ```bash curl -X DELETE http://localhost:8000/api/v1/upload/1/ \ -H "Authorization: Bearer YOUR_TOKEN" ``` **成功响应** (200 OK) ```json { "detail": "文件已标记为删除" } ``` **说明**: - 软删除不会物理删除文件,只是标记为已删除 - 软删除后的文件无法通过常规接口查询 - 可以通过恢复接口恢复文件 --- ### 4. 恢复已删除文件 **请求** ``` POST /api/v1/upload/{id}/restore/ Authorization: Bearer ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | integer | 是 | 文件ID | **请求示例** ```bash curl -X POST http://localhost:8000/api/v1/upload/1/restore/ \ -H "Authorization: Bearer YOUR_TOKEN" ``` **成功响应** (200 OK) ```json { "detail": "文件已恢复", "data": { "id": 1, "path": "uploads/2025/11/17/a1b2c3d4e5f6...hex.pdf", "file_url": "/media/uploads/2025/11/17/a1b2c3d4e5f6...hex.pdf", "owner": 1, "owner_username": "admin", "is_deleted": false, "original_filename": "document.pdf", "file_size": 1048576, "content_type": "application/pdf", "created_at": "2025-11-17T10:30:00Z", "updated_at": "2025-11-17T10:30:00Z" } } ``` **错误响应** (400 Bad Request) ```json { "detail": "文件未被删除,无需恢复" } ``` --- ### 5. 永久删除文件 **请求** ``` DELETE /api/v1/upload/{id}/permanent_delete/ Authorization: Bearer ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | integer | 是 | 文件ID | **请求示例** ```bash curl -X DELETE http://localhost:8000/api/v1/upload/1/permanent_delete/ \ -H "Authorization: Bearer YOUR_TOKEN" ``` **成功响应** (204 No Content) ```json { "detail": "文件已永久删除" } ``` **说明**: - 永久删除会物理删除文件和数据库记录 - 此操作不可恢复,请谨慎使用 - 建议仅在确认不需要时使用 --- ## 使用示例 ### Python (requests) ```python import requests # 配置 BASE_URL = "http://localhost:8000/api/v1" TOKEN = "your_jwt_token" headers = {"Authorization": f"Bearer {TOKEN}"} # 1. 上传文件 with open('document.pdf', 'rb') as f: files = {'file': f} response = requests.post( f"{BASE_URL}/upload/", headers=headers, files=files ) file_data = response.json() file_id = file_data['id'] print(f"上传成功,文件ID: {file_id}") # 2. 获取文件信息 response = requests.get( f"{BASE_URL}/upload/{file_id}/", headers=headers ) print(f"文件信息: {response.json()}") # 3. 软删除文件 response = requests.delete( f"{BASE_URL}/upload/{file_id}/", headers=headers ) print(f"软删除: {response.json()}") # 4. 恢复文件 response = requests.post( f"{BASE_URL}/upload/{file_id}/restore/", headers=headers ) print(f"恢复文件: {response.json()}") # 5. 永久删除 response = requests.delete( f"{BASE_URL}/upload/{file_id}/permanent_delete/", headers=headers ) print(f"永久删除完成") ``` ### JavaScript (Axios) ```javascript const axios = require('axios'); const FormData = require('form-data'); const fs = require('fs'); const BASE_URL = 'http://localhost:8000/api/v1'; const TOKEN = 'your_jwt_token'; const headers = { Authorization: `Bearer ${TOKEN}` }; // 1. 上传文件 async function uploadFile() { const formData = new FormData(); formData.append('file', fs.createReadStream('document.pdf')); const response = await axios.post( `${BASE_URL}/upload/`, formData, { headers: { ...headers, ...formData.getHeaders() } } ); console.log('上传成功:', response.data); return response.data.id; } // 2. 获取文件信息 async function getFileInfo(fileId) { const response = await axios.get( `${BASE_URL}/upload/${fileId}/`, { headers } ); console.log('文件信息:', response.data); } // 3. 软删除 async function softDelete(fileId) { const response = await axios.delete( `${BASE_URL}/upload/${fileId}/`, { headers } ); console.log('软删除:', response.data); } // 4. 恢复文件 async function restore(fileId) { const response = await axios.post( `${BASE_URL}/upload/${fileId}/restore/`, {}, { headers } ); console.log('恢复:', response.data); } // 5. 永久删除 async function permanentDelete(fileId) { const response = await axios.delete( `${BASE_URL}/upload/${fileId}/permanent_delete/`, { headers } ); console.log('永久删除完成'); } ``` --- ## 错误码说明 | HTTP状态码 | 说明 | |-----------|------| | 200 | 成功 | | 201 | 创建成功 | | 204 | 删除成功(无内容) | | 400 | 请求参数错误 | | 401 | 未认证或认证失败 | | 403 | 无权限 | | 404 | 资源不存在 | | 413 | 文件过大 | | 500 | 服务器内部错误 | --- ## 最佳实践 1. **文件大小限制**: 单文件最大100MB,超过此限制会返回400错误 2. **文件命名**: 系统自动使用UUID生成随机文件名,原始文件名保存在`original_filename`字段 3. **软删除策略**: 建议先使用软删除,确认不需要后再使用永久删除 4. **文件访问**: 使用返回的`file_url`字段访问文件 5. **权限控制**: 所有接口都需要认证,上传的文件自动关联当前用户 --- ## 注意事项 1. **不支持的操作**: - ❌ 列表查询 (`GET /api/v1/upload/`) - ❌ 批量上传 - ❌ 修改文件 (`PUT/PATCH /api/v1/upload/{id}/`) 2. **文件存储**: - 文件按日期组织:`uploads/YYYY/MM/DD/` - 文件名使用32位十六进制UUID - 保留原始文件扩展名 3. **查询限制**: - 默认查询会过滤掉已软删除的文件 - 要访问已删除文件,需要通过Django Admin或直接数据库查询 4. **安全考虑**: - 所有文件名随机化,防止路径遍历攻击 - 需要JWT认证 - 自动记录上传者信息