初始化2
This commit is contained in:
@@ -20,9 +20,7 @@ type MinIOEngine struct {
|
||||
}
|
||||
|
||||
// NewMinIOEngine 创建 MinIO 存储引擎实例
|
||||
// endpoint 格式: host:port (如 localhost:9000), 不带 http://
|
||||
func NewMinIOEngine(endpoint, accessKey, secretKey, bucket string, useSSL bool) (*MinIOEngine, error) {
|
||||
// 清理 endpoint: 去掉 http:// 或 https:// 前缀
|
||||
cleanEndpoint := endpoint
|
||||
if len(cleanEndpoint) > 7 && cleanEndpoint[:7] == "http://" {
|
||||
cleanEndpoint = cleanEndpoint[7:]
|
||||
@@ -30,12 +28,10 @@ func NewMinIOEngine(endpoint, accessKey, secretKey, bucket string, useSSL bool)
|
||||
if len(cleanEndpoint) > 8 && cleanEndpoint[:8] == "https://" {
|
||||
cleanEndpoint = cleanEndpoint[8:]
|
||||
}
|
||||
// 去掉末尾的 /
|
||||
if len(cleanEndpoint) > 0 && cleanEndpoint[len(cleanEndpoint)-1] == '/' {
|
||||
cleanEndpoint = cleanEndpoint[:len(cleanEndpoint)-1]
|
||||
}
|
||||
|
||||
// 支持匿名访问(公开桶不需要凭证)
|
||||
var creds *credentials.Credentials
|
||||
if accessKey != "" && secretKey != "" {
|
||||
creds = credentials.NewStaticV4(accessKey, secretKey, "")
|
||||
@@ -54,11 +50,11 @@ func NewMinIOEngine(endpoint, accessKey, secretKey, bucket string, useSSL bool)
|
||||
engine := &MinIOEngine{
|
||||
Client: client,
|
||||
BucketName: bucket,
|
||||
Endpoint: endpoint,
|
||||
Endpoint: cleanEndpoint,
|
||||
UseSSL: useSSL,
|
||||
}
|
||||
|
||||
// 有凭证时确保 bucket 存在(匿名访问跳过)
|
||||
// 有凭证时确保 bucket 存在
|
||||
ctx := context.Background()
|
||||
if accessKey != "" {
|
||||
exists, checkErr := client.BucketExists(ctx, bucket)
|
||||
@@ -69,7 +65,6 @@ func NewMinIOEngine(endpoint, accessKey, secretKey, bucket string, useSSL bool)
|
||||
if err := client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}); err != nil {
|
||||
return nil, fmt.Errorf("创建bucket失败: %w", err)
|
||||
}
|
||||
// 设置 bucket 为公开读
|
||||
policy := fmt.Sprintf(`{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
@@ -86,7 +81,7 @@ func NewMinIOEngine(endpoint, accessKey, secretKey, bucket string, useSSL bool)
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
// Upload 上传文件到 MinIO
|
||||
// Upload 上传文件
|
||||
func (e *MinIOEngine) Upload(storageKey string, reader io.Reader, size int64) error {
|
||||
ctx := context.Background()
|
||||
_, err := e.Client.PutObject(ctx, e.BucketName, storageKey, reader, size, minio.PutObjectOptions{
|
||||
@@ -98,7 +93,7 @@ func (e *MinIOEngine) Upload(storageKey string, reader io.Reader, size int64) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// Download 从 MinIO 下载文件
|
||||
// Download 下载文件
|
||||
func (e *MinIOEngine) Download(storageKey string) (io.ReadCloser, error) {
|
||||
ctx := context.Background()
|
||||
object, err := e.Client.GetObject(ctx, e.BucketName, storageKey, minio.GetObjectOptions{})
|
||||
@@ -108,22 +103,17 @@ func (e *MinIOEngine) Download(storageKey string) (io.ReadCloser, error) {
|
||||
return object, nil
|
||||
}
|
||||
|
||||
// Delete 从 MinIO 删除文件
|
||||
// Delete 删除文件
|
||||
func (e *MinIOEngine) Delete(storageKey string) error {
|
||||
ctx := context.Background()
|
||||
err := e.Client.RemoveObject(ctx, e.BucketName, storageKey, minio.RemoveObjectOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("MinIO删除失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
return e.Client.RemoveObject(ctx, e.BucketName, storageKey, minio.RemoveObjectOptions{})
|
||||
}
|
||||
|
||||
// Exists 判断文件是否存在于 MinIO
|
||||
// Exists 检查文件是否存在
|
||||
func (e *MinIOEngine) Exists(storageKey string) (bool, error) {
|
||||
ctx := context.Background()
|
||||
_, err := e.Client.StatObject(ctx, e.BucketName, storageKey, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
// 检查是否是"不存在"的错误
|
||||
errResponse := minio.ToErrorResponse(err)
|
||||
if errResponse.Code == "NoSuchKey" {
|
||||
return false, nil
|
||||
@@ -133,17 +123,16 @@ func (e *MinIOEngine) Exists(storageKey string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetURL 获取文件的公开访问 URL
|
||||
// GetURL 获取公开访问URL
|
||||
func (e *MinIOEngine) GetURL(storageKey string) string {
|
||||
scheme := "http"
|
||||
if e.UseSSL {
|
||||
scheme = "https"
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/%s/%s", scheme+"://"+e.Endpoint, e.BucketName, storageKey, "")
|
||||
return fmt.Sprintf("%s://%s/%s/%s", scheme, e.Endpoint, e.BucketName, storageKey)
|
||||
}
|
||||
|
||||
// GetPresignedURL 生成预签名下载链接(临时授权)
|
||||
// expiration: 链接有效期
|
||||
// GetPresignedURL 生成预签名下载链接
|
||||
func (e *MinIOEngine) GetPresignedURL(storageKey string, expiration time.Duration) (string, error) {
|
||||
ctx := context.Background()
|
||||
reqParams := make(url.Values)
|
||||
@@ -154,7 +143,23 @@ func (e *MinIOEngine) GetPresignedURL(storageKey string, expiration time.Duratio
|
||||
return presignedURL.String(), nil
|
||||
}
|
||||
|
||||
// GetPresignedPutURL 生成预签名上传链接(用于前端直传)
|
||||
// GetPresignedPreviewURL 生成预签名预览链接(内联展示, 不触发下载)
|
||||
func (e *MinIOEngine) GetPresignedPreviewURL(storageKey string, mimeType string, expiration time.Duration) (string, error) {
|
||||
ctx := context.Background()
|
||||
reqParams := make(url.Values)
|
||||
// S3协议: 通过response-*参数覆盖响应头, 签名时一并签名
|
||||
reqParams.Set("response-content-disposition", "inline")
|
||||
if mimeType != "" {
|
||||
reqParams.Set("response-content-type", mimeType)
|
||||
}
|
||||
presignedURL, err := e.Client.PresignedGetObject(ctx, e.BucketName, storageKey, expiration, reqParams)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("生成预签名预览URL失败: %w", err)
|
||||
}
|
||||
return presignedURL.String(), nil
|
||||
}
|
||||
|
||||
// GetPresignedPutURL 生成预签名上传链接
|
||||
func (e *MinIOEngine) GetPresignedPutURL(storageKey string, expiration time.Duration) (string, error) {
|
||||
ctx := context.Background()
|
||||
presignedURL, err := e.Client.PresignedPutObject(ctx, e.BucketName, storageKey, expiration)
|
||||
@@ -164,12 +169,54 @@ func (e *MinIOEngine) GetPresignedPutURL(storageKey string, expiration time.Dura
|
||||
return presignedURL.String(), nil
|
||||
}
|
||||
|
||||
// GetPreviewURL 获取文件预览链接
|
||||
// 对于图片/文本/PDF等可直接预览的文件, 返回公开URL
|
||||
func (e *MinIOEngine) GetPreviewURL(storageKey string) string {
|
||||
scheme := "http"
|
||||
if e.UseSSL {
|
||||
scheme = "https"
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/%s/%s", scheme+"://"+e.Endpoint, e.BucketName, storageKey, "")
|
||||
// GetPresignedChunkURL 生成分片预签名上传URL
|
||||
// chunkKey: 分片的唯一标识, 如 "2026/07/03/xx/chunk_001"
|
||||
func (e *MinIOEngine) GetPresignedChunkURL(chunkKey string, expiration time.Duration) (string, error) {
|
||||
return e.GetPresignedPutURL(chunkKey, expiration)
|
||||
}
|
||||
|
||||
// MergeChunks 合并分片到目标key
|
||||
// 读取所有分片, 按顺序合并写入目标key
|
||||
func (e *MinIOEngine) MergeChunks(chunkKeys []string, targetKey string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// 创建管道用于流式合并
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
go func() {
|
||||
defer pw.Close()
|
||||
for _, key := range chunkKeys {
|
||||
obj, err := e.Client.GetObject(ctx, e.BucketName, key, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
pw.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(pw, obj)
|
||||
obj.Close()
|
||||
if err != nil {
|
||||
pw.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 上传合并后的文件(流式, 不需要知道总大小)
|
||||
_, err := e.Client.PutObject(ctx, e.BucketName, targetKey, pr, -1, minio.PutObjectOptions{
|
||||
ContentType: "application/octet-stream",
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("合并上传失败: %w", err)
|
||||
}
|
||||
|
||||
// 清理分片
|
||||
for _, key := range chunkKeys {
|
||||
e.Client.RemoveObject(ctx, e.BucketName, key, minio.RemoveObjectOptions{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPreviewURL 获取预览链接
|
||||
func (e *MinIOEngine) GetPreviewURL(storageKey string) string {
|
||||
return e.GetURL(storageKey)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user