Files
SeeyonFileSystem/server/controller/openapi_file_controller.go

375 lines
10 KiB
Go
Raw Permalink Normal View History

2026-07-10 17:33:33 +08:00
package controller
import (
"fmt"
"net/http"
"strconv"
"time"
"seeyon-filesystem/config"
"seeyon-filesystem/dto"
"seeyon-filesystem/middleware"
"seeyon-filesystem/model"
"seeyon-filesystem/service"
"seeyon-filesystem/storage"
"seeyon-filesystem/utils"
"github.com/gin-gonic/gin"
)
// OpenAPIFileController 对外文件API控制器
type OpenAPIFileController struct {
fileService *service.FileService
folderService *service.FolderService
chunkService *service.ChunkUploadService
}
func NewOpenAPIFileController() *OpenAPIFileController {
return &OpenAPIFileController{
fileService: service.NewFileService(),
folderService: service.NewFolderService(),
chunkService: service.NewChunkUploadService(),
}
}
// ========== 1. 根据父文件夹ID分页查询 ==========
// ListByFolder 分页查询文件夹下的文件和子文件夹
// GET /api/openapi/files?folder_id=0&page=1&page_size=20
// 返回树状结构: 文件夹 + 文件
func (c *OpenAPIFileController) ListByFolder(ctx *gin.Context) {
userID := middleware.GetCurrentUserID(ctx)
folderIDStr := ctx.DefaultQuery("folder_id", "0")
folderID, _ := strconv.ParseUint(folderIDStr, 10, 64)
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(ctx.DefaultQuery("page_size", "50"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 200 {
pageSize = 50
}
// 获取用户角色
roleIDs := getUserRoleIDs(userID)
// 查询文件夹
folders, err := getFoldersByParent(folderID, userID, page, pageSize)
if err != nil {
utils.ServerError(ctx, "查询文件夹失败")
return
}
// 查询文件
files, totalFiles, err := getFilesByFolder(folderID, userID, roleIDs, page, pageSize)
if err != nil {
utils.ServerError(ctx, "查询文件失败")
return
}
// 组装树状结构
result := dto.OpenAPIListResult{
FolderID: folderID,
TotalFiles: totalFiles,
Page: page,
PageSize: pageSize,
}
for _, f := range folders {
childCount := getFolderChildCount(f.ID)
result.Folders = append(result.Folders, dto.OpenAPIFolderVO{
ID: dto.StringID(f.ID),
Name: f.Name,
ParentID: dto.StringID(f.ParentID),
ChildCount: childCount,
CreatedAt: f.CreatedAt.Format("2006-01-02 15:04:05"),
})
}
for _, f := range files {
result.Files = append(result.Files, dto.OpenAPIFileVO{
ID: dto.StringID(f.ID),
Name: f.Name,
Extension: f.Extension,
Size: f.Size,
MD5: f.MD5,
MimeType: f.MimeType,
DownloadURL: getCDNOrPresignedURL(&f),
CreatedAt: f.CreatedAt.Format("2006-01-02 15:04:05"),
})
}
utils.Success(ctx, result)
}
// ========== 2. 按名称搜索返回树状结构 ==========
// SearchTree 按文件名/文件夹名搜索, 返回树状结构
// GET /api/openapi/files/search?keyword=报告&page=1&page_size=20
func (c *OpenAPIFileController) SearchTree(ctx *gin.Context) {
userID := middleware.GetCurrentUserID(ctx)
keyword := ctx.Query("keyword")
if keyword == "" {
utils.BadRequest(ctx, "请输入搜索关键词")
return
}
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(ctx.DefaultQuery("page_size", "50"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 200 {
pageSize = 50
}
roleIDs := getUserRoleIDs(userID)
// 搜索文件
files, total, err := searchFilesByName(userID, roleIDs, keyword, page, pageSize)
if err != nil {
utils.ServerError(ctx, "搜索失败")
return
}
// 搜索文件夹
folders, err := searchFoldersByName(userID, keyword, page, pageSize)
if err != nil {
utils.ServerError(ctx, "搜索失败")
return
}
result := dto.OpenAPISearchResult{
Keyword: keyword,
TotalFiles: total,
Page: page,
PageSize: pageSize,
}
for _, f := range folders {
result.Folders = append(result.Folders, dto.OpenAPIFolderVO{
ID: dto.StringID(f.ID),
Name: f.Name,
ParentID: dto.StringID(f.ParentID),
CreatedAt: f.CreatedAt.Format("2006-01-02 15:04:05"),
})
}
for _, f := range files {
result.Files = append(result.Files, dto.OpenAPIFileVO{
ID: dto.StringID(f.ID),
Name: f.Name,
Extension: f.Extension,
Size: f.Size,
MD5: f.MD5,
MimeType: f.MimeType,
DownloadURL: getCDNOrPresignedURL(&f),
CreatedAt: f.CreatedAt.Format("2006-01-02 15:04:05"),
})
}
utils.Success(ctx, result)
}
// ========== 3. 分片上传/下载 ==========
// ChunkUploadInit 初始化分片上传
// POST /api/openapi/files/upload/init
func (c *OpenAPIFileController) ChunkUploadInit(ctx *gin.Context) {
userID := middleware.GetCurrentUserID(ctx)
var req dto.ChunkUploadInitRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
utils.BadRequest(ctx, "参数错误: "+err.Error())
return
}
result, err := c.chunkService.InitUpload(req.FileName, req.FileSize, req.MD5, uint64(req.FolderID), userID, req.PolicyID)
if err != nil {
utils.ServerError(ctx, err.Error())
return
}
utils.Success(ctx, result)
}
// ChunkUpload 上传分片
// PUT /api/openapi/files/upload/chunk/:uploadId/:index
func (c *OpenAPIFileController) ChunkUpload(ctx *gin.Context) {
uploadID := ctx.Param("uploadId")
indexStr := ctx.Param("index")
index, err := strconv.Atoi(indexStr)
if err != nil {
utils.BadRequest(ctx, "分片索引无效")
return
}
checksum := ctx.GetHeader("X-Checksum")
file, err := ctx.FormFile("chunk")
if err != nil {
utils.BadRequest(ctx, "请上传分片文件")
return
}
reader, err := file.Open()
if err != nil {
utils.BadRequest(ctx, "读取分片失败")
return
}
defer reader.Close()
result, err := c.chunkService.UploadChunk(uploadID, index, reader, checksum)
if err != nil {
utils.BadRequest(ctx, err.Error())
return
}
utils.Success(ctx, result)
}
// ChunkMerge 合并分片
// POST /api/openapi/files/upload/merge/:uploadId
func (c *OpenAPIFileController) ChunkMerge(ctx *gin.Context) {
uploadID := ctx.Param("uploadId")
result, err := c.chunkService.MergeChunks(uploadID)
if err != nil {
utils.ServerError(ctx, err.Error())
return
}
utils.Success(ctx, result)
}
// ChunkProgress 查询上传进度
// GET /api/openapi/files/upload/progress/:uploadId
func (c *OpenAPIFileController) ChunkProgress(ctx *gin.Context) {
uploadID := ctx.Param("uploadId")
result, err := c.chunkService.GetUploadProgress(uploadID)
if err != nil {
utils.NotFound(ctx, err.Error())
return
}
utils.Success(ctx, result)
}
// Download 下载文件
// GET /api/openapi/files/:id/download
func (c *OpenAPIFileController) Download(ctx *gin.Context) {
userID := middleware.GetCurrentUserID(ctx)
fileID, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
if err != nil {
utils.BadRequest(ctx, "无效的文件ID")
return
}
reader, file, err := c.fileService.Download(fileID, userID)
if err != nil {
utils.ServerError(ctx, err.Error())
return
}
defer reader.Close()
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", file.Name))
ctx.Header("Content-Type", file.MimeType)
ctx.Header("Content-Length", strconv.FormatInt(file.Size, 10))
ctx.DataFromReader(http.StatusOK, file.Size, file.MimeType, reader, nil)
}
// Preview 预览文件
// GET /api/openapi/files/:id/preview
func (c *OpenAPIFileController) Preview(ctx *gin.Context) {
userID := middleware.GetCurrentUserID(ctx)
fileID, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
if err != nil {
utils.BadRequest(ctx, "无效的文件ID")
return
}
reader, file, err := c.fileService.Download(fileID, userID)
if err != nil {
utils.ServerError(ctx, err.Error())
return
}
defer reader.Close()
ctx.Header("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", file.Name))
ctx.Header("Content-Type", file.MimeType)
ctx.Header("Content-Length", strconv.FormatInt(file.Size, 10))
ctx.DataFromReader(http.StatusOK, file.Size, file.MimeType, reader, nil)
}
// ========== 辅助函数 ==========
func getUserRoleIDs(userID uint64) []uint64 {
var userRoles []model.UserRole
config.DB.Where("user_id = ?", userID).Find(&userRoles)
ids := make([]uint64, 0, len(userRoles))
for _, ur := range userRoles {
ids = append(ids, ur.RoleID)
}
return ids
}
func getFoldersByParent(parentID, ownerID uint64, page, pageSize int) ([]model.Folder, error) {
var folders []model.Folder
err := config.DB.Where("parent_id = ? AND owner_id = ? AND status = 1", parentID, ownerID).
Order("name ASC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&folders).Error
return folders, err
}
func getFilesByFolder(folderID, userID uint64, roleIDs []uint64, page, pageSize int) ([]model.File, int64, error) {
var files []model.File
query := config.DB.Where("folder_id = ? AND owner_id = ? AND status = 1", folderID, userID)
var total int64
query.Model(&model.File{}).Count(&total)
err := query.Order("name ASC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&files).Error
return files, total, err
}
func getFolderChildCount(folderID uint64) int64 {
var count int64
config.DB.Model(&model.Folder{}).Where("parent_id = ? AND status = 1", folderID).Count(&count)
return count
}
func searchFilesByName(userID uint64, roleIDs []uint64, keyword string, page, pageSize int) ([]model.File, int64, error) {
var files []model.File
query := config.DB.Where("owner_id = ? AND status = 1 AND name LIKE ?", userID, "%"+keyword+"%")
var total int64
query.Model(&model.File{}).Count(&total)
err := query.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&files).Error
return files, total, err
}
func searchFoldersByName(ownerID uint64, keyword string, page, pageSize int) ([]model.Folder, error) {
var folders []model.Folder
err := config.DB.Where("owner_id = ? AND status = 1 AND name LIKE ?", ownerID, "%"+keyword+"%").
Order("name ASC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&folders).Error
return folders, err
}
func getCDNOrPresignedURL(file *model.File) string {
if file.StorageKey == "" {
return ""
}
engine, err := storage.GetEngineByPolicyID(file.StoragePolicyID)
if err != nil {
return ""
}
if localEngine, ok := engine.(*storage.LocalEngine); ok {
return localEngine.GetURL(file.StorageKey)
}
if minioEngine, ok := engine.(*storage.MinIOEngine); ok {
url, err := minioEngine.GetPresignedURL(file.StorageKey, 24*time.Hour)
if err == nil {
return url
}
}
return ""
}