前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go Excel导出工具封装

Go Excel导出工具封装

原创
作者头像
Johns
修改2022-06-30 10:31:08
5.4K2
修改2022-06-30 10:31:08
举报
文章被收录于专栏:代码工具

1. 相关库调研

最近在用go开发一个管理端,需要提供一个Excel导出的功能。于是去调研了一下Go的两个常用的Excel库:

tealeg/xlsx 简单好用,但是功能有限,在单元格仅能插入字符串类型。 进它仓库看了一下,发现这个库没有维护了。

excelize 用起来比较复杂,需要通过指定excel的sheet坐标来定位单元格进行读取或者插入数据,但是功能更强大。这个仓库现在还有人维护,我之前提了个issue,半天就回复了。

对比两个库后,决定基于excelize封装出一个工具方法, 每次只需要指定表头和数据就行。

2. 封装代码

参考excelize的官方demo,发现它每次写excel时都需要指定内容在Sheet的坐标。Sheet的坐标分为列坐标和行坐标。

  • 列坐标: A~Z, ( A~Z)( A~Z), ( A~Z)( A~Z)( A~Z) .... 1~26列用A~Z 表示。超过26列的小于676列的从AA开始结尾于ZZ, 大于676的从AAA开始, 按照此规则依次类推。
  • 行坐标: 从1开始到1048576, 超过的部分则需要切到下一个Sheet了
代码语言:txt
复制
package utils

/**
 * @Description
 * @Author ggr
 * @Date 2021/7/8 21:06
 **/

import (
	"github.com/360EntSecGroup-Skylar/excelize/v2"
	"strconv"
)

// maxCharCount 最多26个字符A-Z
const maxCharCount = 26

// ExportExcel 导出Excel文件
// sheetName 工作表名称, 注意这里不要取sheet1这种名字,否则导致文件打开时发生部分错误。
// headers 列名切片, 表头
// rows 数据切片,是一个二维数组
func ExportExcel(sheetName string, headers []string, rows [][]interface{}) (*excelize.File, error) {
	f := excelize.NewFile()
	sheetIndex := f.NewSheet(sheetName)
	maxColumnRowNameLen := 1 + len(strconv.Itoa(len(rows)))
	columnCount := len(headers)
	if columnCount > maxCharCount {
		maxColumnRowNameLen++
	} else if columnCount > maxCharCount*maxCharCount {
		maxColumnRowNameLen += 2
	}
	columnNames := make([][]byte, 0, columnCount)
	for i, header := range headers {
		columnName := getColumnName(i, maxColumnRowNameLen)
		columnNames = append(columnNames, columnName)
		// 初始化excel表头,这里的index从1开始要注意
		curColumnName := getColumnRowName(columnName, 1)
		err := f.SetCellValue(sheetName, curColumnName, header)
		if err != nil {
			return nil, err
		}
	}
	for rowIndex, row := range rows {
		for columnIndex, columnName := range columnNames {
			// 从第二行开始
			err := f.SetCellValue(sheetName, getColumnRowName(columnName, rowIndex+2), row[columnIndex])
			if err != nil {
				return nil, err
			}
		}
	}
	f.SetActiveSheet(sheetIndex)
	return f, nil
}

// getColumnName 生成列名
// Excel的列名规则是从A-Z往后排;超过Z以后用两个字母表示,比如AA,AB,AC;两个字母不够以后用三个字母表示,比如AAA,AAB,AAC
// 这里做数字到列名的映射:0 -> A, 1 -> B, 2 -> C
// maxColumnRowNameLen 表示名称框的最大长度,假设数据是10行,1000列,则最后一个名称框是J1000(如果有表头,则是J1001),是4位
// 这里根据 maxColumnRowNameLen 生成切片,后面生成名称框的时候可以复用这个切片,而无需扩容
func getColumnName(column, maxColumnRowNameLen int) []byte {
	const A = 'A'
	if column < maxCharCount {
		// 第一次就分配好切片的容量
		slice := make([]byte, 0, maxColumnRowNameLen)
		return append(slice, byte(A+column))
	} else {
		// 递归生成类似AA,AB,AAA,AAB这种形式的列名
		return append(getColumnName(column/maxCharCount-1, maxColumnRowNameLen), byte(A+column%maxCharCount))
	}
}

// getColumnRowName 生成名称框
// Excel的名称框是用A1,A2,B1,B2来表示的,这里需要传入前一步生成的列名切片,然后直接加上行索引来生成名称框,就无需每次分配内存
func getColumnRowName(columnName []byte, rowIndex int) (columnRowName string) {
	l := len(columnName)
	columnName = strconv.AppendInt(columnName, int64(rowIndex), 10)
	columnRowName = string(columnName)
    // 将列名恢复回去
	columnName = columnName[:l]
	return
}

3. 测试用例

代码语言:txt
复制
func TestExportExcel(t *testing.T) {

	t.Run("", func(t *testing.T) {
		headers := []string{"用户名", "性别", "年龄"}
		values := [][]interface{}{
			{"测试", "1", "男"},
			{
				"ggr1", "1", "男",
			},
		}
		f, _ := ExportExcel("sheet1", headers, values)
		err := f.SaveAs("test.xlsx")
		fmt.Sprintln(err)
	})

}

如果是要在web使用, excelize.File提供了一个Write方法,可以将其写入到http.ResponseWriter对象w中,并设置如下响应头部。

代码语言:txt
复制
// 文件名使用英文,否则会中文乱码
fileName = "test.xlsx"
w.Header().Add("Content-Type", "application/octet-stream")
w.Header().Add("Content-Disposition", "attachment; filename="+fileName)
w.Header().Add("Content-Transfer-Encoding", "binary")

4. 踩坑记录

我当时用的excelize 是v2.4.0版本的,这个版本的SheetName参数是大小写敏感不能设置为sheet1,否则你打开文件时会报发生部分错误。排查了好久才定位出来😢。

具体问题已经提交issuer给了excelize的官方了, 官方及时给予了回复并告知会在下一个版本更新,这一点超赞。

image.png
image.png

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 相关库调研
  • 2. 封装代码
  • 3. 测试用例
  • 4. 踩坑记录
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档