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