fonchain-artistinfo/pkg/util/excel/excelInter.go

254 lines
6.4 KiB
Go
Raw Normal View History

// Package excel -----------------------------
// @file : templateInter.go
// @author : JJXu
// @contact : wavingbear@163.com
// @time : 2022/7/23 15:34
// -------------------------------------------
package excel
import (
"errors"
"fmt"
"github.com/xuri/excelize/v2"
"io"
"log"
"path/filepath"
"reflect"
"sync"
)
var (
ErrSheetNotExist = errors.New("sheet does not exist")
ErrSheetDataFrormat = errors.New("sheet data format must be slice or struct")
//ErrSheetNameNotInTemplate = errors.New("sheet name not in emailTemplate")
)
//======================================================================================================================
// Sheet define
// 抽象工作簿
type Sheet interface {
GetData() any
SheetName() string
}
// 实例化工作簿
func NewSheet(sheetName string, datas any) Sheet {
return &newSheetDefine{
Datas: datas,
Name: sheetName,
}
}
// 定义一个能够通用的工作簿结构此结构必须遵循Sheet接口规范
type newSheetDefine struct {
Datas any
Name string
}
func (s *newSheetDefine) GetData() any {
return s.Datas
}
func (s *newSheetDefine) SheetName() string {
return s.Name
}
//======================================================================================================================
// Sheet define
// WriteToExcel 通过模板文件写入数据并另存为
// param fileName : 文件名
// param filesSuffix : 文件后缀名生成函数
// param fileRoot : 导出目录
// param templatePath : 模板文件路径
// param sheets : Sheet类型的数据类型为[]Sheet
// return path : 文件路径
// return exfileName : 导出后的文件名
// return err
func WriteToExcel(fileName string, fileRoot string, templatePath string, sheets ...Sheet) (path string, exFileName string, err error) {
var exc *Excel
exc, err = NewExcelTemplate(fileName, fileRoot, templatePath, sheets...)
if err != nil {
return
}
return exc.WriteToFile()
}
// ReadDataFromExcel 从excel文件读取数据
func ReadDataFromExcel(filepath string, sheetName string, handler func(rowIndex int, rows []string)) error {
ex := Excel{OriginFilePath: filepath}
return ex.ReadSheetData(sheetName, handler)
}
// ReadDataFromBytes 从io口读取数据用户http上传的附件
func ReadDataFromBytes(file io.Reader, sheetName string, handler func(rowIndex int, rows []string)) error {
exce, err := excelize.OpenReader(file)
if err != nil {
return err
}
var ex = Excel{ex: exce}
return ex.ReadSheetData(sheetName, handler)
}
// 新建工作表实例
func NewExcelTemplate(fileName string, fileRoot string, templatePath string, sheets ...Sheet) (exc *Excel, err error) {
exc = &Excel{
SaveRoot: fileRoot,
SaveName: fileName,
OriginFilePath: templatePath,
rwLock: sync.RWMutex{},
}
if sheets != nil {
err = exc.AddSheets(sheets...)
if err != nil {
return
}
}
return exc, nil
}
type Excel struct {
ex *excelize.File
SaveRoot string
SaveName string
OriginFilePath string
Sheets map[string]Sheet
rwLock sync.RWMutex
Opts []Option
}
// UseOption 使用可选项
func (s *Excel) UseOption(opts ...Option) {
if opts != nil {
s.Opts = append(s.Opts, opts...)
}
}
// 添加工作簿
// 注意如果添加相同的工作簿,之前的会被覆盖
func (s *Excel) AddSheets(sheets ...Sheet) (err error) {
if s.Sheets == nil {
s.Sheets = make(map[string]Sheet, 0)
}
for _, sheet := range sheets {
var sheetName = sheet.SheetName()
s.Sheets[sheetName] = sheet
}
return
}
// 删除工作簿
func (s *Excel) DeleteSheets(sheetName string) error {
if s.Sheets == nil {
return nil
} else if s.Sheets[sheetName] != nil {
delete(s.Sheets, sheetName)
} else {
return ErrSheetNotExist
}
return nil
}
// 读取工作簿
func (s *Excel) ReadSheetData(sheetName string, handler func(rowIndex int, rows []string)) (err error) {
if s.ex == nil {
s.ex, err = excelize.OpenFile(s.OriginFilePath)
if err != nil {
return
}
}
datas, err := s.ex.GetRows(sheetName)
for i, rows := range datas {
handler(i, rows)
}
return nil
}
// 写入到文件
func (s *Excel) WriteToFile() (path string, fileName string, err error) {
if s.ex == nil {
s.ex, err = excelize.OpenFile(s.OriginFilePath)
if err != nil {
return
}
}
if s.Opts != nil {
for _, opt := range s.Opts {
opt(s)
}
}
//插入数据
var cellNameList []string
for sheetName, st := range s.Sheets {
var firstRow = s.getFirstEmptyRowIndex(s.ex, sheetName)
var SheetData = reflect.ValueOf(st.GetData())
var SheetType = reflect.TypeOf(st.GetData())
switch SheetData.Kind() {
case reflect.Slice:
cellNameList = s.getJsonFieldList(SheetType)
var rowLen = SheetData.Len()
for i := 0; i < rowLen; i++ {
var dataMap = s.dataToMap(SheetData.Index(i), SheetType)
for column, v := range cellNameList {
var axis = GetCellIndex(i+firstRow+1, column+1)
err = s.ex.SetCellValue(sheetName, axis, dataMap[v])
if err != nil {
fmt.Println(err.Error())
}
}
}
case reflect.Struct:
cellNameList = s.getJsonFieldList(SheetType)
var dataMap = s.dataToMap(SheetData, SheetType)
for column, v := range cellNameList {
var axis = GetCellIndex(firstRow+1, column+1)
err = s.ex.SetCellValue(sheetName, axis, dataMap[v])
if err != nil {
fmt.Println(err.Error())
}
}
default:
return "", "", ErrSheetDataFrormat
}
}
//保存
path = filepath.ToSlash(filepath.Join(s.SaveRoot, s.SaveName))
s.rwLock.Lock()
if err = s.ex.SaveAs(path); err != nil {
log.Println(fmt.Sprintf("save file error :%v", err))
s.rwLock.Unlock()
return
}
s.rwLock.Unlock()
return
}
// getJsonFieldList 获取json字段列表
func (s *Excel) getJsonFieldList(sheetType reflect.Type) (tagList []string) {
t := sheetType.Elem()
for i := 0; i < t.NumField(); i++ {
tagList = append(tagList, t.Field(i).Tag.Get("json"))
}
return
}
// dataToMap 数据转字典
func (s *Excel) dataToMap(sheet reflect.Value, sheetType reflect.Type) (dataMap map[string]any) {
dataMap = make(map[string]any)
t := sheetType.Elem()
for i := 0; i < t.NumField(); i++ {
dataMap[t.Field(i).Tag.Get("json")] = sheet.Field(i).Interface()
}
return dataMap
}
// getFirstEmptyRowIndex 获取首个空行的索引位置
func (s *Excel) getFirstEmptyRowIndex(ex *excelize.File, sheetName string) (index int) {
rows, err := ex.GetRows(sheetName)
if err != nil {
return 1
}
return len(rows)
}