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 }