package service import ( "context" "errors" "fmt" "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/text/gregex" "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/grand" "github.com/tencentyun/cos-go-sdk-v5" "github.com/tencentyun/cos-go-sdk-v5/debug" "io" "log" "net/http" "net/url" "strconv" "strings" "time" "tyj_admin/api/v1/common" "tyj_admin/internal/consts" "tyj_admin/internal/model/entity" ) type IUpload interface { UploadFile(ctx context.Context, file *ghttp.UploadFile, checkFileType string, source int) (result common.UploadResponse, err error) UploadFiles(ctx context.Context, files []*ghttp.UploadFile, checkFileType string, source int) (result common.UploadMultipleRes, err error) } type uploadTmpl struct{} func Upload() IUpload { return &uploadTmpl{} } // 上传多文件 func (s *uploadTmpl) UploadFiles(ctx context.Context, files []*ghttp.UploadFile, checkFileType string, source int) (result common.UploadMultipleRes, err error) { for _, item := range files { f, e := s.UploadFile(ctx, item, checkFileType, source) if e != nil { return } result = append(result, &f) } return } // 上传单文件 func (s *uploadTmpl) UploadFile(ctx context.Context, file *ghttp.UploadFile, checkFileType string, source int) (result common.UploadResponse, err error) { // 检查文件类型 err = s.CheckType(ctx, checkFileType, file) if err != nil { return } // 检查文件大小 err = s.CheckSize(ctx, checkFileType, file) if err != nil { return } // 非图片文件只能上传至本地 if checkFileType == consts.CheckFileTypeFile { source = consts.SourceDownload } switch source { // 上传至本地 case consts.SourceLocal: result, err = s.UploadLocal(ctx, file) // 上传至本地 case consts.SourceDownload: result, err = s.UploadLocalDownload(ctx, file) // 上传至腾讯云 case consts.SourceTencent: result, err = s.UploadTencent(ctx, file) default: err = errors.New("source参数错误!") } if err != nil { return } return } // 上传至腾讯云 func (s *uploadTmpl) UploadTencent(ctx context.Context, file *ghttp.UploadFile) (result common.UploadResponse, err error) { v, err := g.Cfg().Get(ctx, "upload.tencentCOS") if err != nil { return } m := v.MapStrVar() var ( upPath = m["upPath"].String() rawUrl = m["rawUrl"].String() secretID = m["secretID"].String() secretKey = m["secretKey"].String() ) name := gfile.Basename(file.Filename) name = strings.ToLower(strconv.FormatInt(gtime.TimestampNano(), 36) + grand.S(6)) name = name + gfile.Ext(file.Filename) path := upPath + name url, _ := url.Parse(rawUrl) b := &cos.BaseURL{BucketURL: url} client := cos.NewClient(b, &http.Client{ Transport: &cos.AuthorizationTransport{ SecretID: secretID, SecretKey: secretKey, Transport: &debug.DebugRequestTransport{ RequestHeader: false, RequestBody: false, ResponseHeader: false, ResponseBody: false, }, }, }) opt := &cos.ObjectPutOptions{ ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ ContentLength: int64(file.Size), }, } var f io.ReadCloser f, err = file.Open() if err != nil { return } defer f.Close() _, err = client.Object.Put(context.Background(), path, f, opt) result = common.UploadResponse{ Size: file.Size, Path: rawUrl + path, FullPath: rawUrl + path, Name: file.Filename, Type: file.Header.Get("Content-type"), } return } // 上传本地 func (s *uploadTmpl) UploadLocal(ctx context.Context, file *ghttp.UploadFile) (result common.UploadResponse, err error) { if file == nil { err = errors.New("文件必须!") return } r := g.RequestFromCtx(ctx) urlPerfix := fmt.Sprintf("http://%s/", r.Host) p := strings.Trim(consts.UploadPath, "/") sp := s.getStaticPath(ctx) if sp != "" { sp = strings.Trim(sp, "/") } nowData := time.Now().Format("2006-01-02") // 包含静态文件夹的路径 fullDirPath := sp + "/" + p + "/" + nowData fileName, err := file.Save(fullDirPath, true) if err != nil { return } // 不含静态文件夹的路径 fullPath := p + "/" + nowData + "/" + fileName result = common.UploadResponse{ Size: file.Size, Path: fullPath, FullPath: urlPerfix + fullPath, Name: file.Filename, Type: file.Header.Get("Content-type"), } return } // 上传本地download func (s *uploadTmpl) UploadLocalDownload(ctx context.Context, file *ghttp.UploadFile) (result common.UploadResponse, err error) { if file == nil { err = errors.New("文件必须!") return } r := g.RequestFromCtx(ctx) urlPerfix := fmt.Sprintf("http://%s/", r.Host) p := strings.Trim(consts.DownloadPath, "/") //sp := s.getStaticPath(ctx) //if sp != "" { // sp = strings.Trim(sp, "/") //} //nowData := time.Now().Format("2006-01-02") // 包含静态文件夹的路径 //fullDirPath := p fileName, err := file.Save(p) if err != nil { return } // 不含静态文件夹的路径 fullPath := p + "/" + fileName log.Printf("fullPath: %s, name: %s", fullPath, file.Filename) result = common.UploadResponse{ Size: file.Size, Path: fullPath, FullPath: urlPerfix + fullPath, Name: file.Filename, Type: file.Header.Get("Content-type"), } return } // 检查上传文件大小 func (s *uploadTmpl) CheckSize(ctx context.Context, checkFileType string, file *ghttp.UploadFile) (err error) { var ( configSize *entity.SysConfig ) if checkFileType == consts.CheckFileTypeFile { //获取上传大小配置 configSize, err = s.getUpConfig(ctx, consts.FileSizeKey) if err != nil { return } } else if checkFileType == consts.CheckFileTypeImg { //获取上传大小配置 configSize, err = s.getUpConfig(ctx, consts.ImgSizeKey) if err != nil { return } } else { return errors.New(fmt.Sprintf("文件检查类型错误:%s|%s", consts.CheckFileTypeFile, consts.CheckFileTypeImg)) } var rightSize bool rightSize, err = s.checkSize(configSize.ConfigValue, file.Size) if err != nil { return } if !rightSize { err = gerror.New("上传文件超过最大尺寸:" + configSize.ConfigValue) return } return } // 检查上传文件类型 func (s *uploadTmpl) CheckType(ctx context.Context, checkFileType string, file *ghttp.UploadFile) (err error) { var ( configType *entity.SysConfig ) if checkFileType == consts.CheckFileTypeFile { //获取上传类型配置 configType, err = s.getUpConfig(ctx, consts.FileTypeKey) if err != nil { return } } else if checkFileType == consts.CheckFileTypeImg { //获取上传类型配置 configType, err = s.getUpConfig(ctx, consts.ImgTypeKey) if err != nil { return } } else { return errors.New(fmt.Sprintf("文件检查类型错误:%s|%s", consts.CheckFileTypeFile, consts.CheckFileTypeImg)) } rightType := s.checkFileType(file.Filename, configType.ConfigValue) if !rightType { err = gerror.New("上传文件类型错误,只能包含后缀为:" + configType.ConfigValue + "的文件。") return } return } // 获取上传配置 func (s *uploadTmpl) getUpConfig(ctx context.Context, key string) (config *entity.SysConfig, err error) { config, err = Config().GetConfigByKey(ctx, key) if err != nil { return } if config == nil { err = gerror.New("上传文件类型未设置,请在后台配置" + key) return } return } // 判断上传文件类型是否合法 func (s *uploadTmpl) checkFileType(fileName, typeString string) bool { suffix := gstr.SubStrRune(fileName, gstr.PosRRune(fileName, ".")+1, gstr.LenRune(fileName)-1) imageType := gstr.Split(typeString, ",") rightType := false for _, v := range imageType { if gstr.Equal(suffix, v) { rightType = true break } } return rightType } // 检查文件大小是否合法 func (s *uploadTmpl) checkSize(configSize string, fileSize int64) (bool, error) { match, err := gregex.MatchString(`^([0-9]+)(?i:([a-z]*))$`, configSize) if err != nil { return false, err } if len(match) == 0 { err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB)") return false, err } var cfSize int64 switch gstr.ToUpper(match[2]) { case "MB", "M": cfSize = gconv.Int64(match[1]) * 1024 * 1024 case "KB", "K": cfSize = gconv.Int64(match[1]) * 1024 case "": cfSize = gconv.Int64(match[1]) } if cfSize == 0 { err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB),最大单位为MB") return false, err } return cfSize >= fileSize, nil } // 静态文件夹目录 func (s *uploadTmpl) getStaticPath(ctx context.Context) string { value, _ := g.Cfg().Get(ctx, "server.serverRoot") if !value.IsEmpty() { return value.String() } return "" }