php csv导出不完全,PHP 导出CSV文件部分行错位

经过一个下午的折腾,上周五还是发现出现该异常情况的原因。概括来说还是中文转编码的问题,即utf-8转gbk出现的小部分行缺少空格或者双引号的问题。

出现问题的原因:

在进行导出的过程中,接收其他程序传过来的数组参数,均为utf-8编码,我这边在调用fwrite或者fputcvs写入文件的过程中并没有进行 iconv("UTF-8", "GB2312//IGNORE", $header),而是将文件保存在了 export目录下,然后由vuejs做的后台调用位于同application下的一个共用类的 export方法,该方法是这样的:

public function export()

{

$file = \Request::get('file');

header('Content-Type: application/vnd.ms-excel');

header('Content-Disposition: attachment;filename=export.csv');

header('Cache-Control: max-age=0');

$contents = file_get_contents(\Env::get('runtime_path') . '/export/' . $file);

echo iconv("UTF-8", "GB2312//IGNORE", $contents);

@unlink(RUNTIME_PATH . '/export/' . $filename);

exit;

}

问题就出在 echo iconv("UTF-8", "GB2312//IGNORE", $contents); 这句代码上面,这里看到是对传入的整个文件进行转编码的,这里未进行测试到底多少行的数据会出现缺少逗号引号的问题。

最终的解决思路是,把共用类中对整个文件的转编码放到写入方法中,逐条转编码就解决了该问题。

更改后的代码:

public static function createCsv($data, $header = [], $filename = '')

{

// 参数判断

$data = is_object($data) ? $data->toArray() : $data;

$header = is_array($header) ? $header : [];

$filename = (1 > strlen(trim($filename))) ? 'csv-' : trim($filename);

if (empty($data)) {

return false;

}

// 文件名/目录

$filename = $filename . date("YmdHis", time()) . rand(1000, 9999) . ".csv";

$dir = Env::get('runtime_path') . '/export/';

if (!is_dir($dir) && !mkdir($dir)) {

return false;

}

// 打开文件指针资源

$handle = fopen($dir . $filename, 'w+');

if (!$handle) {

return false;

}

// 写入文件header头

if (!empty($header)) {

foreach ($header as $key => $item) {

$header[$key] = iconv("UTF-8", "GB2312//IGNORE", $item);

}

$res = fputcsv($handle, $header);

if (!$res) {

return false;

}

}

// 判断header是索引数组还是关联数组

$is_assoc = array_keys($header) !== range(0, count($header) - 1);

// 写入文件内容

$frequency = 0; // 频率

$limit = 100000;

foreach ($data as $datum) {

$frequency++;

if ($limit == $frequency) {

// 刷新输出buffer

ob_flush();

flush();

$frequency = 0;

}

// 如果是关联数组,则获取内容中和头部key相对应的值

if ($is_assoc) {

$csv = "";

foreach(array_keys($header) as $item) {

$datum[$item] = iconv("UTF-8", "GB2312//IGNORE", $item);

$val = str_replace('"', '""', $datum[$item]); // 将单个双引号替换为两个双引号

$csv .= '"' . $val . '",'; // 为每个字符增加双引号,并添加逗号分割符

}

$csv = substr($csv, 0, -1); // 去掉每行最后一个逗号

$csv .= "\n"; // 添加换行符

$res = @fwrite($handle, $csv);

} else {

$res = fputcsv($handle, $datum);

}

if (!$res) {

return false;

}

}

// 关闭指针资源

fclose($handle);

return Url::build('erp/Common/export', 'file=' . $filename);

}

public function export()

{

$file = \Request::get('file');

header('Content-Type: application/vnd.ms-excel');

header('Content-Disposition: attachment;filename=export.csv');

header('Cache-Control: max-age=0');

$contents = file_get_contents(\Env::get('runtime_path') . '/export/' . $file);

echo $contents;

exit;

}

调用步骤:

先调用createCsv生成文件,再调用export下载。

———————————————————————分割线——————————————————————

另外一种解决思路:

csv文件直接存储 UTF-8 编码:

即不需要进行 iconv 转编码,这样会有个小问题就是MacOS中的Excel无法自动识utf-8编码,中文会出现乱码的情况。

原因:

Excel在读取csv的时候是通过读取文件头上的bom来识别编码的,如果文件头无bom信息,则默认按照unicode编码读取。(这个bom是微软自己定义的一种文件头部协定,顾名思义存储在文件头部,存储内容就是标识文件编码的信息。)而我们生成csv的平台不一定遵循微软的bom协议,导致如果输出非unicode编码的csv文件(例如utf-8),并且没有生成bom信息的话,Excel自动按照unicode编码读取,就会出现乱码问题了。

作者:李蛟 链接:https://www.zhihu.com/questio...

来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

根据知乎查到的答案,原来是这里未遵循微软的bom协议。原因找到了,解决方案就能出了。

解决方法:

fopen 方法下写入bom头,这里简单写一下步骤

function createCsv($filename, $header, $data) {

$handle = fopen($filename, 'w+');

// 添加BOM,标识为UTF-8格式

fwrite($handle, chr(0xEF).chr(0xBB).chr(0xBF));

// 写入头部

fputcsv($handle, $header);

// 逐行写入内容

foreach ($data as $datum) {

fputcsv($handle, $datum);

}

// 关闭指针资源

fclose($handle);

}

另外再补充一下 Bom 简介

在UCS 编码中有一个叫做”ZERO WIDTH NO-BREAKSPACE”的字符,它的编码是FEFF。

FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。

UCS规范建议我们在传输字节流前,先传输字符”ZERO WIDTH NO-BREAK SPACE”。

这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;

如果收到FFFE,就表明这个字节流是Little-Endian的。

因此字符”ZERO WIDTH NO-BREAK SPACE”又被称作BOM。

UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。

字符”ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是EF BB BF。

所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

Windows就是使用BOM来标记文本文件的编码方式的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值