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 } // GetFullPath 获取文件在磁盘上的完整路径 func (e *LocalEngine) GetFullPath(storageKey string) string { return filepath.Join(e.BasePath, 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) }