
为什么直接用 csv.Writer 写响应体容易出错
很多开发者一上来就用 csv.NewWriter(w) 往 http.ResponseWriter 里写,结果浏览器打不开或文件损坏。根本原因是没设置正确的 HTTP 头,也没触发 flush —— CSV 是纯文本流,但浏览器需要明确知道“这是个可下载的文件”,否则会尝试渲染乱码。
- 必须提前调用
w.Header().Set("Content-Type", "text/csv; charset=utf-8") - 必须加
Content-Disposition告诉浏览器保存为文件:w.Header().Set("Content-Disposition", `attachment; filename="data.csv"`) -
csv.Writer的缓冲默认不自动 flush,得手动wr.Flush(),否则最后一行可能丢掉 - 如果数据量大,别一次性加载进内存再写;应边查边写,避免 OOM
如何处理中文字段和 Excel 打开乱码问题
Go 默认输出 UTF-8,但 Windows 版 Excel 默认用 ANSI(GBK/GB2312)打开 CSV,直接双击就会显示“锟斤拷”。这不是 Go 的错,是 Excel 的老毛病。
- 最稳妥方案:在 CSV 文件开头插入 UTF-8 BOM(
\uFEFF),Excel 就能正确识别编码 - 写法很简单:
w.Write([]byte("\uFEFF")),放在csv.NewWriter之前 - 不要用
golang.org/x/text/encoding/charmap转 GBK —— 现代系统和浏览器都不认,反而破坏兼容性 - 字段含逗号、换行符、双引号时,
csv.Writer会自动加引号和转义,不用手动处理
从数据库查数据并流式写入 CSV 的典型结构
假设你用 database/sql 查 users 表,想导出 ID、Name、Email 三列。关键不是“怎么查”,而是“怎么把查出来的每一行立刻写出去”。
使用 qbo-mileage CLI 及用户凭证,从 Airtable、Outlook 或 Google Calendar 记录生成 QuickBooks Online 里程 CSV 文件。
rows, err := db.Query("SELECT id, name, email FROM users WHERE status = ?", status)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
w.Header().Set("Content-Disposition", `attachment; filename="users.csv"`)
// 写 BOM
w.Write([]byte("\uFEFF"))
wr := csv.NewWriter(w)
// 写表头
wr.Write([]string{"ID", "姓名", "邮箱"})
for rows.Next() {
var id int
var name, email string
if err := rows.Scan(&id, &name, &email); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
wr.Write([]string{strconv.Itoa(id), name, email})
}
wr.Flush() // 必须!否则最后一行可能丢失
- 别用
rows.Slice或sqlx.StructScan全部加载到内存再遍历 —— 导出 10 万行就可能吃光内存 - 如果字段来自 struct,用
reflect取字段名做 header 很方便,但注意性能损耗;简单场景建议硬编码 header - 遇到空值(NULL)时,
sql.NullString等类型要显式判断.Valid,否则Scan会报错
用 Gin 或 Echo 框架时的常见适配点
Gin 和 Echo 默认的 c.Writer / c.Response() 是包装过的,但底层仍是 http.ResponseWriter,所以核心逻辑不变。唯一要注意的是中间件可能已写过 header。
立即学习“go语言免费学习笔记(深入)”;
- 在 Gin 中,确保没调过
c.JSON或c.String—— 那会提前 commit header,再设Content-Disposition就无效 - Echo 的
c.Response().Writer就是原始http.ResponseWriter,可直接传给csv.NewWriter - 如果用了 gzip 中间件(如 Gin 的
gin.Gzip()),CSV 压缩后 Excel 打不开,务必排除该路由或禁用压缩 - 别依赖框架的 “file download” 辅助方法(如
c.File()),那是为读取本地文件设计的,不适用于动态生成内容

















