package service import ( "bytes" "encoding/base64" "encoding/json" "errors" "fmt" "io" "io/ioutil" "net/url" "github.com/fonchain_enterprise/fonchain-main/pkg/logic" "github.com/fonchain_enterprise/fonchain-main/pkg/model" "github.com/disintegration/imaging" "mime/multipart" "os" "path" "path/filepath" "strconv" "strings" "sync" "dubbo.apache.org/dubbo-go/v3/common/logger" "github.com/fonchain_enterprise/fonchain-main/pkg/config" "github.com/fonchain_enterprise/fonchain-main/pkg/e" "github.com/fonchain_enterprise/fonchain-main/pkg/serializer" "github.com/fonchain_enterprise/fonchain-main/pkg/utils" "github.com/fonchain_enterprise/utils/objstorage" "github.com/gin-gonic/gin" uuid "github.com/satori/go.uuid" ffmpeg "github.com/u2takey/ffmpeg-go" "go.uber.org/zap" ) var ( wg sync.WaitGroup ) const ( MediaPath = "./runtime/" RouteType = "static/" VideoType = "video" ImageType = "image" PngType = "png" ArtworkFilePath = "artwork" ArtworkChunkBasePath = "./runtime/tmp/artworks" ) func UploadImg(c *gin.Context) { var err error var filename string var fileFullName string source := c.PostForm("source") mask := c.PostForm("mask") action := c.PostForm("action") defineFileName := c.PostForm("defineFileName") urlParam := c.PostForm("urlParam") if mask == "" { mask = "default" } mediaType := c.PostForm("type") zap.L().Info("UploadImg 1", zap.Any("mask", mask)) var BasePath string if mediaType == "" || mediaType == ImageType { mediaType = ImageType } BasePath = fmt.Sprintf("%s%s", MediaPath, mediaType) //BaseRoute = fmt.Sprintf("%s%s", RouteType, mediaType) var isCompress int if cStr, ok := c.GetPostForm("is_compress"); ok { var errS error isCompress, errS = strconv.Atoi(cStr) if errS != nil { ResponseQuickMsg(c, e.Failed, errS.Error(), nil) return } } zap.L().Info("UploadImg 2 ", zap.Any("mask", mask)) // 检验参数 if mask == "" || source == "" { ResponseQuickMsg(c, e.Failed, e.GetMsg(e.InvalidParams), nil) return } file, err := c.FormFile("file") // 检验文件 if err != nil { zap.L().Error("Upload FormFile err", zap.Error(err)) ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } logger.Errorf("UploadImg 3 %+v", mask) // 判断是不是视频或者需要压缩 var oriUrl string if isCompress != 1 && mediaType != "video" && action == "" { oriUrl, err = quickBos(file, mediaType, mask, source, defineFileName) if err != nil { ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } if urlParam != "" { oriUrl = fmt.Sprintf("%s?%s", oriUrl, urlParam) } ResponseQuickMsg(c, e.Ok, e.GetMsg(e.Success), map[string]interface{}{ "ori_url": oriUrl, }) return } logger.Errorf("UploadImg 4 %+v", mask) //创建文件名 fileExt := strings.ToLower(path.Ext(file.Filename)) if defineFileName != "" { fileFullName = defineFileName } else { newUu, _err := uuid.NewV4() if _err != nil { ResponseQuickMsg(c, e.Failed, _err.Error(), nil) return } filename = newUu.String() fileFullName = fmt.Sprintf("%s%s", filename, fileExt) } //检测文件夹 不存在就创建 imgPath := fmt.Sprintf("%s/%s/%s", BasePath, source, mask) _, err = utils.CheckDirPath(imgPath, true) if err != nil { ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } dst := fmt.Sprintf("%s/%s", imgPath, fileFullName) logger.Errorf("UploadImg 5 %+v", mask) // 保存文件至指定路径 err = c.SaveUploadedFile(file, dst) if err != nil { logger.Errorf("Upload FormFile err", err) ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } logger.Errorf("UploadImg 6 %+v", mask) if action == model.ImgActionRotate { fileFullName = fmt.Sprintf("%s%s", filename, fileExt) newDst := fmt.Sprintf("%s/%s_rotate%v", imgPath, filename, fileExt) if err = logic.MakeThumbnail(dst, newDst); err != nil { //ResponseQuickMsg(c, e.Failed, e.GetMsg(e.ERROR_ROTATE_IMG), nil) //return } else { _ = os.Remove(dst) dst = newDst } } //localUrl := fmt.Sprintf("%s/%s/%s/%s/%s", config.ServerDM, BaseRoute, source, mask, fileFullName) var data map[string]string = make(map[string]string, 2) //data["ori_url"] = localUrl if int32(isCompress) == 1 { //压缩图片并存储在原图路径,命名格式xx.jpg_small.jpg fileFullName = fmt.Sprintf("%s_small%s", filename, fileExt) newDst := fmt.Sprintf("%s/%s", imgPath, fileFullName) //compressUrl := fmt.Sprintf("%s/%s/%s/%s/%s", config.ServerDM, BaseRoute, source, mask, fileFullName) err = utils.CompressJPG(dst, newDst) compressUrl, err := PutBos(newDst, mediaType, true) if err != nil { logger.Errorf("Upload compress err", err) ResponseQuickMsg(c, e.Failed, err.Error(), data) return } data["compress_url"] = compressUrl } logger.Errorf("UploadImg 7 %+v", mask) // 如果是视频需要截图图片做封面 if mediaType == VideoType { videoCover := fmt.Sprintf("%s/%s", imgPath, filename) _, err = GetSnapshot(dst, videoCover, 1) if err != nil { zap.L().Error("GetSnapshot err", zap.Error(err)) ResponseQuickMsg(c, e.Failed, "获取封面失败", err.Error()) return } logger.Errorf("UploadImg 8 %+v", mask) zap.L().Info("UploadImg 8.1 videoCover", zap.Any("videoCover", videoCover)) //data["cover_url"] = fmt.Sprintf("%s/%s/%s/%s/%s", config.ServerDM, BaseRoute, source, mask, fmt.Sprintf("%s.%s", filename, PngType)) coverUrl, err := PutBos(videoCover+"."+PngType, mediaType, true) if urlParam != "" { coverUrl = fmt.Sprintf("%s?%s", coverUrl, urlParam) } data["cover_url"] = coverUrl if err != nil { zap.L().Error("Upload GetSnapshot err", zap.Error(err)) ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } //ResponseQuickMsg(c, e.Ok, e.GetMsg(e.SUCCESS), data) //return } ossUrl, err := PutBos(dst, mediaType, true) if err != nil { ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } if urlParam != "" { ossUrl = fmt.Sprintf("%s?%s", ossUrl, urlParam) } data["ori_url"] = ossUrl ResponseQuickMsg(c, e.Ok, e.GetMsg(e.SUCCESS), data) return } func quickBos(file *multipart.FileHeader, mediaType string, mask string, source string, defineFileName string) (url string, err error) { newFile, _ := file.Open() var filename string defer newFile.Close() if defineFileName != "" { filename = defineFileName } else { uuids, _ := uuid.NewV4() filename = uuids.String() filename = fmt.Sprintf("%s%s", filename, filepath.Ext(file.Filename)) } filePath := fmt.Sprintf("%s/%s/%s/%s", mediaType, mask, source, filename) fileBytes, _ := ioutil.ReadAll(newFile) if mediaType == "image" { if err = BaiduCheckImage(fileBytes); err != nil { return } } var objectName string = fmt.Sprintf("%s/%s/%s", config.ConfigData.Oss.BaseDir, config.Env, filePath) BOSClient, _ := objstorage.NewOSS(config.ConfigData.Oss.AccessKeyId, config.ConfigData.Oss.AccessKeySecret, config.ConfigData.Oss.Endpoint) _, err = BOSClient.PutObjectFromBytes(config.ConfigData.Oss.BucketName, objectName, fileBytes) if err != nil { logger.Errorf("quickOss err", err) return } //url = fmt.Sprintf("%s%s%s/%s", config.BosHttp, config.BosBucketName, config.BosUrl, objectName) url = fmt.Sprintf("%s/%s", config.ConfigData.Oss.CdnHost, objectName) return } // UploadMulti 批量上传 func UploadMulti(c *gin.Context) { form, err := c.MultipartForm() source := c.PostForm("source") mask := c.PostForm("mask") mediaType := c.PostForm("type") if mask == "" { mask = "0" } if mediaType == "" || mediaType == "image" { mediaType = "image" } if source == "" { ResponseQuickMsg(c, e.Failed, e.GetMsg(e.InvalidParams), nil) return } if err != nil { logger.Errorf("UploadMulti err", err) ResponseMsg(c, e.InvalidParams, serializer.Response{ Msg: err.Error(), Code: e.Failed, Error: err, }) return } var data = make(map[string]string, len(form.File)) wg.Add(len(form.File)) var fileCh = make(chan string, len(form.File)) for _, files := range form.File { for _, file := range files { go func() { defer wg.Done() var uploadInfo model.UploadInfo disp := file.Header["Content-Disposition"] if len(disp) > 0 { dispKv := strings.Split(disp[0], ";") for _, vv := range dispKv { vv = strings.Trim(vv, " ") if vv[:4] == "name" { uploadInfo.FileKName = vv[strings.Index(vv, "\"")+1 : strings.LastIndex(vv, "\"")] } } } newFile, _ := file.Open() defer newFile.Close() uuids, _ := uuid.NewV4() filePath := fmt.Sprintf("%s/%s/%s/%s%s", mediaType, mask, source, uuids, filepath.Ext(file.Filename)) fileBytes, _ := ioutil.ReadAll(newFile) if mediaType == "image" { if err = BaiduCheckImage(fileBytes); err != nil { uploadInfo.Err = err.Error() return } } var objectName string = fmt.Sprintf("%s/%s/%s", config.ConfigData.Oss.BaseDir, config.Env, filePath) BOSClient, _ := objstorage.NewOSS(config.ConfigData.Oss.AccessKeyId, config.ConfigData.Oss.AccessKeySecret, config.ConfigData.Oss.Endpoint) _, err = BOSClient.PutObjectFromBytes(config.ConfigData.Oss.BucketName, objectName, fileBytes) if err != nil { logger.Errorf("quickBos err", err) return } //uploadInfo.Url = fmt.Sprintf("%s%s%s/%s", config.BosHttp, config.BosBucketName, config.BosUrl, objectName) uploadInfo.Url = fmt.Sprintf("%s/%s", config.ConfigData.Oss.CdnHost, objectName) infoByte, _ := json.Marshal(uploadInfo) fileCh <- string(infoByte) }() } } wg.Wait() close(fileCh) for v := range fileCh { if v != "" { var uploadInfo model.UploadInfo if err = json.Unmarshal([]byte(v), &uploadInfo); err == nil { data[uploadInfo.FileKName] = uploadInfo.Url } } } ResponseMsg(c, e.UpdatePasswordSuccess, serializer.Response{ Msg: e.GetMsg(e.SUCCESS), Status: e.Ok, Data: data, }) } func PutBos(filePath string, mediaType string, needRemove bool) (url string, err error) { BOSClient, err := objstorage.NewOSS(config.ConfigData.Oss.AccessKeyId, config.ConfigData.Oss.AccessKeySecret, config.ConfigData.Oss.Endpoint) if err != nil { logger.Errorf("PutBos NewOss err ", err) err = errors.New(e.GetMsg(e.ErrorUploadBos)) return } f, err := os.Open(filePath) if err != nil { logger.Errorf("PutBos Open err %+v", err.Error()) return } fileBytes, _ := io.ReadAll(f) f.Close() //删除本地文件 if needRemove { os.Remove(filePath) } if mediaType == "image" { if err = BaiduCheckImage(fileBytes); err != nil { return } } filePath = strings.Replace(filePath, "./runtime", "", 1) var objectName string = fmt.Sprintf("%s/%s%s", config.ConfigData.Oss.BaseDir, config.Env, filePath) _, err = BOSClient.PutObjectFromBytes(config.ConfigData.Oss.BucketName, objectName, fileBytes) if err != nil { logger.Errorf("PutBos PutObject err %+v", err.Error()) err = errors.New(e.GetMsg(e.ErrorUploadBos)) return } //url = fmt.Sprintf("%s%s%s/%s", config.BosHttp, config.BosBucketName, config.BosUrl, objectName) url = fmt.Sprintf("%s/%s", config.ConfigData.Oss.CdnHost, objectName) return } // PutBosWithName 自定义osspath func PutBosWithName(filePath string, needRemove bool, ossPath string) (url string, err error) { BOSClient, err := objstorage.NewOSS(config.ConfigData.Oss.AccessKeyId, config.ConfigData.Oss.AccessKeySecret, config.ConfigData.Oss.Endpoint) if err != nil { logger.Errorf("PutBosWithName err1 ", err) err = errors.New(e.GetMsg(e.ErrorUploadBos)) return } if ossPath == "" { ossPath = filePath[1:] } var objectName string = fmt.Sprintf("%s/%s%s", config.ConfigData.Oss.BaseDir, config.Env, ossPath) _, err = BOSClient.PutObject(config.ConfigData.Oss.BucketName, objectName, filePath) if err != nil { logger.Errorf("PutBosWithName err2 ", err) err = errors.New(e.GetMsg(e.ErrorUploadBos)) return } //删除本地文件 if needRemove { _ = os.Remove(filePath) } //url = fmt.Sprintf("%s%s%s/%s", config.BosHttp, config.BosBucketName, config.BosUrl, objectName) url = fmt.Sprintf("%s/%s", config.ConfigData.Oss.CdnHost, objectName) return } func GetSnapshot(videoPath, snapshotPath string, frameNum int) (snapshotName string, err error) { buf := bytes.NewBuffer(nil) zap.L().Info("GetSnapshot", zap.Any("videoPath", videoPath)) err = ffmpeg.Input(videoPath). Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}). Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}). WithOutput(buf, os.Stdout). Run() if err != nil { zap.L().Error("GetSnapshot Input err", zap.Error(err)) return "", err } img, err := imaging.Decode(buf) if err != nil { zap.L().Error("GetSnapshot Decode err", zap.Error(err)) return "", err } err = imaging.Save(img, snapshotPath+"."+PngType) if err != nil { zap.L().Error("GetSnapshot Save err", zap.Error(err)) return "", err } names := strings.Split(snapshotPath, "\\") snapshotName = names[len(names)-1] + "." + PngType return } // BaiduCheckImage 图片鉴黄 func BaiduCheckImage(imageByte []byte) (err error) { return var ( accesstoken string response string ) sourcestring := base64.StdEncoding.EncodeToString(imageByte) if accesstoken, err = logic.GetImageAccessToken(); err != nil { return err } host := "https://aip.baidubce.com/rest/2.0/solution/v1/img_censor/v2/user_defined?access_token=[" + accesstoken + "]" if response, err = utils.PostForm(host, url.Values{"image": {sourcestring}}); err != nil { logger.Error("user_defined PostForm err", err) return err } var res struct { ErrorCode int64 `json:"error_code"` ErrorMsg string `json:"error_msg"` Conclusion string `json:"conclusion"` Log_id uint64 `json:"log_id"` IsHitMd5 bool `json:"isHitMd5"` ConclusionType int64 `json:"conclusionType"` } if err = json.Unmarshal([]byte(response), &res); err != nil { err = errors.New(e.GetMsg(e.JsonUnmarshal)) return } logger.Error("user_defined res", res) if res.ErrorCode != 0 || res.ErrorMsg != "" { return errors.New(e.GetMsg(e.ERROR_BAIDU_FAIL)) } if res.Conclusion != "合规" && res.Conclusion != "疑似" { return errors.New(e.GetMsg(e.ERROR_BAIDU_IMAGE)) } return nil }