github.com/easysoft/zendata@v0.0.0-20240513203326-705bd5a7fd67/internal/pkg/service/excel.go (about)

     1  package service
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/easysoft/zendata/internal/pkg/domain"
    12  	"github.com/easysoft/zendata/internal/pkg/helper"
    13  
    14  	"github.com/360EntSecGroup-Skylar/excelize/v2"
    15  	consts "github.com/easysoft/zendata/internal/pkg/const"
    16  	fileUtils "github.com/easysoft/zendata/pkg/utils/file"
    17  	i118Utils "github.com/easysoft/zendata/pkg/utils/i118"
    18  	logUtils "github.com/easysoft/zendata/pkg/utils/log"
    19  	"github.com/easysoft/zendata/pkg/utils/vari"
    20  	"github.com/fatih/color"
    21  	"gorm.io/gorm"
    22  )
    23  
    24  type ExcelService struct {
    25  	ExpressionService *ExpressionService `inject:""`
    26  }
    27  
    28  func (s *ExcelService) generateFieldValuesFromExcel(filePath, sheet string, field *domain.DefField, total int) (
    29  	values map[string][]interface{}) {
    30  	values = map[string][]interface{}{}
    31  
    32  	// sql has variable expr
    33  	if filePath == "" || helper.IsSelectExcelWithExpr(*field) {
    34  		return
    35  	}
    36  
    37  	dbName := s.getDbName(filePath)
    38  
    39  	if !fileUtils.IsDir(filePath) { // file
    40  		firstSheet := s.ConvertSingleExcelToSQLiteIfNeeded(dbName, filePath)
    41  		if sheet == "" {
    42  			sheet = firstSheet
    43  		}
    44  	} else { // dir, for article generation only
    45  		s.ConvertWordExcelsToSQLiteIfNeeded(dbName, filePath)
    46  	}
    47  
    48  	list, fieldSelect := s.ReadDataFromSQLite(*field, dbName, sheet, total, filePath)
    49  	// get index list for data retrieve
    50  	numbs := helper.GenerateItems(int64(0), int64(len(list)-1), int64(1), 0, false, 1, "", 0)
    51  
    52  	// get data by index
    53  	index := 0
    54  	for _, numb := range numbs {
    55  		item := list[numb.(int64)%(int64(len(list)))]
    56  
    57  		if index >= consts.MaxNumb {
    58  			break
    59  		}
    60  
    61  		values[fieldSelect] = append(values[fieldSelect], item)
    62  		index = index + 1
    63  	}
    64  
    65  	return
    66  }
    67  
    68  func (s *ExcelService) getDbName(path string) (dbName string) {
    69  	dbName = strings.Replace(path, vari.WorkDir+consts.ResDirData+consts.PthSep, "", -1)
    70  	dbName = strings.Replace(dbName, consts.PthSep, "_", -1)
    71  	dbName = strings.Replace(dbName, ".", "_", -1)
    72  
    73  	return
    74  }
    75  
    76  func (s *ExcelService) ConvertSingleExcelToSQLiteIfNeeded(dbName string, path string) (firstSheet string) {
    77  	excel, err := excelize.OpenFile(path)
    78  	if err != nil {
    79  		logUtils.PrintTo(i118Utils.I118Prt.Sprintf("fail_to_read_file", path))
    80  		return
    81  	}
    82  
    83  	firstSheet = excel.GetSheetList()[0]
    84  	changed, sqlBeforeCompleted := s.isExcelChanged(path)
    85  	if !changed {
    86  		return
    87  	}
    88  
    89  	for _, sheet := range excel.GetSheetList() {
    90  		rows, err := excel.GetRows(sheet)
    91  
    92  		if len(rows) == 0 {
    93  			continue
    94  		}
    95  
    96  		dropTemplate := `DROP TABLE IF EXISTS %s;`
    97  		ddlTemplate := `CREATE TABLE %s (
    98  						%s
    99  					);`
   100  		insertTemplate := "INSERT INTO %s (%s) VALUES %s"
   101  
   102  		colDefine := ""
   103  		colList := ""
   104  		colCount := 0
   105  		index := 0
   106  		for _, col := range rows[0] {
   107  			colCount++
   108  
   109  			val := strings.TrimSpace(col)
   110  			if index > 0 {
   111  				colDefine = colDefine + ",\n"
   112  				colList = colList + ", "
   113  			}
   114  
   115  			colProp := ""
   116  			if val == "seq" {
   117  				colProp = "CHAR (5) PRIMARY KEY ASC UNIQUE"
   118  			} else {
   119  				colProp = "VARCHAR"
   120  			}
   121  			colDefine = "    " + colDefine + "`" + val + "` " + colProp
   122  
   123  			colList = colList + "`" + val + "`"
   124  			index++
   125  		}
   126  
   127  		valList := ""
   128  		for rowIndex, row := range rows {
   129  			if rowIndex == 0 {
   130  				continue
   131  			}
   132  
   133  			if rowIndex > 1 {
   134  				valList = valList + ", "
   135  			}
   136  			valList = valList + "("
   137  
   138  			dataColCount := 0
   139  			for colIndex, colCell := range row {
   140  				if colIndex >= colCount {
   141  					break
   142  				}
   143  
   144  				if colIndex > 0 {
   145  					valList = valList + ", "
   146  				}
   147  				colCell = strings.Replace(colCell, "'", "''", -1)
   148  				valList = valList + "'" + colCell + "'"
   149  
   150  				dataColCount++
   151  			}
   152  
   153  			for dataColCount < colCount {
   154  				valList = valList + ", ''"
   155  				dataColCount++
   156  			}
   157  
   158  			valList = valList + ")"
   159  		}
   160  
   161  		tableName := dbName + "_" + sheet
   162  		dropSql := fmt.Sprintf(dropTemplate, tableName)
   163  		ddl := fmt.Sprintf(ddlTemplate, tableName, colDefine)
   164  		insertSql := fmt.Sprintf(insertTemplate, tableName, colList, valList)
   165  
   166  		err = vari.DB.Exec(dropSql).Error
   167  		if err != nil {
   168  			logUtils.PrintTo(i118Utils.I118Prt.Sprintf("fail_to_drop_table", tableName, err.Error()))
   169  			return
   170  		}
   171  
   172  		err = vari.DB.Exec(ddl).Error
   173  		if err != nil {
   174  			logUtils.PrintTo(i118Utils.I118Prt.Sprintf("fail_to_create_table", tableName, err.Error()))
   175  			return
   176  		}
   177  
   178  		err = vari.DB.Exec(insertSql).Error
   179  		if err != nil {
   180  			logUtils.PrintTo(i118Utils.I118Prt.Sprintf("fail_to_exec_query", insertSql, err.Error()))
   181  			return
   182  		}
   183  
   184  		if changed {
   185  			err = vari.DB.Exec(sqlBeforeCompleted).Error
   186  			if err != nil {
   187  				logUtils.PrintTo(i118Utils.I118Prt.Sprintf("fail_to_exec_query", sqlBeforeCompleted, err.Error()))
   188  			}
   189  		}
   190  	}
   191  
   192  	return
   193  }
   194  
   195  func (s *ExcelService) ConvertWordExcelsToSQLiteIfNeeded(tableName string, dir string) {
   196  	changed, sqlBeforeCompleted := s.isExcelChanged(dir)
   197  	if !changed {
   198  		return
   199  	}
   200  
   201  	files := make([]string, 0)
   202  	fileUtils.GetFilesByExtInDir(dir, ".xlsx", &files)
   203  
   204  	seq := 1
   205  	insertSqls := make([]string, 0)
   206  	ddlFields := make([]string, 0)
   207  	ddlFields = append(ddlFields, "    `词语` VARCHAR DEFAULT ''")
   208  
   209  	colMap := map[string]bool{}
   210  	for _, file := range files {
   211  		s.genImportExcelSqls(file, tableName, &seq, &ddlFields, &insertSqls, &colMap)
   212  	}
   213  
   214  	dropSql := `DROP TABLE IF EXISTS ` + tableName + `;`
   215  	err := vari.DB.Exec(dropSql).Error
   216  	if err != nil {
   217  		log.Println(i118Utils.I118Prt.Sprintf("fail_to_drop_table", tableName, err.Error()))
   218  		return
   219  	}
   220  
   221  	ddlTemplate := "CREATE TABLE " + tableName + "(\n" +
   222  		"\t`seq` CHAR (5) PRIMARY KEY ASC UNIQUE,\n" +
   223  		"%s" +
   224  		"\n);"
   225  	ddlSql := fmt.Sprintf(ddlTemplate, strings.Join(ddlFields, ", \n"))
   226  	err = vari.DB.Exec(ddlSql).Error
   227  	if err != nil {
   228  		log.Println(i118Utils.I118Prt.Sprintf("fail_to_create_table", tableName, err.Error()))
   229  		return
   230  	}
   231  
   232  	insertSql := strings.Join(insertSqls, "\n")
   233  	err = vari.DB.Exec(insertSql).Error
   234  	if err != nil {
   235  		log.Println(i118Utils.I118Prt.Sprintf("fail_to_exec_query", insertSql, err.Error()))
   236  		return
   237  	}
   238  
   239  	if changed {
   240  		err = vari.DB.Exec(sqlBeforeCompleted).Error
   241  		if err != nil {
   242  			logUtils.PrintTo(i118Utils.I118Prt.Sprintf("fail_to_exec_query", sqlBeforeCompleted, err.Error()))
   243  		}
   244  	}
   245  
   246  	return
   247  }
   248  
   249  func (s *ExcelService) ReadDataFromSQLite(field domain.DefField, dbName string, tableName string, total int, filePath string) (
   250  	[]string, string) {
   251  	list := make([]string, 0)
   252  
   253  	fieldSelect := field.Select
   254  	from := dbName
   255  	if tableName != "" {
   256  		from += "_" + tableName
   257  	}
   258  
   259  	where := strings.TrimSpace(field.Where)
   260  	if vari.GlobalVars.DefData.Type == consts.DefTypeArticle {
   261  		if where == "" {
   262  			where = "y"
   263  		}
   264  
   265  		cols := strings.Split(fieldSelect, "-")
   266  		wheres := ""
   267  		for index, col := range cols {
   268  			if index == 0 {
   269  				wheres += fmt.Sprintf("`%s` = '%s'", col, "y")
   270  			} else {
   271  				wheres += " AND "
   272  				wheres += fmt.Sprintf("`%s` = '%s'", col, where)
   273  			}
   274  		}
   275  
   276  		where = wheres
   277  
   278  	} else {
   279  		if where == "" {
   280  			where = "1=1"
   281  		}
   282  	}
   283  
   284  	if field.Rand {
   285  		where += " ORDER BY RANDOM() "
   286  	}
   287  
   288  	if !strings.Contains(where, "LIMIT") {
   289  		if total > consts.MaxNumb {
   290  			total = consts.MaxNumb
   291  		}
   292  		if field.Limit > 0 && total > field.Limit {
   293  			total = field.Limit
   294  		}
   295  
   296  		where = where + fmt.Sprintf(" LIMIT %d", total)
   297  	}
   298  
   299  	colStr := fieldSelect
   300  	if vari.GlobalVars.DefData.Type == consts.DefTypeArticle {
   301  		colStr = "`词语` AS `" + fieldSelect + "`"
   302  	}
   303  
   304  	sqlStr := fmt.Sprintf("SELECT %s FROM `%s` WHERE %s", colStr, from, where)
   305  	rows, err := vari.DB.Raw(sqlStr).Rows()
   306  
   307  	if err != nil {
   308  		logUtils.PrintTo(i118Utils.I118Prt.Sprintf("fail_to_exec_query", "", err.Error()))
   309  		logUtils.PrintToWithColor(i118Utils.I118Prt.Sprintf("pls_check_excel", filePath), color.FgRed)
   310  
   311  		return list, ""
   312  	}
   313  
   314  	defer rows.Close()
   315  	valMapArr := make([]map[string]string, 0)
   316  	columns, err := rows.Columns()
   317  	colNum := len(columns)
   318  
   319  	colIndexToName := map[int]string{}
   320  	for index, col := range columns {
   321  		colIndexToName[index] = col
   322  	}
   323  
   324  	var values = make([]interface{}, colNum)
   325  	for i := range values {
   326  		var itf string
   327  		values[i] = &itf
   328  	}
   329  
   330  	for rows.Next() {
   331  		err = rows.Scan(values...)
   332  		if err != nil {
   333  			logUtils.PrintTo(i118Utils.I118Prt.Sprintf("fail_to_parse_row", err.Error()))
   334  			return list, ""
   335  		}
   336  
   337  		rowMap := map[string]string{}
   338  		for index, v := range values {
   339  			item := v.(*string)
   340  
   341  			rowMap[colIndexToName[index]] = *item
   342  		}
   343  
   344  		valMapArr = append(valMapArr, rowMap)
   345  	}
   346  
   347  	for _, item := range valMapArr {
   348  		idx := 0
   349  		for _, val := range item {
   350  			if idx > 0 {
   351  				break
   352  			}
   353  			list = append(list, val)
   354  			idx++
   355  		}
   356  	}
   357  
   358  	return list, fieldSelect
   359  }
   360  
   361  func (s *ExcelService) genExcelValuesWithExpr(field *domain.DefField, fieldNameToValuesMap map[string][]interface{}) (
   362  	values []interface{}) {
   363  	selects := s.ExpressionService.ReplaceVariableValues(field.Select, fieldNameToValuesMap)
   364  	wheres := s.ExpressionService.ReplaceVariableValues(field.Where, fieldNameToValuesMap)
   365  
   366  	childMapValues := make([][]interface{}, 0)
   367  	for index, slct := range selects {
   368  		temp := *field
   369  		temp.Select = slct
   370  		temp.Where = wheres[index%len(wheres)]
   371  
   372  		resFile, _, sheet := fileUtils.GetResProp(temp.From, temp.FileDir)
   373  
   374  		selectCount := vari.GlobalVars.Total/len(selects) + 1
   375  		mp := s.generateFieldValuesFromExcel(resFile, sheet, &temp, selectCount) // re-generate values
   376  		for _, items := range mp {
   377  			childMapValues = append(childMapValues, items)
   378  		}
   379  	}
   380  	for index := 0; len(values) < vari.GlobalVars.Total; {
   381  		for i := range selects {
   382  			values = append(values, childMapValues[i][index%len(childMapValues[i])])
   383  		}
   384  		index++
   385  	}
   386  
   387  	return
   388  }
   389  
   390  type ExcelChangedResult struct {
   391  	Id         uint
   392  	Path       string
   393  	ChangeTime int64 `gorm:"column:changeTime"`
   394  }
   395  
   396  func (s *ExcelService) isExcelChanged(path string) (changed bool, sqlBeforeCompleted string) {
   397  	if !fileUtils.FileExist(path) {
   398  		return
   399  	}
   400  
   401  	fileChangeTime := time.Time{}.Unix()
   402  	if !fileUtils.IsDir(path) {
   403  		fileChangeTime = s.getFileModTime(path).Unix()
   404  	} else {
   405  		fileChangeTime = s.getDirModTime(path).Unix()
   406  	}
   407  
   408  	sqlStr := fmt.Sprintf("SELECT id, path, changeTime FROM %s "+
   409  		"WHERE path = '%s' "+
   410  		"ORDER BY changeTime DESC "+
   411  		"LIMIT 1;",
   412  		consts.SqliteTrackTable, path)
   413  
   414  	record := ExcelChangedResult{}
   415  	err := vari.DB.Raw(sqlStr).Scan(&record).Error
   416  
   417  	if err != nil && err != gorm.ErrRecordNotFound {
   418  		logUtils.PrintTo(i118Utils.I118Prt.Sprintf("fail_to_exec_query", sqlStr, err.Error()))
   419  
   420  		changed = true
   421  		return
   422  	}
   423  
   424  	found := false
   425  
   426  	if record.Id > 0 { // found
   427  		found = true
   428  
   429  		if path == record.Path && record.ChangeTime < fileChangeTime { // update exist record
   430  			changed = true
   431  		}
   432  	} else { // not found, to add a record
   433  		changed = true
   434  	}
   435  
   436  	if changed {
   437  		if !found {
   438  			sqlBeforeCompleted = fmt.Sprintf("INSERT INTO %s(path, changeTime) VALUES('%s', %d)",
   439  				consts.SqliteTrackTable, path, fileChangeTime)
   440  		} else {
   441  			sqlBeforeCompleted = fmt.Sprintf("UPDATE %s SET changeTime = %d WHERE path = '%s'",
   442  				consts.SqliteTrackTable, fileChangeTime, path)
   443  		}
   444  	}
   445  
   446  	return
   447  }
   448  
   449  func (s *ExcelService) getDirModTime(path string) (dirChangeTime time.Time) {
   450  	files := make([]string, 0)
   451  	fileUtils.GetFilesByExtInDir(path, "", &files)
   452  
   453  	for _, file := range files {
   454  		time := s.getFileModTime(file)
   455  		if dirChangeTime.Unix() < time.Unix() {
   456  			dirChangeTime = time
   457  		}
   458  	}
   459  
   460  	return
   461  }
   462  
   463  func (s *ExcelService) getFileModTime(path string) time.Time {
   464  	f, err := os.Open(path)
   465  	if err != nil {
   466  		log.Println("open file error:" + path)
   467  		return time.Now()
   468  	}
   469  	defer f.Close()
   470  
   471  	fi, err := f.Stat()
   472  	if err != nil {
   473  		log.Println("stat fileinfo error")
   474  		return time.Now()
   475  	}
   476  
   477  	fileChangeTime := fi.ModTime()
   478  
   479  	//timeStr := fileChangeTime.Format("2006-01-02 15:04:05")
   480  	//logUtils.Screen(i118Utils.I118Prt.Sprintf("file_change_time", timeStr))
   481  
   482  	return fileChangeTime
   483  }
   484  
   485  func (s *ExcelService) genImportExcelSqls(filePath, tableName string, seq *int, ddlFields, insertSqls *[]string, colMap *map[string]bool) {
   486  	excel, err := excelize.OpenFile(filePath)
   487  	if err != nil {
   488  		log.Println("fail to read file " + filePath + ", error: " + err.Error())
   489  		return
   490  	}
   491  
   492  	fileName := fileUtils.GetFileName(filePath)
   493  	fileName = strings.TrimSuffix(fileName, "词库")
   494  
   495  	colPrefix := fileName // stringUtils.GetPinyin(fileName)
   496  	*ddlFields = append(*ddlFields, "    `"+colPrefix+"` VARCHAR DEFAULT ''")
   497  
   498  	for sheetIndex, sheet := range excel.GetSheetList() {
   499  		rows, _ := excel.GetRows(sheet)
   500  		if len(rows) == 0 {
   501  			continue
   502  		}
   503  
   504  		colDefine := ""
   505  		colList := make([]string, 0)
   506  
   507  		colCount := 0
   508  		index := 0
   509  		// gen col list for ddl and insert cols
   510  		for colIndex, col := range rows[0] {
   511  			val := strings.TrimSpace(col)
   512  
   513  			if sheetIndex == 0 && val == "" {
   514  				break
   515  			}
   516  			colCount++
   517  
   518  			colList = append(colList, val)
   519  
   520  			colNames := val
   521  			colNameArr := strings.Split(colNames, "-")
   522  			for _, colName := range colNameArr {
   523  				if (*colMap)[colName] == false {
   524  					colType := "VARCHAR"
   525  					colDefine = "    " + "`" + colName + "` " + colType + " DEFAULT ''"
   526  
   527  					if colIndex == 0 {
   528  						colName = "词语"
   529  					} else { // first already added
   530  						*ddlFields = append(*ddlFields, colDefine)
   531  					}
   532  
   533  					(*colMap)[colName] = true
   534  				}
   535  			}
   536  
   537  			index++
   538  		}
   539  
   540  		insertTemplate := "INSERT INTO `" + tableName + "` (%s) VALUES (%s);"
   541  		// gen values for insert
   542  		for rowIndex, row := range rows {
   543  			if rowIndex == 0 { // ignore title line
   544  				continue
   545  			}
   546  
   547  			record := map[string]interface{}{}
   548  			record[colPrefix] = "y"
   549  			record["seq"] = *seq
   550  			*seq += 1
   551  
   552  			for colIndex, col := range row {
   553  				if colIndex >= len(colList) {
   554  					break
   555  				}
   556  
   557  				colNames := colList[colIndex]
   558  
   559  				val := strings.ToLower(strings.TrimSpace(col))
   560  				if val == "" {
   561  					continue
   562  				}
   563  
   564  				if colIndex == 0 { // word
   565  					record["词语"] = val
   566  				} else {
   567  					if val == "y" || val == "b" || val == "f" || val == "m" {
   568  						val = "y"
   569  					} else {
   570  						val = ""
   571  					}
   572  
   573  					colNameArr := strings.Split(colNames, "-")
   574  					for _, colName := range colNameArr {
   575  						record[colName] = val
   576  					}
   577  				}
   578  			}
   579  
   580  			cols := make([]string, 0)
   581  			vals := make([]string, 0)
   582  
   583  			for key, val := range record {
   584  				cols = append(cols, "`"+key+"`")
   585  
   586  				valStr := ""
   587  				switch val.(type) {
   588  				case int:
   589  					valStr = strconv.Itoa(val.(int))
   590  				default:
   591  					valStr = "'" + val.(string) + "'"
   592  				}
   593  
   594  				vals = append(vals, valStr)
   595  			}
   596  
   597  			insertSql := fmt.Sprintf(insertTemplate, strings.Join(cols, ","), strings.Join(vals, ","))
   598  
   599  			*insertSqls = append(*insertSqls, insertSql)
   600  		}
   601  	}
   602  }