{"record":{"id":"51beff41a8a93dc0","repo":"flipped-aurora/gin-vue-admin","slug":"error-51beff","errorCode":null,"errorMessage":"对象未被删除","messagePattern":"对象未被删除","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/utils/upload/aliyun_oss.go","lineNumber":121,"sourceCode":"\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。\nfunc (*AliyunOSS) ListFiles(ctx context.Context, prefix, cursor string, limit int) ([]FileInfo, string, bool, error) {\n\tif limit <= 0 {\n\t\tlimit = 100\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, \"\", false, errors.New(\"function AliyunOSS.NewBucket() Failed, err:\" + err.Error())\n\t}\n\n\tresult, err := bucket.ListObjects(oss.Prefix(prefix), oss.Marker(cursor), oss.MaxKeys(limit))","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/utils/upload/aliyun_oss.go#L103-L139","documentation":"Not an SDK error: AliyunOSS.DeleteFiles (server/utils/upload/aliyun_oss.go:121) compares the requested keys against result.DeletedObjects returned by OSS (non-quiet mode) and wraps any key absent from the deleted list in a DeleteFailure{Key, Err:\"对象未被删除\"}. The batch call succeeded; specific objects simply were not reported as deleted. Since OSS delete of a missing key normally succeeds, absence from the list usually means the key never existed, already had no version to delete, or a key-name mismatch (case, encoding, prefix).","triggerScenarios":"Deleting keys that don't exist in the bucket (wrong prefix, wrong BasePath baked into the key), key case/special-character encoding differing from what's stored, or keys deleted concurrently by another process/job between listing and deletion.","commonSituations":"Cleanup routines built from stale DB paths after the bucket was purged or BasePath config changed, double-delete of the same upload, case-sensitive key mismatches (OSS keys are case-sensitive), URL-encoded keys passed unencoded or vice versa.","solutions":["Treat 'object not found' as success in most cleanup flows — log it at warning level instead of failing","Verify the exact key exists first with bucket.IsObjectExist or ListObjects with the key as prefix; compare byte-for-byte with what's stored in DB","Check BasePath/prefix config hasn't changed since the objects were uploaded","Note: in quiet mode OSS returns no DeletedObjects at all — confirm the SDK call is not using oss.DeleteObjectsQuiet(true), which would make every key look 'not deleted'"],"exampleFix":"// before\nif _, ok := deleted[key]; !ok {\n\tfailed = append(failed, DeleteFailure{Key: key, Err: errors.New(\"对象未被删除\")})\n}\n// after: distinguish missing-object (benign) from real failures\nif _, ok := deleted[key]; !ok {\n\texist, _ := bucket.IsObjectExist(key)\n\tif !exist {\n\t\tlogger.Warn(\"object already absent, treating as deleted\", key)\n\t\tcontinue\n\t}\n\tfailed = append(failed, DeleteFailure{Key: key, Err: errors.New(\"对象未被删除\")})\n}","handlingStrategy":"fallback","validationCode":"// before deleting, confirm keys actually exist\nfor _, key := range keys {\n\tok, err := bucket.IsObjectExist(key)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !ok {\n\t\tlogger.Warnf(\"skip delete, key not in bucket: %s\", key)\n\t}\n}","typeGuard":"func isBenignMissingKey(f DeleteFailure) bool {\n\treturn f.Err != nil && f.Err.Error() == \"对象未被删除\"\n}","tryCatchPattern":"failed, err := uploadSvc.DeleteFiles(ctx, keys)\nif err != nil {\n\treturn fmt.Errorf(\"batch delete call failed: %w\", err)\n}\nfor _, f := range failed {\n\tif isBenignMissingKey(f) {\n\t\tlogger.Warnf(\"key already absent, ignoring: %s\", f.Key)\n\t\tcontinue\n\t}\n\t// surface genuinely stuck keys for manual review\n\tlogger.Errorf(\"delete reported failure for %s: %v\", f.Key, f.Err)\n}","preventionTips":["Treat delete-of-missing-key as success — OSS deletes are idempotent by design","Recompute keys from current BasePath config rather than stale stored URLs","Remember keys are case-sensitive and stored byte-for-byte; normalize once at upload time","Do not enable quiet mode if you rely on DeletedObjects for the missing-key comparison"],"tags":["oss","aliyun","storage","idempotency","cleanup"],"backgroundTag":"oss-object-not-deleted","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}