Files
SeeyonFileSystem/server/service/file_access_service.go

189 lines
5.3 KiB
Go
Raw Permalink Normal View History

2026-07-03 16:01:08 +08:00
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
}