2025-12-26 13:59:02 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
2026-06-11 09:14:45 +08:00
|
|
|
|
"io"
|
2026-04-22 12:49:27 +08:00
|
|
|
|
"net/url"
|
2025-12-26 13:59:02 +08:00
|
|
|
|
"oss/consts"
|
|
|
|
|
|
"oss/dao"
|
2026-04-15 17:00:09 +08:00
|
|
|
|
"oss/minio"
|
2025-12-26 13:59:02 +08:00
|
|
|
|
"oss/model/dto"
|
|
|
|
|
|
"oss/model/entity"
|
|
|
|
|
|
"time"
|
2025-12-30 10:13:11 +08:00
|
|
|
|
|
2026-04-22 12:49:27 +08:00
|
|
|
|
"github.com/gogf/gf/v2/net/ghttp"
|
2026-03-18 13:17:59 +08:00
|
|
|
|
"github.com/gogf/gf/v2/os/glog"
|
|
|
|
|
|
|
2026-06-10 16:30:36 +08:00
|
|
|
|
"gitea.redpowerfuture.com/red-future/common/utils"
|
2025-12-30 10:13:11 +08:00
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
2025-12-26 13:59:02 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type file struct{}
|
|
|
|
|
|
|
|
|
|
|
|
// File 存储文件服务
|
|
|
|
|
|
var File = new(file)
|
|
|
|
|
|
|
2026-04-22 08:45:45 +08:00
|
|
|
|
func (f *file) DownloadToFile(ctx context.Context, req *dto.DownloadToFileReq) (err error) {
|
|
|
|
|
|
return minio.DownloadToFile(ctx, req.FileURL, req.LocalPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-22 12:49:27 +08:00
|
|
|
|
// DownloadToBrowser 下载文件到浏览器
|
|
|
|
|
|
func (f *file) DownloadToBrowser(ctx context.Context, req *dto.DownloadToBrowserReq) (err error) {
|
2026-06-11 09:14:45 +08:00
|
|
|
|
// 拿到流,而不是全量字节数组
|
|
|
|
|
|
reader, fileName, contentType, fileSize, err := minio.DownloadToBrowser(ctx, req.FileURL)
|
2026-04-22 12:49:27 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-11 09:14:45 +08:00
|
|
|
|
// 重要:确保流最后关闭
|
|
|
|
|
|
if closer, ok := reader.(io.Closer); ok {
|
|
|
|
|
|
defer closer.Close()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-22 12:49:27 +08:00
|
|
|
|
r := ghttp.RequestFromCtx(ctx)
|
2026-06-11 09:14:45 +08:00
|
|
|
|
resp := r.Response
|
|
|
|
|
|
|
|
|
|
|
|
resp.Header().Set("Content-Type", contentType)
|
|
|
|
|
|
resp.Header().Set("Content-Disposition", fmt.Sprintf(
|
|
|
|
|
|
`attachment; filename="%s"; filename*=UTF-8''%s`,
|
|
|
|
|
|
fileName, url.PathEscape(fileName),
|
|
|
|
|
|
))
|
|
|
|
|
|
resp.Header().Set("Content-Length", fmt.Sprintf("%d", fileSize))
|
|
|
|
|
|
resp.Header().Del("Content-Encoding")
|
|
|
|
|
|
resp.Header().Del("Transfer-Encoding")
|
|
|
|
|
|
data, err := io.ReadAll(reader)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
resp.WriteExit(data)
|
2026-04-22 12:49:27 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-11 09:14:45 +08:00
|
|
|
|
func (f *file) DeleteFile(ctx context.Context, req *dto.DeleteFileReq) (err error) {
|
|
|
|
|
|
return minio.DeleteFile(ctx, req.FileURL)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 13:59:02 +08:00
|
|
|
|
func (f *file) UploadFile(ctx context.Context, req *dto.UploadFileReq) (res *dto.UploadFileRes, err error) {
|
2026-06-11 09:14:45 +08:00
|
|
|
|
fileReq := new(dto.UploadFileReq)
|
|
|
|
|
|
if !g.IsEmpty(req.File) {
|
|
|
|
|
|
fileReq.Files = &ghttp.UploadFiles{req.File}
|
|
|
|
|
|
files, err := f.UploadFiles(ctx, fileReq)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
res = files.Items[0]
|
|
|
|
|
|
} else if !g.IsEmpty(req.Files) {
|
|
|
|
|
|
fileReq.Files = req.Files
|
|
|
|
|
|
res, err = f.UploadFiles(ctx, fileReq)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return nil, gerror.New("上传内容不能为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UploadFiles 上传多个文件(新接口)
|
|
|
|
|
|
func (f *file) UploadFiles(ctx context.Context, req *dto.UploadFileReq) (res *dto.UploadFileRes, err error) {
|
|
|
|
|
|
var files = *req.Files
|
|
|
|
|
|
|
|
|
|
|
|
totalAdd := 0
|
|
|
|
|
|
for _, file := range files {
|
|
|
|
|
|
if file == nil {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
totalAdd += gconv.Int(file.Size)
|
|
|
|
|
|
}
|
|
|
|
|
|
tenantId, err := f.reserveTenantOssSize(ctx, totalAdd)
|
2025-12-26 13:59:02 +08:00
|
|
|
|
if err != nil {
|
2026-06-11 09:14:45 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
prefix, _ := utils.GetFileAddressPrefix(ctx)
|
|
|
|
|
|
items := make([]*dto.UploadFileRes, 0, len(files))
|
|
|
|
|
|
|
|
|
|
|
|
for _, file := range files {
|
|
|
|
|
|
if file == nil {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fileURL, fileName, fileFormat, e := minio.UploadFile(ctx, file)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
glog.Errorf(ctx, "上传文件失败: %v", e)
|
|
|
|
|
|
return nil, e
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ossEntity := &dto.UploadFile{
|
|
|
|
|
|
TenantId: tenantId,
|
|
|
|
|
|
FileURL: fileURL,
|
|
|
|
|
|
FileSize: gconv.Int(file.Size),
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, e = dao.File.Insert(ctx, ossEntity); e != nil {
|
|
|
|
|
|
return nil, e
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
items = append(items, &dto.UploadFileRes{
|
|
|
|
|
|
FileURL: fileURL,
|
|
|
|
|
|
FileSize: gconv.Int(file.Size),
|
|
|
|
|
|
FileName: fileName,
|
|
|
|
|
|
FileFormat: fileFormat,
|
|
|
|
|
|
FileAddressPrefix: prefix,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
fileRes := &dto.UploadFileRes{
|
|
|
|
|
|
FileAddressPrefix: prefix,
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
}
|
|
|
|
|
|
return fileRes, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (f *file) reserveTenantOssSize(ctx context.Context, addSize int) (tenantId uint64, err error) {
|
|
|
|
|
|
if addSize < 0 {
|
|
|
|
|
|
err = gerror.New("addSize 不能为负数")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
// 获取租户id
|
|
|
|
|
|
user, e := utils.GetUserInfo(ctx)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
err = e
|
2025-12-30 18:45:37 +08:00
|
|
|
|
glog.Errorf(ctx, "获取用户信息失败: %v", err)
|
2025-12-26 13:59:02 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-06-11 09:14:45 +08:00
|
|
|
|
tenantId = user.TenantId
|
|
|
|
|
|
|
2025-12-26 13:59:02 +08:00
|
|
|
|
// 获取redis-租户存储容量总数key
|
|
|
|
|
|
tenantOssTotalKey := fmt.Sprintf(consts.TenantOssTotalKey, gconv.String(user.TenantId))
|
|
|
|
|
|
// 获取redis-租户存储-锁key
|
|
|
|
|
|
fileLockKey := fmt.Sprintf(consts.FileLockKey, gconv.String(user.TenantId))
|
2025-12-29 14:42:56 +08:00
|
|
|
|
|
2026-06-11 09:14:45 +08:00
|
|
|
|
success, e := utils.Lock(ctx, fileLockKey, gconv.Int64(time.Minute*1), func(ctx context.Context) error {
|
2025-12-29 14:42:56 +08:00
|
|
|
|
// 获取redis-租户存储容量总数
|
2026-06-11 09:14:45 +08:00
|
|
|
|
get, e := g.Redis().Get(ctx, tenantOssTotalKey)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
glog.Errorf(ctx, "获取redis-租户存储容量总数失败: %v", e)
|
|
|
|
|
|
return e
|
2025-12-26 13:59:02 +08:00
|
|
|
|
}
|
2025-12-29 14:42:56 +08:00
|
|
|
|
tenantOssTotalEntity := &entity.TenantOssTotal{}
|
|
|
|
|
|
if g.IsEmpty(get) {
|
|
|
|
|
|
//查询数据库-获取租户存储容量总数
|
|
|
|
|
|
getByTenantIdReq := &dto.GetByTenantIdReq{
|
2026-01-12 19:08:27 +08:00
|
|
|
|
TenantId: user.TenantId,
|
2025-12-29 14:42:56 +08:00
|
|
|
|
}
|
2026-06-11 09:14:45 +08:00
|
|
|
|
tenantOssTotalRes, e := dao.TenantOssTotal.GetOneByTenantId(ctx, getByTenantIdReq)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
glog.Errorf(ctx, "查询数据库-获取租户存储容量总数失败: %v", e)
|
|
|
|
|
|
return e
|
2025-12-29 14:42:56 +08:00
|
|
|
|
}
|
2026-03-18 13:17:59 +08:00
|
|
|
|
if tenantOssTotalRes == nil || g.IsEmpty(tenantOssTotalRes.Id) {
|
2026-01-12 19:08:27 +08:00
|
|
|
|
// 数据库中没有该租户的记录,创建默认配置
|
2025-12-29 14:42:56 +08:00
|
|
|
|
tenantOssTotalEntity.TenantId = user.TenantId
|
2025-12-30 18:45:37 +08:00
|
|
|
|
tenantOssTotalEntity.UsedOssSize = 0
|
2025-12-31 09:53:21 +08:00
|
|
|
|
tenantOssTotalEntity.TotalOssSize = g.Cfg().MustGet(ctx, "oss.capacitySize").Int() * 1024 * 1024
|
2025-12-29 14:42:56 +08:00
|
|
|
|
} else {
|
2026-03-18 13:17:59 +08:00
|
|
|
|
tenantOssTotalEntity = tenantOssTotalRes
|
2025-12-29 14:42:56 +08:00
|
|
|
|
}
|
2025-12-26 13:59:02 +08:00
|
|
|
|
} else {
|
2025-12-29 14:42:56 +08:00
|
|
|
|
// 反序列化-redis获取租户存储容量总数
|
2026-06-11 09:14:45 +08:00
|
|
|
|
if e = gconv.Struct(get, tenantOssTotalEntity); e != nil {
|
|
|
|
|
|
glog.Errorf(ctx, "反序列化-redis获取租户存储容量总数失败: %v", e)
|
|
|
|
|
|
return e
|
2025-12-29 14:42:56 +08:00
|
|
|
|
}
|
2025-12-26 13:59:02 +08:00
|
|
|
|
}
|
2026-01-12 19:08:27 +08:00
|
|
|
|
tenantId = tenantOssTotalEntity.TenantId
|
2026-06-11 09:14:45 +08:00
|
|
|
|
usedAfter := tenantOssTotalEntity.UsedOssSize + addSize
|
|
|
|
|
|
total := tenantOssTotalEntity.TotalOssSize
|
|
|
|
|
|
|
|
|
|
|
|
if usedAfter > total {
|
|
|
|
|
|
return gerror.New("存储服务内存不足")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置redis-租户存储容量总数(超时时间10分钟)
|
2026-03-19 17:35:38 +08:00
|
|
|
|
tenantOssTotalKeyMap := dto.UpdateUsedOssReq{
|
2025-12-30 18:45:37 +08:00
|
|
|
|
TenantId: tenantId,
|
2026-06-11 09:14:45 +08:00
|
|
|
|
UsedOssSize: usedAfter,
|
|
|
|
|
|
TotalOssSize: total,
|
2026-03-19 17:35:38 +08:00
|
|
|
|
Creator: user.UserName,
|
2026-01-12 19:08:27 +08:00
|
|
|
|
Updater: user.UserName,
|
2025-12-30 18:45:37 +08:00
|
|
|
|
}
|
2026-06-11 09:14:45 +08:00
|
|
|
|
if e = g.Redis().SetEX(ctx, tenantOssTotalKey, tenantOssTotalKeyMap, gconv.Int64(time.Minute*10)); e != nil {
|
|
|
|
|
|
glog.Errorf(ctx, "修改redis-租户存储容量总数失败: %v", e)
|
|
|
|
|
|
return e
|
2025-12-29 14:42:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
2026-06-11 09:14:45 +08:00
|
|
|
|
if e != nil {
|
|
|
|
|
|
err = e
|
2026-03-24 16:23:04 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-06-11 09:14:45 +08:00
|
|
|
|
if !success {
|
|
|
|
|
|
err = gerror.New("存储服务内存不足")
|
2026-04-22 08:45:45 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|