🐛 添加被忽略的必要文件
This commit is contained in:
117
server/utils/upload/local.go
Normal file
117
server/utils/upload/local.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package upload
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.echol.cn/loser/ai_wanjia/global"
|
||||
"git.echol.cn/loser/ai_wanjia/utils"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var mu sync.Mutex
|
||||
|
||||
type Local struct{}
|
||||
|
||||
//@author: [piexlmax](https://github.com/piexlmax)
|
||||
//@author: [ccfish86](https://github.com/ccfish86)
|
||||
//@author: [SliverHorn](https://github.com/SliverHorn)
|
||||
//@object: *Local
|
||||
//@function: UploadFile
|
||||
//@description: 上传文件,按来源平台和用户ID分目录存储
|
||||
//@param: file *multipart.FileHeader, ctx UploadContext
|
||||
//@return: string, string, error
|
||||
|
||||
func (*Local) UploadFile(file *multipart.FileHeader, ctx UploadContext) (string, string, error) {
|
||||
// 读取文件后缀
|
||||
ext := filepath.Ext(file.Filename)
|
||||
// 读取文件名并加密
|
||||
name := strings.TrimSuffix(file.Filename, ext)
|
||||
name = utils.MD5V([]byte(name))
|
||||
// 拼接新文件名
|
||||
filename := name + "_" + time.Now().Format("20060102150405") + ext
|
||||
|
||||
// 生成按平台+用户+日期分组的子目录
|
||||
subDir := ctx.SubDir()
|
||||
storePath := filepath.Join(global.GVA_CONFIG.Local.StorePath, subDir)
|
||||
|
||||
// 尝试创建目录
|
||||
mkdirErr := os.MkdirAll(storePath, os.ModePerm)
|
||||
if mkdirErr != nil {
|
||||
global.GVA_LOG.Error("function os.MkdirAll() failed", zap.Any("err", mkdirErr.Error()))
|
||||
return "", "", errors.New("function os.MkdirAll() failed, err:" + mkdirErr.Error())
|
||||
}
|
||||
|
||||
// 拼接绝对存储路径和访问路径
|
||||
p := filepath.Join(storePath, filename)
|
||||
// key 为相对路径(含子目录),用于后续删除时定位文件
|
||||
key := subDir + "/" + filename
|
||||
// 若配置了域名前缀,则返回完整 URL;否则返回相对路径
|
||||
fileURL := global.GVA_CONFIG.Local.Path + "/" + key
|
||||
if base := strings.TrimRight(global.GVA_CONFIG.Local.BaseURL, "/"); base != "" {
|
||||
fileURL = base + "/" + global.GVA_CONFIG.Local.Path + "/" + key
|
||||
}
|
||||
|
||||
f, openError := file.Open()
|
||||
if openError != nil {
|
||||
global.GVA_LOG.Error("function file.Open() failed", zap.Any("err", openError.Error()))
|
||||
return "", "", errors.New("function file.Open() failed, err:" + openError.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
out, createErr := os.Create(p)
|
||||
if createErr != nil {
|
||||
global.GVA_LOG.Error("function os.Create() failed", zap.Any("err", createErr.Error()))
|
||||
return "", "", errors.New("function os.Create() failed, err:" + createErr.Error())
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, copyErr := io.Copy(out, f)
|
||||
if copyErr != nil {
|
||||
global.GVA_LOG.Error("function io.Copy() failed", zap.Any("err", copyErr.Error()))
|
||||
return "", "", errors.New("function io.Copy() failed, err:" + copyErr.Error())
|
||||
}
|
||||
return fileURL, key, nil
|
||||
}
|
||||
|
||||
//@author: [piexlmax](https://github.com/piexlmax)
|
||||
//@author: [ccfish86](https://github.com/ccfish86)
|
||||
//@author: [SliverHorn](https://github.com/SliverHorn)
|
||||
//@object: *Local
|
||||
//@function: DeleteFile
|
||||
//@description: 删除文件
|
||||
//@param: key string
|
||||
//@return: error
|
||||
|
||||
func (*Local) DeleteFile(key string) error {
|
||||
if key == "" {
|
||||
return errors.New("key不能为空")
|
||||
}
|
||||
|
||||
// 防止路径遍历:拒绝 .. 及非法字符(/ 允许,用于子目录)
|
||||
if strings.Contains(key, "..") || strings.ContainsAny(key, `\*?"<>|`) {
|
||||
return errors.New("非法的key")
|
||||
}
|
||||
|
||||
p := filepath.Join(global.GVA_CONFIG.Local.StorePath, key)
|
||||
|
||||
if _, err := os.Stat(p); os.IsNotExist(err) {
|
||||
return errors.New("文件不存在")
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
err := os.Remove(p)
|
||||
if err != nil {
|
||||
return errors.New("文件删除失败: " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user