运营同学有一个需求提给了我,说是每周一和周五需要到系统手动导出所有成交报告,而系统是根据城市进行分库的,导完一个城市的成交报告需要切换系统的登录城市。这个步骤异常繁琐,希望我能给个解决方案来解放他的双手。
在项目拉取了一个 hotfix 分支就开搞,通过命令触发接口导出数据生成 CSV 文件并通过附件发送给我。
测试完成,数据完整。发给运营同学准备测试数据,然后气氛有点奇怪。
运营同学:乱码。
我:试试导入?
运营同学:还是乱码。
乱码?UTF-8 还乱码?这 Office 用得啥编码格式。
搜索一通,有说是 Excel 的 Bug,可以通过加 BOM 头搞定。
BOM 头是啥呢?
在 UCS 编码中有一个叫做 "Zero Width No-Break Space " ,中文译名作 "零宽无间断间隔" 的字符,它的编码是 FEFF。而 FFFE 在 UCS 中是不存在的字符,所以不应该出现在实际传输中。UCS 规范建议我们在传输字节流前,先传输字符 "Zero Width No-Break Space"。这样如果接收者收到 FEFF,就表明这个字节流是 Big-Endian 的;如果收到 FFFE,就表明这个字节流是 Little- Endian 的。因此字符 "Zero Width No-Break Space" ("零宽无间断间隔")又被称作 BOM。
类似 WINDOWS 自带的记事本等软件,在保存一个以 UTF-8 编码的文件时,会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF,即 BOM)。它是一串隐藏的字符,用于让记事本等编辑器识别这个文件是否以 UTF-8 编码。对于一般的文件,这样并不会产生什么麻烦。但对于 PHP 来说,BOM 是个大麻烦。
再补充一份不同编码的字节顺序标记的表。
| 编码 | 表示 (十六进制) | 表示 (十进制) | |:-------------------------------------------|:------------------------------------------|:-------------------------------------------| | UTF-8 | EF BB BF | 239 187 191 | | UTF-16(大端序) | FE FF | 254 255 | | UTF-16(小端序) | FF FE | 255 254 | | UTF-32(大端序) | 00 00 FE FF | 0 0 254 255 | | UTF-32(小端序) | FF FE 00 00 | 255 254 0 0 | | UTF-7 | 2B 2F 76 和以下的一个字节:[38 / 39 / 2B / 2F] | 43 47 118 和以下的一个字节:[56 / 57 / 43 / 47] | | en:UTF-1 | F7 64 4C | 247 100 76 | | en:UTF-EBCDIC | DD 73 66 73 | 221 115 102 115 | | en:Standard Compression Scheme for Unicode | 0E FE FF | 14 254 255 | | en:BOCU-1 | FB EE 28及可能跟随着FF | 251 238 40及可能跟随着255 | | GB-18030 | 84 31 95 33 | 132 49 149 51 |
也就是说,只需要在 CSV 头部加一个 BOM 头就可以解决乱码。
<?php
// 设置 UTF-8 BOM 头
$bom = chr(239).chr(187).chr(191);
$filecontents = $bom . $filecontents;
...
嗯,记一笔,运营同学欠我一顿饭。