实现文件上传
This commit is contained in:
@@ -2,12 +2,22 @@ package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"platform/pkg/env"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gen/field"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -16,6 +26,128 @@ var Article = &articleService{}
|
||||
|
||||
type articleService struct{}
|
||||
|
||||
var articleUploadMimeExt = map[string]string{
|
||||
"image/gif": ".gif",
|
||||
"image/jpeg": ".jpg",
|
||||
"image/png": ".png",
|
||||
"image/webp": ".webp",
|
||||
}
|
||||
|
||||
type ArticleUploadResult struct {
|
||||
URL string `json:"url"`
|
||||
Path string `json:"path"`
|
||||
OriginalName string `json:"original_name"`
|
||||
Size int64 `json:"size"`
|
||||
MimeType string `json:"mime_type"`
|
||||
}
|
||||
|
||||
func (s *articleService) UploadImage(fileHeader *multipart.FileHeader, baseURL string) (*ArticleUploadResult, error) {
|
||||
if fileHeader == nil {
|
||||
return nil, core.NewBizErr("缺少上传文件")
|
||||
}
|
||||
if fileHeader.Size > int64(env.ArticleUploadMaxBytes) {
|
||||
return nil, core.NewBizErr(fmt.Sprintf("图片大小不能超过 %s", formatUploadSizeLimit(env.ArticleUploadMaxBytes)))
|
||||
}
|
||||
|
||||
mimeType, ext, err := detectArticleImage(fileHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
year := now.Format("2006")
|
||||
month := now.Format("01")
|
||||
fileName := uuid.NewString() + ext
|
||||
relativePath := path.Join("/uploads", "article", year, month, fileName)
|
||||
targetDir := filepath.Join(env.UploadDir, "article", year, month)
|
||||
finalPath := filepath.Join(targetDir, fileName)
|
||||
|
||||
if err := os.MkdirAll(targetDir, 0o755); err != nil {
|
||||
return nil, core.NewServErr("创建上传目录失败", err)
|
||||
}
|
||||
|
||||
src, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("打开上传文件失败", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
tmp, err := os.CreateTemp(targetDir, "upload-*"+ext)
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("创建临时文件失败", err)
|
||||
}
|
||||
|
||||
tmpPath := tmp.Name()
|
||||
finished := false
|
||||
defer func() {
|
||||
if !finished {
|
||||
_ = tmp.Close()
|
||||
_ = os.Remove(tmpPath)
|
||||
}
|
||||
}()
|
||||
|
||||
limitedReader := &io.LimitedReader{R: src, N: int64(env.ArticleUploadMaxBytes) + 1}
|
||||
written, err := io.Copy(tmp, limitedReader)
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("保存上传文件失败", err)
|
||||
}
|
||||
if written > int64(env.ArticleUploadMaxBytes) {
|
||||
return nil, core.NewBizErr(fmt.Sprintf("图片大小不能超过 %s", formatUploadSizeLimit(env.ArticleUploadMaxBytes)))
|
||||
}
|
||||
if err := tmp.Close(); err != nil {
|
||||
return nil, core.NewServErr("关闭临时文件失败", err)
|
||||
}
|
||||
if err := os.Rename(tmpPath, finalPath); err != nil {
|
||||
return nil, core.NewServErr("保存上传文件失败", err)
|
||||
}
|
||||
finished = true
|
||||
|
||||
cleanBaseURL := strings.TrimRight(baseURL, "/")
|
||||
url := relativePath
|
||||
if cleanBaseURL != "" {
|
||||
url = cleanBaseURL + relativePath
|
||||
}
|
||||
|
||||
return &ArticleUploadResult{
|
||||
URL: url,
|
||||
Path: relativePath,
|
||||
OriginalName: filepath.Base(fileHeader.Filename),
|
||||
Size: written,
|
||||
MimeType: mimeType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func detectArticleImage(fileHeader *multipart.FileHeader) (string, string, error) {
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
return "", "", core.NewServErr("打开上传文件失败", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buf := make([]byte, 512)
|
||||
n, err := io.ReadFull(file, buf)
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
return "", "", core.NewServErr("读取上传文件失败", err)
|
||||
}
|
||||
|
||||
mimeType := http.DetectContentType(buf[:n])
|
||||
ext, ok := articleUploadMimeExt[mimeType]
|
||||
if !ok {
|
||||
return "", "", core.NewBizErr("仅支持 JPG、PNG、WEBP、GIF 图片")
|
||||
}
|
||||
return mimeType, ext, nil
|
||||
}
|
||||
|
||||
func formatUploadSizeLimit(bytes int) string {
|
||||
if bytes%(1024*1024) == 0 {
|
||||
return fmt.Sprintf("%d MB", bytes/(1024*1024))
|
||||
}
|
||||
if bytes%1024 == 0 {
|
||||
return fmt.Sprintf("%d KB", bytes/1024)
|
||||
}
|
||||
return fmt.Sprintf("%d bytes", bytes)
|
||||
}
|
||||
|
||||
func (s *articleService) Page(req *PageArticleReq) (result []*m.Article, count int64, err error) {
|
||||
do := q.Article.Where()
|
||||
if req.Keyword != nil && *req.Keyword != "" {
|
||||
|
||||
Reference in New Issue
Block a user