375 lines
10 KiB
Go
375 lines
10 KiB
Go
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 ""
|
|
}
|