// 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) }