2022-09-27 17:59:05 +08:00
|
|
|
package uploader
|
|
|
|
|
|
|
|
import (
|
2022-10-27 18:09:27 +08:00
|
|
|
"bytes"
|
2022-09-27 17:59:05 +08:00
|
|
|
"fmt"
|
2022-10-27 18:09:27 +08:00
|
|
|
"io"
|
2023-03-23 11:52:28 +08:00
|
|
|
"io/ioutil"
|
2022-09-27 17:59:05 +08:00
|
|
|
"mime/multipart"
|
2022-11-14 16:27:10 +08:00
|
|
|
"net/http"
|
2022-10-27 18:09:27 +08:00
|
|
|
"os"
|
2022-09-27 17:59:05 +08:00
|
|
|
"path"
|
|
|
|
"path/filepath"
|
2022-11-14 16:27:10 +08:00
|
|
|
"strings"
|
2022-09-27 17:59:05 +08:00
|
|
|
|
2022-10-24 16:51:05 +08:00
|
|
|
"github.com/answerdev/answer/internal/base/reason"
|
|
|
|
"github.com/answerdev/answer/internal/service/service_config"
|
2022-11-09 11:00:34 +08:00
|
|
|
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
2023-02-06 19:04:05 +08:00
|
|
|
"github.com/answerdev/answer/pkg/checker"
|
2022-10-24 16:51:05 +08:00
|
|
|
"github.com/answerdev/answer/pkg/dir"
|
|
|
|
"github.com/answerdev/answer/pkg/uid"
|
2023-03-08 16:29:55 +08:00
|
|
|
"github.com/answerdev/answer/plugin"
|
2022-10-27 18:09:27 +08:00
|
|
|
"github.com/disintegration/imaging"
|
2022-09-27 17:59:05 +08:00
|
|
|
"github.com/gin-gonic/gin"
|
2023-03-23 11:52:28 +08:00
|
|
|
exifremove "github.com/scottleedavis/go-exif-remove"
|
2022-09-27 17:59:05 +08:00
|
|
|
"github.com/segmentfault/pacman/errors"
|
2023-03-08 16:29:55 +08:00
|
|
|
"github.com/segmentfault/pacman/log"
|
2022-09-27 17:59:05 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2022-10-27 20:27:34 +08:00
|
|
|
avatarSubPath = "avatar"
|
|
|
|
avatarThumbSubPath = "avatar_thumb"
|
|
|
|
postSubPath = "post"
|
2022-11-14 16:27:10 +08:00
|
|
|
brandingSubPath = "branding"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
subPathList = []string{
|
|
|
|
avatarSubPath,
|
|
|
|
avatarThumbSubPath,
|
|
|
|
postSubPath,
|
|
|
|
brandingSubPath,
|
|
|
|
}
|
|
|
|
FormatExts = map[string]imaging.Format{
|
|
|
|
".jpg": imaging.JPEG,
|
|
|
|
".jpeg": imaging.JPEG,
|
|
|
|
".png": imaging.PNG,
|
2023-02-06 19:04:05 +08:00
|
|
|
//".gif": imaging.GIF,
|
|
|
|
//".tif": imaging.TIFF,
|
|
|
|
//".tiff": imaging.TIFF,
|
|
|
|
//".bmp": imaging.BMP,
|
2022-11-14 16:27:10 +08:00
|
|
|
}
|
2022-09-27 17:59:05 +08:00
|
|
|
)
|
|
|
|
|
2023-05-31 16:25:47 +08:00
|
|
|
type UploaderService interface {
|
|
|
|
UploadAvatarFile(ctx *gin.Context) (url string, err error)
|
|
|
|
UploadPostFile(ctx *gin.Context) (url string, err error)
|
|
|
|
UploadBrandingFile(ctx *gin.Context) (url string, err error)
|
2023-06-13 17:38:22 +08:00
|
|
|
AvatarThumbFile(ctx *gin.Context, fileName string, size int) (url string, err error)
|
2023-05-31 16:25:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// uploaderService uploader service
|
|
|
|
type uploaderService struct {
|
2022-11-09 11:00:34 +08:00
|
|
|
serviceConfig *service_config.ServiceConfig
|
2023-06-02 16:53:04 +08:00
|
|
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
2022-09-27 17:59:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewUploaderService new upload service
|
2022-11-09 17:42:10 +08:00
|
|
|
func NewUploaderService(serviceConfig *service_config.ServiceConfig,
|
2023-06-02 16:53:04 +08:00
|
|
|
siteInfoService siteinfo_common.SiteInfoCommonService) UploaderService {
|
2022-11-14 16:27:10 +08:00
|
|
|
for _, subPath := range subPathList {
|
|
|
|
err := dir.CreateDirIfNotExist(filepath.Join(serviceConfig.UploadPath, subPath))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2022-09-27 17:59:05 +08:00
|
|
|
}
|
2023-05-31 16:25:47 +08:00
|
|
|
return &uploaderService{
|
2022-11-09 17:42:10 +08:00
|
|
|
serviceConfig: serviceConfig,
|
|
|
|
siteInfoService: siteInfoService,
|
2022-09-27 17:59:05 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-14 16:27:10 +08:00
|
|
|
// UploadAvatarFile upload avatar file
|
2023-05-31 16:25:47 +08:00
|
|
|
func (us *uploaderService) UploadAvatarFile(ctx *gin.Context) (url string, err error) {
|
2023-03-08 16:29:55 +08:00
|
|
|
url, err = us.tryToUploadByPlugin(ctx, plugin.UserAvatar)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if len(url) > 0 {
|
|
|
|
return url, nil
|
|
|
|
}
|
|
|
|
|
2022-11-14 16:27:10 +08:00
|
|
|
// max size
|
|
|
|
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 5*1024*1024)
|
|
|
|
_, file, err := ctx.Request.FormFile("file")
|
|
|
|
if err != nil {
|
2023-02-06 19:04:05 +08:00
|
|
|
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
2022-11-14 16:27:10 +08:00
|
|
|
}
|
|
|
|
fileExt := strings.ToLower(path.Ext(file.Filename))
|
|
|
|
if _, ok := FormatExts[fileExt]; !ok {
|
2023-02-06 19:04:05 +08:00
|
|
|
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
2022-11-14 16:27:10 +08:00
|
|
|
}
|
|
|
|
|
2022-09-27 17:59:05 +08:00
|
|
|
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
|
|
|
avatarFilePath := path.Join(avatarSubPath, newFilename)
|
|
|
|
return us.uploadFile(ctx, file, avatarFilePath)
|
|
|
|
}
|
|
|
|
|
2023-06-13 17:38:22 +08:00
|
|
|
func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, fileName string, size int) (url string, err error) {
|
2022-10-28 11:15:46 +08:00
|
|
|
if size > 1024 {
|
|
|
|
size = 1024
|
|
|
|
}
|
2023-06-13 17:38:22 +08:00
|
|
|
|
2022-10-28 11:15:46 +08:00
|
|
|
thumbFileName := fmt.Sprintf("%d_%d@%s", size, size, fileName)
|
2023-06-13 17:38:22 +08:00
|
|
|
thumbFilePath := fmt.Sprintf("%s/%s/%s", us.serviceConfig.UploadPath, avatarThumbSubPath, thumbFileName)
|
|
|
|
avatarfile, err := os.ReadFile(thumbFilePath)
|
2022-10-28 09:52:10 +08:00
|
|
|
if err == nil {
|
2023-06-13 17:38:22 +08:00
|
|
|
return thumbFilePath, nil
|
2022-10-28 09:52:10 +08:00
|
|
|
}
|
2023-06-13 17:38:22 +08:00
|
|
|
filePath := fmt.Sprintf("%s/avatar/%s", us.serviceConfig.UploadPath, fileName)
|
2022-11-14 16:27:10 +08:00
|
|
|
avatarfile, err = os.ReadFile(filePath)
|
2022-10-27 18:09:27 +08:00
|
|
|
if err != nil {
|
2023-06-13 17:38:22 +08:00
|
|
|
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
2022-10-28 09:52:10 +08:00
|
|
|
}
|
|
|
|
reader := bytes.NewReader(avatarfile)
|
|
|
|
img, err := imaging.Decode(reader)
|
|
|
|
if err != nil {
|
2023-06-13 17:38:22 +08:00
|
|
|
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
2022-10-28 09:52:10 +08:00
|
|
|
}
|
2022-10-28 11:15:46 +08:00
|
|
|
new_image := imaging.Fill(img, size, size, imaging.Center, imaging.Linear)
|
2022-10-28 09:52:10 +08:00
|
|
|
var buf bytes.Buffer
|
|
|
|
fileSuffix := path.Ext(fileName)
|
2022-10-28 10:29:26 +08:00
|
|
|
|
|
|
|
_, ok := FormatExts[fileSuffix]
|
2022-10-27 20:27:34 +08:00
|
|
|
|
2022-10-28 09:52:10 +08:00
|
|
|
if !ok {
|
2023-06-13 17:38:22 +08:00
|
|
|
return "", fmt.Errorf("img extension not exist")
|
2022-10-27 18:09:27 +08:00
|
|
|
}
|
2022-10-28 10:43:53 +08:00
|
|
|
err = imaging.Encode(&buf, new_image, FormatExts[fileSuffix])
|
2022-10-28 09:52:10 +08:00
|
|
|
if err != nil {
|
2023-06-13 17:38:22 +08:00
|
|
|
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
2022-10-28 09:52:10 +08:00
|
|
|
}
|
|
|
|
thumbReader := bytes.NewReader(buf.Bytes())
|
2022-12-16 16:35:55 +08:00
|
|
|
err = dir.CreateDirIfNotExist(path.Join(us.serviceConfig.UploadPath, avatarThumbSubPath))
|
|
|
|
if err != nil {
|
2023-06-13 17:38:22 +08:00
|
|
|
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
2022-12-16 16:35:55 +08:00
|
|
|
}
|
2022-10-28 09:52:10 +08:00
|
|
|
avatarFilePath := path.Join(avatarThumbSubPath, thumbFileName)
|
|
|
|
savefilePath := path.Join(us.serviceConfig.UploadPath, avatarFilePath)
|
|
|
|
out, err := os.Create(savefilePath)
|
|
|
|
if err != nil {
|
2023-06-13 17:38:22 +08:00
|
|
|
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
2022-10-28 09:52:10 +08:00
|
|
|
}
|
|
|
|
defer out.Close()
|
|
|
|
_, err = io.Copy(out, thumbReader)
|
|
|
|
if err != nil {
|
2023-06-13 17:38:22 +08:00
|
|
|
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
2022-10-28 09:52:10 +08:00
|
|
|
}
|
2023-06-13 17:38:22 +08:00
|
|
|
return savefilePath, nil
|
2022-10-27 18:09:27 +08:00
|
|
|
}
|
|
|
|
|
2023-05-31 16:25:47 +08:00
|
|
|
func (us *uploaderService) UploadPostFile(ctx *gin.Context) (
|
2022-09-27 17:59:05 +08:00
|
|
|
url string, err error) {
|
2023-08-02 17:27:42 +08:00
|
|
|
url, err = us.tryToUploadByPlugin(ctx, plugin.UserPost)
|
2023-03-08 16:29:55 +08:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if len(url) > 0 {
|
|
|
|
return url, nil
|
|
|
|
}
|
|
|
|
|
2022-11-14 16:27:10 +08:00
|
|
|
// max size
|
|
|
|
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
|
|
|
_, file, err := ctx.Request.FormFile("file")
|
|
|
|
if err != nil {
|
2023-02-06 19:04:05 +08:00
|
|
|
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
2022-11-14 16:27:10 +08:00
|
|
|
}
|
|
|
|
fileExt := strings.ToLower(path.Ext(file.Filename))
|
|
|
|
if _, ok := FormatExts[fileExt]; !ok {
|
2023-02-06 19:04:05 +08:00
|
|
|
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
2022-11-14 16:27:10 +08:00
|
|
|
}
|
|
|
|
|
2022-09-27 17:59:05 +08:00
|
|
|
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
|
|
|
avatarFilePath := path.Join(postSubPath, newFilename)
|
|
|
|
return us.uploadFile(ctx, file, avatarFilePath)
|
|
|
|
}
|
|
|
|
|
2023-05-31 16:25:47 +08:00
|
|
|
func (us *uploaderService) UploadBrandingFile(ctx *gin.Context) (
|
2022-11-14 16:27:10 +08:00
|
|
|
url string, err error) {
|
2023-08-02 17:27:42 +08:00
|
|
|
url, err = us.tryToUploadByPlugin(ctx, plugin.AdminBranding)
|
2023-03-08 16:29:55 +08:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if len(url) > 0 {
|
|
|
|
return url, nil
|
|
|
|
}
|
|
|
|
|
2022-11-14 16:27:10 +08:00
|
|
|
// max size
|
|
|
|
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
|
|
|
_, file, err := ctx.Request.FormFile("file")
|
|
|
|
if err != nil {
|
2023-02-06 19:04:05 +08:00
|
|
|
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
2022-11-14 16:27:10 +08:00
|
|
|
}
|
|
|
|
fileExt := strings.ToLower(path.Ext(file.Filename))
|
2022-11-15 15:20:41 +08:00
|
|
|
_, ok := FormatExts[fileExt]
|
2022-11-16 10:57:28 +08:00
|
|
|
if !ok && fileExt != ".ico" {
|
2023-02-06 19:04:05 +08:00
|
|
|
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
2022-11-14 16:27:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
|
|
|
avatarFilePath := path.Join(brandingSubPath, newFilename)
|
|
|
|
return us.uploadFile(ctx, file, avatarFilePath)
|
|
|
|
}
|
|
|
|
|
2023-05-31 16:25:47 +08:00
|
|
|
func (us *uploaderService) uploadFile(ctx *gin.Context, file *multipart.FileHeader, fileSubPath string) (
|
2022-09-27 17:59:05 +08:00
|
|
|
url string, err error) {
|
2022-11-09 11:00:34 +08:00
|
|
|
siteGeneral, err := us.siteInfoService.GetSiteGeneral(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2022-09-27 17:59:05 +08:00
|
|
|
filePath := path.Join(us.serviceConfig.UploadPath, fileSubPath)
|
|
|
|
if err := ctx.SaveUploadedFile(file, filePath); err != nil {
|
|
|
|
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
|
|
|
}
|
2023-02-06 19:04:05 +08:00
|
|
|
|
|
|
|
src, err := file.Open()
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
|
|
|
}
|
|
|
|
defer src.Close()
|
2023-03-23 11:52:28 +08:00
|
|
|
Dexif(filePath, filePath)
|
2023-02-06 19:04:05 +08:00
|
|
|
|
|
|
|
if !checker.IsSupportedImageFile(src, filepath.Ext(fileSubPath)) {
|
|
|
|
return "", errors.BadRequest(reason.UploadFileUnsupportedFileFormat)
|
|
|
|
}
|
|
|
|
|
2022-11-09 11:00:34 +08:00
|
|
|
url = fmt.Sprintf("%s/uploads/%s", siteGeneral.SiteUrl, fileSubPath)
|
2022-09-27 17:59:05 +08:00
|
|
|
return url, nil
|
|
|
|
}
|
2023-03-08 16:29:55 +08:00
|
|
|
|
2023-05-31 16:25:47 +08:00
|
|
|
func (us *uploaderService) tryToUploadByPlugin(ctx *gin.Context, source plugin.UploadSource) (
|
2023-03-08 16:29:55 +08:00
|
|
|
url string, err error) {
|
|
|
|
_ = plugin.CallStorage(func(fn plugin.Storage) error {
|
|
|
|
resp := fn.UploadFile(ctx, source)
|
|
|
|
if resp.OriginalError != nil {
|
|
|
|
log.Errorf("upload file by plugin failed, err: %v", resp.OriginalError)
|
|
|
|
err = errors.BadRequest("").WithMsg(resp.DisplayErrorMsg.Translate(ctx)).WithError(err)
|
|
|
|
} else {
|
|
|
|
url = resp.FullURL
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return url, err
|
|
|
|
}
|
2023-04-11 11:28:57 +08:00
|
|
|
|
2023-03-23 11:52:28 +08:00
|
|
|
func Dexif(filepath string, destpath string) error {
|
|
|
|
img, err := ioutil.ReadFile(filepath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
noExifBytes, err := exifremove.Remove(img)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = os.WriteFile(destpath, noExifBytes, 0644)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|