287 lines
6.8 KiB
Go
287 lines
6.8 KiB
Go
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
|
|
} |