CSV导出必须加UTF-8 BOM(\xEF\xBB\xBF)且用fwrite写入php://output,禁用echo/print调试;Excel导出需用PhpSpreadsheet分批写入或改用异步队列,避免内存溢出与输出流污染。

CSV导出必须加BOM,否则Windows下Excel打不开中文
直接用 fputcsv() 写入 php://output 是最轻量的方式,但 Windows 版 Excel 默认不识别 UTF-8 编码,会把中文显示成乱码。唯一稳定解法是手动写入 UTF-8 BOM 头:\xEF\xBB\xBF。
常见错误包括:在 header() 前有空格或 echo、用 print_r() 调试后忘记删、开启 display_errors 导致错误信息混入输出流——这些都会让 CSV 文件损坏,浏览器提示“无法打开文件”或弹出下载但内容为空。
- 响应头必须完整:
Content-Type: text/csv; charset=utf-8+Content-Disposition: attachment; filename="data.csv" - 用
fopen('php://output', 'w'),不要用临时文件再读取,避免内存和IO浪费 - 写 BOM 必须用
fwrite()或fprintf(),不能用echo(可能被缓冲干扰) - 每行数据传给
fputcsv()前确保是字符串数组,数值类型会自动转,但null或false会变成空字符串,需提前处理
导出Excel要用PhpSpreadsheet,别碰PHPExcel
PHPExcel 已停止维护多年,PhpSpreadsheet 是其官方继任者,支持 PHP 7.4+ 和现代 Composer 生态。Echo 框架本身不提供 Excel 功能,所有逻辑都在控制器里完成:查数据 → 构建 Spreadsheet 对象 → 写入 → 输出流响应。
关键点不是“怎么装库”,而是“怎么避免内存爆掉”。10 万行数据直接塞进内存生成 .xlsx 很容易触发 Fatal error: Allowed memory size exhausted。解决思路是分批写入或改用 CSV;如果必须 Excel,优先用 setCellValueByColumnAndRow() 替代整行 fromArray(),减少中间数组拷贝。
使用 qbo-mileage CLI 及用户凭证,从 Airtable、Outlook 或 Google Calendar 记录生成 QuickBooks Online 里程 CSV 文件。
- 安装命令:
composer require phpoffice/phpspreadsheet - 输出时用
php://output流,不要save()到磁盘再读取 - 务必设置
ob_end_clean()在header()前,防止 Echo 的中间件输出干扰二进制流 - 中文字段要调用
$sheet->getStyle('A1')->getFont()->setName('SimSun'),否则默认字体不显示中文
Echo 的响应流控制比 Laravel 更敏感
Echo 是轻量级框架,没有内置的响应包装器,echo、print、var_dump() 一旦出现在导出逻辑中,就会破坏二进制文件结构。比如你在 for 循环里加了 error_log("row $i"),日志虽不显示,但若配置了 log_errors = On 且 error_log 指向 stdout,就可能污染输出流。
另一个坑是中间件顺序。如果你在路由上挂了 Logger 或 Recover 中间件,它们可能在导出前已向响应体写入内容(如 JSON 错误页),导致 Excel 文件开头多出几行文本,用户双击打不开。
- 导出接口应单独定义路由,不套通用中间件
- 在 handler 开头立刻调用
ob_end_clean()和ob_start(),确保输出缓冲干净 - 用
Response::Writer时,别调用WriteString(),直接Write()二进制字节 - 调试时改用
file_put_contents('/tmp/debug.log', ...),避开任何输出函数
大表导出别硬扛,优先走流式CSV + 后台队列
超过 5 万行的数据,强行在 Web 请求里生成 Excel 几乎必然超时或 OOM。即使加了 set_time_limit(0) 和 ini_set('memory_limit', '-1'),用户端也会因连接中断而失败。
真实生产环境的解法是:前端点击“导出”只提交任务,后端用 exec('php artisan export:run '.$jobId.' > /dev/null &') 或 Redis 队列异步执行,完成后发通知或生成可下载链接。CSV 可以流式处理(逐行 fputcsv),Excel 不行——PhpSpreadsheet 必须构建完整对象后才能写入,所以大数据量 Excel 导出本质是伪实时,得接受延迟。
- 小数据(
- 中等数据(1–5 万行):强制用 CSV,加 BOM,前端提示“建议用 Excel 打开”
- 大数据(> 5 万行):必须异步,返回
{"status":"processing","job_id":"exp_abc123"},由定时任务轮询生成状态 - 别信“边查边写 Excel”的方案——
PhpSpreadsheet没有真正的流式写入 API
Cache-Control: no-cache 就以为安全,但 IE 和旧版 Edge 会缓存 attachment 响应,第二次点下载拿到的是上次的文件。真正有效的是三连:Cache-Control: no-store, no-cache, must-revalidate + Pragma: no-cache + Expires: 0。

















