Files
web_template/internal/service/upload.go
2026-09-09 02:39:57 +08:00

182 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"time"
"gotest/internal/config"
"gotest/internal/storage"
)
// UploadService 文件上传服务
type UploadService struct {
storage storage.Storage
cfg *config.Config
}
// NewUploadService 创建上传服务实例
func NewUploadService(s storage.Storage, cfg *config.Config) *UploadService {
return &UploadService{storage: s, cfg: cfg}
}
// allowedModules 允许的模块列表
var allowedModules = map[string]string{
"enterprise": "enterprise",
"shop": "shop",
"food": "food",
"game": "game",
"app": "app",
"music": "music",
"common": "common",
}
// Upload 上传文件,返回访问 URL
// module 指定上传到的模块(enterprise/shop/food/game/app/music/common)
// filename 原始文件名,contentType MIME 类型,fileSize 文件大小,reader 文件内容流
func (s *UploadService) Upload(module string, filename string, contentType string, fileSize int64, reader io.Reader) (string, error) {
// 校验文件大小
if fileSize > s.cfg.Upload.MaxSize {
return "", fmt.Errorf("文件大小超过限制(最大 %d 字节)", s.cfg.Upload.MaxSize)
}
// 校验文件类型
allowed := false
for _, t := range s.cfg.Upload.AllowedTypes {
if t == contentType {
allowed = true
break
}
}
if !allowed {
return "", fmt.Errorf("不支持的文件类型: %s", contentType)
}
// 校验并规范化模块名
mod := allowedModules[module]
if mod == "" {
mod = "common"
}
// 生成存储 key:模块名/日期/时间戳文件名
ext := filepath.Ext(filename)
key := fmt.Sprintf("%s/%s%s", mod, time.Now().Format("2006/01/02/150405.000000"), ext)
url, err := s.storage.Upload(key, reader, contentType)
if err != nil {
return "", fmt.Errorf("文件上传失败: %w", err)
}
return url, nil
}
// FileInfo 文件信息
type FileInfo struct {
Name string `json:"name"`
URL string `json:"url"`
Size int64 `json:"size"`
ModTime string `json:"mod_time"`
IsDir bool `json:"is_dir"`
}
// ListFiles 列出指定模块下的所有文件(递归遍历子目录)
func (s *UploadService) ListFiles(module string) ([]FileInfo, error) {
mod := allowedModules[module]
if mod == "" {
mod = "common"
}
dirPath := filepath.Join(s.cfg.Upload.Path, mod)
var result []FileInfo
// 递归遍历
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil // 跳过错误
}
if info.IsDir() {
return nil // 跳过目录
}
// 计算相对路径作为 key
relPath, err := filepath.Rel(s.cfg.Upload.Path, path)
if err != nil {
return nil
}
relPath = filepath.ToSlash(relPath) // 统一用 /
url := s.storage.GetURL(relPath)
result = append(result, FileInfo{
Name: info.Name(),
URL: url,
Size: info.Size(),
ModTime: info.ModTime().Format("2006-01-02 15:04:05"),
IsDir: false,
})
return nil
})
if err != nil {
if os.IsNotExist(err) {
return []FileInfo{}, nil
}
return nil, fmt.Errorf("读取目录失败: %w", err)
}
// 按修改时间倒序
sort.Slice(result, func(i, j int) bool {
return result[i].ModTime > result[j].ModTime
})
return result, nil
}
// DeleteFile 删除指定模块下的文件
// filename 可以是文件名或完整相对路径(如 2026/07/10/xxx.png)
func (s *UploadService) DeleteFile(module, filename string) error {
mod := allowedModules[module]
if mod == "" {
mod = "common"
}
// 防止路径穿越:去掉模块名前缀(如果有的话)和 ..
cleanName := strings.ReplaceAll(filename, "..", "")
cleanName = strings.TrimPrefix(cleanName, mod+"/")
cleanName = strings.TrimPrefix(cleanName, mod)
key := filepath.Join(mod, cleanName)
key = filepath.ToSlash(key)
return s.storage.Delete(key)
}
// GetModules 返回所有支持的模块
func (s *UploadService) GetModules() []map[string]string {
return []map[string]string{
{"key": "enterprise", "label": "企业官网"},
{"key": "shop", "label": "跨境电商"},
{"key": "food", "label": "外卖平台"},
{"key": "game", "label": "游戏平台"},
{"key": "app", "label": "应用商店"},
{"key": "music", "label": "音乐平台"},
{"key": "common", "label": "公共资源"},
}
}
// EnsureUploadDir 确保上传目录和各模块子目录存在
func (s *UploadService) EnsureUploadDir() {
basePath := s.cfg.Upload.Path
for mod := range allowedModules {
os.MkdirAll(filepath.Join(basePath, mod), 0755)
}
}
// sanitizeFilename 清理文件名中的危险字符
func sanitizeFilename(name string) string {
name = strings.ReplaceAll(name, "..", "")
name = strings.ReplaceAll(name, "/", "_")
name = strings.ReplaceAll(name, "\\", "_")
return name
}