mirror of https://gitee.com/answerdev/answer.git
feat: unified file upload interface
This commit is contained in:
parent
23217f92b7
commit
f6fa850aa0
|
@ -179,7 +179,8 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
|||
notificationService := notification2.NewNotificationService(dataData, notificationRepo, notificationCommon)
|
||||
notificationController := controller.NewNotificationController(notificationService)
|
||||
dashboardController := controller.NewDashboardController(dashboardService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_backyardReportController, userBackyardController, reasonController, themeController, siteInfoController, siteinfoController, notificationController, dashboardController)
|
||||
uploadController := controller.NewUploadController(uploaderService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_backyardReportController, userBackyardController, reasonController, themeController, siteInfoController, siteinfoController, notificationController, dashboardController, uploadController)
|
||||
swaggerRouter := router.NewSwaggerRouter(swaggerConf)
|
||||
uiRouter := router.NewUIRouter()
|
||||
authUserMiddleware := middleware.NewAuthUserMiddleware(authService)
|
||||
|
|
|
@ -43,4 +43,5 @@ const (
|
|||
InstallCreateTableFailed = "error.database.create_table_failed"
|
||||
InstallConfigFailed = "error.install.create_config_failed"
|
||||
SiteInfoNotFound = "error.site_info.not_found"
|
||||
UploadFileSourceUnsupported = "error.upload.source_unsupported"
|
||||
)
|
||||
|
|
|
@ -21,4 +21,5 @@ var ProviderSetController = wire.NewSet(
|
|||
NewNotificationController,
|
||||
NewSiteinfoController,
|
||||
NewDashboardController,
|
||||
NewUploadController,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/service/uploader"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// file is uploaded by markdown(or something else) editor
|
||||
fileFromPost = "post"
|
||||
// file is used to change the user's avatar
|
||||
fileFromAvatar = "avatar"
|
||||
// file is logo/icon images
|
||||
fileFromBranding = "branding"
|
||||
)
|
||||
|
||||
// UploadController upload controller
|
||||
type UploadController struct {
|
||||
uploaderService *uploader.UploaderService
|
||||
}
|
||||
|
||||
// NewUploadController new controller
|
||||
func NewUploadController(uploaderService *uploader.UploaderService) *UploadController {
|
||||
return &UploadController{
|
||||
uploaderService: uploaderService,
|
||||
}
|
||||
}
|
||||
|
||||
// UploadFile upload file
|
||||
// @Summary upload file
|
||||
// @Description upload file
|
||||
// @Tags Upload
|
||||
// @Accept multipart/form-data
|
||||
// @Security ApiKeyAuth
|
||||
// @Param source formData string true "identify the source of the file upload" Enums(post, avatar, branding)
|
||||
// @Param file formData file true "file"
|
||||
// @Success 200 {object} handler.RespBody{data=string}
|
||||
// @Router /answer/api/v1/file [post]
|
||||
func (uc *UploadController) UploadFile(ctx *gin.Context) {
|
||||
var (
|
||||
url string
|
||||
err error
|
||||
)
|
||||
|
||||
source := ctx.PostForm("source")
|
||||
switch source {
|
||||
case fileFromAvatar:
|
||||
url, err = uc.uploaderService.UploadAvatarFile(ctx)
|
||||
case fileFromPost:
|
||||
url, err = uc.uploaderService.UploadPostFile(ctx)
|
||||
case fileFromBranding:
|
||||
url, err = uc.uploaderService.UploadBrandingFile(ctx)
|
||||
default:
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.UploadFileSourceUnsupported), nil)
|
||||
return
|
||||
}
|
||||
handler.HandleResponse(ctx, err, url)
|
||||
}
|
|
@ -1,10 +1,6 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
|
@ -17,7 +13,6 @@ import (
|
|||
"github.com/answerdev/answer/internal/service/uploader"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
// UserController user controller
|
||||
|
@ -378,66 +373,6 @@ func (uc *UserController) UserUpdateInterface(ctx *gin.Context) {
|
|||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// UploadUserAvatar godoc
|
||||
// @Summary UserUpdateInfo
|
||||
// @Description UserUpdateInfo
|
||||
// @Tags User
|
||||
// @Accept multipart/form-data
|
||||
// @Security ApiKeyAuth
|
||||
// @Param file formData file true "file"
|
||||
// @Success 200 {object} handler.RespBody{data=string}
|
||||
// @Router /answer/api/v1/user/avatar/upload [post]
|
||||
func (uc *UserController) UploadUserAvatar(ctx *gin.Context) {
|
||||
// max size
|
||||
var filesMax int64 = 5 << 20
|
||||
var valuesMax int64 = 5
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, filesMax+valuesMax)
|
||||
_, header, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(header.Filename))
|
||||
if fileExt != ".jpg" && fileExt != ".png" && fileExt != ".jpeg" {
|
||||
log.Errorf("upload file format is not supported: %s", fileExt)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
url, err := uc.uploaderService.UploadAvatarFile(ctx, header, fileExt)
|
||||
handler.HandleResponse(ctx, err, url)
|
||||
}
|
||||
|
||||
// UploadUserPostFile godoc
|
||||
// @Summary upload user post file
|
||||
// @Description upload user post file
|
||||
// @Tags User
|
||||
// @Accept multipart/form-data
|
||||
// @Security ApiKeyAuth
|
||||
// @Param file formData file true "file"
|
||||
// @Success 200 {object} handler.RespBody{data=string}
|
||||
// @Router /answer/api/v1/user/post/file [post]
|
||||
func (uc *UserController) UploadUserPostFile(ctx *gin.Context) {
|
||||
// max size
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
||||
_, header, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(header.Filename))
|
||||
if fileExt != ".jpg" && fileExt != ".png" && fileExt != ".jpeg" {
|
||||
log.Errorf("upload file format is not supported: %s", fileExt)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
url, err := uc.uploaderService.UploadPostFile(ctx, header, fileExt)
|
||||
handler.HandleResponse(ctx, err, url)
|
||||
}
|
||||
|
||||
// ActionRecord godoc
|
||||
// @Summary ActionRecord
|
||||
// @Description ActionRecord
|
||||
|
|
|
@ -28,6 +28,7 @@ type AnswerAPIRouter struct {
|
|||
siteinfoController *controller.SiteinfoController
|
||||
notificationController *controller.NotificationController
|
||||
dashboardController *controller.DashboardController
|
||||
uploadController *controller.UploadController
|
||||
}
|
||||
|
||||
func NewAnswerAPIRouter(
|
||||
|
@ -52,7 +53,7 @@ func NewAnswerAPIRouter(
|
|||
siteinfoController *controller.SiteinfoController,
|
||||
notificationController *controller.NotificationController,
|
||||
dashboardController *controller.DashboardController,
|
||||
|
||||
uploadController *controller.UploadController,
|
||||
) *AnswerAPIRouter {
|
||||
return &AnswerAPIRouter{
|
||||
langController: langController,
|
||||
|
@ -76,6 +77,7 @@ func NewAnswerAPIRouter(
|
|||
notificationController: notificationController,
|
||||
siteinfoController: siteinfoController,
|
||||
dashboardController: dashboardController,
|
||||
uploadController: uploadController,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,8 +182,6 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
|||
r.PUT("/user/password", a.userController.UserModifyPassWord)
|
||||
r.PUT("/user/info", a.userController.UserUpdateInfo)
|
||||
r.PUT("/user/interface", a.userController.UserUpdateInterface)
|
||||
r.POST("/user/avatar/upload", a.userController.UploadUserAvatar)
|
||||
r.POST("/user/post/file", a.userController.UploadUserPostFile)
|
||||
r.POST("/user/notice/set", a.userController.UserNoticeSet)
|
||||
|
||||
// vote
|
||||
|
@ -196,6 +196,9 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
|||
r.GET("/notification/page", a.notificationController.GetList)
|
||||
r.PUT("/notification/read/state/all", a.notificationController.ClearUnRead)
|
||||
r.PUT("/notification/read/state", a.notificationController.ClearIDUnRead)
|
||||
|
||||
// upload file
|
||||
r.POST("/file", a.uploadController.UploadFile)
|
||||
}
|
||||
|
||||
func (a *AnswerAPIRouter) RegisterAnswerCmsAPIRouter(r *gin.RouterGroup) {
|
||||
|
|
|
@ -4,12 +4,14 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/service/service_config"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
|
@ -24,6 +26,25 @@ const (
|
|||
avatarSubPath = "avatar"
|
||||
avatarThumbSubPath = "avatar_thumb"
|
||||
postSubPath = "post"
|
||||
brandingSubPath = "branding"
|
||||
)
|
||||
|
||||
var (
|
||||
subPathList = []string{
|
||||
avatarSubPath,
|
||||
avatarThumbSubPath,
|
||||
postSubPath,
|
||||
brandingSubPath,
|
||||
}
|
||||
FormatExts = map[string]imaging.Format{
|
||||
".jpg": imaging.JPEG,
|
||||
".jpeg": imaging.JPEG,
|
||||
".png": imaging.PNG,
|
||||
".gif": imaging.GIF,
|
||||
".tif": imaging.TIFF,
|
||||
".tiff": imaging.TIFF,
|
||||
".bmp": imaging.BMP,
|
||||
}
|
||||
)
|
||||
|
||||
// UploaderService user service
|
||||
|
@ -35,13 +56,11 @@ type UploaderService struct {
|
|||
// NewUploaderService new upload service
|
||||
func NewUploaderService(serviceConfig *service_config.ServiceConfig,
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService) *UploaderService {
|
||||
err := dir.CreateDirIfNotExist(filepath.Join(serviceConfig.UploadPath, avatarSubPath))
|
||||
for _, subPath := range subPathList {
|
||||
err := dir.CreateDirIfNotExist(filepath.Join(serviceConfig.UploadPath, subPath))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = dir.CreateDirIfNotExist(filepath.Join(serviceConfig.UploadPath, postSubPath))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &UploaderService{
|
||||
serviceConfig: serviceConfig,
|
||||
|
@ -49,23 +68,26 @@ func NewUploaderService(serviceConfig *service_config.ServiceConfig,
|
|||
}
|
||||
}
|
||||
|
||||
func (us *UploaderService) UploadAvatarFile(ctx *gin.Context, file *multipart.FileHeader, fileExt string) (
|
||||
url string, err error) {
|
||||
// UploadAvatarFile upload avatar file
|
||||
func (us *UploaderService) UploadAvatarFile(ctx *gin.Context) (url string, err error) {
|
||||
// max size
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 5*1024*1024)
|
||||
_, file, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(file.Filename))
|
||||
if _, ok := FormatExts[fileExt]; !ok {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
||||
avatarFilePath := path.Join(avatarSubPath, newFilename)
|
||||
return us.uploadFile(ctx, file, avatarFilePath)
|
||||
}
|
||||
|
||||
var FormatExts = map[string]imaging.Format{
|
||||
".jpg": imaging.JPEG,
|
||||
".jpeg": imaging.JPEG,
|
||||
".png": imaging.PNG,
|
||||
".gif": imaging.GIF,
|
||||
".tif": imaging.TIFF,
|
||||
".tiff": imaging.TIFF,
|
||||
".bmp": imaging.BMP,
|
||||
}
|
||||
|
||||
func (us *UploaderService) AvatarThumbFile(ctx *gin.Context, uploadPath, fileName string, size int) (
|
||||
avatarfile []byte, err error) {
|
||||
if size > 1024 {
|
||||
|
@ -73,12 +95,12 @@ func (us *UploaderService) AvatarThumbFile(ctx *gin.Context, uploadPath, fileNam
|
|||
}
|
||||
thumbFileName := fmt.Sprintf("%d_%d@%s", size, size, fileName)
|
||||
thumbfilePath := fmt.Sprintf("%s/%s/%s", uploadPath, avatarThumbSubPath, thumbFileName)
|
||||
avatarfile, err = ioutil.ReadFile(thumbfilePath)
|
||||
avatarfile, err = os.ReadFile(thumbfilePath)
|
||||
if err == nil {
|
||||
return avatarfile, nil
|
||||
}
|
||||
filePath := fmt.Sprintf("%s/avatar/%s", uploadPath, fileName)
|
||||
avatarfile, err = ioutil.ReadFile(filePath)
|
||||
avatarfile, err = os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return avatarfile, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
|
@ -117,13 +139,46 @@ func (us *UploaderService) AvatarThumbFile(ctx *gin.Context, uploadPath, fileNam
|
|||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (us *UploaderService) UploadPostFile(ctx *gin.Context, file *multipart.FileHeader, fileExt string) (
|
||||
func (us *UploaderService) UploadPostFile(ctx *gin.Context) (
|
||||
url string, err error) {
|
||||
// max size
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
||||
_, file, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(file.Filename))
|
||||
if _, ok := FormatExts[fileExt]; !ok {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
||||
avatarFilePath := path.Join(postSubPath, newFilename)
|
||||
return us.uploadFile(ctx, file, avatarFilePath)
|
||||
}
|
||||
|
||||
func (us *UploaderService) UploadBrandingFile(ctx *gin.Context) (
|
||||
url string, err error) {
|
||||
// max size
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
||||
_, file, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(file.Filename))
|
||||
if _, ok := FormatExts[fileExt]; !ok {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
||||
avatarFilePath := path.Join(brandingSubPath, newFilename)
|
||||
return us.uploadFile(ctx, file, avatarFilePath)
|
||||
}
|
||||
|
||||
func (us *UploaderService) uploadFile(ctx *gin.Context, file *multipart.FileHeader, fileSubPath string) (
|
||||
url string, err error) {
|
||||
siteGeneral, err := us.siteInfoService.GetSiteGeneral(ctx)
|
||||
|
|
Loading…
Reference in New Issue