版本: V1.0
日期: 2026-01-XX
适用场景: 聊天记录中的图片、视频、文件等附件存储与访问
统一消息平台使用 MinIO 对象存储服务来管理聊天记录中的文件附件(图片、视频、文档等)。为了确保文件安全,系统采用双重权限控制机制:
MinIO Bucket: unified-message-files
├── messages/
│ ├── {user_id}/
│ │ ├── {year}/
│ │ │ ├── {month}/
│ │ │ │ ├── {uuid}.{ext}
文件路径格式: messages/{user_id}/{year}/{month}/{uuid}.{ext}
示例: messages/123/2026/01/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpg
1. 用户上传文件 (需认证)
↓
2. 存储到私有Bucket
↓
3. 返回object_key
↓
4. 保存消息记录(content=object_key)
↓
5. 返回预签名URL(1小时有效)
6. 用户查询消息列表 (需认证)
↓
7. 权限过滤(仅查询自己的消息)
↓
8. 返回消息列表
↓
9. 为文件类型生成预签名URL
↓
10. 返回消息(含签名URL)
POST /api/v1/messages/uploadmultipart/form-datacurl -X POST "{{API_BASE_URL}}/messages/upload" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-F "file=@/path/to/image.jpg"
{
"url": "https://minio.example.com/bucket/messages/123/2026/01/uuid.jpg?X-Amz-Algorithm=...&X-Amz-Expires=3600",
"key": "messages/123/2026/01/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpg",
"filename": "image.jpg",
"content_type": "image/jpeg",
"size": 102400
}
| 字段 | 类型 | 说明 |
|---|---|---|
url |
string | 预签名访问 URL(1小时有效) |
key |
string | MinIO 对象键(object_key),用于存储到消息表 |
filename |
string | 原始文件名 |
content_type |
string | MIME 类型 |
size |
int | 文件大小(字节) |
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/v1/messages/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
const result = await response.json();
// 使用 result.key 发送消息
return result;
}
系统支持以下文件类型:
image/jpeg, image/png, image/gif, image/webpvideo/mp4, video/quicktime, video/x-msvideoapplication/pdf, application/msword, application/vnd.openxmlformats-officedocument.*, text/plain文件大小限制: 最大 50MB
上传文件后,使用返回的 key 作为消息内容发送。
curl -X POST "{{API_BASE_URL}}/messages/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"receiver_id": 456,
"type": "MESSAGE",
"content_type": "IMAGE",
"title": "分享图片",
"content": "messages/123/2026/01/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpg"
}'
重要提示:
content 字段应存储 key(object_key),而不是完整的 URLGET /api/v1/messages/curl -X GET "{{API_BASE_URL}}/messages/?skip=0&limit=20" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
[
{
"id": 1001,
"sender_id": 123,
"receiver_id": 456,
"type": "MESSAGE",
"content_type": "IMAGE",
"title": "分享图片",
"content": "https://minio.example.com/bucket/messages/123/2026/01/uuid.jpg?X-Amz-Algorithm=...&X-Amz-Expires=3600",
"is_read": false,
"created_at": "2026-01-15T10:30:00"
}
]
receiver_id == current_user.id 的消息content_type 为 IMAGE、VIDEO、FILE 的消息,系统自动将 content 字段中的 key 转换为预签名 URLasync function fetchMessages() {
const response = await fetch('/api/v1/messages/', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const messages = await response.json();
messages.forEach(msg => {
if (['IMAGE', 'VIDEO', 'FILE'].includes(msg.content_type)) {
// content 已经是预签名 URL,直接使用
console.log('文件URL:', msg.content);
}
});
return messages;
}
GET /api/v1/messages/history/{other_user_id}curl -X GET "{{API_BASE_URL}}/messages/history/456?skip=0&limit=50" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
系统会验证:
sender_id 或 receiver_id 必须包含当前用户预签名 URL(Presigned URL)是 MinIO/S3 提供的一种临时访问授权机制。它允许在不需要公开 Bucket 的情况下,为特定对象生成一个有时效性的访问链接。
预签名 URL 在以下场景自动生成:
url 字段_process_message_content() 函数自动处理https://minio.example.com:9004/unified-message-files/messages/123/2026/01/uuid.jpg
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=ACCESS_KEY%2F20260115%2Fus-east-1%2Fs3%2Faws4_request
&X-Amz-Date=20260115T103000Z
&X-Amz-Expires=3600
&X-Amz-SignedHeaders=host
&X-Amz-Signature=abc123...
不要缓存预签名 URL
错误处理
async function loadImage(url) {
try {
const response = await fetch(url);
if (!response.ok) {
// URL 可能已过期,重新获取消息
await refreshMessages();
}
return response.blob();
} catch (error) {
console.error('加载图片失败:', error);
}
}
content 字段存储的是 key,不是完整 URL只有在有权限查看消息时才生成预签名 URL
权限验证
所有消息查询接口都已内置权限验证
不要绕过权限检查直接生成预签名 URL
文件清理
考虑实现定期清理机制,删除长时间未访问的文件
可以在删除消息时同步删除 MinIO 中的文件
A: 为了安全考虑,所有文件访问都通过预签名 URL。即使 URL 过期,用户仍可以通过查询消息接口重新获取有效的 URL。
A: 目前默认 1 小时有效期。如需调整,可以修改 backend/app/core/minio.py 中的 get_presigned_url() 方法的 expires 参数。
# 修改为 24 小时
presigned_url = minio_storage.get_presigned_url(object_name, expires=timedelta(hours=24))
A: 不能。Bucket 设置为私有模式,所有文件必须通过预签名 URL 访问。即使知道文件路径,没有有效的预签名 URL 也无法访问。
A: 预签名 URL 支持直接下载。可以在前端添加下载按钮:
function downloadFile(url, filename) {
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
}
A: 前端检测到 URL 过期(HTTP 403/404)时,重新调用消息查询接口获取新的预签名 URL。
A: 不能。系统会验证消息权限,只有有权限查看消息的用户才能获取文件访问链接。这确保了文件访问的安全性。
A: 是上传者的 user_id。文件路径格式为 messages/{uploader_user_id}/{year}/{month}/{uuid}.{ext}。
backend/app/core/minio.pybackend/app/api/v1/endpoints/messages_upload.pybackend/app/api/v1/endpoints/messages.py_process_message_content() 函数MinIO 相关配置在 backend/app/core/config.py 中:
MINIO_ENDPOINT: str = "https://api.hnyunzhu.com:9004"
MINIO_ACCESS_KEY: str = "your_access_key"
MINIO_SECRET_KEY: str = "your_secret_key"
MINIO_BUCKET_NAME: str = "unified-message-files"
MINIO_SECURE: bool = False
如有问题或建议,请联系开发团队或提交 Issue。
相关文档: