部分重构
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user