Files
full/module/base/fts/internal/logic/handler.go
2026-09-22 21:15:34 +08:00

161 lines
4.0 KiB
Go
Raw 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 logic
import (
"crypto/sha256"
"encoding/hex"
"io"
"log"
"mime/multipart"
"os"
"path/filepath"
"regexp"
"strings"
"bsm/full/module/base/fts/internal/config"
"bsm/full/module/base/fts/internal/impl"
"bsm/full/module/base/fts/internal/models"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/bsm-sdk/core/middleware"
"git.apinb.com/bsm-sdk/core/types"
"git.apinb.com/bsm-sdk/core/utils"
"github.com/gin-gonic/gin"
)
// bucketPattern bucket名称只允许字母、数字、下划线和中划线
var bucketPattern = regexp.MustCompile(`^[A-Za-z0-9_-]{1,64}$`)
// Handler 上传文件
func Handler(c *gin.Context) {
var (
provider = strings.ToLower(c.PostForm("provider"))
bucket = strings.ToLower(c.PostForm("bucket"))
claims *types.JwtClaims
err error
)
claims, err = middleware.ParseAuth(c)
if err != nil {
log.Printf("获取当前登录用户信息失败: %v\n", err)
infra.Response.Error(c, err)
return
}
if provider == "" || bucket == "" {
infra.Response.Error(c, errcode.NewError(400, "参数错误"))
return
}
// 校验 bucket 名称,避免非法字符导致本地路径穿越或写入任意对象存储桶
if !bucketPattern.MatchString(bucket) {
infra.Response.Error(c, errcode.NewError(400, "bucket参数错误"))
return
}
// if !strings.Contains(provider, "local") {
// log.Println("provider参数错误")
// infra.Response.Error(c, errcode.NewError(400, "provider参数错误"))
// return
// }
fh, err := c.FormFile(config.Spec.FtsConfig.InputKey)
if err != nil {
infra.Response.Error(c, err)
return
}
// 获取文件信息
fileSize := fh.Size
if fileSize > config.Spec.FtsConfig.MaxSize {
infra.Response.Error(c, errcode.NewError(400, "文件大小超过限制"))
return
}
// 检测是否是允许上传的文件类型
fileExt := filepath.Ext(fh.Filename)
if !isAllow(fileExt) {
log.Println("不允许上传的文件类型:", fileExt)
infra.Response.Error(c, errcode.NewError(501, "不允许上传的文件类型"))
return
}
fileHash, err := chksum(fh)
if err != nil {
log.Println("文件校验失败:", err)
infra.Response.Error(c, err)
return
}
record := models.FtsRecord{
Identity: utils.UUID(),
OwnerID: claims.ID,
OwnerIdentity: claims.Identity,
Name: fh.Filename,
Ext: fileExt,
Size: uint64(fileSize),
Hash: fileHash,
Status: 0,
}
switch provider {
case "local":
err = LocalUpload(fh, &record, c, bucket)
case "minio":
err = OssUpload(fh, &record, c, bucket)
default:
infra.Response.Error(c, errcode.NewError(400, "provider参数错误"))
return
}
if err != nil {
infra.Response.Error(c, err)
return
}
if err := impl.DBService.Create(&record).Error; err != nil {
// 落库失败时清理已写入的本地文件或对象存储对象,避免残留孤儿文件
removeStoredFile(&record, bucket)
infra.Response.Error(c, err)
return
}
infra.Response.Success(c, record)
}
// removeStoredFile 清理已落盘/已上传的文件。单条 INSERT 无需事务,落库失败时按记录回滚外部存储
func removeStoredFile(record *models.FtsRecord, bucket string) {
if record.LocalPath != "" {
if err := os.Remove(record.LocalPath); err != nil {
log.Printf("清理本地文件失败: %v\n", err)
}
}
if record.OssPath != "" {
if err := RemoveOssObject(bucket, record.OssPath); err != nil {
log.Printf("清理对象存储文件失败: %v\n", err)
}
}
}
func chksum(fh *multipart.FileHeader) (string, error) {
// 2. 打开文件读取内容
file, err := fh.Open()
if err != nil {
log.Println("multipart err:", err)
return "", err
}
defer file.Close()
// 3. 计算文件哈希值sha256
hash := sha256.New()
if _, err = io.Copy(hash, file); err != nil {
log.Println("哈希计算失败:", err)
return "", err
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
func isAllow(extName string) bool {
for _, allowExt := range config.Spec.FtsConfig.Allows {
if extName == allowExt {
return true
}
}
return false
}