Files

118 lines
3.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"net/http"
"path/filepath"
"strings"
"time"
"silk-server-go/internal/middleware"
"silk-server-go/internal/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// maxImageSize 知识库图片大小上限(5MB)
const maxImageSize = 5 << 20
// imageContentType 根据扩展名返回图片 Content-Type
func imageContentType(ext string) (string, bool) {
switch strings.ToLower(ext) {
case ".jpg", ".jpeg":
return "image/jpeg", true
case ".png":
return "image/png", true
case ".webp":
return "image/webp", true
}
return "", false
}
// validateImageFile 校验图片文件名扩展名与大小
func validateImageFile(filename string, size int64) error {
if size <= 0 || size > maxImageSize {
return errors.New("图片大小需在 1B~5MB 之间")
}
ext := strings.ToLower(filepath.Ext(filename))
if _, ok := imageContentType(ext); !ok {
return errors.New("仅支持 jpg/jpeg/png/webp 图片")
}
return nil
}
// buildObjectKey 生成 <prefix>/<日期>/<随机>.ext 的对象键
func buildObjectKey(prefix, filename string) (string, string, error) {
ext := strings.ToLower(filepath.Ext(filename))
if _, ok := imageContentType(ext); !ok {
return "", "", errors.New("仅支持 jpg/jpeg/png/webp 图片")
}
buf := make([]byte, 8)
if _, err := rand.Read(buf); err != nil {
return "", "", err
}
date := time.Now().Format("20060102")
key := fmt.Sprintf("%s/%s/%s%s", prefix, date, hex.EncodeToString(buf), ext)
return key, ext, nil
}
// buildImageKey 生成 knowledge/<日期>/<随机>.ext 的对象键
func buildImageKey(filename string) (string, string, error) {
return buildObjectKey("knowledge", filename)
}
// buildDataKey 生成 <prefix>/<日期>/<随机><ext>(不校验扩展名,由调用方校验)
func buildDataKey(prefix, filename string) (string, string, error) {
ext := strings.ToLower(filepath.Ext(filename))
buf := make([]byte, 8)
if _, err := rand.Read(buf); err != nil {
return "", "", err
}
date := time.Now().Format("20060102")
return fmt.Sprintf("%s/%s/%s%s", prefix, date, hex.EncodeToString(buf), ext), ext, nil
}
// uploadKnowledgeImage 上传知识库图片(multipart 字段名 file
func uploadKnowledgeImage(s3 *service.S3Service, bucket string) gin.HandlerFunc {
return func(c *gin.Context) {
file, header, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请选择图片文件(字段名 file"})
return
}
defer file.Close()
if err := validateImageFile(header.Filename, header.Size); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
key, ext, err := buildImageKey(header.Filename)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
contentType, _ := imageContentType(ext)
if err := s3.EnsureBucket(bucket); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "图片存储不可用: " + err.Error()})
return
}
if err := s3.UploadImage(bucket, key, file, contentType); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "图片上传失败: " + err.Error()})
return
}
url := fmt.Sprintf("%s/%s/%s", s3.Endpoint(), bucket, key)
c.JSON(http.StatusOK, gin.H{"url": url, "name": header.Filename, "size": header.Size})
}
}
// uploadImageRoute 在知识库路由组中注册图片上传(由 RegisterKnowledgeRoutes 调用)
func registerKnowledgeImageRoute(rg *gin.RouterGroup, db *gorm.DB, s3 *service.S3Service, bucket string) {
rg.POST("/knowledge/images", middleware.RequirePermission(db, "knowledge:write"), uploadKnowledgeImage(s3, bucket))
}