package api import ( "image" "net/url" "strings" "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/disintegration/imaging" "github.com/gin-gonic/gin" "github.com/gofrs/uuid" "github.com/rocboss/paopao-ce/global" "github.com/rocboss/paopao-ce/internal/model" "github.com/rocboss/paopao-ce/internal/service" "github.com/rocboss/paopao-ce/pkg/app" "github.com/rocboss/paopao-ce/pkg/convert" "github.com/rocboss/paopao-ce/pkg/errcode" ) func GeneratePath(s string) string { n := len(s) if n <= 2 { return s } return GeneratePath(s[:n-2]) + "/" + s[n-2:] } func GetFileExt(s string) (string, error) { switch s { case "image/png": return ".png", nil case "image/jpg": return ".jpg", nil case "image/jpeg": return ".jpeg", nil case "image/gif": return ".gif", nil case "video/mp4": return ".mp4", nil case "video/quicktime": return ".mov", nil case "application/zip": return ".zip", nil default: return "", errcode.FileInvalidExt.WithDetails("仅允许 png/jpg/gif/mp4/mov/zip 类型") } } func GetImageSize(img image.Rectangle) (int, int) { b := img.Bounds() width := b.Max.X height := b.Max.Y return width, height } func UploadAttachment(c *gin.Context) { response := app.NewResponse(c) svc := service.New(c) uploadType := c.Request.FormValue("type") file, fileHeader, err := c.Request.FormFile("file") if err != nil { global.Logger.Errorf("api.UploadAttachment err: %v", err) response.ToErrorResponse(errcode.FileUploadFailed) return } defer file.Close() if uploadType != "public/video" && uploadType != "public/image" && uploadType != "public/avatar" && uploadType != "attachment" { response.ToErrorResponse(errcode.InvalidParams) return } if fileHeader.Size > 1024*1024*100 { response.ToErrorResponse(errcode.FileInvalidSize.WithDetails("最大允许100MB")) return } fileExt, err := GetFileExt(fileHeader.Header.Get("Content-Type")) if err != nil { global.Logger.Errorf("GetFileExt err: %v", err) response.ToErrorResponse(err.(*errcode.Error)) return } fileReader, err := fileHeader.Open() if err != nil { global.Logger.Errorf("Attachment file read err: %v", err) response.ToErrorResponse(errcode.FileUploadFailed) return } defer fileReader.Close() // 生成随机路径 randomPath := uuid.Must(uuid.NewV4()).String() ossSavePath := uploadType + "/" + GeneratePath(randomPath[:8]) + "/" + randomPath[9:] + fileExt client, err := oss.New(global.AliossSetting.AliossEndpoint, global.AliossSetting.AliossAccessKeyID, global.AliossSetting.AliossAccessKeySecret) if err != nil { global.Logger.Errorf("oss.New err: %v", err) response.ToErrorResponse(errcode.FileUploadFailed) return } bucket, err := client.Bucket("paopao-assets") if err != nil { global.Logger.Errorf("client.Bucket err: %v", err) response.ToErrorResponse(errcode.FileUploadFailed) return } err = bucket.PutObject(ossSavePath, fileReader) if err != nil { global.Logger.Errorf("bucket.PutObject err: %v", err) response.ToErrorResponse(errcode.FileUploadFailed) return } // 构造附件Model attachment := &model.Attachment{ FileSize: fileHeader.Size, Content: "https://" + global.AliossSetting.AliossDomain + "/" + ossSavePath, } if userID, exists := c.Get("UID"); exists { attachment.UserID = userID.(int64) } if uploadType == "public/image" || uploadType == "public/avatar" { attachment.Type = model.ATTACHMENT_TYPE_IMAGE src, err := imaging.Decode(file) if err == nil { attachment.ImgWidth, attachment.ImgHeight = GetImageSize(src.Bounds()) } } if uploadType == "public/video" { attachment.Type = model.ATTACHMENT_TYPE_VIDEO } if uploadType == "attachment" { attachment.Type = model.ATTACHMENT_TYPE_OTHER } attachment, err = svc.CreateAttachment(attachment) if err != nil { global.Logger.Errorf("svc.CreateAttachment err: %v", err) response.ToErrorResponse(errcode.FileUploadFailed) } response.ToResponse(attachment) } func DownloadAttachmentPrecheck(c *gin.Context) { response := app.NewResponse(c) svc := service.New(c) contentID := convert.StrTo(c.Query("id")).MustInt64() // 加载content content, err := svc.GetPostContentByID(contentID) if err != nil { global.Logger.Errorf("svc.GetPostContentByID err: %v", err) response.ToErrorResponse(errcode.InvalidDownloadReq) } user, _ := c.Get("USER") if content.Type == model.CONTENT_TYPE_CHARGE_ATTACHMENT { // 加载post post, err := svc.GetPost(content.PostID) if err != nil { global.Logger.Errorf("svc.GetPost err: %v", err) response.ToResponse(gin.H{ "paid": false, }) return } // 发布者或管理员免费下载 if post.UserID == user.(*model.User).ID || user.(*model.User).IsAdmin { response.ToResponse(gin.H{ "paid": true, }) return } // 检测是否有购买记录 response.ToResponse(gin.H{ "paid": svc.CheckPostAttachmentIsPaid(post.ID, user.(*model.User).ID), }) return } response.ToResponse(gin.H{ "paid": false, }) } func DownloadAttachment(c *gin.Context) { response := app.NewResponse(c) svc := service.New(c) contentID := convert.StrTo(c.Query("id")).MustInt64() // 加载content content, err := svc.GetPostContentByID(contentID) if err != nil { global.Logger.Errorf("svc.GetPostContentByID err: %v", err) response.ToErrorResponse(errcode.InvalidDownloadReq) } // 收费附件 if content.Type == model.CONTENT_TYPE_CHARGE_ATTACHMENT { user, _ := c.Get("USER") // 加载post post, err := svc.GetPost(content.PostID) if err != nil { global.Logger.Errorf("svc.GetPost err: %v", err) response.ToResponse(gin.H{ "paid": false, }) return } paidFlag := false // 发布者或管理员免费下载 if post.UserID == user.(*model.User).ID || user.(*model.User).IsAdmin { paidFlag = true } // 检测是否有购买记录 if svc.CheckPostAttachmentIsPaid(post.ID, user.(*model.User).ID) { paidFlag = true } if !paidFlag { // 未购买,则尝试购买 err := svc.BuyPostAttachment(&model.Post{ Model: &model.Model{ ID: post.ID, }, UserID: post.UserID, AttachmentPrice: post.AttachmentPrice, }, user.(*model.User)) if err != nil { global.Logger.Errorf("svc.BuyPostAttachment err: %v", err) if err == errcode.InsuffientDownloadMoney { response.ToErrorResponse(errcode.InsuffientDownloadMoney) } else { response.ToErrorResponse(errcode.DownloadExecFail) } return } } } // 开始构造下载地址 client, err := oss.New(global.AliossSetting.AliossEndpoint, global.AliossSetting.AliossAccessKeyID, global.AliossSetting.AliossAccessKeySecret) if err != nil { global.Logger.Errorf("oss.New err: %v", err) response.ToErrorResponse(errcode.DownloadReqError) return } bucket, err := client.Bucket("paopao-assets") if err != nil { global.Logger.Errorf("client.Bucket err: %v", err) response.ToErrorResponse(errcode.DownloadReqError) return } // 签名 objectKey := strings.Replace(content.Content, "https://"+global.AliossSetting.AliossDomain+"/", "", -1) signedURL, err := bucket.SignURL(objectKey, oss.HTTPGet, 60) if err != nil { global.Logger.Errorf("client.SignURL err: %v", err) response.ToErrorResponse(errcode.DownloadReqError) return } ur, err := url.Parse(signedURL) if err != nil { global.Logger.Errorf("url.Parse err: %v", err) response.ToErrorResponse(errcode.DownloadReqError) return } epath, err := url.PathUnescape(ur.Path) if err != nil { global.Logger.Errorf("url.PathUnescape err: %v", err) response.ToErrorResponse(errcode.DownloadReqError) return } ur.Path = epath ur.RawPath = epath response.ToResponse(ur.String()) }