{"record":{"id":"ad8c1e9749c38e36","repo":"flipped-aurora/gin-vue-admin","slug":"function-bucketmanager-batch-failed-err","errorCode":null,"errorMessage":"function bucketManager.Batch() failed, err:","messagePattern":"function bucketManager\\.Batch\\(\\) failed, err:","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/utils/upload/qiniu.go","lineNumber":112,"sourceCode":"// DeleteFiles 批量删除对象，通过通用 Batch 接口，逐项检查 code（200/204 成功）。\nfunc (*Qiniu) DeleteFiles(ctx context.Context, keys []string) ([]DeleteFailure, error) {\n\tif len(keys) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tbucket := global.GVA_CONFIG.Qiniu.Bucket\n\toperations := make([]string, 0, len(keys))\n\tfor _, key := range keys {\n\t\toperations = append(operations, storage.URIDelete(bucket, key))\n\t}\n\n\tbucketManager := newBucketManager()\n\tret, err := bucketManager.Batch(operations)\n\tif err != nil {\n\t\t// 部分 key 失败时 Batch 仍可能返回结果，这里仅当完全没有结果时视为致命错误。\n\t\tif len(ret) == 0 {\n\t\t\tlogger.WithCtx(ctx).Mod(\"upload\").Err(err).Error(\"function bucketManager.Batch() failed\")\n\t\t\treturn nil, errors.New(\"function bucketManager.Batch() failed, err:\" + err.Error())\n\t\t}\n\t\tlogger.WithCtx(ctx).Mod(\"upload\").Err(err).Error(\"function bucketManager.Batch() partial failure\")\n\t}\n\n\tfailed := make([]DeleteFailure, 0)\n\tfor i, op := range ret {\n\t\t// 200 / 204 视为删除成功\n\t\tif op.Code != http.StatusOK && op.Code != http.StatusNoContent {\n\t\t\tvar key string\n\t\t\tif i < len(keys) {\n\t\t\t\tkey = keys[i]\n\t\t\t}\n\t\t\tfailed = append(failed, DeleteFailure{Key: key, Err: fmt.Errorf(\"delete failed, code: %d\", op.Code)})\n\t\t}\n\t}\n\treturn failed, nil\n}\n","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/utils/upload/qiniu.go#L94-L130","documentation":"Wraps a fatal error from Qiniu's bucketManager.Batch() in DeleteFiles(). Batch deletes objects in bulk; the code only treats it as fatal when the batch result slice is empty (no per-key results at all).","triggerScenarios":"Calling DeleteFiles(ctx, keys) when the Batch call fails entirely: auth failure, invalid bucket, oversized batch, or network error, with len(ret)==0.","commonSituations":"Expired/incorrect Qiniu credentials, batch containing too many keys for one request, bucket region mismatch, transient network outage.","solutions":["Inspect the wrapped err suffix for the underlying Qiniu error code","Reduce batch size (split keys into smaller chunks, e.g. <=1000 per Batch)","Re-check Qiniu AK/SK and bucket configuration","Retry on transient network errors; per-key failures are returned via DeleteFailure, not this error"],"exampleFix":"// before\nuploadQiniu.DeleteFiles(ctx, thousandsOfKeys) // one huge Batch may fail fatally\n// after\nfor chunk := range slices.Chunk(keys, 500) {\n    uploadQiniu.DeleteFiles(ctx, chunk)\n}","handlingStrategy":"retry","validationCode":"if len(keys) > 1000 {\n    return fmt.Errorf(\"batch too large: %d keys\", len(keys))\n}","typeGuard":null,"tryCatchPattern":"failed, err := q.DeleteFiles(ctx, keys)\nif err != nil {\n    if isTransient(err) {\n        // retry with backoff\n    }\n    return err\n}\n// handle partial failures in `failed` slice separately","preventionTips":["Chunk key lists to a sane batch size","Check per-key DeleteFailure results instead of assuming all-or-nothing","Keep Qiniu credentials valid and rotate before expiry"],"tags":["qiniu","upload","batch-delete","object-storage"],"backgroundTag":"object-storage-batch-failed","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}