package main import ( "bufio" "flag" "fmt" "fwh-toolbox-server/domain" "os" "regexp" "strconv" "strings" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/logger" ) type MacroItem struct { MacroName string // 宏名 VID_XXX PID_XXX SubNo uint // 子序号-在本组ID中的序号 TotalNo uint // 总序号-在总内存中的序号 Type string // 数据类型 UINT32 INT32 FLOAT CHAR* Description string // 描述 } // 由 vid_macro.h 和 pid_macro.h 生成 EMS 参数字典 SQLite(每次全新生成,不保留旧数据)。 // 在 service/ 目录下执行:go run ./utils/emspara/updateCmd -out ./emspara.db // 部署时由 cmd/deploy_emspara.sh 调用,生成后拷贝到部署目录,进程启动时加载到 Redis(utils/emspara)。 func main() { vidPath := flag.String("vid", "./utils/emspara/updateCmd/vid_macro.h", "VID 头文件") pidPath := flag.String("pid", "./utils/emspara/updateCmd/pid_macro.h", "PID 头文件") outPath := flag.String("out", "./emspara.db", "输出的 SQLite 文件") flag.Parse() vidCount, pidCount, err := generate(*vidPath, *pidPath, *outPath) if err != nil { fmt.Fprintf(os.Stderr, "生成 EMS 参数字典失败: %v\n", err) os.Exit(1) } fmt.Printf("已生成 %s:VID %d 条,PID %d 条\n", *outPath, vidCount, pidCount) } // generate 先写入同目录的临时文件,全部成功后再改名替换 outPath,失败时不影响已有文件 func generate(vidPath, pidPath, outPath string) (int64, int64, error) { vidFile, err := os.Open(vidPath) if err != nil { return 0, 0, err } defer vidFile.Close() pidFile, err := os.Open(pidPath) if err != nil { return 0, 0, err } defer pidFile.Close() tmpPath := outPath + ".tmp" _ = os.Remove(tmpPath) defer os.Remove(tmpPath) db, err := gorm.Open(sqlite.Open(tmpPath), &gorm.Config{Logger: logger.Discard}) if err != nil { return 0, 0, err } if err := fillMacros(db, vidFile, pidFile); err != nil { return 0, 0, err } var vidCount, pidCount int64 db.Model(&domain.ParamsVidMacroModel{}).Count(&vidCount) db.Model(&domain.ParamsPidMacroModel{}).Count(&pidCount) if sqlDB, err := db.DB(); err == nil { sqlDB.Close() } if vidCount == 0 || pidCount == 0 { return 0, 0, fmt.Errorf("没有解析到参数(VID %d 条,PID %d 条),请检查头文件格式", vidCount, pidCount) } return vidCount, pidCount, os.Rename(tmpPath, outPath) } func fillMacros(db *gorm.DB, vidFile, pidFile *os.File) error { if err := db.AutoMigrate(&domain.ParamsVidMacroModel{}, &domain.ParamsPidMacroModel{}); err != nil { return err } // 先解析为列表(同一编号以最后出现的为准),再分表批量写入 var vids []domain.ParamsVidMacroModel if err := parserMacroFile(vidFile, macroHandler(func(it MacroItem) { vids = append(vids, domain.ParamsVidMacroModel{ID: it.TotalNo, MacroName: it.MacroName, Description: it.Description}) })); err != nil { return fmt.Errorf("解析 VID 头文件: %w", err) } var pids []domain.ParamsPidMacroModel if err := parserMacroFile(pidFile, macroHandler(func(it MacroItem) { pids = append(pids, domain.ParamsPidMacroModel{ID: it.TotalNo, MacroName: it.MacroName, Description: it.Description}) })); err != nil { return fmt.Errorf("解析 PID 头文件: %w", err) } return db.Transaction(func(tx *gorm.DB) error { // 每次写入都从 tx 新建链式调用:复用同一个带 Clauses 的 *gorm.DB 会共用 Statement,后一张表的数据会写进前一张表 if err := tx.Clauses(clause.OnConflict{UpdateAll: true}).CreateInBatches(vids, 500).Error; err != nil { return err } return tx.Clauses(clause.OnConflict{UpdateAll: true}).CreateInBatches(pids, 500).Error }) } // macroHandler 解析一行宏定义,按分组(PE、MPPT、APbox2.0 等)展开后逐个交给 add func macroHandler(add func(it MacroItem)) func(line string) error { return func(line string) error { item, err := parserMacroLine(line) if err != nil { return err } // 非 macro 数据行 if item == nil { return nil } for _, it := range checkMacroDataGroup(*item) { if it.TotalNo == 0 { // 0无法插入,抛弃 continue } add(it) } return nil } } func parserMacroFile(file *os.File, fn func(line string) error) error { // 使用 transform 将 GBK 转成 UTF-8 // reader := transform.NewReader(file, simplifiedchinese.GBK.NewDecoder()) // scanner := bufio.NewScanner(reader) scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() // 已经是 UTF-8 的字符串 line = strings.TrimSpace(line) // 去掉前后空格、制表符和换行符 if err := fn(line); err != nil { fmt.Printf("[Warn]\nline: %s\nerror: %s\n", line, err.Error()) } } return scanner.Err() } var macroLineRe = regexp.MustCompile(`^(\w+)\s*,\s*///<\s*\((\d+)/(\d+)\)\s*(UINT32|UNIT32|INT32|FLOAT|CHAR\*|None)\s+(.*)$`) func parserMacroLine(line string) (*MacroItem, error) { if strings.HasPrefix(line, "VID_") || strings.HasPrefix(line, "PID_") { // macro 数据 // 正则匹配: // ^(\w+),\s*///<\s*\((\d+)/(\d+)\)\s*(\w+)\s+(.*)$ // 解释: // 1. ^(\w+), -> 宏名 + 逗号 // 2. \s*///<\s* -> 注释标记,空格可有可无 // 3. \((\d+)/(\d+)\) -> 括号中的两个数字 // 4. (UINT32|...|None) -> 类型;头文件中有 UNIT32 笔误,按 UINT32 处理;None 为 *_MAX_NUM 等占位行 // 5. (.*)$ -> 剩余描述 // 宏名与逗号之间允许有空格(头文件中存在 "PID_AWS_4G_SWITCH ," 这样的写法) matches := macroLineRe.FindStringSubmatch(line) if len(matches) == 6 { macroName := matches[1] subNoStr := matches[2] totalNoStr := matches[3] typ := matches[4] desc := matches[5] if typ == "None" { return nil, nil } if typ == "UNIT32" { typ = "UINT32" } subNo, err := strconv.ParseUint(subNoStr, 10, 64) if err != nil { return nil, err } totalNo, err := strconv.ParseUint(totalNoStr, 10, 64) if err != nil { return nil, err } return &MacroItem{ MacroName: macroName, SubNo: uint(subNo), TotalNo: uint(totalNo), Type: typ, Description: desc, }, nil } else { return nil, fmt.Errorf("无法匹配该行") } } // 非macro 数据 return nil, nil } func checkMacroDataGroup(item MacroItem) []MacroItem { // 排除_MAX_NUM if strings.HasSuffix(item.MacroName, "_MAX_NUM") { return []MacroItem{} } var subMaxNum uint = 0 var groupNum uint = 1 if strings.HasPrefix(item.MacroName, "VID_PE_") { // VID_PE_ 每组128个数据,共16组 subMaxNum = 128 groupNum = 16 } else if strings.HasPrefix(item.MacroName, "VID_BMS_") { // NOSONAR // VID_BMS_ 每组128个数据,共16组 subMaxNum = 128 groupNum = 16 } else if strings.HasPrefix(item.MacroName, "VID_MPPT_") { // NOSONAR // VID_MPPT_ 每组128个数据,共16组 subMaxNum = 128 groupNum = 16 } else if strings.HasPrefix(item.MacroName, "VID_APBOX20_") { // VID_APBOX20_ 每组256个数据,共4组 subMaxNum = 256 groupNum = 4 } else if strings.HasPrefix(item.MacroName, "PID_APOWER_") { // NOSONAR // PID_APOWER_ 每组128个数据,共16组 subMaxNum = 128 groupNum = 16 } else if strings.HasPrefix(item.MacroName, "PID_APBOX20_") { // NOSONAR // VID_APBOX20_ 每组256个数据,共4组 subMaxNum = 256 groupNum = 4 } if groupNum == 1 { return []MacroItem{item} } var itemList []MacroItem = []MacroItem{} for i := uint(0); i < groupNum; i++ { itemList = append(itemList, MacroItem{ MacroName: fmt.Sprintf("%s_%s", item.MacroName, strconv.FormatUint(uint64(i+1), 10)), SubNo: item.SubNo, TotalNo: item.TotalNo + (i * subMaxNum), Type: item.Type, Description: item.Description, }) } return itemList }