第一版完成
This commit is contained in:
353
internal/service/attachment.go
Normal file
353
internal/service/attachment.go
Normal file
@@ -0,0 +1,353 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"cms-api/internal/dao"
|
||||
"cms-api/internal/model"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
)
|
||||
|
||||
type sAttachment struct{}
|
||||
|
||||
func Attachment() *sAttachment {
|
||||
return &sAttachment{}
|
||||
}
|
||||
|
||||
// Upload 上传文件
|
||||
func (s *sAttachment) Upload(ctx context.Context, file *ghttp.UploadFile, uploadType string) (*model.Attachment, error) {
|
||||
// 检查文件大小
|
||||
maxSize := int64(10 * 1024 * 1024) // 10MB
|
||||
if file.Size > maxSize {
|
||||
return nil, gerror.New("文件大小不能超过10MB")
|
||||
}
|
||||
|
||||
// 检查文件类型
|
||||
if !s.isAllowedFileType(file.Filename) {
|
||||
return nil, gerror.New("不支持的文件类型")
|
||||
}
|
||||
|
||||
// 生成文件名和路径
|
||||
fileName, filePath, fileUrl := s.generateFilePath(file.Filename, uploadType)
|
||||
|
||||
// 确保目录存在
|
||||
dir := filepath.Dir(filePath)
|
||||
if !gfile.Exists(dir) {
|
||||
if err := gfile.Mkdir(dir); err != nil {
|
||||
return nil, gerror.New("创建目录失败")
|
||||
}
|
||||
}
|
||||
|
||||
// 保存文件
|
||||
if _, err := file.Save(filePath, true); err != nil {
|
||||
return nil, gerror.New("保存文件失败: " + err.Error())
|
||||
}
|
||||
|
||||
// 获取上传者ID
|
||||
uploadedBy := gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("admin_id"))
|
||||
if uploadedBy == 0 {
|
||||
uploadedBy = gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("user_id"))
|
||||
}
|
||||
|
||||
// 获取客户端IP
|
||||
uploadIp := g.RequestFromCtx(ctx).GetClientIp()
|
||||
|
||||
// 创建附件记录
|
||||
attachment := &model.Attachment{
|
||||
OriginalName: file.Filename,
|
||||
FileName: fileName,
|
||||
FilePath: filePath,
|
||||
FileUrl: fileUrl,
|
||||
FileSize: file.Size,
|
||||
FileType: s.getFileType(file.Filename),
|
||||
MimeType: s.getMimeType(file.Filename),
|
||||
FileExt: s.getFileExt(file.Filename),
|
||||
StorageType: "local",
|
||||
UploadIp: uploadIp,
|
||||
UploadedBy: uploadedBy,
|
||||
UsageCount: 0,
|
||||
CreatedAt: gtime.Now(),
|
||||
UpdatedAt: gtime.Now(),
|
||||
}
|
||||
|
||||
id, err := dao.Attachment.Create(ctx, attachment)
|
||||
if err != nil {
|
||||
// 如果数据库保存失败,删除已上传的文件
|
||||
os.Remove(filePath)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attachment.Id = int(id)
|
||||
return attachment, nil
|
||||
}
|
||||
|
||||
// GetById 根据ID获取附件信息
|
||||
func (s *sAttachment) GetById(ctx context.Context, id int) (*model.Attachment, error) {
|
||||
return dao.Attachment.GetById(ctx, id)
|
||||
}
|
||||
|
||||
// List 获取附件列表
|
||||
func (s *sAttachment) List(ctx context.Context, req *model.AttachmentListRequest) (*model.PageResponse, error) {
|
||||
var (
|
||||
page = req.Page
|
||||
pageSize = req.PageSize
|
||||
)
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 设置分页参数
|
||||
req.Page = page
|
||||
req.PageSize = pageSize
|
||||
|
||||
// 获取列表
|
||||
attachments, total, err := dao.Attachment.List(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.PageResponse{
|
||||
List: attachments,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: (total + pageSize - 1) / pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Delete 删除附件
|
||||
func (s *sAttachment) Delete(ctx context.Context, id int) error {
|
||||
// 检查附件是否存在
|
||||
attachment, err := s.GetById(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if attachment == nil {
|
||||
return gerror.New("附件不存在")
|
||||
}
|
||||
|
||||
// 删除物理文件
|
||||
if gfile.Exists(attachment.FilePath) {
|
||||
if err := os.Remove(attachment.FilePath); err != nil {
|
||||
g.Log().Error(ctx, "删除物理文件失败:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除数据库记录
|
||||
return dao.Attachment.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// UpdateUsageCount 更新使用次数
|
||||
func (s *sAttachment) UpdateUsageCount(ctx context.Context, id int) error {
|
||||
return dao.Attachment.IncrementUsageCount(ctx, id)
|
||||
}
|
||||
|
||||
// GetStats 获取附件统计信息
|
||||
func (s *sAttachment) GetStats(ctx context.Context) (map[string]interface{}, error) {
|
||||
// 获取总附件数
|
||||
totalCount, err := dao.Attachment.GetCount(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取总文件大小
|
||||
totalSize, err := dao.Attachment.GetTotalSize(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取各类型文件数量
|
||||
imageCount, err := dao.Attachment.GetCountByType(ctx, "image")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
documentCount, err := dao.Attachment.GetCountByType(ctx, "document")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
videoCount, err := dao.Attachment.GetCountByType(ctx, "video")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"total_count": totalCount,
|
||||
"total_size": totalSize,
|
||||
"image_count": imageCount,
|
||||
"document_count": documentCount,
|
||||
"video_count": videoCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isAllowedFileType 检查文件类型是否允许
|
||||
func (s *sAttachment) isAllowedFileType(filename string) bool {
|
||||
allowedExts := []string{
|
||||
".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", // 图片
|
||||
".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt", // 文档
|
||||
".mp4", ".avi", ".mov", ".wmv", ".flv", ".mkv", // 视频
|
||||
".mp3", ".wav", ".flac", ".aac", // 音频
|
||||
".zip", ".rar", ".7z", ".tar", ".gz", // 压缩包
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
for _, allowedExt := range allowedExts {
|
||||
if ext == allowedExt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// generateFilePath 生成文件路径
|
||||
func (s *sAttachment) generateFilePath(originalName, uploadType string) (string, string, string) {
|
||||
// 获取文件扩展名
|
||||
ext := filepath.Ext(originalName)
|
||||
|
||||
// 生成唯一文件名
|
||||
hash := gmd5.MustEncrypt(fmt.Sprintf("%s_%d_%s", originalName, time.Now().UnixNano(), grand.S(8)))
|
||||
fileName := hash + ext
|
||||
|
||||
// 根据日期创建目录结构
|
||||
now := time.Now()
|
||||
dateDir := fmt.Sprintf("%d/%02d/%02d", now.Year(), now.Month(), now.Day())
|
||||
|
||||
// 根据上传类型创建子目录
|
||||
if uploadType == "" {
|
||||
uploadType = "general"
|
||||
}
|
||||
|
||||
// 构建完整路径
|
||||
relativePath := fmt.Sprintf("uploads/%s/%s/%s", uploadType, dateDir, fileName)
|
||||
fullPath := filepath.Join("storage", relativePath)
|
||||
fileUrl := "/" + strings.ReplaceAll(relativePath, "\\", "/")
|
||||
|
||||
return fileName, fullPath, fileUrl
|
||||
}
|
||||
|
||||
// getFileType 获取文件类型
|
||||
func (s *sAttachment) getFileType(filename string) string {
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
|
||||
imageExts := []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"}
|
||||
documentExts := []string{".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt"}
|
||||
videoExts := []string{".mp4", ".avi", ".mov", ".wmv", ".flv", ".mkv"}
|
||||
audioExts := []string{".mp3", ".wav", ".flac", ".aac"}
|
||||
archiveExts := []string{".zip", ".rar", ".7z", ".tar", ".gz"}
|
||||
|
||||
for _, imageExt := range imageExts {
|
||||
if ext == imageExt {
|
||||
return "image"
|
||||
}
|
||||
}
|
||||
|
||||
for _, docExt := range documentExts {
|
||||
if ext == docExt {
|
||||
return "document"
|
||||
}
|
||||
}
|
||||
|
||||
for _, videoExt := range videoExts {
|
||||
if ext == videoExt {
|
||||
return "video"
|
||||
}
|
||||
}
|
||||
|
||||
for _, audioExt := range audioExts {
|
||||
if ext == audioExt {
|
||||
return "audio"
|
||||
}
|
||||
}
|
||||
|
||||
for _, archiveExt := range archiveExts {
|
||||
if ext == archiveExt {
|
||||
return "archive"
|
||||
}
|
||||
}
|
||||
|
||||
return "other"
|
||||
}
|
||||
|
||||
// getMimeType 获取MIME类型
|
||||
func (s *sAttachment) getMimeType(filename string) string {
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
|
||||
mimeTypes := map[string]string{
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".png": "image/png",
|
||||
".gif": "image/gif",
|
||||
".bmp": "image/bmp",
|
||||
".webp": "image/webp",
|
||||
".pdf": "application/pdf",
|
||||
".doc": "application/msword",
|
||||
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
".xls": "application/vnd.ms-excel",
|
||||
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
".ppt": "application/vnd.ms-powerpoint",
|
||||
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
".txt": "text/plain",
|
||||
".mp4": "video/mp4",
|
||||
".avi": "video/x-msvideo",
|
||||
".mov": "video/quicktime",
|
||||
".wmv": "video/x-ms-wmv",
|
||||
".flv": "video/x-flv",
|
||||
".mkv": "video/x-matroska",
|
||||
".mp3": "audio/mpeg",
|
||||
".wav": "audio/wav",
|
||||
".flac": "audio/flac",
|
||||
".aac": "audio/aac",
|
||||
".zip": "application/zip",
|
||||
".rar": "application/x-rar-compressed",
|
||||
".7z": "application/x-7z-compressed",
|
||||
".tar": "application/x-tar",
|
||||
".gz": "application/gzip",
|
||||
}
|
||||
|
||||
if mimeType, exists := mimeTypes[ext]; exists {
|
||||
return mimeType
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
|
||||
// getFileExt 获取文件扩展名
|
||||
func (s *sAttachment) getFileExt(filename string) string {
|
||||
ext := filepath.Ext(filename)
|
||||
if len(ext) > 0 {
|
||||
return ext[1:] // 去掉点号
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除附件
|
||||
func (s *sAttachment) BatchDelete(ctx context.Context, ids []int) error {
|
||||
for _, id := range ids {
|
||||
if err := s.Delete(ctx, id); err != nil {
|
||||
g.Log().Error(ctx, "批量删除附件失败, ID:", id, "错误:", err)
|
||||
// 继续删除其他文件,不中断整个过程
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByIds 根据ID列表获取附件
|
||||
func (s *sAttachment) GetByIds(ctx context.Context, ids []int) ([]*model.Attachment, error) {
|
||||
return dao.Attachment.GetByIds(ctx, ids)
|
||||
}
|
||||
Reference in New Issue
Block a user