后台 Grid 能正常显示,但导出的 CSV 在 Excel 打开后中文乱码;记录稍多又报 Allowed memory size exhausted。这是两个问题:乱码属于文件解释方式,内存溢出属于导出实现一次加载过多数据。
先判断文件是不是 UTF-8
file -bi export.csv
xxd -l 16 export.csv
iconv -f UTF-8 -t UTF-8 export.csv -o /dev/null
UTF-8 CSV 被 Excel 按本地编码打开时可能乱码。需要兼容 Excel 的场景可在输出开头加入 UTF-8 BOM,但 API 或数据交换文件未必应该带 BOM。
$stream->write("");
$stream->writeCsv($headers);
foreach ($rows as $row) {
$stream->writeCsv($row);
}
不要用 getData() 装入整个集合
导出十万行时,集合、扩展属性和二维数组会同时驻留内存。使用分页分批读取,并直接写临时文件流。
$pageSize = 1000;
for ($page = 1; ; $page++) {
$collection->setPageSize($pageSize)->setCurPage($page);
$collection->load();
if (!count($collection)) { break; }
foreach ($collection as $item) {
$stream->writeCsv($this->rowBuilder->build($item));
}
$collection->clear();
}
不要在每一行调用 Repository 加载完整实体,这会产生 N+1 查询。Grid 需要哪些列,就在集合 SQL 中选择哪些列;复杂展示字段与导出字段可以使用不同数据提供器。
区分 PHP 限制与代理超时
php -i | grep -E 'memory_limit|max_execution_time'
grep -RniE 'Allowed memory size|Maximum execution time' var/log | tail -n 30
tail -n 50 /var/log/nginx/error.log
浏览器得到 504 而 PHP 仍在运行,说明同步大导出不适合一次后台请求。将任务放入消息队列,完成后生成受权限保护的下载文件,比无限增加 Nginx timeout 更可靠。
用包含逗号、双引号、换行和中文的测试行检查转义,再用接近生产规模的数据测峰值内存。文件能下载但列错位,同样不能算成功。
导出文件也要防止公式注入
客户名称、备注等字段如果以 =、+、- 或 @ 开头,Excel 可能把它解释成公式。面向人工下载的 CSV 应在输出层按业务规则转义不可信文本,同时保留原始数据导出渠道用于系统交换。
异步导出文件应设置过期时间、下载权限和清理任务,不能永久留在公开 media 路径。

