CodeSky 代码之空

随手记录自己的学习过程

在 Golang 中导出一个 excel 可识别的 csv

2020-12-27 20:52分类: Go评论: 0

好久没有更新博客了,稍微写个几行吧……不然都快不会写东西了 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 一样,就再也没有不知道识别成啥的烦恼了,原因有以下几点:

  1. BOM 头增加了无用的空间
  2. 协议限制(主要出现在把 BOM 头识别成字符,还是头上)

基于此,添加 BOM 头还是需要考虑一些兼容性的问题,尤其是如果是在程序之间相互通信的时候,怎么处理 BOM 头或许是一个大难题,还是要因地制宜的去选择「是否添加 BOM 头」。

因此,最终还是加上了一个参数 options,拿来配置是否需要使用 BOM 头,来让函数变得更加通用,适用于不同的场景。

参考资料:

评论 (0)