修复全量审查问题;设置接口PATCH化;回传指纹加固
CI / test (push) Successful in 32s

This commit is contained in:
2026-07-22 16:51:23 +08:00
parent 0614ef22af
commit f51fb6c722
66 changed files with 3997 additions and 687 deletions
+64 -16
View File
@@ -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),