fix: 初验针对修改
This commit is contained in:
31
internal/storage/aliyun/client.go
Normal file
31
internal/storage/aliyun/client.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package aliyun
|
||||
|
||||
import (
|
||||
"git.apinb.com/ops/files/internal/config"
|
||||
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
|
||||
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *oss.Client
|
||||
bucket string
|
||||
presignTTL int64
|
||||
}
|
||||
|
||||
func New(cfg config.AliyunStorageConf, accessTTLSeconds int64) (*Client, error) {
|
||||
ossConfig := oss.LoadDefaultConfig().
|
||||
WithRegion(cfg.Region).
|
||||
WithEndpoint(cfg.Endpoint).
|
||||
WithCredentialsProvider(credentials.NewStaticCredentialsProvider(cfg.AccessKeyID, cfg.AccessKeySecret)).
|
||||
WithSignatureVersion(oss.SignatureVersionV4)
|
||||
|
||||
return &Client{
|
||||
client: oss.NewClient(ossConfig),
|
||||
bucket: cfg.Bucket,
|
||||
presignTTL: accessTTLSeconds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Provider() string { return "aliyun" }
|
||||
|
||||
func (c *Client) Container() string { return c.bucket }
|
||||
99
internal/storage/aliyun/object.go
Normal file
99
internal/storage/aliyun/object.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package aliyun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"git.apinb.com/ops/files/internal/storage"
|
||||
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
|
||||
)
|
||||
|
||||
// Open 返回对象存储中的只读内容流。
|
||||
func (c *Client) Open(ctx context.Context, objectKey string, byteRange *storage.ByteRange) (io.ReadCloser, error) {
|
||||
if strings.TrimSpace(objectKey) == "" {
|
||||
return nil, fmt.Errorf("对象键不能为空")
|
||||
}
|
||||
request := &oss.GetObjectRequest{
|
||||
Bucket: oss.Ptr(c.bucket),
|
||||
Key: oss.Ptr(objectKey),
|
||||
}
|
||||
if byteRange != nil {
|
||||
rangeHeader := fmt.Sprintf("bytes=%d-%d", byteRange.Start, byteRange.End)
|
||||
request.Range = &rangeHeader
|
||||
}
|
||||
result, err := c.client.GetObject(ctx, request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取对象: %w", err)
|
||||
}
|
||||
if result == nil || result.Body == nil {
|
||||
return nil, fmt.Errorf("对象内容为空")
|
||||
}
|
||||
return result.Body, nil
|
||||
}
|
||||
|
||||
func (c *Client) Stat(ctx context.Context, objectKey string) (storage.ObjectInfo, error) {
|
||||
if strings.TrimSpace(objectKey) == "" {
|
||||
return storage.ObjectInfo{}, fmt.Errorf("对象键不能为空")
|
||||
}
|
||||
|
||||
result, err := c.client.GetObjectMeta(ctx, &oss.GetObjectMetaRequest{
|
||||
Bucket: oss.Ptr(c.bucket),
|
||||
Key: oss.Ptr(objectKey),
|
||||
})
|
||||
if err != nil {
|
||||
var serviceError *oss.ServiceError
|
||||
if errors.As(err, &serviceError) && serviceError.Code == "NoSuchKey" {
|
||||
return storage.ObjectInfo{}, storage.ErrObjectNotFound
|
||||
}
|
||||
return storage.ObjectInfo{}, fmt.Errorf("查询对象元数据: %w", err)
|
||||
}
|
||||
if result == nil {
|
||||
return storage.ObjectInfo{}, fmt.Errorf("对象元数据为空")
|
||||
}
|
||||
if result.ContentLength < 0 {
|
||||
return storage.ObjectInfo{}, fmt.Errorf("对象大小无效: %d", result.ContentLength)
|
||||
}
|
||||
if result.ETag == nil {
|
||||
return storage.ObjectInfo{}, fmt.Errorf("对象 ETag 缺失")
|
||||
}
|
||||
etag := strings.TrimSpace(strings.Trim(strings.TrimSpace(*result.ETag), "\""))
|
||||
if etag == "" {
|
||||
return storage.ObjectInfo{}, fmt.Errorf("对象 ETag 无效")
|
||||
}
|
||||
if result.LastModified == nil || result.LastModified.IsZero() {
|
||||
return storage.ObjectInfo{}, fmt.Errorf("对象最后修改时间缺失")
|
||||
}
|
||||
|
||||
return storage.ObjectInfo{
|
||||
Size: result.ContentLength,
|
||||
StorageETag: etag,
|
||||
LastModified: *result.LastModified,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Delete(ctx context.Context, objectKey string) error {
|
||||
if strings.TrimSpace(objectKey) == "" {
|
||||
return fmt.Errorf("对象键不能为空")
|
||||
}
|
||||
|
||||
_, err := c.client.DeleteObject(ctx, &oss.DeleteObjectRequest{
|
||||
Bucket: oss.Ptr(c.bucket),
|
||||
Key: oss.Ptr(objectKey),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("删除对象: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Status(ctx context.Context) (storage.RuntimeStatus, error) {
|
||||
_, err := c.client.ListObjectsV2(ctx, &oss.ListObjectsV2Request{Bucket: oss.Ptr(c.bucket), MaxKeys: 1})
|
||||
if err != nil {
|
||||
return storage.RuntimeStatus{Provider: c.Provider(), Container: c.Container()}, fmt.Errorf("检查 OSS 存储状态: %w", err)
|
||||
}
|
||||
return storage.RuntimeStatus{Provider: c.Provider(), Container: c.Container(), Writable: true}, nil
|
||||
}
|
||||
37
internal/storage/aliyun/upload.go
Normal file
37
internal/storage/aliyun/upload.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package aliyun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/ops/files/internal/storage"
|
||||
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
|
||||
)
|
||||
|
||||
func (c *Client) PrepareUpload(ctx context.Context, request storage.UploadRequest) (storage.UploadInstruction, error) {
|
||||
if strings.TrimSpace(request.ObjectKey) == "" {
|
||||
return storage.UploadInstruction{}, fmt.Errorf("对象键不能为空")
|
||||
}
|
||||
if strings.TrimSpace(request.ContentType) == "" {
|
||||
return storage.UploadInstruction{}, fmt.Errorf("内容类型不能为空")
|
||||
}
|
||||
|
||||
result, err := c.client.Presign(ctx, &oss.PutObjectRequest{
|
||||
Bucket: oss.Ptr(c.bucket),
|
||||
Key: oss.Ptr(request.ObjectKey),
|
||||
ContentType: oss.Ptr(request.ContentType),
|
||||
ForbidOverwrite: oss.Ptr("true"),
|
||||
}, oss.PresignExpires(time.Duration(c.presignTTL)*time.Second))
|
||||
if err != nil {
|
||||
return storage.UploadInstruction{}, fmt.Errorf("生成上传预签名: %w", err)
|
||||
}
|
||||
|
||||
return storage.UploadInstruction{
|
||||
Method: result.Method,
|
||||
URL: result.URL,
|
||||
Headers: result.SignedHeaders,
|
||||
ExpiresAt: result.Expiration,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user