package repository import ( "context" "io" "cloudnest/internal/domain/file/repository" "cloudnest/internal/infrastructure/storage" "cloudnest/internal/pkg/errors" "github.com/minio/minio-go/v7" ) // fileRepository is the MinIO implementation of FileRepository. // fileRepository 是 FileRepository 的 MinIO 实现。 // // This struct implements the FileRepository interface using MinIO for object storage operations. // // 此结构体使用 MinIO 实现了 FileRepository 接口,用于对象存储操作。 type fileRepository struct { minio *storage.MinioClient } // NewFileRepository creates a new file repository. // NewFileRepository 创建一个新的文件仓储。 // // Parameters: // - minio: The MinIO client for storage operations. // // Returns: // - repository.FileRepository: A FileRepository implementation. // // 参数: // - minio: 用于存储操作的 MinIO 客户端。 // // 返回值: // - repository.FileRepository: FileRepository 的实现。 func NewFileRepository(minio *storage.MinioClient) repository.FileRepository { return &fileRepository{minio: minio} } // Upload uploads a file to the object storage. // Upload 将文件上传到对象存储。 // // Parameters: // - bucket: The target bucket name. // - objectName: The unique name/path for the object. // - reader: The file content reader. // - size: The size of the file in bytes. // - contentType: The MIME type of the file. // // Returns: // - error: An error if the operation fails. // // 参数: // - bucket: 目标桶名称。 // - objectName: 对象的唯一名称/路径。 // - reader: 文件内容读取器。 // - size: 文件大小(字节)。 // - contentType: 文件的 MIME 类型。 // // 返回值: // - error: 如果操作失败则返回错误。 func (r *fileRepository) Upload(bucket, objectName string, reader io.Reader, size int64, contentType string) error { _, err := r.minio.Client.PutObject(context.Background(), bucket, objectName, reader, size, minio.PutObjectOptions{ContentType: contentType}) if err != nil { return errors.InternalError("上传文件失败", err) } return nil } // List retrieves a list of files under the specified prefix with metadata. // List 获取指定前缀下的文件列表及其元数据。 // // Parameters: // - bucket: The bucket name. // - prefix: The prefix/path to filter files. // // Returns: // - []repository.FileInfo: A list of file information. // - error: An error if the operation fails. // // 参数: // - bucket: 桶名称。 // - prefix: 用于过滤文件的前缀/路径。 // // 返回值: // - []repository.FileInfo: 文件信息列表。 // - error: 如果操作失败则返回错误。 func (r *fileRepository) List(bucket, prefix string) ([]repository.FileInfo, error) { var files []repository.FileInfo for obj := range r.minio.Client.ListObjects(context.Background(), bucket, minio.ListObjectsOptions{Prefix: prefix}) { if obj.Err != nil { return nil, errors.InternalError("获取文件列表失败", obj.Err) } files = append(files, repository.FileInfo{ Key: obj.Key, Size: obj.Size, LastModified: obj.LastModified.Format("2006-01-02 15:04:05"), }) } return files, nil } // PresignDownload generates a pre-signed URL for downloading a file. // PresignDownload 生成用于下载文件的预签名 URL。 // // Parameters: // - bucket: The bucket name. // - objectName: The name/path of the object. // // Returns: // - string: The pre-signed download URL. // - error: An error if the operation fails. // // Note: // - The URL expires after storage.PresignDuration (default 24 hours). // - Returns errors.NotFoundError if the file doesn't exist. // // 参数: // - bucket: 桶名称。 // - objectName: 对象的名称/路径。 // // 返回值: // - string: 预签名下载 URL。 // - error: 如果操作失败则返回错误。 // // 注意: // - URL 在 storage.PresignDuration(默认 24 小时)后过期。 // - 如果文件不存在,返回 errors.NotFoundError。 func (r *fileRepository) PresignDownload(bucket, objectName string) (string, error) { url, err := r.minio.Client.PresignedGetObject(context.Background(), bucket, objectName, storage.PresignDuration, nil) if err != nil { return "", errors.NotFound("文件不存在") } return url.String(), nil } // Delete removes a file from the object storage. // Delete 从对象存储中删除文件。 // // Parameters: // - bucket: The bucket name. // - objectName: The name/path of the object to delete. // // Returns: // - error: An error if the operation fails. // // 参数: // - bucket: 桶名称。 // - objectName: 要删除的对象的名称/路径。 // // 返回值: // - error: 如果操作失败则返回错误。 func (r *fileRepository) Delete(bucket, objectName string) error { err := r.minio.Client.RemoveObject(context.Background(), bucket, objectName, minio.RemoveObjectOptions{}) if err != nil { return errors.InternalError("删除文件失败", err) } return nil } // GetObject retrieves a file as a readable stream from the object storage. // GetObject 从对象存储中获取文件的可读流。 // // Parameters: // - bucket: The bucket name. // - objectName: The name/path of the object. // // Returns: // - io.ReadCloser: A readable stream of the file content. Caller must close it. // - int64: The file size in bytes. // - string: The Content-Type of the file. // - error: An error if the operation fails. // // 参数: // - bucket: 桶名称。 // - objectName: 对象的名称/路径。 // // 返回值: // - io.ReadCloser: 文件内容的可读流。调用方必须关闭它。 // - int64: 文件大小(字节)。 // - string: 文件的 Content-Type。 // - error: 如果操作失败则返回错误。 func (r *fileRepository) GetObject(bucket, objectName string) (io.ReadCloser, int64, string, error) { obj, err := r.minio.Client.GetObject(context.Background(), bucket, objectName, minio.GetObjectOptions{}) if err != nil { return nil, 0, "", errors.NotFound("文件不存在") } info, err := obj.Stat() if err != nil { obj.Close() return nil, 0, "", errors.NotFound("文件不存在") } contentType := info.ContentType if contentType == "" { contentType = "application/octet-stream" } return obj, info.Size, contentType, nil } func (r *fileRepository) GetObjectInfo(bucket, objectName string) (int64, string, error) { info, err := r.minio.Client.StatObject(context.Background(), bucket, objectName, minio.StatObjectOptions{}) if err != nil { return 0, "", errors.NotFound("文件不存在") } contentType := info.ContentType if contentType == "" { contentType = "application/octet-stream" } return info.Size, contentType, nil }