// Copyright 2022 ROC. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package api import ( "image" "github.com/disintegration/imaging" "github.com/gin-gonic/gin" "github.com/gofrs/uuid" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core/cs" "github.com/rocboss/paopao-ce/internal/servants/web/broker" "github.com/rocboss/paopao-ce/pkg/app" "github.com/rocboss/paopao-ce/pkg/convert" "github.com/rocboss/paopao-ce/pkg/errcode" "github.com/sirupsen/logrus" ) var uploadAttachmentTypeMap = map[string]cs.AttachmentType{ "public/image": cs.AttachmentTypeImage, "public/avatar": cs.AttachmentTypeImage, "public/video": cs.AttachmentTypeVideo, "attachment": cs.AttachmentTypeOther, } 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 fileCheck(uploadType string, size int64) error { if uploadType != "public/video" && uploadType != "public/image" && uploadType != "public/avatar" && uploadType != "attachment" { return errcode.InvalidParams } if size > 1024*1024*100 { return errcode.FileInvalidSize.WithDetails("最大允许100MB") } return nil } func UploadAttachment(c *gin.Context) { response := app.NewResponse(c) uploadType := c.Request.FormValue("type") file, fileHeader, err := c.Request.FormFile("file") if err != nil { logrus.Errorf("api.UploadAttachment err: %v", err) response.ToErrorResponse(errcode.FileUploadFailed) return } defer file.Close() if err = fileCheck(uploadType, fileHeader.Size); err != nil { cErr, _ := err.(*errcode.Error) response.ToErrorResponse(cErr) return } contentType := fileHeader.Header.Get("Content-Type") fileExt, err := GetFileExt(fileHeader.Header.Get("Content-Type")) if err != nil { logrus.Errorf("GetFileExt err: %v", err) response.ToErrorResponse(err.(*errcode.Error)) return } // 生成随机路径 randomPath := uuid.Must(uuid.NewV4()).String() ossSavePath := uploadType + "/" + GeneratePath(randomPath[:8]) + "/" + randomPath[9:] + fileExt objectUrl, err := objectStorage.PutObject(ossSavePath, file, fileHeader.Size, contentType, false) if err != nil { logrus.Errorf("putObject err: %v", err) response.ToErrorResponse(errcode.FileUploadFailed) return } // 构造附件Model attachment := &cs.Attachment{ FileSize: fileHeader.Size, Content: objectUrl, } if userID, exists := c.Get("UID"); exists { attachment.UserID = userID.(int64) } attachment.Type = uploadAttachmentTypeMap[uploadType] if attachment.Type == cs.AttachmentTypeImage { var src image.Image src, err = imaging.Decode(file) if err == nil { attachment.ImgWidth, attachment.ImgHeight = GetImageSize(src.Bounds()) } } attachment.ID, err = broker.CreateAttachment(attachment) if err != nil { logrus.Errorf("service.CreateAttachment err: %v", err) response.ToErrorResponse(errcode.FileUploadFailed) } response.ToResponse(attachment) } func DownloadAttachmentPrecheck(c *gin.Context) { response := app.NewResponse(c) contentID := convert.StrTo(c.Query("id")).MustInt64() // 加载content content, err := broker.GetPostContentByID(contentID) if err != nil { logrus.Errorf("service.GetPostContentByID err: %v", err) response.ToErrorResponse(errcode.InvalidDownloadReq) } user, _ := c.Get("USER") if content.Type == core.ContentTypeChargeAttachment { // 加载post post, err := broker.GetPost(content.PostID) if err != nil { logrus.Errorf("service.GetPost err: %v", err) response.ToResponse(gin.H{ "paid": false, }) return } // 发布者或管理员免费下载 if post.UserID == user.(*core.User).ID || user.(*core.User).IsAdmin { response.ToResponse(gin.H{ "paid": true, }) return } // 检测是否有购买记录 response.ToResponse(gin.H{ "paid": broker.CheckPostAttachmentIsPaid(post.ID, user.(*core.User).ID), }) return } response.ToResponse(gin.H{ "paid": false, }) } func DownloadAttachment(c *gin.Context) { response := app.NewResponse(c) contentID := convert.StrTo(c.Query("id")).MustInt64() // 加载content content, err := broker.GetPostContentByID(contentID) if err != nil { logrus.Errorf("service.GetPostContentByID err: %v", err) response.ToErrorResponse(errcode.InvalidDownloadReq) } // 收费附件 if content.Type == core.ContentTypeChargeAttachment { user, _ := c.Get("USER") // 加载post post, err := broker.GetPost(content.PostID) if err != nil { logrus.Errorf("service.GetPost err: %v", err) response.ToResponse(gin.H{ "paid": false, }) return } paidFlag := false // 发布者或管理员免费下载 if post.UserID == user.(*core.User).ID || user.(*core.User).IsAdmin { paidFlag = true } // 检测是否有购买记录 if broker.CheckPostAttachmentIsPaid(post.ID, user.(*core.User).ID) { paidFlag = true } if !paidFlag { // 未购买,则尝试购买 err := broker.BuyPostAttachment(&core.Post{ Model: &core.Model{ ID: post.ID, }, UserID: post.UserID, AttachmentPrice: post.AttachmentPrice, }, user.(*core.User)) if err != nil { logrus.Errorf("service.BuyPostAttachment err: %v", err) if err == errcode.InsuffientDownloadMoney { response.ToErrorResponse(errcode.InsuffientDownloadMoney) } else { response.ToErrorResponse(errcode.DownloadExecFail) } return } } } objectKey := objectStorage.ObjectKey(content.Content) signedURL, err := objectStorage.SignURL(objectKey, 60) if err != nil { logrus.Errorf("client.SignURL err: %v", err) response.ToErrorResponse(errcode.DownloadReqError) return } response.ToResponse(gin.H{ "signed_url": signedURL, }) }