208 lines
4.9 KiB
Go
208 lines
4.9 KiB
Go
/**
|
||
* package service
|
||
* 作用:附件管理服务
|
||
*/
|
||
package service
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"mime"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
"xk-websocket-v2/internal/model"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// AttachmentService 附件服务结构体
|
||
type AttachmentService struct {
|
||
DB *gorm.DB
|
||
}
|
||
|
||
// AttachmentSvc 全局单例
|
||
var AttachmentSvc *AttachmentService
|
||
|
||
/**
|
||
* InitAttachmentService
|
||
* 功能:初始化附件服务
|
||
*/
|
||
func InitAttachmentService(db *gorm.DB) {
|
||
AttachmentSvc = &AttachmentService{DB: db}
|
||
// 创建上传目录
|
||
os.MkdirAll("./uploads/images", os.ModePerm)
|
||
os.MkdirAll("./uploads/videos", os.ModePerm)
|
||
}
|
||
|
||
/**
|
||
* UploadFile
|
||
* 功能:上传文件
|
||
* @param userID 上传者ID
|
||
* @param fileName 文件名
|
||
* @param fileType 文件类型(image/video)
|
||
* @param fileSize 文件大小
|
||
* @param fileData 文件数据
|
||
* @returns 附件信息和错误
|
||
*/
|
||
func (s *AttachmentService) UploadFile(userID, fileName, fileType string, fileSize int64, fileData io.Reader) (*model.Attachment, error) {
|
||
// 验证文件类型
|
||
if fileType != "image" && fileType != "video" {
|
||
return nil, errors.New("文件类型必须是image或video")
|
||
}
|
||
|
||
// 验证文件大小
|
||
if fileType == "image" && fileSize > model.MaxImageSize {
|
||
return nil, fmt.Errorf("图片大小不能超过%dMB", model.MaxImageSize/(1024*1024))
|
||
}
|
||
if fileType == "video" && fileSize > model.MaxVideoSize {
|
||
return nil, fmt.Errorf("视频大小不能超过%dMB", model.MaxVideoSize/(1024*1024))
|
||
}
|
||
|
||
// 验证文件扩展名
|
||
ext := strings.ToLower(filepath.Ext(fileName))
|
||
allowedExts := s.getAllowedExtensions(fileType)
|
||
if !contains(allowedExts, ext) {
|
||
return nil, fmt.Errorf("不支持的文件类型,允许的类型: %v", allowedExts)
|
||
}
|
||
|
||
// 生成唯一文件名
|
||
timestamp := time.Now().UnixNano()
|
||
randomStr := fmt.Sprintf("%d", timestamp%1000000)
|
||
newFileName := fmt.Sprintf("%d_%s%s", timestamp, randomStr, ext)
|
||
|
||
// 确定保存路径
|
||
var saveDir string
|
||
if fileType == "image" {
|
||
saveDir = "./uploads/images"
|
||
} else {
|
||
saveDir = "./uploads/videos"
|
||
}
|
||
|
||
filePath := filepath.Join(saveDir, newFileName)
|
||
|
||
// 保存文件
|
||
file, err := os.Create(filePath)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建文件失败: %v", err)
|
||
}
|
||
defer file.Close()
|
||
|
||
_, err = io.Copy(file, fileData)
|
||
if err != nil {
|
||
os.Remove(filePath) // 删除失败的文件
|
||
return nil, fmt.Errorf("保存文件失败: %v", err)
|
||
}
|
||
|
||
// 生成访问URL
|
||
fileURL := fmt.Sprintf("/uploads/%s/%s", fileType+"s", newFileName)
|
||
|
||
// 获取MIME类型
|
||
mimeType := mime.TypeByExtension(ext)
|
||
if mimeType == "" {
|
||
mimeType = "application/octet-stream"
|
||
}
|
||
|
||
// 创建附件记录
|
||
attachment := model.Attachment{
|
||
UploaderID: userID,
|
||
FileName: fileName,
|
||
FileType: fileType,
|
||
FileSize: fileSize,
|
||
FilePath: filePath,
|
||
FileURL: fileURL,
|
||
MimeType: mimeType,
|
||
}
|
||
|
||
if err := s.DB.Create(&attachment).Error; err != nil {
|
||
os.Remove(filePath) // 删除文件
|
||
return nil, fmt.Errorf("保存附件记录失败: %v", err)
|
||
}
|
||
|
||
return &attachment, nil
|
||
}
|
||
|
||
/**
|
||
* GetAttachment
|
||
* 功能:获取附件信息
|
||
*/
|
||
func (s *AttachmentService) GetAttachment(attachmentID uint) (*model.Attachment, error) {
|
||
var attachment model.Attachment
|
||
result := s.DB.First(&attachment, attachmentID)
|
||
if result.Error != nil {
|
||
return nil, result.Error
|
||
}
|
||
return &attachment, nil
|
||
}
|
||
|
||
/**
|
||
* DeleteAttachment
|
||
* 功能:删除附件(验证上传者权限)
|
||
*/
|
||
func (s *AttachmentService) DeleteAttachment(attachmentID uint, userID string) error {
|
||
var attachment model.Attachment
|
||
if err := s.DB.First(&attachment, attachmentID).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
// 验证权限
|
||
if attachment.UploaderID != userID {
|
||
return errors.New("无权删除此附件")
|
||
}
|
||
|
||
// 删除文件
|
||
if _, err := os.Stat(attachment.FilePath); err == nil {
|
||
os.Remove(attachment.FilePath)
|
||
}
|
||
|
||
// 删除记录
|
||
return s.DB.Delete(&attachment).Error
|
||
}
|
||
|
||
/**
|
||
* GetUserAttachments
|
||
* 功能:获取用户附件列表
|
||
*/
|
||
func (s *AttachmentService) GetUserAttachments(userID, fileType string, page, pageSize int) ([]model.Attachment, int64, error) {
|
||
var attachments []model.Attachment
|
||
var total int64
|
||
|
||
query := s.DB.Where("uploader_id = ?", userID)
|
||
if fileType != "" {
|
||
query = query.Where("file_type = ?", fileType)
|
||
}
|
||
|
||
// 获取总数
|
||
query.Model(&model.Attachment{}).Count(&total)
|
||
|
||
// 分页查询
|
||
offset := (page - 1) * pageSize
|
||
result := query.Order("created_at DESC").
|
||
Offset(offset).
|
||
Limit(pageSize).
|
||
Find(&attachments)
|
||
|
||
return attachments, total, result.Error
|
||
}
|
||
|
||
// 辅助函数:获取允许的文件扩展名
|
||
func (s *AttachmentService) getAllowedExtensions(fileType string) []string {
|
||
if fileType == "image" {
|
||
return []string{".jpg", ".jpeg", ".png", ".gif", ".webp"}
|
||
}
|
||
return []string{".mp4", ".avi", ".mov", ".wmv", ".flv", ".mkv"}
|
||
}
|
||
|
||
// 辅助函数:检查字符串是否在切片中
|
||
func contains(slice []string, item string) bool {
|
||
for _, s := range slice {
|
||
if s == item {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|