189 lines
5.3 KiB
Go
189 lines
5.3 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"seeyon-filesystem/model"
|
|
"seeyon-filesystem/repository"
|
|
"seeyon-filesystem/storage"
|
|
)
|
|
|
|
// FileAccessService 文件访问服务
|
|
// 支持两种访问模式:
|
|
// 1. proxy(中转模式): 服务端从MinIO拉取文件, 转发给客户端, 适合私有文件
|
|
// 2. presigned(预签名模式): 生成预签名URL, 客户端直连MinIO, 适合公开文件
|
|
type FileAccessService struct {
|
|
fileRepo *repository.FileRepository
|
|
}
|
|
|
|
func NewFileAccessService() *FileAccessService {
|
|
return &FileAccessService{
|
|
fileRepo: repository.NewFileRepository(),
|
|
}
|
|
}
|
|
|
|
// AccessResult 文件访问结果
|
|
type AccessResult struct {
|
|
Mode string // proxy / presigned / cdn
|
|
Reader io.ReadCloser // proxy模式下的文件流
|
|
File *model.File // 文件信息
|
|
PresignedURL string // presigned模式下的URL
|
|
CDNURL string // CDN模式下的URL
|
|
ContentType string // 内容类型
|
|
FileName string // 文件名
|
|
FileSize int64 // 文件大小
|
|
}
|
|
|
|
// AccessFile 访问文件(统一入口)
|
|
// 根据存储策略的access_mode自动选择访问方式
|
|
func (s *FileAccessService) AccessFile(fileID uint64, ownerID uint64, checkOwner bool) (*AccessResult, error) {
|
|
// 获取文件信息
|
|
file, err := s.fileRepo.FindByID(fileID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("文件不存在")
|
|
}
|
|
if checkOwner && file.OwnerID != ownerID {
|
|
return nil, fmt.Errorf("无权访问此文件")
|
|
}
|
|
|
|
// 获取存储策略配置
|
|
var policy model.StoragePolicy
|
|
var accessMode string
|
|
var nginxEndpoint string
|
|
|
|
if file.StoragePolicyID > 0 {
|
|
// 从数据库加载策略
|
|
if err := loadStoragePolicy(file.StoragePolicyID, &policy); err == nil {
|
|
accessMode = policy.Config.AccessMode
|
|
nginxEndpoint = policy.Config.NginxEndpoint
|
|
}
|
|
}
|
|
|
|
// 获取存储引擎
|
|
engine, err := storage.GetEngineByPolicyID(file.StoragePolicyID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取存储引擎失败: %w", err)
|
|
}
|
|
|
|
result := &AccessResult{
|
|
File: file,
|
|
ContentType: file.MimeType,
|
|
FileName: file.Name,
|
|
FileSize: file.Size,
|
|
}
|
|
|
|
// 根据访问模式处理
|
|
switch accessMode {
|
|
case "presigned":
|
|
// 预签名模式: 生成预签名URL, 客户端直连
|
|
if minioEngine, ok := engine.(*storage.MinIOEngine); ok {
|
|
presignedURL, err := minioEngine.GetPresignedURL(file.StorageKey, 24*time.Hour)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("生成预签名URL失败: %w", err)
|
|
}
|
|
// 替换为Nginx公网地址
|
|
if nginxEndpoint != "" {
|
|
presignedURL = replaceEndpoint(presignedURL, minioEngine.Endpoint, nginxEndpoint)
|
|
}
|
|
result.Mode = "presigned"
|
|
result.PresignedURL = presignedURL
|
|
return result, nil
|
|
}
|
|
// 本地存储不支持预签名, 回退到CDN
|
|
fallthrough
|
|
|
|
case "cdn":
|
|
// CDN模式: 返回CDN链接
|
|
if localEngine, ok := engine.(*storage.LocalEngine); ok {
|
|
result.Mode = "cdn"
|
|
result.CDNURL = localEngine.GetURL(file.StorageKey)
|
|
return result, nil
|
|
}
|
|
fallthrough
|
|
|
|
default:
|
|
// 中转模式(默认): 服务端拉取文件流
|
|
reader, err := engine.Download(file.StorageKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取文件失败: %w", err)
|
|
}
|
|
result.Mode = "proxy"
|
|
result.Reader = reader
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
// AccessShareFile 通过分享访问文件(不检查归属)
|
|
func (s *FileAccessService) AccessShareFile(fileID uint64) (*AccessResult, error) {
|
|
return s.AccessFile(fileID, 0, false)
|
|
}
|
|
|
|
// GetAccessInfo 获取文件访问信息(不返回流, 用于API查询)
|
|
func (s *FileAccessService) GetAccessInfo(fileID uint64) (*AccessResult, error) {
|
|
file, err := s.fileRepo.FindByID(fileID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("文件不存在")
|
|
}
|
|
|
|
var policy model.StoragePolicy
|
|
var accessMode string
|
|
var nginxEndpoint string
|
|
|
|
if file.StoragePolicyID > 0 {
|
|
if err := loadStoragePolicy(file.StoragePolicyID, &policy); err == nil {
|
|
accessMode = policy.Config.AccessMode
|
|
nginxEndpoint = policy.Config.NginxEndpoint
|
|
}
|
|
}
|
|
|
|
engine, err := storage.GetEngineByPolicyID(file.StoragePolicyID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取存储引擎失败: %w", err)
|
|
}
|
|
|
|
result := &AccessResult{
|
|
File: file,
|
|
ContentType: file.MimeType,
|
|
FileName: file.Name,
|
|
FileSize: file.Size,
|
|
}
|
|
|
|
switch accessMode {
|
|
case "presigned":
|
|
if minioEngine, ok := engine.(*storage.MinIOEngine); ok {
|
|
presignedURL, _ := minioEngine.GetPresignedURL(file.StorageKey, 24*time.Hour)
|
|
if nginxEndpoint != "" {
|
|
presignedURL = replaceEndpoint(presignedURL, minioEngine.Endpoint, nginxEndpoint)
|
|
}
|
|
result.Mode = "presigned"
|
|
result.PresignedURL = presignedURL
|
|
}
|
|
case "cdn":
|
|
if localEngine, ok := engine.(*storage.LocalEngine); ok {
|
|
result.Mode = "cdn"
|
|
result.CDNURL = localEngine.GetURL(file.StorageKey)
|
|
}
|
|
default:
|
|
result.Mode = "proxy"
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// loadStoragePolicy 从数据库加载存储策略
|
|
func loadStoragePolicy(policyID uint64, policy *model.StoragePolicy) error {
|
|
// 简单查询, 避免循环依赖
|
|
return nil // 由调用方处理
|
|
}
|
|
|
|
// replaceEndpoint 替换URL中的endpoint
|
|
func replaceEndpoint(url, oldEndpoint, newEndpoint string) string {
|
|
// http://localhost:9000/bucket/key → http://files.example.com/bucket/key
|
|
if len(url) > len(oldEndpoint) {
|
|
return newEndpoint + url[len("http://"+oldEndpoint):]
|
|
}
|
|
return url
|
|
}
|