This is an automated email from the ASF dual-hosted git repository. linkinstar pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/incubator-answer.git
commit 0ad4defb6bc52c42d70d6dbe54ea5703810a77dc Author: LinkinStars <[email protected]> AuthorDate: Tue Dec 3 17:36:01 2024 +0800 feat(file): implement file download functionality --- internal/router/static_router.go | 17 +++++++++++++++++ internal/service/uploader/upload.go | 19 ++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/internal/router/static_router.go b/internal/router/static_router.go index 5b087431..3fbb12da 100644 --- a/internal/router/static_router.go +++ b/internal/router/static_router.go @@ -22,6 +22,8 @@ package router import ( "github.com/apache/incubator-answer/internal/service/service_config" "github.com/gin-gonic/gin" + "path/filepath" + "strings" ) // StaticRouter static api router @@ -39,4 +41,19 @@ func NewStaticRouter(serviceConfig *service_config.ServiceConfig) *StaticRouter // RegisterStaticRouter register static api router func (a *StaticRouter) RegisterStaticRouter(r *gin.RouterGroup) { r.Static("/uploads", a.serviceConfig.UploadPath) + + r.GET("/download/*filepath", func(c *gin.Context) { + // The filePath such as /download/hash/123.png + filePath := c.Param("filepath") + // The download filename is 123.png + downloadFilename := filepath.Base(filePath) + + // After trimming, the downloadLink is /uploads/hash + downloadLink := strings.TrimSuffix(filePath, "/"+downloadFilename) + // After add the extension, the downloadLink is /uploads/hash.png + downloadLink += filepath.Ext(downloadFilename) + + downloadLink = filepath.Join(a.serviceConfig.UploadPath, downloadLink) + c.FileAttachment(downloadLink, downloadFilename) + }) } diff --git a/internal/service/uploader/upload.go b/internal/service/uploader/upload.go index a8d9c0cc..6baf00d1 100644 --- a/internal/service/uploader/upload.go +++ b/internal/service/uploader/upload.go @@ -25,6 +25,7 @@ import ( "io" "mime/multipart" "net/http" + "net/url" "os" "path" "path/filepath" @@ -237,7 +238,7 @@ func (us *uploaderService) UploadPostAttachment(ctx *gin.Context) ( fileExt := strings.ToLower(path.Ext(fileHeader.Filename)) newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt) avatarFilePath := path.Join(postSubPath, newFilename) - return us.uploadAttachmentFile(ctx, fileHeader, avatarFilePath) + return us.uploadAttachmentFile(ctx, fileHeader, fileHeader.Filename, avatarFilePath) } func (us *uploaderService) UploadBrandingFile(ctx *gin.Context) ( @@ -304,8 +305,8 @@ func (us *uploaderService) uploadImageFile(ctx *gin.Context, file *multipart.Fil return url, nil } -func (us *uploaderService) uploadAttachmentFile(ctx *gin.Context, file *multipart.FileHeader, fileSubPath string) ( - url string, err error) { +func (us *uploaderService) uploadAttachmentFile(ctx *gin.Context, file *multipart.FileHeader, originalFilename, fileSubPath string) ( + downloadUrl string, err error) { siteGeneral, err := us.siteInfoService.GetSiteGeneral(ctx) if err != nil { return "", err @@ -315,8 +316,16 @@ func (us *uploaderService) uploadAttachmentFile(ctx *gin.Context, file *multipar return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack() } - url = fmt.Sprintf("%s/uploads/%s", siteGeneral.SiteUrl, fileSubPath) - return url, nil + // The original filename is 123.png + // The local saved path is /UploadPath/hash.png + // The download link wil be /download/hash/123.png. + // When downloading, the download link will be redirect to the local saved path. And the download filename will be 123.png. + ext := filepath.Ext(fileSubPath) + // Need url encode the original filename. Because the filename may contain special characters that conflict with the markdown syntax. + originalFilename = url.QueryEscape(originalFilename) + downloadPath := strings.TrimSuffix(fileSubPath, ext) + "/" + originalFilename + downloadUrl = fmt.Sprintf("%s/download/%s", siteGeneral.SiteUrl, downloadPath) + return downloadUrl, nil } func (us *uploaderService) tryToUploadByPlugin(ctx *gin.Context, source plugin.UploadSource) (
