{"record":{"id":"92f99c9d4d67ee74","repo":"flipped-aurora/gin-vue-admin","slug":"function-bucket-deleteobjects-failed-err","errorCode":null,"errorMessage":"function bucket.DeleteObjects() failed, err:","messagePattern":"function bucket\\.DeleteObjects\\(\\) failed, err:","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/utils/upload/aliyun_oss.go","lineNumber":109,"sourceCode":"\treturn exist, nil\n}\n\n// DeleteFiles 批量删除对象，对比入参与已删除列表，未删的收集为 DeleteFailure。\nfunc (*AliyunOSS) DeleteFiles(ctx context.Context, keys []string) ([]DeleteFailure, error) {\n\tif len(keys) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tbucket, err := NewBucket()\n\tif err != nil {\n\t\tlogger.WithCtx(ctx).Mod(\"upload\").Err(err).Error(\"function AliyunOSS.NewBucket() Failed\")\n\t\treturn nil, errors.New(\"function AliyunOSS.NewBucket() Failed, err:\" + err.Error())\n\t}\n\n\tresult, err := bucket.DeleteObjects(keys)\n\tif err != nil {\n\t\tlogger.WithCtx(ctx).Mod(\"upload\").Err(err).Error(\"function bucket.DeleteObjects() failed\")\n\t\treturn nil, errors.New(\"function bucket.DeleteObjects() failed, err:\" + err.Error())\n\t}\n\n\t// 默认非 quiet 模式，OSS 会返回已删除的 key 列表，与之对比找出未删项。\n\tdeleted := make(map[string]struct{}, len(result.DeletedObjects))\n\tfor _, k := range result.DeletedObjects {\n\t\tdeleted[k] = struct{}{}\n\t}\n\n\tfailed := make([]DeleteFailure, 0)\n\tfor _, key := range keys {\n\t\tif _, ok := deleted[key]; !ok {\n\t\t\tfailed = append(failed, DeleteFailure{Key: key, Err: errors.New(\"对象未被删除\")})\n\t\t}\n\t}\n\treturn failed, nil\n}\n\n// ListFiles 按前缀列举对象，cursor 映射为 OSS 的 marker。","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/utils/upload/aliyun_oss.go#L91-L127","documentation":"Returned by AliyunOSS.DeleteFiles (server/utils/upload/aliyun_oss.go:109) when bucket.DeleteObjects(keys) — the OSS DeleteMultipleObjects API — fails after a valid bucket client was created. The request reached (or tried to reach) OSS and was rejected: causes include authentication, permission (oss:DeleteObject), request size limits, or network errors. Unlike the per-key comparison that follows, this error aborts the whole batch.","triggerScenarios":"Calling DeleteFiles with >1000 keys (OSS hard limit per DeleteMultipleObjects call), RAM user without oss:DeleteObject permission, expired/invalid credentials, network failure mid-request, or keys with invalid characters that violate OSS key constraints.","commonSituations":"Bulk cleanup jobs deleting thousands of objects in one call, restricted IAM policies granting only read/upload, key rotation between request signing and execution, batch sizes not chunked before reaching this wrapper.","solutions":["Chunk keys into batches of ≤1000 before calling DeleteObjects","Grant the RAM user oss:DeleteObject on the bucket resources","Read the wrapped err (oss.ServiceError) for the exact code: AccessDenied → permissions, SignatureDoesNotMatch → wrong secret, RequestTimeout → network","Retry transient network errors with backoff; OSS batch delete is idempotent for already-deleted keys"],"exampleFix":"// before\nresult, err := bucket.DeleteObjects(keys)\nif err != nil {\n\treturn nil, errors.New(\"function bucket.DeleteObjects() failed, err:\" + err.Error())\n}\n// after: chunk to OSS limit of 1000 keys per call\nconst maxBatch = 1000\nfor i := 0; i < len(keys); i += maxBatch {\n\tend := i + maxBatch\n\tif end > len(keys) {\n\t\tend = len(keys)\n\t}\n\tif _, err := bucket.DeleteObjects(keys[i:end]); err != nil {\n\t\treturn nil, fmt.Errorf(\"function bucket.DeleteObjects() failed: %w\", err)\n\t}\n}","handlingStrategy":"retry","validationCode":"func chunkForOSS(keys []string, size int) [][]string {\n\tif size <= 0 || size > 1000 {\n\t\tsize = 1000\n\t}\n\tvar batches [][]string\n\tfor i := 0; i < len(keys); i += size {\n\t\tend := i + size\n\t\tif end > len(keys) {\n\t\t\tend = len(keys)\n\t\t}\n\t\tbatches = append(batches, keys[i:end])\n\t}\n\treturn batches\n}","typeGuard":"func isRetryableOSSErr(err error) bool {\n\tvar se oss.ServiceError\n\tif !errors.As(err, &se) {\n\t\treturn true // network-level errors are usually retryable\n\t}\n\treturn se.StatusCode == 429 || se.StatusCode >= 500 || se.Code == \"RequestTimeout\"\n}","tryCatchPattern":"for _, batch := range chunkForOSS(keys, 1000) {\n\terr := retryWithBackoff(3, func() error {\n\t\t_, err := bucket.DeleteObjects(batch)\n\t\treturn err\n\t})\n\tif err != nil {\n\t\tif se, ok := isOSSServiceError(err); ok && se.StatusCode == 403 {\n\t\t\treturn fmt.Errorf(\"permission denied deleting objects (check oss:DeleteObject): %w\", err)\n\t\t}\n\t\treturn fmt.Errorf(\"batch delete failed: %w\", err)\n\t}\n}","preventionTips":["Never send more than 1000 keys per DeleteObjects call — chunk always","Ensure the RAM policy includes oss:DeleteObject for the bucket/objects","Retry with exponential backoff on 5xx/timeouts; OSS batch delete is safe to retry","Keep batch delete idempotent: deleted-again keys still count as success"],"tags":["oss","aliyun","storage","permissions","batch-delete"],"backgroundTag":"oss-batch-delete-failed","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}