{"record":{"id":"38fd4d29e68dc201","repo":"flipped-aurora/gin-vue-admin","slug":"d-38fd4d","errorCode":null,"errorMessage":"分片 %d 校验失败","messagePattern":"分片 (.+?) 校验失败","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service/media/media_upload.go","lineNumber":86,"sourceCode":"\tresp.UploadID = up.ID\n\tresp.UploadedChunks = idx\n\treturn resp, nil\n}\n\nfunc (s *MediaUploadService) SaveChunk(ctx context.Context, userID, uploadID uint, index int, chunkHash string, data []byte) error {\n\tvar up media.MediaUpload\n\tif err := global.GVA_DB.WithContext(ctx).First(&up, uploadID).Error; err != nil {\n\t\treturn errors.New(\"上传会话不存在\")\n\t}\n\tif up.UserID != userID {\n\t\treturn errors.New(\"无权操作该上传\")\n\t}\n\tif up.Status != media.UploadStatusUploading {\n\t\treturn errors.New(\"上传会话状态不允许收片\")\n\t}\n\tsum := md5.Sum(data)\n\tif hex.EncodeToString(sum[:]) != chunkHash {\n\t\treturn fmt.Errorf(\"分片 %d 校验失败\", index)\n\t}\n\tif _, err := upload.SaveChunkFile(uploadID, index, data); err != nil {\n\t\treturn err\n\t}\n\trec := media.MediaUploadChunk{UploadID: uploadID, ChunkIndex: index, ChunkHash: chunkHash, Size: int64(len(data))}\n\treturn global.GVA_DB.WithContext(ctx).Clauses(clause.OnConflict{\n\t\tColumns:   []clause.Column{{Name: \"upload_id\"}, {Name: \"chunk_index\"}},\n\t\tDoNothing: true,\n\t}).Create(&rec).Error\n}\n\nfunc (s *MediaUploadService) Complete(ctx context.Context, userID, uploadID uint) (media.FileUploadAndDownload, error) {\n\tvar m media.FileUploadAndDownload\n\tvar up media.MediaUpload\n\tif err := global.GVA_DB.WithContext(ctx).First(&up, uploadID).Error; err != nil {\n\t\treturn m, errors.New(\"上传会话不存在\")\n\t}\n\tif up.UserID != userID {","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/service/media/media_upload.go#L68-L104","documentation":"SaveChunk verifies each uploaded chunk's integrity by computing the MD5 of the received bytes and comparing it to the chunkHash supplied by the client. On mismatch it returns \"分片 %d 校验失败\" (chunk N verification failed) and does not persist the chunk. This protects the merged file from corrupted or mismatched pieces.","triggerScenarios":"Client computes the hash over different bytes than it sends (pre-encryption vs post-encryption), client hashes the wrong offset/slice, chunk index/hash mismatch in the request, or bytes corrupted in transit without TLS or altered by a proxy rewriting the body.","commonSituations":"Frontend reading file.slice() with off-by-one boundaries while hashing the whole slice differently; retries sending a newer file version whose chunk content changed after the hash was computed; middleware transforming request bodies (compression/gzip handled twice); race where the same uploadID is used for two different files.","solutions":["Recompute the chunk MD5 client-side immediately before sending the exact bytes being transmitted","Ensure the file is not modified between hashing and upload (close writers, retry from a stable snapshot)","Verify the client uses the same byte range boundaries for hashing and for the body","Retry the failed chunk upload once the hash is recomputed; the chunk is rejected atomically so no partial state is written","Check any proxy/middleware that may mutate the request body between client and server"],"exampleFix":"// before: hash computed at selection time, file edited afterwards\nconst hash = md5(await file.slice(start, end))\nawait fetch(url, { method: 'PUT', body: await file.slice(start, end) })\n// after: read slice once, hash the same buffer you send\nconst buf = new Uint8Array(await file.slice(start, end).arrayBuffer())\nconst hash = md5(buf)\nawait fetch(url, { method: 'PUT', body: buf })","handlingStrategy":"retry","validationCode":"// client: hash the exact buffer you will send\nconst buf = new Uint8Array(await file.slice(start, end).arrayBuffer())\nif (md5(buf) !== expectedChunkHash) {\n    throw new Error('local chunk hash mismatch before upload')\n}","typeGuard":null,"tryCatchPattern":"err := svc.SaveChunk(ctx, uploadID, index, data, chunkHash)\nif err != nil && strings.Contains(err.Error(), \"校验失败\") {\n    // recompute hash client-side and re-upload that single chunk\n    return retryChunkUpload(uploadID, index, data)\n}","preventionTips":["Hash and send the identical byte slice in one operation","Never mutate the file between hashing and upload","Retry failed chunks with a freshly computed hash before calling Complete","Don't reuse an uploadID across different files"],"tags":["upload","integrity","md5","chunked-upload"],"backgroundTag":"checksum-mismatch","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}