@@ -97,7 +97,7 @@ type UpdateBucketInput struct {
|
||||
type CreatePARInput struct {
|
||||
Name string
|
||||
ObjectName string // 空 = 桶级(配合 AnyObjectReadWrite 前缀用法)
|
||||
AccessType string // ObjectRead / ObjectWrite / ObjectReadWrite / AnyObjectReadWrite
|
||||
AccessType string // ObjectRead / ObjectWrite / ObjectReadWrite / AnyObjectRead / AnyObjectWrite / AnyObjectReadWrite
|
||||
ExpiresHours int
|
||||
}
|
||||
|
||||
@@ -288,6 +288,48 @@ func (c *RealClient) DeleteBucket(ctx context.Context, cred Credentials, region,
|
||||
return nil
|
||||
}
|
||||
|
||||
// AbortAllMultipartUploads 实现 Client:中止桶内全部未完成分片上传并删除已传分片。
|
||||
// 未完成分片会让「已清空」的桶仍以非空拒绝删除;404 视为桶已不存在,幂等成功。
|
||||
func (c *RealClient) AbortAllMultipartUploads(ctx context.Context, cred Credentials, region, bucket string) error {
|
||||
oc, err := c.osClient(cred, region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ns, err := c.GetObjectStorageNamespace(ctx, cred, region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for page := (*string)(nil); ; {
|
||||
resp, err := oc.ListMultipartUploads(ctx, objectstorage.ListMultipartUploadsRequest{
|
||||
NamespaceName: &ns, BucketName: &bucket, Page: page,
|
||||
})
|
||||
if err != nil {
|
||||
return ignoreNotFound(fmt.Errorf("list multipart uploads: %w", err))
|
||||
}
|
||||
for _, up := range resp.Items {
|
||||
if _, err := oc.AbortMultipartUpload(ctx, objectstorage.AbortMultipartUploadRequest{
|
||||
NamespaceName: &ns, BucketName: &bucket, ObjectName: up.Object, UploadId: up.UploadId,
|
||||
}); err != nil {
|
||||
if err := ignoreNotFound(fmt.Errorf("abort multipart upload: %w", err)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if page = resp.OpcNextPage; page == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ignoreNotFound 把上游 404 归一为 nil(资源已不存在,操作目的已达成)。
|
||||
func ignoreNotFound(err error) error {
|
||||
var se common.ServiceError
|
||||
if errors.As(err, &se) && se.GetHTTPStatusCode() == 404 {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// isBucketNotEmptyErr 识别「桶非空」类删除拒绝:对象/版本/分片报 BucketNotEmpty,
|
||||
// 仅剩活跃 PAR 时 OCI 报 409 且消息为 Active Preauthenticated Requests still exist,
|
||||
// 两者都可经后台清空(对象版本+PAR)后重删。
|
||||
@@ -582,21 +624,7 @@ func (c *RealClient) CreatePAR(ctx context.Context, cred Credentials, region, bu
|
||||
if err != nil {
|
||||
return PAR{}, err
|
||||
}
|
||||
if in.ExpiresHours <= 0 {
|
||||
in.ExpiresHours = 24
|
||||
}
|
||||
details := objectstorage.CreatePreauthenticatedRequestDetails{
|
||||
Name: &in.Name,
|
||||
AccessType: objectstorage.CreatePreauthenticatedRequestDetailsAccessTypeEnum(in.AccessType),
|
||||
TimeExpires: &common.SDKTime{Time: time.Now().Add(time.Duration(in.ExpiresHours) * time.Hour)},
|
||||
}
|
||||
if in.ObjectName != "" {
|
||||
details.ObjectName = &in.ObjectName
|
||||
}
|
||||
// 桶级 PAR(AnyObject*)必须显式桶列举动作,缺省会被 OCI 拒绝;一律放开列举便于收件人浏览
|
||||
if strings.HasPrefix(in.AccessType, "AnyObject") {
|
||||
details.BucketListingAction = objectstorage.PreauthenticatedRequestBucketListingActionListobjects
|
||||
}
|
||||
details := createPARDetails(in, time.Now())
|
||||
resp, err := oc.CreatePreauthenticatedRequest(ctx, objectstorage.CreatePreauthenticatedRequestRequest{
|
||||
NamespaceName: &ns, BucketName: &bucket, CreatePreauthenticatedRequestDetails: details,
|
||||
})
|
||||
@@ -606,6 +634,26 @@ func (c *RealClient) CreatePAR(ctx context.Context, cred Credentials, region, bu
|
||||
return toPAR(resp.PreauthenticatedRequest, oc.Endpoint()), nil
|
||||
}
|
||||
|
||||
// createPARDetails 集中组装 SDK 请求,避免 accessType 与桶列举权限组合回归。
|
||||
func createPARDetails(in CreatePARInput, now time.Time) objectstorage.CreatePreauthenticatedRequestDetails {
|
||||
hours := in.ExpiresHours
|
||||
if hours <= 0 {
|
||||
hours = 24
|
||||
}
|
||||
details := objectstorage.CreatePreauthenticatedRequestDetails{
|
||||
Name: &in.Name,
|
||||
AccessType: objectstorage.CreatePreauthenticatedRequestDetailsAccessTypeEnum(in.AccessType),
|
||||
TimeExpires: &common.SDKTime{Time: now.Add(time.Duration(hours) * time.Hour)},
|
||||
}
|
||||
if in.ObjectName != "" {
|
||||
details.ObjectName = &in.ObjectName
|
||||
}
|
||||
if in.AccessType == "AnyObjectRead" || in.AccessType == "AnyObjectReadWrite" {
|
||||
details.BucketListingAction = objectstorage.PreauthenticatedRequestBucketListingActionListobjects
|
||||
}
|
||||
return details
|
||||
}
|
||||
|
||||
func toPAR(p objectstorage.PreauthenticatedRequest, endpoint string) PAR {
|
||||
out := PAR{
|
||||
ID: deref(p.Id),
|
||||
|
||||
Reference in New Issue
Block a user