{"record":{"id":"132492c30423afbf","repo":"flipped-aurora/gin-vue-admin","slug":"d-w-132492","errorCode":null,"errorMessage":"缺少分片 %d: %w","messagePattern":"缺少分片 (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/utils/upload/chunk.go","lineNumber":59,"sourceCode":"}\n\n// MergeChunks 按 0..total-1 顺序流式合并到 dstPath,返回成品 md5\nfunc MergeChunks(uploadID uint, total int, dstPath string) (string, error) {\n\tif err := os.MkdirAll(filepath.Dir(dstPath), os.ModePerm); err != nil {\n\t\treturn \"\", err\n\t}\n\tout, err := os.Create(dstPath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer out.Close()\n\th := md5.New()\n\tw := io.MultiWriter(out, h)\n\tfor i := 0; i < total; i++ {\n\t\tp := filepath.Join(ChunkDir(uploadID), fmt.Sprintf(\"%d.part\", i))\n\t\tin, err := os.Open(p)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"缺少分片 %d: %w\", i, err)\n\t\t}\n\t\tif _, err := io.Copy(w, in); err != nil {\n\t\t\tin.Close()\n\t\t\treturn \"\", err\n\t\t}\n\t\tin.Close()\n\t}\n\treturn hex.EncodeToString(h.Sum(nil)), nil\n}\n\n// RemoveUploadDir 删除某次上传的暂存目录\nfunc RemoveUploadDir(uploadID uint) error {\n\treturn os.RemoveAll(ChunkDir(uploadID))\n}\n\n// ReceivedIndexes 扫描暂存目录已存在的分片索引(机会式恢复用,可选)\nfunc ReceivedIndexes(uploadID uint) []int {\n\tentries, err := os.ReadDir(ChunkDir(uploadID))","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/utils/upload/chunk.go#L41-L77","documentation":"MergeChunks reassembles uploaded chunk files (<i>.part) under ChunkDir(uploadID) into the final file while computing MD5. If any expected chunk file cannot be opened, it aborts with \"缺少分片 %d: %w\". This guards against incomplete uploads being merged into corrupt files.","triggerScenarios":"Calling MergeChunks(uploadID, total, ...) when one or more files ChunkDir(uploadID)/i.part for i in [0,total) do not exist (os.Open fails) — typically because an earlier chunk upload failed or was never sent.","commonSituations":"Client aborted mid-upload and the UI still requested merge; network drop caused chunk 3 of 10 to fail; chunk index numbering mismatch (client sends 1-based, server expects 0-based); cleanup job removed partial chunk dirs.","solutions":["Re-upload the missing chunk(s) reported by the error index before calling MergeChunks.","Verify the client sends every chunk index 0..total-1 and confirm chunk completeness server-side before merging.","Check ChunkDir(uploadID) for the expected .part files (ls the directory) to find which are missing.","If chunks were cleaned up, restart the whole chunked upload with a new uploadID."],"exampleFix":"// before\nmerged, _ := MergeChunks(id, 5, dest) // chunk 3 never uploaded\n\n// after\nfor i := 0; i < 5; i++ {\n    if _, err := os.Stat(filepath.Join(ChunkDir(id), fmt.Sprintf(\"%d.part\", i))); err != nil {\n        uploadChunk(id, i, data[i]) // re-send missing chunk\n    }\n}\nmerged, err := MergeChunks(id, 5, dest)","handlingStrategy":"validation","validationCode":"func chunksComplete(uploadID string, total int) error {\n    for i := 0; i < total; i++ {\n        p := filepath.Join(ChunkDir(uploadID), fmt.Sprintf(\"%d.part\", i))\n        if _, err := os.Stat(p); err != nil {\n            return fmt.Errorf(\"chunk %d missing\", i)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := chunksComplete(id, total); err != nil {\n    return retryMissingChunks(id, err) // re-upload missing parts before merge\n}\nmd5, err := upload.MergeChunks(id, total, dest)\nif err != nil {\n    return fmt.Errorf(\"merge aborted: %w\", err)\n}","preventionTips":["Track uploaded chunk indexes server-side and expose a completeness check endpoint","Have the client confirm all chunk ACKs before requesting merge","Use 0-based chunk indexing consistently on both sides","Persist chunk progress so failed uploads can resume"],"tags":["upload","file-io","chunks"],"backgroundTag":"missing-chunk-file","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}