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

140 lines
3.8 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 (
"context"
"errors"
"io"
"log"
"mime/multipart"
"os"
"path/filepath"
"strings"
"time"
"bsm/full/module/base/fts/internal/config"
"bsm/full/module/base/fts/internal/models"
"git.apinb.com/bsm-sdk/core/utils"
"github.com/gin-gonic/gin"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// LocalUpload 本地上传
func LocalUpload(fh *multipart.FileHeader, record *models.FtsRecord, ctx *gin.Context, bucket string) (err error) {
subdirpath := NewSubdir(record.OwnerIdentity)
// 拼接后校验路径必须位于配置的上传根目录内,防止路径穿越
saveDir, err := safeJoin(config.Spec.Local.UploadDir, bucket, subdirpath)
if err != nil {
log.Println("存储路径非法:", err)
return err
}
// 创建目录并确保权限正确
if err = os.MkdirAll(saveDir, 0755); err != nil {
log.Println("目录创建失败:", err)
return errors.New("目录创建失败")
}
// 保存文件到指定路径
fileName := utils.ULID() + record.Ext
savePath := filepath.Join(saveDir, fileName)
// 使用自定义方式保存文件,确保权限控制
file, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Println("文件创建失败:", err)
return err
}
defer file.Close()
src, err := fh.Open()
if err != nil {
log.Println("文件打开失败:", err)
return err
}
defer src.Close()
if _, err = io.Copy(file, src); err != nil {
log.Println("文件保存失败:", err)
return err
}
// 再次确认文件权限
if err = os.Chmod(savePath, 0644); err != nil {
log.Printf("警告: 设置文件权限失败: %v", err)
return err
}
record.LocalPath = savePath
record.SaveName = fileName
record.ResultUrl = config.Spec.Local.Site + "/" + bucket + "/" + subdirpath + "/" + fileName
return nil
}
// OssUpload 上传文件到指定的MinIO存储桶
func OssUpload(file *multipart.FileHeader, record *models.FtsRecord, c *gin.Context, bucket string) (err error) {
minioClient, err := newMinioClient()
if err != nil {
return err
}
// 设置保存格式为: 年/identity/identity后3位+filename
subdirpath := NewSubdir(record.OwnerIdentity)
fileName := utils.ULID() + record.Ext
savePath := filepath.Join(subdirpath, fileName)
// Open the file
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
_, err = minioClient.PutObject(context.Background(), bucket, savePath, src, file.Size, minio.PutObjectOptions{ContentType: "application/octet-stream"})
if err != nil {
return err
}
record.OssPath = savePath
record.ResultUrl = config.Spec.MinioOss.Site + "/" + bucket + "/" + savePath
return nil
}
// RemoveOssObject 删除已上传到MinIO的对象用于落库失败后的清理
func RemoveOssObject(bucket, objectName string) error {
if bucket == "" || objectName == "" {
return nil
}
minioClient, err := newMinioClient()
if err != nil {
return err
}
return minioClient.RemoveObject(context.Background(), bucket, objectName, minio.RemoveObjectOptions{})
}
// newMinioClient 创建MinIO客户端
func newMinioClient() (*minio.Client, error) {
return minio.New(config.Spec.MinioOss.Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(config.Spec.MinioOss.AccessKeyID, config.Spec.MinioOss.AccessKeySecret, ""),
Secure: config.Spec.MinioOss.UseSSL,
})
}
// safeJoin 在根目录下安全拼接子路径,拒绝越出根目录的路径,防止路径穿越
func safeJoin(root string, sub ...string) (string, error) {
root = filepath.Clean(root)
target := filepath.Join(append([]string{root}, sub...)...)
if target != root && !strings.HasPrefix(target, root+string(filepath.Separator)) {
return "", errors.New("存储路径越界")
}
return target, nil
}
func NewSubdir(identity string) string {
ym := time.Now().Format("2006-01")
return ym + "/" + identity[0:2]
}