部分重构

This commit is contained in:
2026-03-31 20:06:53 +08:00
parent 6d79836682
commit 498cc78dce
43 changed files with 6049 additions and 131 deletions
+287
View File
@@ -0,0 +1,287 @@
package service
import (
"mime"
"mime/multipart"
"ops/internal/repository"
"ops/models"
"path/filepath"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type FileService interface {
UploadFile(c *gin.Context, userID uint, fileHeader *multipart.FileHeader, fileType, description string) (UploadResponse, bool)
GetFileList(userID uint, fileType string, page, entries int) (FileListResponse, bool)
GetFileByID(fileID uint, userID uint) (*models.TabFileInfo_, bool)
GetFileByHash(hash string) (*models.TabFileInfo_, bool)
DeleteFile(fileID uint, userID uint) bool
DownloadFile(c *gin.Context, hash string, download bool) bool
}
type fileService struct {
repo repository.FileRepository
}
func NewFileService(db *gorm.DB) FileService {
return &fileService{
repo: repository.NewFileRepository(db),
}
}
// 响应结构体
type UploadResponse struct {
FileID uint `json:"file_id"`
Name string `json:"name"`
SHA256 string `json:"sha256"`
Mime string `json:"mime"`
Size int64 `json:"size"`
DownloadURL string `json:"download_url"`
PreviewURL string `json:"preview_url"`
CreatedAt string `json:"created_at"`
}
type FileListResponse struct {
Files []FileInfo `json:"files"`
Total int64 `json:"total"`
Page int `json:"page"`
Pages int `json:"pages"`
}
type FileInfo struct {
FileID uint `json:"file_id"`
Name string `json:"name"`
SHA256 string `json:"sha256"`
Mime string `json:"mime"`
Size int64 `json:"size"`
Type string `json:"type"`
CreatedAt string `json:"created_at"`
}
func (s *fileService) UploadFile(c *gin.Context, userID uint, fileHeader *multipart.FileHeader, fileType, description string) (UploadResponse, bool) {
// 验证文件大小
if fileHeader.Size > int64(models.ConfigsFile.MaxSize) {
return UploadResponse{}, false
}
// 验证文件最小大小
if fileHeader.Size < 512 {
return UploadResponse{}, false
}
// 验证文件名
if fileHeader.Filename == "" {
return UploadResponse{}, false
}
// 安全处理文件名
filename := filepath.Base(fileHeader.Filename)
// 计算文件哈希
hashStr, err := models.SHA256HashFile(fileHeader)
if err != nil {
return UploadResponse{}, false
}
// 获取文件MIME类型
mimeType, err := models.GetFileMime(fileHeader)
if err != nil {
return UploadResponse{}, false
}
// 验证MIME类型(如果是图片)
if fileType == "image" {
if models.ConfigsFile.AllowImageMime[mimeType] == "" {
return UploadResponse{}, false
}
}
// 构建文件保存路径
var savePath string
switch fileType {
case "image":
savePath = filepath.Join(models.ConfigsFile.Pahts["image"], hashStr)
default:
savePath = filepath.Join(models.ConfigsFile.Pahts["default"], hashStr)
}
// 检查文件是否已存在
if models.FileExists(savePath) {
// 如果文件已存在,增加使用计数
existingFile, err := s.repo.GetFileByHash(hashStr)
if err == nil && existingFile != nil {
s.repo.IncrementFileUsage(existingFile.ID)
}
} else {
// 保存文件到磁盘
if err := c.SaveUploadedFile(fileHeader, savePath); err != nil {
return UploadResponse{}, false
}
}
// 检查数据库中是否已存在该文件
existingFile, _ := s.repo.GetFileByHash(hashStr)
if existingFile != nil {
// 更新使用计数
s.repo.IncrementFileUsage(existingFile.ID)
return UploadResponse{
FileID: existingFile.ID,
Name: filename,
SHA256: hashStr,
Mime: mimeType,
Size: fileHeader.Size,
DownloadURL: "/api/v1/files/download/" + hashStr,
PreviewURL: "/api/v1/files/get/" + hashStr,
CreatedAt: existingFile.Date.Format("2006-01-02T15:04:05Z"),
}, true
}
// 创建新的文件记录
newFile := &models.TabFileInfo_{
Name: filename,
Path: savePath,
Sha256: hashStr,
Mime: mimeType,
Type: fileType,
UserID: userID,
Date: time.Now(),
}
if err := s.repo.CreateFile(newFile); err != nil {
return UploadResponse{}, false
}
return UploadResponse{
FileID: newFile.ID,
Name: filename,
SHA256: hashStr,
Mime: mimeType,
Size: fileHeader.Size,
DownloadURL: "/api/v1/files/download/" + hashStr,
PreviewURL: "/api/v1/files/get/" + hashStr,
CreatedAt: newFile.Date.Format("2006-01-02T15:04:05Z"),
}, true
}
func (s *fileService) GetFileList(userID uint, fileType string, page, entries int) (FileListResponse, bool) {
// 验证分页参数
if entries <= 0 || entries > 100 {
return FileListResponse{}, false
}
if page <= 0 {
return FileListResponse{}, false
}
files, total, err := s.repo.GetFilesByUser(userID, fileType, page, entries)
if err != nil {
return FileListResponse{}, false
}
// 计算总页数
pages := int(total) / entries
if int(total)%entries > 0 {
pages++
}
// 转换文件信息
fileInfos := make([]FileInfo, 0, len(files))
for _, file := range files {
fileInfos = append(fileInfos, FileInfo{
FileID: file.ID,
Name: file.Name,
SHA256: file.Sha256,
Mime: file.Mime,
Type: file.Type,
CreatedAt: file.Date.Format("2006-01-02T15:04:05Z"),
})
}
return FileListResponse{
Files: fileInfos,
Total: total,
Page: page,
Pages: pages,
}, true
}
func (s *fileService) GetFileByID(fileID uint, userID uint) (*models.TabFileInfo_, bool) {
file, err := s.repo.GetFileByID(fileID)
if err != nil {
return nil, false
}
// 检查文件所有权
if file.UserID != userID {
return nil, false
}
return file, true
}
func (s *fileService) GetFileByHash(hash string) (*models.TabFileInfo_, bool) {
file, err := s.repo.GetFileByHash(hash)
if err != nil {
return nil, false
}
return file, true
}
func (s *fileService) DeleteFile(fileID uint, userID uint) bool {
// 首先检查文件所有权
file, err := s.repo.GetFileByID(fileID)
if err != nil {
return false
}
if file.UserID != userID {
return false
}
// 删除文件记录
if err := s.repo.DeleteFile(fileID); err != nil {
return false
}
// 注意:这里不删除物理文件,因为可能还有其他引用
// 如果需要删除物理文件,需要检查引用计数
return true
}
func (s *fileService) DownloadFile(c *gin.Context, hash string, download bool) bool {
file, err := s.repo.GetFileByHash(hash)
if err != nil {
return false
}
// 检查文件是否存在
if !models.FileExists(file.Path) {
return false
}
// 设置响应头
if download {
// 下载模式
c.Header("Content-Disposition", "attachment; filename=\""+file.Name+"\"")
} else {
// 预览模式
ext := filepath.Ext(file.Name)
if ext != "" {
mimeType := mime.TypeByExtension(ext)
if mimeType != "" {
c.Header("Content-Type", mimeType)
}
}
}
c.Header("Content-Type", "application/octet-stream")
c.File(file.Path)
// 增加使用计数
s.repo.IncrementFileUsage(file.ID)
return true
}