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

好久没有更新博客了,稍微写个几行吧……不然都快不会写东西了 emmm。

众所周知,导出表格是中后台一个非常普遍的需求,而 Golang 官方库中就提供了 encoding/csv

最简单的应用在官方里写的很明白:

package main

import (
    "encoding/csv"
    "log"
    "os"
)

func main() {
    records := [][]string{
        {"first_name", "last_name", "username"},
        {"Rob", "Pike", "rob"},
        {"Ken", "Thompson", "ken"},
        {"Robert", "Griesemer", "gri"},
    }

    w := csv.NewWriter(os.Stdout)

    for _, record := range records {
        if err := w.Write(record); err != nil {
            log.Fatalln("error writing record to csv:", err)
        }
    }

    // Write any buffered data to the underlying writer (standard output).
    w.Flush()

    if err := w.Error(); err != nil {
        log.Fatal(err)
    }
}

看上去思路好像没什么问题,我们不是只要依次写入就可以了吗?

似乎是这样,说干就干。

首先考虑一点:对于函数的用户,不应该自己去处理成 string,应该由内部自己处理,因此传入的类型应当是一个 interface{} 或者 []interface{},因此我们现在先写一个函数,把任何类型转成 string,这样官方 csv 函数才能被使用:

func interfaceSliceToStringSlice(dataList []interface{}) (strList []string) {
    for _, data := range dataList {
        strList = append(strList, fmt.Sprintf("%v", data))
    }
    return
}

然后,再调用,将表头和每行内容挨个处理,似乎就完事儿了?

func (s *Service) toCsv(ctx context.Context, header []interface{}, dataList [][]interface{}) (result []byte, err error) {
    buffer := bytes.NewBuffer(nil)
    record := csv.NewWriter(buffer)
    if header != nil {
        err = record.Write(interfaceSliceToStringSlice(header))
        if err != nil {
            log.Error("toCsv Header Error: %v", err)
            return
        }
    }
    for _, data := range dataList {
        writeErr := record.Write(interfaceSliceToStringSlice(data))
        if writeErr != nil {
            err = writeErr
            log.Error("toCsv Content Error: %v", err)
            return
        }
    }
    record.Flush()
    if err = record.Error(); err != nil {
        log.Error("toCsv Flush error: %v", err)
        return
    }
    result = buffer.Bytes()
    return
}

看上去仿佛是这样,结果运营一用,嘿,Excel 打不开,WPS 和 Numbers 明明都是好好的,在网上查了一下 Excel 乱码的问题,说的是什么默认用的 unicode 解析,而我们打包出来的是 UTF8,编码识别有问题所以不对。当时就很迷惑:UTF8 他喵的不就是一种 unicode 吗?

解决方案其实就是为了做一件事情,那就是压入 BOM 头,BOM 头是一个什么东西呢?——其实就是指定文件编码。

如果是 UTF8,在头部加上 \xef\xbb\xbf 就可以在 Excel 中被识别了。

对应的 BOM 头关系可见下表:

BytesEncoding Form
00 00 FE FFUTF-32, big-endian
FF FE 00 00UTF-32, little-endian
FE FFUTF-16, big-endian
FF FEUTF-16, little-endian
EF BB BFUTF-8

说到这里了,既然 BOM 头这么好,为什么不大家都加上 BOM 头呢,就跟 content/type 一样,就再也没有不知道识别成啥的烦恼了,原因有以下几点:

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

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

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

参考资料:

植入部分

如果您觉得文章不错,可以通过赞助支持我。

如果您不希望打赏,也可以通过关闭广告屏蔽插件的形式帮助网站运作。

标签: 知识, 语法

添加新评论