package service import ( "context" "fmt" "io" "net/url" "oss/consts" "oss/dao" "oss/minio" "oss/model/dto" "oss/model/entity" "time" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/glog" "gitea.redpowerfuture.com/red-future/common/utils" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) type file struct{} // File 存储文件服务 var File = new(file) func (f *file) DownloadToFile(ctx context.Context, req *dto.DownloadToFileReq) (err error) { return minio.DownloadToFile(ctx, req.FileURL, req.LocalPath) } // DownloadToBrowser 下载文件到浏览器 func (f *file) DownloadToBrowser(ctx context.Context, req *dto.DownloadToBrowserReq) (err error) { // 拿到流,而不是全量字节数组 reader, fileName, contentType, fileSize, err := minio.DownloadToBrowser(ctx, req.FileURL) if err != nil { return err } // 重要:确保流最后关闭 if closer, ok := reader.(io.Closer); ok { defer closer.Close() } r := ghttp.RequestFromCtx(ctx) 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) return } func (f *file) DeleteFile(ctx context.Context, req *dto.DeleteFileReq) (err error) { return minio.DeleteFile(ctx, req.FileURL) } func (f *file) UploadFile(ctx context.Context, req *dto.UploadFileReq) (res *dto.UploadFileRes, err error) { 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) if err != nil { 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 glog.Errorf(ctx, "获取用户信息失败: %v", err) return } tenantId = user.TenantId // 获取redis-租户存储容量总数key tenantOssTotalKey := fmt.Sprintf(consts.TenantOssTotalKey, gconv.String(user.TenantId)) // 获取redis-租户存储-锁key fileLockKey := fmt.Sprintf(consts.FileLockKey, gconv.String(user.TenantId)) success, e := utils.Lock(ctx, fileLockKey, gconv.Int64(time.Minute*1), func(ctx context.Context) error { // 获取redis-租户存储容量总数 get, e := g.Redis().Get(ctx, tenantOssTotalKey) if e != nil { glog.Errorf(ctx, "获取redis-租户存储容量总数失败: %v", e) return e } tenantOssTotalEntity := &entity.TenantOssTotal{} if g.IsEmpty(get) { //查询数据库-获取租户存储容量总数 getByTenantIdReq := &dto.GetByTenantIdReq{ TenantId: user.TenantId, } tenantOssTotalRes, e := dao.TenantOssTotal.GetOneByTenantId(ctx, getByTenantIdReq) if e != nil { glog.Errorf(ctx, "查询数据库-获取租户存储容量总数失败: %v", e) return e } if tenantOssTotalRes == nil || g.IsEmpty(tenantOssTotalRes.Id) { // 数据库中没有该租户的记录,创建默认配置 tenantOssTotalEntity.TenantId = user.TenantId tenantOssTotalEntity.UsedOssSize = 0 tenantOssTotalEntity.TotalOssSize = g.Cfg().MustGet(ctx, "oss.capacitySize").Int() * 1024 * 1024 } else { tenantOssTotalEntity = tenantOssTotalRes } } else { // 反序列化-redis获取租户存储容量总数 if e = gconv.Struct(get, tenantOssTotalEntity); e != nil { glog.Errorf(ctx, "反序列化-redis获取租户存储容量总数失败: %v", e) return e } } tenantId = tenantOssTotalEntity.TenantId usedAfter := tenantOssTotalEntity.UsedOssSize + addSize total := tenantOssTotalEntity.TotalOssSize if usedAfter > total { return gerror.New("存储服务内存不足") } // 设置redis-租户存储容量总数(超时时间10分钟) tenantOssTotalKeyMap := dto.UpdateUsedOssReq{ TenantId: tenantId, UsedOssSize: usedAfter, TotalOssSize: total, Creator: user.UserName, Updater: user.UserName, } if e = g.Redis().SetEX(ctx, tenantOssTotalKey, tenantOssTotalKeyMap, gconv.Int64(time.Minute*10)); e != nil { glog.Errorf(ctx, "修改redis-租户存储容量总数失败: %v", e) return e } return nil }) if e != nil { err = e return } if !success { err = gerror.New("存储服务内存不足") return } return }