forked from free-lancers/electricity_bill_calc_service
feat(excel):尝试增加Excel通用解析过程。
This commit is contained in:
161
excel/abstract.go
Normal file
161
excel/abstract.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"electricity_bill_calc/utils"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
type ColumnRecognizer struct {
|
||||
Pattern []string
|
||||
Tag string
|
||||
MatchIndex int
|
||||
}
|
||||
|
||||
type ExcelAnalyzer[T any] struct {
|
||||
File *excelize.File
|
||||
ActivedSheet string
|
||||
Regconizers []ColumnRecognizer
|
||||
}
|
||||
|
||||
type ExcelAnalysisError struct {
|
||||
Row int
|
||||
Col int
|
||||
Err error
|
||||
}
|
||||
|
||||
func NewColumnRecognizer(tag string, patterns ...string) ColumnRecognizer {
|
||||
return ColumnRecognizer{
|
||||
Pattern: patterns,
|
||||
Tag: tag,
|
||||
MatchIndex: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ColumnRecognizer) Recognize(cellValue string) bool {
|
||||
for _, p := range r.Pattern {
|
||||
if strings.Contains(cellValue, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewExcelAnalyzer[T any](file io.Reader, recognizers []ColumnRecognizer) (*ExcelAnalyzer[T], error) {
|
||||
excelFile, err := excelize.OpenReader(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sheets := excelFile.GetSheetList()
|
||||
return &ExcelAnalyzer[T]{
|
||||
File: excelFile,
|
||||
ActivedSheet: sheets[0],
|
||||
Regconizers: recognizers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ExcelAnalyzer[T]) AddRecognizer(recognizer ColumnRecognizer) {
|
||||
a.Regconizers = append(a.Regconizers, recognizer)
|
||||
}
|
||||
|
||||
func (a *ExcelAnalyzer[T]) analysisTitleRow(cells []string) {
|
||||
for col, content := range cells {
|
||||
for _, recognizer := range a.Regconizers {
|
||||
if recognizer.Recognize(content) {
|
||||
recognizer.MatchIndex = col
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ExcelAnalyzer[T]) Analysis(bean T) ([]T, []ExcelAnalysisError) {
|
||||
errs := make([]ExcelAnalysisError, 0)
|
||||
|
||||
rows, err := a.File.GetRows(a.ActivedSheet)
|
||||
if err != nil {
|
||||
errs = append(errs, ExcelAnalysisError{Row: 0, Col: 0, Err: err})
|
||||
return make([]T, 0), errs
|
||||
}
|
||||
|
||||
elementType := reflect.TypeOf(bean)
|
||||
collections := reflect.MakeSlice(elementType, 0, 0)
|
||||
for rowIndex, cols := range rows {
|
||||
// 标题行,需要完成识别动作
|
||||
if rowIndex == 0 {
|
||||
a.analysisTitleRow(cols)
|
||||
continue
|
||||
}
|
||||
// 非标题行,创建一个需要收集的目标实例,然后逐识别器开始赋值
|
||||
instance := reflect.New(elementType)
|
||||
for i := 0; i < elementType.NumField(); i++ {
|
||||
field := elementType.Field(i)
|
||||
// 循环目标实例中的字段,与识别器匹配以后,取出指定列的值
|
||||
if alias, ok := field.Tag.Lookup("excel"); ok {
|
||||
for _, recognizer := range a.Regconizers {
|
||||
if alias == recognizer.Tag && recognizer.MatchIndex != -1 {
|
||||
actualField := instance.FieldByName(field.Name)
|
||||
matchValue := cols[recognizer.MatchIndex]
|
||||
switch field.Type.String() {
|
||||
case "string":
|
||||
actualField.Set(reflect.ValueOf(matchValue))
|
||||
case "*string":
|
||||
if len(matchValue) > 0 {
|
||||
actualField.Elem().Set(reflect.ValueOf(&matchValue))
|
||||
}
|
||||
case "decimal.Decimal":
|
||||
decimalValue, err := decimal.NewFromString(matchValue)
|
||||
if err != nil {
|
||||
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: fmt.Errorf("单元格内容应为纯数字内容。%w", err)})
|
||||
actualField.Set(reflect.ValueOf(decimal.Zero))
|
||||
} else {
|
||||
actualField.Set(reflect.ValueOf(decimalValue))
|
||||
}
|
||||
case "decimal.NullDecimal":
|
||||
nullValue := decimal.NewNullDecimal(decimal.Zero)
|
||||
nullValue.Valid = false
|
||||
if len(matchValue) == 0 {
|
||||
actualField.Set(reflect.ValueOf(nullValue))
|
||||
} else {
|
||||
decimalValue, err := decimal.NewFromString(matchValue)
|
||||
if err != nil {
|
||||
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: fmt.Errorf("单元格内容应为纯数字内容。%w", err)})
|
||||
actualField.Set(reflect.ValueOf((nullValue)))
|
||||
} else {
|
||||
actualField.Set(reflect.ValueOf(decimalValue))
|
||||
}
|
||||
}
|
||||
case "int64", "int":
|
||||
if len(matchValue) == 0 {
|
||||
actualField.SetInt(0)
|
||||
} else {
|
||||
v, err := strconv.Atoi(matchValue)
|
||||
if err != nil {
|
||||
errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: fmt.Errorf("单元格内容应为不带小鼠的整数。%w", err)})
|
||||
actualField.SetInt(0)
|
||||
} else {
|
||||
actualField.SetInt(int64(v))
|
||||
}
|
||||
}
|
||||
case "bool":
|
||||
if utils.ContainsInsensitive(matchValue, []string{"是", "yes", "y", "true", "t"}) {
|
||||
actualField.SetBool(true)
|
||||
} else {
|
||||
actualField.SetBool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
collections = reflect.Append(collections, instance.Elem())
|
||||
}
|
||||
|
||||
return collections.Interface().([]T), errs
|
||||
}
|
22
excel/meter_archive.go
Normal file
22
excel/meter_archive.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"electricity_bill_calc/model"
|
||||
"io"
|
||||
)
|
||||
|
||||
var meter04kVExcelRecognizers = []ColumnRecognizer{
|
||||
{Pattern: []string{"表号"}, Tag: "code", MatchIndex: -1},
|
||||
{Pattern: []string{"户名"}, Tag: "name", MatchIndex: -1},
|
||||
{Pattern: []string{"户址"}, Tag: "address", MatchIndex: -1},
|
||||
{Pattern: []string{"联系人"}, Tag: "contact", MatchIndex: -1},
|
||||
{Pattern: []string{"电话"}, Tag: "phone", MatchIndex: -1},
|
||||
{Pattern: []string{"倍率"}, Tag: "ratio", MatchIndex: -1},
|
||||
{Pattern: []string{"序号"}, Tag: "seq", MatchIndex: -1},
|
||||
{Pattern: []string{"公共", "公共设备"}, Tag: "public", MatchIndex: -1},
|
||||
{Pattern: []string{"摊薄"}, Tag: "dilute", MatchIndex: -1},
|
||||
}
|
||||
|
||||
func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.Meter04KV], error) {
|
||||
return NewExcelAnalyzer[model.Meter04KV](file, meter04kVExcelRecognizers)
|
||||
}
|
Reference in New Issue
Block a user