初始化
This commit is contained in:
37
server/storage/engine.go
Normal file
37
server/storage/engine.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Package storage 提供文件存储引擎抽象层
|
||||
// 通过接口解耦文件存储, 支持本地磁盘、MinIO、S3等多种后端
|
||||
package storage
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Engine 存储引擎接口
|
||||
// 所有存储后端(local/minio/s3/oss)都必须实现此接口
|
||||
type Engine interface {
|
||||
// Upload 上传文件
|
||||
// storageKey: 存储路径标识, reader: 文件内容流, size: 文件大小
|
||||
// 返回实际的storageKey和错误
|
||||
Upload(storageKey string, reader io.Reader, size int64) error
|
||||
|
||||
// Download 下载文件
|
||||
// 返回文件内容流, 调用方负责关闭
|
||||
Download(storageKey string) (io.ReadCloser, error)
|
||||
|
||||
// Delete 删除文件
|
||||
Delete(storageKey string) error
|
||||
|
||||
// Exists 判断文件是否存在
|
||||
Exists(storageKey string) (bool, error)
|
||||
|
||||
// GetURL 获取文件访问URL
|
||||
// 本地存储返回相对路径, 对象存储返回完整URL
|
||||
GetURL(storageKey string) string
|
||||
}
|
||||
|
||||
// StorageResult 上传结果
|
||||
type StorageResult struct {
|
||||
StorageKey string // 存储标识
|
||||
Size int64 // 文件大小
|
||||
MD5 string // 文件MD5
|
||||
}
|
||||
112
server/storage/factory.go
Normal file
112
server/storage/factory.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"seeyon-filesystem/config"
|
||||
"seeyon-filesystem/model"
|
||||
)
|
||||
|
||||
var engines = make(map[string]Engine)
|
||||
var policyEngines = make(map[uint64]Engine)
|
||||
|
||||
func Register(policyType string, engine Engine) {
|
||||
engines[policyType] = engine
|
||||
}
|
||||
|
||||
func GetEngine(policy *model.StoragePolicy) (Engine, error) {
|
||||
if engine, ok := policyEngines[policy.ID]; ok {
|
||||
return engine, nil
|
||||
}
|
||||
engine, ok := engines[policy.Type]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("不支持的存储类型: %s", policy.Type)
|
||||
}
|
||||
if policy.Type == "local" && policy.Config.BasePath != "" {
|
||||
cfg := config.AppConfig.Storage
|
||||
engine = NewLocalEngineWithCDN(policy.Config.BasePath, cfg.CDNHost, cfg.CDNPathPrefix)
|
||||
}
|
||||
policyEngines[policy.ID] = engine
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
func GetEngineByPolicyID(policyID uint64) (Engine, error) {
|
||||
if engine, ok := policyEngines[policyID]; ok {
|
||||
return engine, nil
|
||||
}
|
||||
var policy model.StoragePolicy
|
||||
if err := config.DB.First(&policy, policyID).Error; err != nil {
|
||||
return nil, fmt.Errorf("存储策略不存在: %d", policyID)
|
||||
}
|
||||
return GetEngine(&policy)
|
||||
}
|
||||
|
||||
func GetDefaultEngine() Engine {
|
||||
if engine, ok := engines["local"]; ok {
|
||||
return engine
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitEngines 初始化所有存储引擎
|
||||
func InitEngines(localBasePath string) {
|
||||
cfg := config.AppConfig.Storage
|
||||
|
||||
// 注册默认本地存储引擎(带CDN配置)
|
||||
localEngine := NewLocalEngineWithCDN(localBasePath, cfg.CDNHost, cfg.CDNPathPrefix)
|
||||
Register("local", localEngine)
|
||||
|
||||
if cfg.CDNHost != "" {
|
||||
log.Printf("CDN已启用: %s%s", cfg.CDNHost, cfg.CDNPathPrefix)
|
||||
}
|
||||
|
||||
// 从数据库加载存储策略
|
||||
var policies []model.StoragePolicy
|
||||
if err := config.DB.Find(&policies).Error; err != nil {
|
||||
log.Printf("加载存储策略失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, policy := range policies {
|
||||
switch policy.Type {
|
||||
case "local":
|
||||
basePath := localBasePath
|
||||
if policy.Config.BasePath != "" {
|
||||
basePath = policy.Config.BasePath
|
||||
}
|
||||
policyEngines[policy.ID] = NewLocalEngineWithCDN(basePath, cfg.CDNHost, cfg.CDNPathPrefix)
|
||||
log.Printf("初始化本地存储: %s -> %s (CDN: %s)", policy.Name, basePath, cfg.CDNHost+cfg.CDNPathPrefix)
|
||||
|
||||
case "minio":
|
||||
engine, err := NewMinIOEngine(
|
||||
policy.Config.Endpoint,
|
||||
policy.Config.AccessKey,
|
||||
policy.Config.SecretKey,
|
||||
policy.Config.Bucket,
|
||||
policy.Config.UseSSL,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("初始化MinIO失败 [%s]: %v", policy.Name, err)
|
||||
continue
|
||||
}
|
||||
policyEngines[policy.ID] = engine
|
||||
log.Printf("初始化MinIO: %s -> %s/%s", policy.Name, policy.Config.Endpoint, policy.Config.Bucket)
|
||||
|
||||
case "s3", "oss":
|
||||
log.Printf("S3/OSS暂未实现: %s", policy.Name)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("存储引擎初始化完成, 已加载 %d 个策略", len(policyEngines))
|
||||
}
|
||||
|
||||
func GetMinIOEngine(policyID uint64) (*MinIOEngine, error) {
|
||||
engine, err := GetEngineByPolicyID(policyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if minioEngine, ok := engine.(*MinIOEngine); ok {
|
||||
return minioEngine, nil
|
||||
}
|
||||
return nil, fmt.Errorf("策略 %d 不是MinIO类型", policyID)
|
||||
}
|
||||
113
server/storage/local.go
Normal file
113
server/storage/local.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LocalEngine 本地磁盘存储引擎
|
||||
type LocalEngine struct {
|
||||
BasePath string // 存储根目录
|
||||
CDNHost string // CDN地址, 如 http://localhost:3090
|
||||
CDNPathPrefix string // CDN路径前缀, 如 /public
|
||||
}
|
||||
|
||||
// NewLocalEngine 创建本地存储引擎实例
|
||||
func NewLocalEngine(basePath string) *LocalEngine {
|
||||
os.MkdirAll(basePath, 0755)
|
||||
return &LocalEngine{BasePath: basePath}
|
||||
}
|
||||
|
||||
// NewLocalEngineWithCDN 创建带CDN配置的本地存储引擎
|
||||
func NewLocalEngineWithCDN(basePath, cdnHost, cdnPathPrefix string) *LocalEngine {
|
||||
os.MkdirAll(basePath, 0755)
|
||||
return &LocalEngine{
|
||||
BasePath: basePath,
|
||||
CDNHost: cdnHost,
|
||||
CDNPathPrefix: cdnPathPrefix,
|
||||
}
|
||||
}
|
||||
|
||||
// Upload 上传文件到本地磁盘
|
||||
func (e *LocalEngine) Upload(storageKey string, reader io.Reader, size int64) error {
|
||||
fullPath := filepath.Join(e.BasePath, storageKey)
|
||||
dir := filepath.Dir(fullPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("创建目录失败: %w", err)
|
||||
}
|
||||
|
||||
dst, err := os.Create(fullPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建文件失败: %w", err)
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err := io.Copy(dst, reader); err != nil {
|
||||
os.Remove(fullPath)
|
||||
return fmt.Errorf("写入文件失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Download 从本地磁盘下载文件
|
||||
func (e *LocalEngine) Download(storageKey string) (io.ReadCloser, error) {
|
||||
fullPath := filepath.Join(e.BasePath, storageKey)
|
||||
file, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("文件不存在: %s", storageKey)
|
||||
}
|
||||
return nil, fmt.Errorf("打开文件失败: %w", err)
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// Delete 从本地磁盘删除文件
|
||||
func (e *LocalEngine) Delete(storageKey string) error {
|
||||
fullPath := filepath.Join(e.BasePath, storageKey)
|
||||
err := os.Remove(fullPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("删除文件失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists 判断文件是否存在于本地磁盘
|
||||
func (e *LocalEngine) Exists(storageKey string) (bool, error) {
|
||||
fullPath := filepath.Join(e.BasePath, storageKey)
|
||||
_, err := os.Stat(fullPath)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// GetURL 获取文件访问URL
|
||||
// 配置了CDN: http://localhost:3090/public/2026/07/02/ab/xxx.txt
|
||||
// 未配置CDN: /storage/2026/07/02/ab/xxx.txt
|
||||
func (e *LocalEngine) GetURL(storageKey string) string {
|
||||
if e.CDNHost != "" {
|
||||
// 确保路径格式正确
|
||||
prefix := strings.TrimRight(e.CDNPathPrefix, "/")
|
||||
key := strings.TrimLeft(storageKey, "/")
|
||||
return fmt.Sprintf("%s%s/%s", e.CDNHost, prefix, key)
|
||||
}
|
||||
return "/storage/" + storageKey
|
||||
}
|
||||
|
||||
// GenerateStorageKey 生成存储路径
|
||||
func GenerateStorageKey(md5, extension string) string {
|
||||
now := time.Now()
|
||||
day := now.Format("2006/01/02")
|
||||
if extension != "" {
|
||||
return fmt.Sprintf("%s/%s/%s.%s", day, md5[:2], md5, extension)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/%s", day, md5[:2], md5)
|
||||
}
|
||||
Reference in New Issue
Block a user