You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

分块下载合并二进制文件哈希不符致损坏,求助排查问题

问题排查方向
  • 未校验HTTP响应状态码:你直接调用http.Get后就复制内容,但如果服务器返回4xx/5xx错误(比如分块不存在、权限问题),resp.Body里会是错误页面的HTML内容而非分块文件。二进制文件混入HTML后哈希必然错误,文本文件可能刚好没触发这类错误。应该在获取响应后先检查resp.StatusCode == http.StatusOK,非200则直接报错。

  • 未验证分块哈希:上传时校验了分块哈希,但下载阶段没验证每个下载的分块哈希是否和hash.Hash一致。如果分块在传输中损坏(丢包、篡改),写入文件后会导致整体哈希错误。建议下载每个分块后,先计算其哈希再写入目标文件,或者先保存到临时文件校验后再合并。

  • 分块顺序错误:确认fileInfo.FileInfo.FileBlocks的顺序是否和上传时的拆分顺序完全一致。文本文件顺序错误可能还能勉强读取,但二进制文件(图片、视频等)顺序错乱会直接损坏,哈希也会不符。可以打印每个分块的索引和哈希,对比上传时的拆分顺序。

  • HTTP自动解压篡改二进制内容:Go的http.Client默认会自动解压Content-Encoding为gzip/deflate的响应内容。如果服务器错误地给二进制分块设置了Content-Encoding: gzip,客户端自动解压后会改变分块的原始二进制内容。可以通过设置http.Client的Transport.DisableCompression = true禁用自动解压,测试是否解决问题。

  • io.Copy未完整写入分块:你打印了n但没对比分块的预期大小。如果io.Copy返回的n不等于该分块的实际大小(比如网络中断导致提前结束),会导致目标文件缺失部分内容,哈希错误。可以通过resp.ContentLength获取预期大小,对比n是否一致,不一致则报错重试。

优化后的代码示例
out, err := os.Create(fileInfo.FileInfo.FileName)
if err != nil {
    panic(err)
}
defer out.Close() // 用defer确保文件关闭

// 禁用HTTP自动解压,避免二进制内容被篡改
client := &http.Client{
    Transport: &http.Transport{
        DisableCompression: true,
    },
}

for _, hash := range fileInfo.FileInfo.FileBlocks {
    keeper := getKeeperByHash(hash.Hash, fileInfo.HasHash)
    log.Println("Downloading", hash.Hash)
    downloadURL := &url.URL{
        Scheme: "http",
        Host:   fmt.Sprintf("%s:%d", keeper.Ip, keeper.Port),
        Path:   "file/get",
    }
    param := url.Values{}
    param.Add("hash", hash.Hash)
    downloadURL.RawQuery = param.Encode()
    
    resp, err := client.Get(downloadURL.String())
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close() // 确保每个响应体都被关闭
    
    // 检查响应状态码
    if resp.StatusCode != http.StatusOK {
        panic(fmt.Sprintf("download block %s failed: status code %d", hash.Hash, resp.StatusCode))
    }
    
    // 检查分块大小(假设hash结构体包含Size字段)
    if resp.ContentLength != hash.Size {
        panic(fmt.Sprintf("block %s size mismatch: expected %d, got %d", hash.Hash, hash.Size, resp.ContentLength))
    }
    
    n, err := io.Copy(out, resp.Body)
    log.Println("written bytes:", n)
    if err != nil {
        panic(err)
    }
    
    // 验证写入大小是否符合预期
    if n != hash.Size {
        panic(fmt.Sprintf("block %s not fully written: expected %d, wrote %d", hash.Hash, hash.Size, n))
    }
}

hash1, err := GetFileHashAlt(fileInfo.FileInfo.FileName)
if err != nil {
    panic(err) // 不要忽略哈希计算的错误
}
if hash1 != fileInfo.FileInfo.FileHash {
    log.Println("calculated hash:", hash1)
    log.Println("expected hash:", fileInfo.FileInfo.FileHash)
    panic("HASH NOT THE SAME")
}
log.Println("hash match:", hash1)

内容的提问来源于stack exchange,提问作者balamutik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 23:30:38