文件管理
文件管理模块提供文件上传、下载、删除等功能,支持阿里云 OSS 云存储服务。
接口概览
文件上传
| 方法 | 路径 | 描述 | 权限要求 |
|---|---|---|---|
| POST | /api/v1/oss/upload | 上传单个文件 | 登录用户 |
| POST | /api/v1/oss/batch-upload | 批量上传文件 | 登录用户 |
文件删除
| 方法 | 路径 | 描述 | 权限要求 |
|---|---|---|---|
| DELETE | /api/v1/oss/delete | 删除单个文件 | 登录用户 |
| DELETE | /api/v1/oss/batch-delete | 批量删除文件 | 登录用户 |
文件信息
| 方法 | 路径 | 描述 | 权限要求 |
|---|---|---|---|
| GET | /api/v1/oss/file-info | 获取文件信息 | 登录用户 |
| GET | /api/v1/oss/files | 获取文件列表 | 登录用户 |
预签名URL
| 方法 | 路径 | 描述 | 权限要求 |
|---|---|---|---|
| POST | /api/v1/oss/presigned-url | 生成预签名URL | 登录用户 |
配置信息
| 方法 | 路径 | 描述 | 权限要求 |
|---|---|---|---|
| GET | /api/v1/oss/config | 获取OSS配置信息 | 登录用户 |
上传单个文件
上传单个文件到云存储服务。
接口信息
- URL:
/api/v1/oss/upload - 方法:
POST - 内容类型:
multipart/form-data - 认证: 需要登录
请求参数
| 字段 | 类型 | 必填 | 描述 | 示例 |
|---|---|---|---|---|
| file | File | 是 | 要上传的文件 | - |
| folder | string | 否 | 文件夹路径 | "documents/2024/01" |
| description | string | 否 | 文件描述 | "项目文档" |
| is_public | bool | 否 | 是否公开访问 | false |
请求示例
bash
curl -X POST http://localhost:8080/api/v1/oss/upload \
-H "Authorization: Bearer <your-token>" \
-F "file=@/path/to/document.pdf" \
-F "folder=documents/2024/01" \
-F "description=项目文档" \
-F "is_public=false"响应示例
成功响应 (200)
json
{
"code": 200,
"message": "上传成功",
"data": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"filename": "document.pdf",
"original_name": "项目需求文档.pdf",
"mime_type": "application/pdf",
"size": 2048576,
"url": "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/documents/2024/01/f47ac10b-58cc-4372-a567-0e02b2c3d479.pdf",
"download_url": "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/documents/2024/01/f47ac10b-58cc-4372-a567-0e02b2c3d479.pdf?Expires=1705123200&OSSAccessKeyId=LTAI4GCH1vX6DKqJWxd6****&Signature=...",
"folder": "documents/2024/01",
"description": "项目文档",
"is_public": false,
"uploaded_by": {
"id": 1,
"name": "John Doe"
},
"created_at": "2024-01-01T12:00:00Z"
},
"timestamp": "2024-01-01T12:00:00Z"
}批量上传文件
同时上传多个文件。
接口信息
- URL:
/api/v1/oss/batch-upload - 方法:
POST - 内容类型:
multipart/form-data - 认证: 需要登录
请求参数
| 字段 | 类型 | 必填 | 描述 | 示例 |
|---|---|---|---|---|
| files | File[] | 是 | 要上传的文件列表 | - |
| folder | string | 否 | 文件夹路径 | "documents/batch" |
| description | string | 否 | 批量描述 | "批量上传文档" |
| is_public | bool | 否 | 是否公开访问 | false |
请求示例
bash
curl -X POST http://localhost:8080/api/v1/oss/batch-upload \
-H "Authorization: Bearer <your-token>" \
-F "files=@/path/to/doc1.pdf" \
-F "files=@/path/to/doc2.pdf" \
-F "files=@/path/to/image.png" \
-F "folder=documents/batch" \
-F "description=批量上传文档"响应示例
成功响应 (200)
json
{
"code": 200,
"message": "批量上传完成",
"data": {
"total": 3,
"success": 3,
"failed": 0,
"results": [
{
"id": "file1-id",
"filename": "doc1.pdf",
"original_name": "需求文档.pdf",
"size": 1024000,
"url": "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/documents/batch/doc1.pdf",
"status": "success"
},
{
"id": "file2-id",
"filename": "doc2.pdf",
"original_name": "设计文档.pdf",
"size": 2048576,
"url": "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/documents/batch/doc2.pdf",
"status": "success"
},
{
"id": "file3-id",
"filename": "image.png",
"original_name": "界面截图.png",
"size": 512000,
"url": "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/documents/batch/image.png",
"status": "success"
}
]
},
"timestamp": "2024-01-01T12:00:00Z"
}获取文件信息
获取指定文件的详细信息。
接口信息
- URL:
/api/v1/oss/file-info - 方法:
GET - 认证: 需要登录
查询参数
| 参数 | 类型 | 必填 | 描述 | 示例 |
|---|---|---|---|---|
| file_id | string | 是 | 文件ID | "f47ac10b-58cc-4372-a567-0e02b2c3d479" |
请求示例
bash
GET /api/v1/oss/file-info?file_id=f47ac10b-58cc-4372-a567-0e02b2c3d479响应示例
成功响应 (200)
json
{
"code": 200,
"message": "获取成功",
"data": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"filename": "document.pdf",
"original_name": "项目需求文档.pdf",
"mime_type": "application/pdf",
"size": 2048576,
"size_formatted": "2.0 MB",
"url": "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/documents/2024/01/document.pdf",
"download_url": "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/documents/2024/01/document.pdf?Expires=1705123200&Signature=...",
"folder": "documents/2024/01",
"description": "项目文档",
"is_public": false,
"uploaded_by": {
"id": 1,
"name": "John Doe",
"avatar": "https://example.com/avatar.jpg"
},
"created_at": "2024-01-01T12:00:00Z",
"last_accessed_at": "2024-01-01T15:30:00Z",
"download_count": 5
},
"timestamp": "2024-01-01T12:00:00Z"
}获取文件列表
获取文件列表,支持分页和筛选。
接口信息
- URL:
/api/v1/oss/files - 方法:
GET - 认证: 需要登录
查询参数
| 参数 | 类型 | 必填 | 描述 | 默认值 |
|---|---|---|---|---|
| page | int | 否 | 页码 | 1 |
| page_size | int | 否 | 每页数量 | 20 |
| folder | string | 否 | 文件夹路径筛选 | - |
| search | string | 否 | 搜索关键词 | - |
| mime_type | string | 否 | 文件类型筛选 | - |
| uploaded_by | int | 否 | 上传者ID筛选 | - |
| date_from | string | 否 | 开始日期 | - |
| date_to | string | 否 | 结束日期 | - |
| sort_by | string | 否 | 排序字段(name, size, created_at) | created_at |
| sort_order | string | 否 | 排序方向(asc, desc) | desc |
请求示例
bash
GET /api/v1/oss/files?page=1&page_size=20&folder=documents&search=项目&sort_by=created_at&sort_order=desc响应示例
成功响应 (200)
json
{
"code": 200,
"message": "获取成功",
"data": {
"files": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"filename": "document.pdf",
"original_name": "项目需求文档.pdf",
"mime_type": "application/pdf",
"size": 2048576,
"size_formatted": "2.0 MB",
"folder": "documents/2024/01",
"description": "项目文档",
"is_public": false,
"uploaded_by": {
"id": 1,
"name": "John Doe"
},
"created_at": "2024-01-01T12:00:00Z"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total": 1,
"pages": 1
},
"summary": {
"total_files": 1,
"total_size": 2048576,
"total_size_formatted": "2.0 MB"
}
},
"timestamp": "2024-01-01T12:00:00Z"
}删除单个文件
删除指定的文件。
接口信息
- URL:
/api/v1/oss/delete - 方法:
DELETE - 认证: 需要登录
请求参数
| 字段 | 类型 | 必填 | 描述 | 示例 |
|---|---|---|---|---|
| file_id | string | 是 | 文件ID | "f47ac10b-58cc-4372-a567-0e02b2c3d479" |
请求示例
json
{
"file_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}响应示例
成功响应 (200)
json
{
"code": 200,
"message": "删除成功",
"data": {
"deleted": true,
"file_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"timestamp": "2024-01-01T12:00:00Z"
}批量删除文件
同时删除多个文件。
接口信息
- URL:
/api/v1/oss/batch-delete - 方法:
DELETE - 认证: 需要登录
请求参数
| 字段 | 类型 | 必填 | 描述 | 示例 |
|---|---|---|---|---|
| file_ids | string[] | 是 | 文件ID列表 | ["id1", "id2", "id3"] |
请求示例
json
{
"file_ids": [
"f47ac10b-58cc-4372-a567-0e02b2c3d479",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"12345678-90ab-cdef-1234-567890abcdef"
]
}响应示例
成功响应 (200)
json
{
"code": 200,
"message": "批量删除完成",
"data": {
"total": 3,
"success": 2,
"failed": 1,
"results": [
{
"file_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "success"
},
{
"file_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "success"
},
{
"file_id": "12345678-90ab-cdef-1234-567890abcdef",
"status": "failed",
"error": "文件不存在"
}
]
},
"timestamp": "2024-01-01T12:00:00Z"
}生成预签名URL
生成文件的预签名访问URL,用于临时访问私有文件。
接口信息
- URL:
/api/v1/oss/presigned-url - 方法:
POST - 认证: 需要登录
请求参数
| 字段 | 类型 | 必填 | 描述 | 示例 |
|---|---|---|---|---|
| file_id | string | 是 | 文件ID | "f47ac10b-58cc-4372-a567-0e02b2c3d479" |
| expires_in | int | 否 | 过期时间(秒) | 3600 |
请求示例
json
{
"file_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"expires_in": 3600
}响应示例
成功响应 (200)
json
{
"code": 200,
"message": "生成成功",
"data": {
"presigned_url": "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/documents/2024/01/document.pdf?Expires=1705123200&OSSAccessKeyId=LTAI4GCH1vX6DKqJWxd6****&Signature=abc123...",
"expires_at": "2024-01-01T13:00:00Z",
"expires_in": 3600
},
"timestamp": "2024-01-01T12:00:00Z"
}获取OSS配置信息
获取OSS存储的配置信息(公开信息)。
接口信息
- URL:
/api/v1/oss/config - 方法:
GET - 认证: 需要登录
响应示例
成功响应 (200)
json
{
"code": 200,
"message": "获取成功",
"data": {
"max_file_size": 104857600,
"max_file_size_formatted": "100 MB",
"allowed_types": [
"image/jpeg",
"image/png",
"image/gif",
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"text/plain"
],
"storage_endpoint": "https://your-bucket.oss-cn-hangzhou.aliyuncs.com",
"features": {
"multipart_upload": true,
"resumable_upload": true,
"image_processing": true,
"video_processing": false
}
},
"timestamp": "2024-01-01T12:00:00Z"
}数据模型
FileResponse
typescript
interface FileResponse {
id: string; // 文件唯一ID
filename: string; // 存储文件名
original_name: string; // 原始文件名
mime_type: string; // MIME类型
size: number; // 文件大小(字节)
size_formatted: string; // 格式化的文件大小
url: string; // 文件访问URL
download_url?: string; // 下载URL(包含签名)
folder?: string; // 文件夹路径
description?: string; // 文件描述
is_public: boolean; // 是否公开
uploaded_by: User; // 上传者信息
created_at: string; // 创建时间
last_accessed_at?: string; // 最后访问时间
download_count?: number; // 下载次数
}FileListResponse
typescript
interface FileListResponse {
files: FileResponse[]; // 文件列表
pagination: { // 分页信息
page: number;
page_size: number;
total: number;
pages: number;
};
summary: { // 汇总信息
total_files: number;
total_size: number;
total_size_formatted: string;
};
}错误代码
| 错误代码 | 描述 | 解决方案 |
|---|---|---|
| 400 | 请求参数错误 | 检查请求参数格式和必填字段 |
| 401 | 未认证 | 检查 JWT token 是否有效 |
| 403 | 无权限 | 确认用户具有相应权限 |
| 404 | 文件不存在 | 检查文件ID是否正确 |
| 413 | 文件过大 | 检查文件大小是否超出限制 |
| 415 | 不支持的文件类型 | 检查文件类型是否允许 |
| 500 | 服务器内部错误 | 联系系统管理员 |
| 503 | 存储服务不可用 | 检查OSS配置和网络连接 |
集成示例
React Upload Component
jsx
import React, { useState } from 'react';
import axios from 'axios';
const FileUpload = () => {
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [files, setFiles] = useState([]);
const handleFileSelect = (event) => {
setFiles(Array.from(event.target.files));
};
const handleUpload = async () => {
if (files.length === 0) return;
setUploading(true);
setProgress(0);
try {
const token = localStorage.getItem('token');
const formData = new FormData();
files.forEach(file => {
formData.append('files', file);
});
formData.append('folder', 'uploads/' + new Date().toISOString().split('T')[0]);
formData.append('description', '批量上传文件');
const response = await axios.post('/api/v1/oss/batch-upload', formData, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
setProgress(percentCompleted);
}
});
console.log('上传成功:', response.data);
setFiles([]);
setProgress(0);
} catch (error) {
console.error('上传失败:', error.response?.data?.message || error.message);
} finally {
setUploading(false);
}
};
return (
<div className="file-upload">
<h3>文件上传</h3>
<div className="upload-area">
<input
type="file"
multiple
onChange={handleFileSelect}
disabled={uploading}
/>
<button
onClick={handleUpload}
disabled={uploading || files.length === 0}
>
{uploading ? '上传中...' : '上传文件'}
</button>
</div>
{uploading && (
<div className="progress">
<div>上传进度: {progress}%</div>
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
</div>
</div>
)}
{files.length > 0 && (
<div className="file-list">
<h4>待上传文件:</h4>
<ul>
{files.map((file, index) => (
<li key={index}>
{file.name} ({(file.size / 1024 / 1024).toFixed(2)} MB)
</li>
))}
</ul>
</div>
)}
</div>
);
};
export default FileUpload;Python Upload Client
python
import requests
import os
from typing import List, Optional
class OSSClient:
def __init__(self, base_url: str, token: str):
self.base_url = base_url
self.token = token
self.headers = {
'Authorization': f'Bearer {token}'
}
def upload_file(self, file_path: str, folder: Optional[str] = None,
description: Optional[str] = None) -> dict:
"""上传单个文件"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
with open(file_path, 'rb') as f:
files = {'file': f}
data = {}
if folder:
data['folder'] = folder
if description:
data['description'] = description
response = requests.post(
f'{self.base_url}/api/v1/oss/upload',
headers=self.headers,
files=files,
data=data
)
response.raise_for_status()
return response.json()
def upload_multiple_files(self, file_paths: List[str],
folder: Optional[str] = None,
description: Optional[str] = None) -> dict:
"""批量上传文件"""
files = []
try:
for file_path in file_paths:
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
files.append((
'files',
(os.path.basename(file_path), open(file_path, 'rb'))
))
data = {}
if folder:
data['folder'] = folder
if description:
data['description'] = description
response = requests.post(
f'{self.base_url}/api/v1/oss/batch-upload',
headers=self.headers,
files=files,
data=data
)
response.raise_for_status()
return response.json()
finally:
# 关闭所有文件
for _, (_, f) in files:
f.close()
def get_file_info(self, file_id: str) -> dict:
"""获取文件信息"""
response = requests.get(
f'{self.base_url}/api/v1/oss/file-info',
headers=self.headers,
params={'file_id': file_id}
)
response.raise_for_status()
return response.json()
def delete_file(self, file_id: str) -> dict:
"""删除文件"""
response = requests.delete(
f'{self.base_url}/api/v1/oss/delete',
headers=self.headers,
json={'file_id': file_id}
)
response.raise_for_status()
return response.json()
def generate_presigned_url(self, file_id: str, expires_in: int = 3600) -> dict:
"""生成预签名URL"""
response = requests.post(
f'{self.base_url}/api/v1/oss/presigned-url',
headers=self.headers,
json={'file_id': file_id, 'expires_in': expires_in}
)
response.raise_for_status()
return response.json()
# 使用示例
client = OSSClient('http://localhost:8080', 'your-jwt-token')
# 上传单个文件
try:
result = client.upload_file(
'/path/to/document.pdf',
folder='documents/2024/01',
description='项目文档'
)
print(f"上传成功: {result['data']['url']}")
except Exception as e:
print(f"上传失败: {e}")
# 批量上传
try:
files = ['/path/to/doc1.pdf', '/path/to/doc2.pdf', '/path/to/image.png']
result = client.upload_multiple_files(
files,
folder='batch-upload',
description='批量上传文档'
)
print(f"批量上传完成: 成功 {result['data']['success']}/{result['data']['total']} 个文件")
except Exception as e:
print(f"批量上传失败: {e}")