# 文件存储模块设计文档
## 概述
为项目新增通用文件存储服务模块,支持三种可配置的存储后端(本地文件系统、阿里云 OSS、MinIO),前端提供文件 CRUD 和预览功能。
**定位**:通用存储服务,可用于个人文件管理、公共资源管理、新闻图片视频等多种业务场景。
## 设计决定
| 项目 | 决定 | 原因 |
|------|------|------|
| 文件归属 | 混合模式(公开/私有标记) | 灵活适配多种业务场景 |
| 目录结构 | 平铺列表 + 分类标签 | 简单高效,适合通用存储 |
| 预览类型 | 图片 + 视频 + PDF | 覆盖主流文件类型 |
| 存储切换 | 配置文件单选 | 简单清晰,重启生效 |
| 架构模式 | Strategy Pattern 存储抽象层 | 干净、可扩展、易测试 |
## 后端架构
### 1. Storage 接口(Strategy Pattern)
```go
// backend/internal/storage/storage.go
type Storage interface {
Upload(ctx context.Context, key string, reader io.Reader, size int64, contentType string) error
Delete(ctx context.Context, key string) error
GetURL(ctx context.Context, key string) (string, error)
Exists(ctx context.Context, key string) (bool, error)
}
```
三种实现:
| 实现 | 文件 | GetURL 返回 |
|------|------|------------|
| LocalStorage | `storage/local.go` | `/api/v1/file/:id/download` 由后端代理 |
| OSSStorage | `storage/oss.go` | 阿里云签名 URL(带过期时间) |
| MinIOStorage | `storage/minio.go` | Presigned URL |
### 2. 配置结构
```yaml
# etc/base-api.yaml
Storage:
Type: "local" # local / oss / minio
MaxSize: 104857600 # 100MB 单文件限制
Local:
RootDir: "./uploads"
OSS:
Endpoint: "oss-cn-hangzhou.aliyuncs.com"
AccessKeyId: ""
AccessKeySecret: ""
Bucket: ""
MinIO:
Endpoint: "localhost:9000"
AccessKeyId: ""
AccessKeySecret: ""
Bucket: ""
UseSSL: false
```
```go
// backend/internal/config/config.go 新增
type StorageConfig struct {
Type string `json:",default=local"`
MaxSize int64 `json:",default=104857600"` // 100MB
Local struct {
RootDir string `json:",default=./uploads"`
}
OSS struct {
Endpoint string
AccessKeyId string
AccessKeySecret string
Bucket string
}
MinIO struct {
Endpoint string
AccessKeyId string
AccessKeySecret string
Bucket string
UseSSL bool
}
}
```
### 3. 数据模型
```go
// backend/model/file_entity.go
type File struct {
Id int64 `gorm:"primaryKey;autoIncrement" json:"id"`
Name string `gorm:"type:varchar(255);not null" json:"name"`
Key string `gorm:"type:varchar(500);uniqueIndex" json:"key"`
Size int64 `gorm:"not null" json:"size"`
MimeType string `gorm:"type:varchar(100)" json:"mimeType"`
Category string `gorm:"type:varchar(50);index;default:'default'" json:"category"`
IsPublic bool `gorm:"default:false" json:"isPublic"`
UserId int64 `gorm:"index" json:"userId"`
StorageType string `gorm:"type:varchar(20)" json:"storageType"`
Status int `gorm:"default:1" json:"status"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
```
Model 方法:Insert, FindOne, FindList (分页+筛选), Update, Delete
### 4. API 设计
```
// backend/api/file.api
type (
FileUploadResponse {
Code int `json:"code"`
Message string `json:"message"`
Success bool `json:"success"`
Data FileInfo `json:"data,omitempty"`
}
FileInfo {
Id int64 `json:"id"`
Name string `json:"name"`
Key string `json:"key"`
Size int64 `json:"size"`
MimeType string `json:"mimeType"`
Category string `json:"category"`
IsPublic bool `json:"isPublic"`
UserId int64 `json:"userId"`
StorageType string `json:"storageType"`
Url string `json:"url"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
FileListRequest {
Page int `form:"page,default=1"`
PageSize int `form:"pageSize,default=20"`
Keyword string `form:"keyword,optional"`
Category string `form:"category,optional"`
MimeType string `form:"mimeType,optional"`
}
FileListResponse {
Code int `json:"code"`
Message string `json:"message"`
Success bool `json:"success"`
Data []FileInfo `json:"data"`
Total int64 `json:"total"`
}
FileUpdateRequest {
Name string `json:"name,optional"`
Category string `json:"category,optional"`
IsPublic *bool `json:"isPublic,optional"`
}
FileUrlResponse {
Code int `json:"code"`
Message string `json:"message"`
Success bool `json:"success"`
Url string `json:"url"`
}
)
@server (
prefix: /api/v1
middleware: Cors, Log, Auth, Authz
group: file
)
service base-api {
@handler UploadFile
post /file/upload (returns FileUploadResponse)
@handler GetFileList
get /files (FileListRequest) returns (FileListResponse)
@handler GetFile
get /file/:id returns (FileUploadResponse)
@handler GetFileUrl
get /file/:id/url returns (FileUrlResponse)
@handler UpdateFile
put /file/:id (FileUpdateRequest) returns (FileUploadResponse)
@handler DeleteFile
delete /file/:id returns (FileUploadResponse)
}
```
### 5. 权限策略(Casbin)
```go
// 新增到 seedCasbinPolicies
// user: 文件上传和查看
{"user", "/api/v1/file/upload", "POST"},
{"user", "/api/v1/files", "GET"},
{"user", "/api/v1/file/:id", "GET"},
{"user", "/api/v1/file/:id/url", "GET"},
{"user", "/api/v1/file/:id", "PUT"},
// super_admin: 文件删除
{"super_admin", "/api/v1/file/:id", "DELETE"},
```
业务层权限:
- user 只能查看自己的 + isPublic=true 的文件
- user 只能编辑自己的文件
- admin 可查看和编辑所有文件
- super_admin 可删除任何文件
### 6. Key 生成策略
```
{category}/{YYYY-MM}/{uuid}{ext}
```
例如:`default/2026-02/a1b2c3d4-e5f6.jpg`
## 前端设计
### 路由
`/files` — 文件管理页面,侧边栏新增「文件管理」导航项
### 文件管理页面
**表格列**:
- 文件名(带 MIME 图标)
- 分类(标签)
- 大小(格式化为 KB/MB)
- 类型(图片/视频/PDF/其他)
- 上传者
- 公开状态(开关)
- 上传时间
- 操作(预览、编辑、删除)
**功能**:
- 顶部:上传按钮 + 搜索 + 分类筛选
- 拖拽上传区域 + 点击上传
- 上传进度条
### 预览弹窗
| 类型 | 实现 |
|------|------|
| 图片 (jpg/png/gif/webp) | `
` 直接显示 |
| 视频 (mp4/webm) | `