用excelize/v2流式写入+批量setsheetrows+正确样式绑定,可高效生成下载excel,避免内存爆涨和格式异常。

直接用 excelize/v2 + 流式写入 + 正确样式绑定,就能在 HTTP 响应中高效生成并下载 Excel,无需临时文件、不爆内存、中文和时间格式全正常。
为什么 SetCellValue 一写就慢还崩内存
逐单元格调用 f.SetCellValue("Sheet1", "A1", val) 是最常见也最危险的操作:每次调用都触发内部 map 查找、样式缓存重建、坐标解析,10 万行数据可能吃掉 800MB 内存并耗时 5s+。它本质是为「零星修改」设计的,不是为「批量导出」。
- 改用
f.SetSheetRow(sheetName, "A1", &rowSlice)一次性写整行,性能提升 5–10 倍 - 若数据是结构体切片,先转成
[][]interface{},再用f.SetSheetRows(sheetName, rows, excelize.Options{Raw: true}) - 写完别忘了
f.SaveAs("dummy.xlsx")或直接f.Write(c.Writer)—— 否则只是内存对象,没真正序列化
中文乱码、时间变数字、数字显示为科学计数法
根本不是编码问题,而是 Excel 单元格「存储值」和「显示格式」分离导致的。UTF-8 数据写进去了,但字体没设、数字格式没绑,Excel 打开时自动 fallback 渲染,结果就是方块、45201.625、1.23E+08。
- 中文必须设字体:
"SimSun"(Windows)、"Microsoft YaHei"(通用)、"Noto Sans CJK SC"(Linux/macOS 推荐) - 时间字段要两步走:
excelize.TimeToExcelTime(t, false)转数值 +NumFmt: 22(yyyy-mm-dd h:mm:ss)设样式 - 纯数字列(如身份证、电话)写入前加单引号前缀:
'13812345678,或设单元格格式为@(文本型),否则 Excel 自动转浮点并截断
大数据量(10w+ 行)导出不卡死的关键配置
不是靠堆内存,而是靠「游标读 + 批量写 + 主动 flush」。查库时别 rows.Scan 全收进 []map[string]interface{},那等于把数据库结果复制三份进 Go 内存。
- 数据库层用
for rows.Next() { rows.Scan(&id, &name, &createdAt) }边读边写 - 每累积 500 行,调一次
f.SetSheetRows(...),再调f.SetRowHeight(sheetName, rowNum, 20)(可选)触发放水 - 开头禁用计算:
f.SetCalculation(&excelize.Calculation{CalcMode: "manual"}),避免后台公式重算拖慢 - HTTP 响应头必须设对:
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,Content-Disposition: attachment; filename*=UTF-8''%E6%8A%A5%E8%A1%A8-20260608.xlsx(注意filename*+ URL 编码)
下载失败、文件打不开、提示“已损坏”的硬伤点
90% 是响应流被意外截断或 header 冲突。Gin/echo 等框架里,c.Writer 是底层 http.ResponseWriter,一旦提前 c.JSON 或 c.String 就不可逆地写入了状态码和 body,后续 f.Write(c.Writer) 必然失败。
- 导出 handler 里禁止任何
c.JSON/c.String,只做f.Write(c.Writer)一件事 - 务必检查中间件是否写了 header(比如 JWT 验证中间件偷偷加了
X-User-ID不影响,但加了Content-Length就会冲突) - 开发时用
curl -v http://localhost:8080/export看原始响应头和状态码;不要只信浏览器下载行为 - 如果必须支持断点续传或超大文件,改用
excelize.StreamWriter,但需自行处理 ZIP 结构,复杂度陡增,普通业务不推荐
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











