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

Golang REST API返回损坏Zip文件问题排查求助

问题描述

从MongoDB集合查询记录,将每条记录存为JSON文件后打包成Zip,通过Golang REST API返回。服务器端生成的Zip文件测试正常,但Postman接收响应保存为Zip后文件损坏,且响应文件大小与服务器端一致。尝试修改响应Header、更换Zip读取方式等均无效,需排查原因及解决办法。

相关代码

// each record should be saved in a file
var records []iodefRepo.IodefRecord
if cur, err := h.iodefRepo.IodefCollection().Find(helper.Context(), filter, options.Find().SetSort(M{"received_at": -1})); err != nil {
    return helper.WrapInternalErr("while finding iodef, err=" + err.Error())
} else if err := cur.All(helper.Context(), &records); err != nil {
    return helper.WrapInternalErr("while un-cursoring records, err=" + err.Error())
}

// -------------------------------------------------------
resultFile, err := os.Create(fmt.Sprint(fileName, ".zip"))
if err != nil {
    return helper.WrapInternalErr("while creating the result file, err=" + err.Error())
}

writer := zip.NewWriter(resultFile)
// files is a [][]byte that each element is []byte of json.Unmarshal
for i, f := range files {
    if file, err := writer.Create(fmt.Sprint("IncidentName=", records[i].Document.Incidents[0].IncidentID.Name, ", IncidentData=", records[i].Document.Incidents[0].IncidentID.Data, ".", format)); err != nil {
        return helper.WrapInternalErr("while creating iodef file, err=" + err.Error())
    } else if _, err := file.Write(f); err != nil {
        return helper.WrapInternalErr("while writing to iodef file, err=" + err.Error())
    }
}

helper.AddResponseHeader("Content-Type", "application/zip")
helper.AddResponseHeader("Content-Transfer-Encoding", "binary")
helper.AddResponseHeader("Content-Disposition", "attachment; filename=export.zip")

_ = writer.Close()
_ = resultFile.Close()

if result, err := os.ReadFile(fileName + ".zip"); err != nil {
    return helper.WrapInternalErr("while reading zip file, err=" + err.Error())
} else {
    // this result which is a []byte will be write to standard ResponseWriter
    // the same as err := w.Write(result); mention that I have ckecked and there
    // is no error in any of the steps and everything is done without any errors.
    return helper.WrapOk(result)
}
问题原因及解决办法

核心问题

  1. Zip写入未完成即读取:调用writer.Close()后,底层文件缓冲区可能未完全刷新到磁盘,此时直接读取会导致Zip结构不完整。
  2. 错误被忽略:writer.Close()和resultFile.Close()的错误被直接丢弃,这两个步骤失败会导致Zip损坏,但代码未捕获。
  3. 磁盘IO冗余:没必要先写入磁盘再读取,内存中生成Zip更高效且避免磁盘同步问题。

修复方案

方案一:修复磁盘文件读写流程

若必须使用磁盘文件,需确保写入完全完成后再读取:

// ... 前置查询代码 ...

resultFile, err := os.Create(fmt.Sprint(fileName, ".zip"))
if err != nil {
    return helper.WrapInternalErr("while creating the result file, err=" + err.Error())
}
defer resultFile.Close()

writer := zip.NewWriter(resultFile)

// 写入Zip内容
for i, f := range files {
    file, err := writer.Create(fmt.Sprint("IncidentName=", records[i].Document.Incidents[0].IncidentID.Name, ", IncidentData=", records[i].Document.Incidents[0].IncidentID.Data, ".", format))
    if err != nil {
        return helper.WrapInternalErr("while creating iodef file, err=" + err.Error())
    }
    if _, err := file.Write(f); err != nil {
        return helper.WrapInternalErr("while writing to iodef file, err=" + err.Error())
    }
}

// 必须检查writer关闭错误,这一步会写入Zip目录结构
if err := writer.Close(); err != nil {
    return helper.WrapInternalErr("while closing zip writer, err=" + err.Error())
}
// 强制刷新缓冲区到磁盘
if err := resultFile.Sync(); err != nil {
    return helper.WrapInternalErr("while syncing result file, err=" + err.Error())
}
// 关闭文件后重新读取
if err := resultFile.Close(); err != nil {
    return helper.WrapInternalErr("while closing result file, err=" + err.Error())
}

result, err := os.ReadFile(fileName + ".zip")
if err != nil {
    return helper.WrapInternalErr("while reading zip file, err=" + err.Error())
}

// 设置响应头,移除多余的Content-Transfer-Encoding
helper.AddResponseHeader("Content-Type", "application/zip")
helper.AddResponseHeader("Content-Disposition", "attachment; filename=export.zip")

return helper.WrapOk(result)

方案二:内存中生成Zip(推荐)

直接在内存中生成Zip,避免磁盘IO问题,更高效可靠:

// ... 前置查询代码 ...

var buf bytes.Buffer
writer := zip.NewWriter(&buf)

// 写入Zip内容
for i, f := range files {
    file, err := writer.Create(fmt.Sprint("IncidentName=", records[i].Document.Incidents[0].IncidentID.Name, ", IncidentData=", records[i].Document.Incidents[0].IncidentID.Data, ".", format))
    if err != nil {
        return helper.WrapInternalErr("while creating iodef file, err=" + err.Error())
    }
    if _, err := file.Write(f); err != nil {
        return helper.WrapInternalErr("while writing to iodef file, err=" + err.Error())
    }
}

// 关闭writer完成Zip写入
if err := writer.Close(); err != nil {
    return helper.WrapInternalErr("while closing zip writer, err=" + err.Error())
}

// 设置响应头
helper.AddResponseHeader("Content-Type", "application/zip")
helper.AddResponseHeader("Content-Disposition", "attachment; filename=export.zip")

// 直接返回内存中的字节数组
return helper.WrapOk(buf.Bytes())

额外注意事项

  • 不要忽略任何IO操作错误,尤其是writer.Close(),该步骤负责写入Zip的目录结构,失败会直接导致Zip损坏。
  • Content-Transfer-Encoding: binary在HTTP/1.1中属于冗余设置,默认传输即为二进制,无需添加。
  • 使用defer确保文件或writer被正确关闭,避免资源泄漏和写入不完整问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 01:00:54