You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8.7 KiB
8.7 KiB
文件存储模块设计文档
概述
为项目新增通用文件存储服务模块,支持三种可配置的存储后端(本地文件系统、阿里云 OSS、MinIO),前端提供文件 CRUD 和预览功能。
定位:通用存储服务,可用于个人文件管理、公共资源管理、新闻图片视频等多种业务场景。
设计决定
| 项目 | 决定 | 原因 |
|---|---|---|
| 文件归属 | 混合模式(公开/私有标记) | 灵活适配多种业务场景 |
| 目录结构 | 平铺列表 + 分类标签 | 简单高效,适合通用存储 |
| 预览类型 | 图片 + 视频 + PDF | 覆盖主流文件类型 |
| 存储切换 | 配置文件单选 | 简单清晰,重启生效 |
| 架构模式 | Strategy Pattern 存储抽象层 | 干净、可扩展、易测试 |
后端架构
1. Storage 接口(Strategy Pattern)
// 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. 配置结构
# 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
// 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. 数据模型
// 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)
// 新增到 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) | <img> 直接显示 |
| 视频 (mp4/webm) | <video> HTML5 播放器 |
<iframe> 嵌入查看 |
|
| 其他 | 文件信息卡片 + 下载按钮 |
前端类型
interface FileInfo {
id: number
name: string
key: string
size: number
mimeType: string
category: string
isPublic: boolean
userId: number
storageType: string
url: string
createdAt: string
updatedAt: string
}
文件变更清单
新建文件
后端:
backend/internal/storage/storage.go— 接口定义 + 工厂函数backend/internal/storage/local.go— 本地存储实现backend/internal/storage/oss.go— 阿里云 OSS 实现backend/internal/storage/minio.go— MinIO 实现backend/model/file_entity.go— File GORM 模型backend/model/file_model.go— File 数据访问方法backend/api/file.api— API 定义backend/internal/handler/file/— handlers (goctl 生成)backend/internal/logic/file/— 业务逻辑(6个)
前端:
frontend/react-shadcn/pc/src/pages/FileManagementPage.tsx— 文件管理页面
修改文件
后端:
backend/internal/config/config.go— 新增 StorageConfigbackend/etc/base-api.yaml— 新增 Storage 配置段backend/internal/svc/servicecontext.go— 初始化 Storage + AutoMigrate File + 种子 Casbinbackend/base.api— import file.apibackend/internal/types/types.go— goctl 重新生成backend/internal/handler/routes.go— goctl 重新生成backend/go.mod/go.sum— 新增 OSS/MinIO SDK 依赖
前端:
frontend/react-shadcn/pc/src/types/index.ts— 新增 File 类型frontend/react-shadcn/pc/src/services/api.ts— 新增 file API 方法frontend/react-shadcn/pc/src/App.tsx— 新增 /files 路由frontend/react-shadcn/pc/src/components/layout/Sidebar.tsx— 新增导航项