minio_file_permissions_guide.md 11 KB

消息文件存储与权限控制开发者指南

版本: V1.0
日期: 2026-01-XX
适用场景: 聊天记录中的图片、视频、文件等附件存储与访问


1. 概述

统一消息平台使用 MinIO 对象存储服务来管理聊天记录中的文件附件(图片、视频、文档等)。为了确保文件安全,系统采用双重权限控制机制

  1. 数据库层权限控制:用户只能查询自己相关的消息
  2. 存储层权限控制:通过预签名 URL(Presigned URL)实现临时访问授权

核心特性

  • 私有存储:所有文件存储在私有 Bucket 中,无法直接访问
  • 权限隔离:只有有权限查看消息的用户才能获取文件访问链接
  • 时效控制:预签名 URL 默认 1 小时有效,过期后需重新获取
  • 路径隔离:文件路径包含用户 ID,便于后续扩展细粒度权限

2. 权限控制架构

2.1 存储层设计

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

2.2 权限控制流程

1. 用户上传文件 (需认证)
   ↓
2. 存储到私有Bucket
   ↓
3. 返回object_key
   ↓
4. 保存消息记录(content=object_key)
   ↓
5. 返回预签名URL(1小时有效)

6. 用户查询消息列表 (需认证)
   ↓
7. 权限过滤(仅查询自己的消息)
   ↓
8. 返回消息列表
   ↓
9. 为文件类型生成预签名URL
   ↓
10. 返回消息(含签名URL)

3. 开发者使用指南

3.1 文件上传

接口说明

  • Endpoint: POST /api/v1/messages/upload
  • 认证方式: Bearer Token (用户认证) 或 应用签名认证
  • Content-Type: multipart/form-data

请求示例

curl -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 文件大小(字节)

前端示例 (JavaScript)

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/webp
  • 视频: video/mp4, video/quicktime, video/x-msvideo
  • 文档: application/pdf, application/msword, application/vnd.openxmlformats-officedocument.*, text/plain

文件大小限制: 最大 50MB


3.2 发送带文件的消息

上传文件后,使用返回的 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),而不是完整的 URL
  • 系统会在查询消息时自动为文件类型生成预签名 URL

3.3 查询消息(自动生成文件访问链接)

接口说明

  • Endpoint: GET /api/v1/messages/
  • 认证方式: Bearer Token(仅用户认证)
  • 权限: 用户只能查询自己作为接收者的消息

请求示例

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"
  }
]

权限控制说明

  1. 数据库层过滤:API 自动过滤,只返回 receiver_id == current_user.id 的消息
  2. 自动签名:对于 content_typeIMAGEVIDEOFILE 的消息,系统自动将 content 字段中的 key 转换为预签名 URL
  3. URL 时效:预签名 URL 默认 1 小时有效

前端处理示例

async 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;
}

3.4 聊天历史记录

接口说明

  • Endpoint: GET /api/v1/messages/history/{other_user_id}
  • 认证方式: Bearer Token
  • 权限: 只能查询与当前用户的聊天记录

请求示例

curl -X GET "{{API_BASE_URL}}/messages/history/456?skip=0&limit=50" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

权限验证逻辑

系统会验证:

  • 消息的 sender_idreceiver_id 必须包含当前用户
  • 只有通过验证的消息才会返回,并自动生成文件预签名 URL

4. 预签名 URL 机制详解

4.1 什么是预签名 URL?

预签名 URL(Presigned URL)是 MinIO/S3 提供的一种临时访问授权机制。它允许在不需要公开 Bucket 的情况下,为特定对象生成一个有时效性的访问链接。

4.2 生成时机

预签名 URL 在以下场景自动生成:

  1. 文件上传后:上传接口返回的 url 字段
  2. 查询消息时_process_message_content() 函数自动处理
  3. WebSocket 推送时:实时消息推送中的文件内容

4.3 URL 有效期

  • 默认有效期: 1 小时(3600 秒)
  • 过期处理: URL 过期后,前端需要重新调用消息查询接口获取新的预签名 URL

4.4 URL 格式示例

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...

5. 安全最佳实践

5.1 前端开发建议

  1. 不要缓存预签名 URL

    • URL 有时效性,建议每次显示消息时重新获取
    • 如果 URL 过期,重新调用消息查询接口
  2. 错误处理

    async function loadImage(url) {
     try {
       const response = await fetch(url);
       if (!response.ok) {
         // URL 可能已过期,重新获取消息
         await refreshMessages();
       }
       return response.blob();
     } catch (error) {
       console.error('加载图片失败:', error);
     }
    }
    
    1. 图片预览优化
    2. 对于大文件,考虑在前端添加加载状态
    3. 可以添加图片压缩或缩略图功能

    5.2 后端开发建议

    1. 不要直接暴露 object_key
    2. 消息表中的 content 字段存储的是 key,不是完整 URL
    3. 只有在有权限查看消息时才生成预签名 URL

    4. 权限验证

    5. 所有消息查询接口都已内置权限验证

    6. 不要绕过权限检查直接生成预签名 URL

    7. 文件清理

    8. 考虑实现定期清理机制,删除长时间未访问的文件

    9. 可以在删除消息时同步删除 MinIO 中的文件


    6. 常见问题 (FAQ)

    Q1: 为什么上传文件后返回的 URL 有时效性?

    A: 为了安全考虑,所有文件访问都通过预签名 URL。即使 URL 过期,用户仍可以通过查询消息接口重新获取有效的 URL。

    Q2: 如何延长文件访问 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))
    

Q3: 用户能否直接访问 MinIO 中的文件?

A: 不能。Bucket 设置为私有模式,所有文件必须通过预签名 URL 访问。即使知道文件路径,没有有效的预签名 URL 也无法访问。

Q4: 如何实现文件下载功能?

A: 预签名 URL 支持直接下载。可以在前端添加下载按钮:

function downloadFile(url, filename) {
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
}

Q5: 预签名 URL 过期后如何处理?

A: 前端检测到 URL 过期(HTTP 403/404)时,重新调用消息查询接口获取新的预签名 URL。

Q6: 能否为其他用户生成文件访问链接?

A: 不能。系统会验证消息权限,只有有权限查看消息的用户才能获取文件访问链接。这确保了文件访问的安全性。

Q7: 文件路径中的 user_id 是上传者还是接收者?

A: 是上传者的 user_id。文件路径格式为 messages/{uploader_user_id}/{year}/{month}/{uuid}.{ext}


7. 技术实现细节

7.1 核心代码位置

  • MinIO 存储类: backend/app/core/minio.py
  • 文件上传接口: backend/app/api/v1/endpoints/messages_upload.py
  • 消息查询接口: backend/app/api/v1/endpoints/messages.py
  • URL 生成逻辑: _process_message_content() 函数

7.2 配置说明

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

8. 更新日志

V1.0 (2026-01-XX)

  • 初始版本
  • 实现基于预签名 URL 的文件权限控制
  • 支持图片、视频、文档等文件类型
  • 默认 1 小时 URL 有效期

9. 技术支持

如有问题或建议,请联系开发团队或提交 Issue。

相关文档: