在 Golang 中导出一个 excel 可识别的 csv
好久没有更新博客了,稍微写个几行吧……不然都快不会写东西了 emmm。
众所周知,导出表格是中后台一个非常普遍的需求,而 Golang 官方库中就提供了 encoding/csv
最简单的应用在官方里写的很明白:
1package main
2
3import (
4 "encoding/csv"
5 "log"
6 "os"
7)
8
9func main() {
10 records := [][]string{
11 {"first_name", "last_name", "username"},
12 {"Rob", "Pike", "rob"},
13 {"Ken", "Thompson", "ken"},
14 {"Robert", "Griesemer", "gri"},
15 }
16
17 w := csv.NewWriter(os.Stdout)
18
19 for _, record := range records {
20 if err := w.Write(record); err != nil {
21 log.Fatalln("error writing record to csv:", err)
22 }
23 }
24
25 // Write any buffered data to the underlying writer (standard output).
26 w.Flush()
27
28 if err := w.Error(); err != nil {
29 log.Fatal(err)
30 }
31}
32
看上去思路好像没什么问题,我们不是只要依次写入就可以了吗?
似乎是这样,说干就干。
首先考虑一点:对于函数的用户,不应该自己去处理成 string,应该由内部自己处理,因此传入的类型应当是一个 interface{}
或者 []interface{}
,因此我们现在先写一个函数,把任何类型转成 string,这样官方 csv 函数才能被使用:
1func interfaceSliceToStringSlice(dataList []interface{}) (strList []string) {
2 for _, data := range dataList {
3 strList = append(strList, fmt.Sprintf("%v", data))
4 }
5 return
6}
7
然后,再调用,将表头和每行内容挨个处理,似乎就完事儿了?
1func (s *Service) toCsv(ctx context.Context, header []interface{}, dataList [][]interface{}) (result []byte, err error) {
2 buffer := bytes.NewBuffer(nil)
3 record := csv.NewWriter(buffer)
4 if header != nil {
5 err = record.Write(interfaceSliceToStringSlice(header))
6 if err != nil {
7 log.Error("toCsv Header Error: %v", err)
8 return
9 }
10 }
11 for _, data := range dataList {
12 writeErr := record.Write(interfaceSliceToStringSlice(data))
13 if writeErr != nil {
14 err = writeErr
15 log.Error("toCsv Content Error: %v", err)
16 return
17 }
18 }
19 record.Flush()
20 if err = record.Error(); err != nil {
21 log.Error("toCsv Flush error: %v", err)
22 return
23 }
24 result = buffer.Bytes()
25 return
26}
27
看上去仿佛是这样,结果运营一用,嘿,Excel 打不开,WPS 和 Numbers 明明都是好好的,在网上查了一下 Excel 乱码的问题,说的是什么默认用的 unicode 解析,而我们打包出来的是 UTF8,编码识别有问题所以不对。当时就很迷惑:UTF8 他喵的不就是一种 unicode 吗?
解决方案其实就是为了做一件事情,那就是压入 BOM 头,BOM 头是一个什么东西呢?——其实就是指定文件编码。
如果是 UTF8,在头部加上 \xef\xbb\xbf
就可以在 Excel 中被识别了。
对应的 BOM 头关系可见下表:
Bytes | Encoding Form |
---|---|
00 00 FE FF | UTF-32, big-endian |
FF FE 00 00 | UTF-32, little-endian |
FE FF | UTF-16, big-endian |
FF FE | UTF-16, little-endian |
EF BB BF | UTF-8 |
说到这里了,既然 BOM 头这么好,为什么不大家都加上 BOM 头呢,就跟 content/type 一样,就再也没有不知道识别成啥的烦恼了,原因有以下几点:
- BOM 头增加了无用的空间
- 协议限制(主要出现在把 BOM 头识别成字符,还是头上)
基于此,添加 BOM 头还是需要考虑一些兼容性的问题,尤其是如果是在程序之间相互通信的时候,怎么处理 BOM 头或许是一个大难题,还是要因地制宜的去选择「是否添加 BOM 头」。
因此,最终还是加上了一个参数 options,拿来配置是否需要使用 BOM 头,来让函数变得更加通用,适用于不同的场景。
参考资料:
评论 (0)