导出多表数据必须分Sheet,不能拼成一张大表;需用NewStreamWriter逐表流式写入,配合SetActiveSheet切换目标表,写完调Flush()和Close(),并显式设置中文字体样式。

导出多表数据必须分 Sheet,不能拼成一张大表
Excel 本质是多工作表容器,excelize 的 NewSheet 是轻量操作,但强行把多张表的数据塞进同一个 Sheet(比如加空行分隔),会导致 Excel 打开卡顿、筛选失效、公式错乱,甚至被某些客户端拒绝解析。真实业务中「用户导出订单+订单项+收货地址」这类关联数据,必须按逻辑拆到不同 Sheet。
常见错误是用一个 for 循环遍历所有表结构,然后反复调用 f.SetCellValue 写到同一张表里——这等于手动模拟数据库 JOIN,既难维护又易出错。
- 每个表对应一个独立
sheet,用f.NewSheet("订单")、f.NewSheet("订单项")显式创建 - 写入前先调用
f.SetActiveSheet(index)切换当前目标 Sheet,否则默认写在第一个 Sheet - 若需跨 Sheet 引用(如订单项里显示订单号),用
=订单!A2这类公式,而不是复制粘贴值 - 多 Sheet 导出时,
f.SetActiveSheet只影响后续SetCellValue,不影响已写入内容
避免内存爆炸:大数据量必须用 NewStreamWriter
查出 10 万行订单 + 50 万行订单项,全量加载进内存再写 Excel,Go 程序 RSS 很快突破 1GB。这不是 GC 问题,是 excelize.File 对象本身会缓存全部单元格状态。直接用 f.SetCellValue 循环写,等于把整张表的 XML 结构全保留在内存里。
excelize.NewStreamWriter("Sheet1") 是唯一解:它不建内存模型,只生成 XML 流,边写边刷盘,峰值内存稳定在几 MB。
立即学习“go语言免费学习笔记(深入)”;
- 每张 Sheet 都要单独调用
f.NewStreamWriter(sheetName),不能复用一个sw - 写完当前 Sheet 后必须调
sw.Flush(),否则最后一行丢失 - 所有 Sheet 写完后,再调
f.Close(),否则文件头缺失,Excel 打不开 - 别在
sw上用SetCellStyle或SetColWidth——这些 API 不支持流式模式,会 panic
中文字段名和中文内容必须显式设字体
即使你用 UTF-8 BOM、Content-Type 设对了,Excel 依然显示方块字,根本原因不是编码,而是默认字体不支持中文。Windows Excel 默认用 Calibri,macOS 用 Helvetica,都不含中文字形。
excelize 的样式系统必须主动绑定字体,且仅靠 Font.Name 不够,得配全:Font.Size、Font.Bold、Font.Color 一起设,否则部分 Excel 版本忽略该样式。
- 创建文件后立即定义样式:
styleID, _ := f.NewStyle(&excelize.Style{Font: &excelize.Font{Name: "SimSun", Size: 11}}) - 表头用粗体样式:
styleIDHeader, _ := f.NewStyle(&excelize.Style{Font: &excelize.Font{Name: "SimSun", Size: 11, Bold: true}}) - 应用样式时覆盖整个区域:
f.SetCellStyle("订单", "A1", "Z1", styleIDHeader),不能只设 A1 单元格 - Mac 用户若发现 SimSun 不生效,可试
"PingFang SC"或"Noto Sans CJK SC"
导出接口响应头和流控必须同步处理
浏览器下载大文件时,若后端没及时发数据,Nginx 或 Chrome 会在 60 秒后断连,报 net::ERR_CONNECTION_ABORTED。这不是 Gin 超时,是传输层中断。
解决方案不是调大超时时间,而是让响应体持续有数据产出。CSV 可靠,Excel 必须靠 http.ResponseWriter 的 Flush 配合流式写入。
- 设置
Content-Disposition时用url.QueryEscape处理中文文件名:c.Header("Content-Disposition", "attachment; filename*=UTF-8''"+url.QueryEscape("多表导出.xlsx")) - 设置
Content-Type为application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,不是octet-stream - 写入过程中每完成一个 Sheet,调一次
c.Writer.(http.Flusher).Flush(),强制推送 HTTP chunk - 若用
NewStreamWriter,可在每写满 1000 行后 flush 一次,避免单次阻塞太久
sw.Flush() 和 http.Flusher 的组合调用,漏掉任意一个,前端就只能收到半截文件。


















