导出模块必须支持流式写入而非全量内存加载,即通过 io.Writer 接口逐批写入,避免 json.Marshal 全量加载导致 OOM,函数签名应为 func Export(ctx context.Context, dataChan <-chan Item, w io.Writer) error。

导出模块必须支持流式写入而非全量内存加载
Go 中常见错误是把整个数据集 json.Marshal 成字节切片再写入,一碰到百万级记录就 OOM。真正标准的导出模块得用 io.Writer 接口逐批写入,让调用方决定输出目标(文件、HTTP 响应体、网络连接等)。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 导出函数签名应为
func Export(ctx context.Context, dataChan ,不接收切片或 map - 使用
json.NewEncoder(w)而非json.Marshal,它直接流式编码,内存占用恒定在几 KB - 每写入一条记录后调用
encoder.Encode(item),避免手动拼接 JSON 数组(易出格式错误) - 务必在循环中检查
ctx.Err(),否则超时或取消无法中断导出
导出格式需按 MIME 类型动态切换编码器
用户要 CSV 还是 JSON 还是 Excel,不是靠改代码,而是靠传入的 contentType 参数驱动编码逻辑。硬编码单一格式会卡死后续扩展。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 定义统一入口:
func ExportTo(ctx context.Context, dataChan -
contentType取值限定为"application/json"、"text/csv"、"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - JSON 用
json.NewEncoder;CSV 用csv.NewWriter并提前写 Header(需约定数据结构含Header() []string方法);Excel 用github.com/xuri/excelize/v2的f.SetSheetRow流式追加 - 不要在导出函数里打开文件或创建 HTTP ResponseWriter——那是调用方的事
字段映射与空值处理必须可配置且默认合理
数据库字段名、结构体 tag、导出列名三者不一致是常态。导出模块若强制要求结构体字段名和 CSV 列名一致,等于放弃生产可用性。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 提供
FieldMapper函数类型:type FieldMapper func(interface{}) map[string]interface{},让用户自行做字段提取和重命名 - 默认 mapper 应识别
json:tag,并将空字符串、nil、零值转为空字符串(CSV 中不能写null),而非 panic 或跳过整行 - 对时间字段,默认转为 RFC3339 格式(
t.Format(time.RFC3339)),不依赖本地时区 - 若数据源是
*sql.Rows,需额外封装ScanRow工具函数,避免用户重复写rows.Scan(&v1, &v2, ...)
并发写入与错误传播必须严格控制边界
有人用 goroutine 并发写 CSV,结果行列错乱;也有人把 io.Write 错误吞掉,导致导出文件看似成功实则为空。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 禁止在导出函数内启动 goroutine 写
w——io.Writer多数不并发安全(如os.File、http.ResponseWriter) - 所有
w.Write或encoder.Encode的错误必须立即返回,不重试(重试可能重复写入) - 若需缓冲提升吞吐,只在 encoder 层用
bufio.NewWriter包一层,且最后必须Flush(),否则结尾数据丢失 - 导出完成前不要 close
dataChan,由生产方控制生命周期;模块只负责消费,不关通道
真正难的不是写出能跑的导出函数,而是让下游调用方不用关心缓冲大小、字符编码、BOM 头、时间格式、空值语义这些细节——它们得在模块内部收口,且每个开关都留有可替换的钩子。


















