|
@@ -2,6 +2,7 @@ package main
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"bufio"
|
|
"bufio"
|
|
|
|
|
+ "flag"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"fwh-toolbox-server/domain"
|
|
"fwh-toolbox-server/domain"
|
|
|
"os"
|
|
"os"
|
|
@@ -12,6 +13,7 @@ import (
|
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/driver/sqlite"
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm"
|
|
|
"gorm.io/gorm/clause"
|
|
"gorm.io/gorm/clause"
|
|
|
|
|
+ "gorm.io/gorm/logger"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
type MacroItem struct {
|
|
type MacroItem struct {
|
|
@@ -22,62 +24,92 @@ type MacroItem struct {
|
|
|
Description string // 描述
|
|
Description string // 描述
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 更新vid_macro.h和pid_macro.h的数据到sqlite
|
|
|
|
|
|
|
+// 由 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() {
|
|
func main() {
|
|
|
- // 打开 GBK 编码的文件
|
|
|
|
|
- vidFile, err := os.Open("./utils/emspara/updateCmd/vid_macro.h")
|
|
|
|
|
|
|
+ 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 {
|
|
if err != nil {
|
|
|
- panic(err)
|
|
|
|
|
|
|
+ return 0, 0, err
|
|
|
}
|
|
}
|
|
|
defer vidFile.Close()
|
|
defer vidFile.Close()
|
|
|
|
|
|
|
|
- pidFile, err := os.Open("./utils/emspara/updateCmd/pid_macro.h")
|
|
|
|
|
|
|
+ pidFile, err := os.Open(pidPath)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- panic(err)
|
|
|
|
|
|
|
+ return 0, 0, err
|
|
|
}
|
|
}
|
|
|
defer pidFile.Close()
|
|
defer pidFile.Close()
|
|
|
|
|
|
|
|
- // 打开 SQLite
|
|
|
|
|
- db, err := gorm.Open(sqlite.Open("./utils/emspara/emspara.db"), &gorm.Config{})
|
|
|
|
|
|
|
+ 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 {
|
|
if err != nil {
|
|
|
- panic(err)
|
|
|
|
|
|
|
+ return 0, 0, err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := fillMacros(db, vidFile, pidFile); err != nil {
|
|
|
|
|
+ return 0, 0, err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 自动建表
|
|
|
|
|
- if err := db.AutoMigrate(&domain.ParamsVidMacroModel{}, &domain.ParamsPidMacroModel{}); err != nil {
|
|
|
|
|
- panic(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)
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // 处理vid文件
|
|
|
|
|
- err = parserMacroFile(vidFile, func(line string) error {
|
|
|
|
|
- item, err := parserMacroLine(line)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return err
|
|
|
|
|
- }
|
|
|
|
|
|
|
+func fillMacros(db *gorm.DB, vidFile, pidFile *os.File) error {
|
|
|
|
|
+ if err := db.AutoMigrate(&domain.ParamsVidMacroModel{}, &domain.ParamsPidMacroModel{}); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 非 macro 数据行
|
|
|
|
|
- if item == nil {
|
|
|
|
|
- return nil
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 先解析为列表(同一编号以最后出现的为准),再分表批量写入
|
|
|
|
|
+ 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)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 检查多组数据,如PE、MPPT、APbox2.0等
|
|
|
|
|
- itemList := checkMacroDataGroup(*item)
|
|
|
|
|
- for _, it := range itemList {
|
|
|
|
|
- if it.TotalNo == 0 {
|
|
|
|
|
- // 0无法插入,抛弃
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
- // 插入VID数据
|
|
|
|
|
- db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&domain.ParamsVidMacroModel{ID: it.TotalNo, MacroName: it.MacroName, Description: it.Description})
|
|
|
|
|
|
|
+ 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 nil
|
|
|
|
|
|
|
+ return tx.Clauses(clause.OnConflict{UpdateAll: true}).CreateInBatches(pids, 500).Error
|
|
|
})
|
|
})
|
|
|
- if err != nil {
|
|
|
|
|
- panic(err)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // 处理pid文件
|
|
|
|
|
- err = parserMacroFile(pidFile, func(line string) 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)
|
|
item, err := parserMacroLine(line)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
@@ -88,20 +120,14 @@ func main() {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 检查多组数据,如PE、MPPT、APbox2.0等
|
|
|
|
|
- itemList := checkMacroDataGroup(*item)
|
|
|
|
|
- for _, it := range itemList {
|
|
|
|
|
|
|
+ for _, it := range checkMacroDataGroup(*item) {
|
|
|
if it.TotalNo == 0 {
|
|
if it.TotalNo == 0 {
|
|
|
// 0无法插入,抛弃
|
|
// 0无法插入,抛弃
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
- // 插入VID数据
|
|
|
|
|
- db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&domain.ParamsPidMacroModel{ID: it.TotalNo, MacroName: it.MacroName, Description: it.Description})
|
|
|
|
|
|
|
+ add(it)
|
|
|
}
|
|
}
|
|
|
return nil
|
|
return nil
|
|
|
- })
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- panic(err)
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -113,7 +139,6 @@ func parserMacroFile(file *os.File, fn func(line string) error) error {
|
|
|
for scanner.Scan() {
|
|
for scanner.Scan() {
|
|
|
line := scanner.Text() // 已经是 UTF-8 的字符串
|
|
line := scanner.Text() // 已经是 UTF-8 的字符串
|
|
|
line = strings.TrimSpace(line) // 去掉前后空格、制表符和换行符
|
|
line = strings.TrimSpace(line) // 去掉前后空格、制表符和换行符
|
|
|
- fmt.Println("原始行:", line)
|
|
|
|
|
|
|
|
|
|
if err := fn(line); err != nil {
|
|
if err := fn(line); err != nil {
|
|
|
fmt.Printf("[Warn]\nline: %s\nerror: %s\n", line, err.Error())
|
|
fmt.Printf("[Warn]\nline: %s\nerror: %s\n", line, err.Error())
|
|
@@ -123,6 +148,8 @@ func parserMacroFile(file *os.File, fn func(line string) error) error {
|
|
|
return scanner.Err()
|
|
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) {
|
|
func parserMacroLine(line string) (*MacroItem, error) {
|
|
|
if strings.HasPrefix(line, "VID_") || strings.HasPrefix(line, "PID_") {
|
|
if strings.HasPrefix(line, "VID_") || strings.HasPrefix(line, "PID_") {
|
|
|
// macro 数据
|
|
// macro 数据
|
|
@@ -132,10 +159,10 @@ func parserMacroLine(line string) (*MacroItem, error) {
|
|
|
// 1. ^(\w+), -> 宏名 + 逗号
|
|
// 1. ^(\w+), -> 宏名 + 逗号
|
|
|
// 2. \s*///<\s* -> 注释标记,空格可有可无
|
|
// 2. \s*///<\s* -> 注释标记,空格可有可无
|
|
|
// 3. \((\d+)/(\d+)\) -> 括号中的两个数字
|
|
// 3. \((\d+)/(\d+)\) -> 括号中的两个数字
|
|
|
- // 4. (UINT32|INT32|FLOAT|CHAR\*) -> 类型
|
|
|
|
|
|
|
+ // 4. (UINT32|...|None) -> 类型;头文件中有 UNIT32 笔误,按 UINT32 处理;None 为 *_MAX_NUM 等占位行
|
|
|
// 5. (.*)$ -> 剩余描述
|
|
// 5. (.*)$ -> 剩余描述
|
|
|
- re := regexp.MustCompile(`^(\w+),\s*///<\s*\((\d+)/(\d+)\)\s*(UINT32|INT32|FLOAT|CHAR\*)\s+(.*)$`)
|
|
|
|
|
- matches := re.FindStringSubmatch(line)
|
|
|
|
|
|
|
+ // 宏名与逗号之间允许有空格(头文件中存在 "PID_AWS_4G_SWITCH ," 这样的写法)
|
|
|
|
|
+ matches := macroLineRe.FindStringSubmatch(line)
|
|
|
|
|
|
|
|
if len(matches) == 6 {
|
|
if len(matches) == 6 {
|
|
|
macroName := matches[1]
|
|
macroName := matches[1]
|
|
@@ -143,12 +170,12 @@ func parserMacroLine(line string) (*MacroItem, error) {
|
|
|
totalNoStr := matches[3]
|
|
totalNoStr := matches[3]
|
|
|
typ := matches[4]
|
|
typ := matches[4]
|
|
|
desc := matches[5]
|
|
desc := matches[5]
|
|
|
-
|
|
|
|
|
- fmt.Println("宏名:", macroName)
|
|
|
|
|
- fmt.Println("子序号:", subNoStr)
|
|
|
|
|
- fmt.Println("总序号:", totalNoStr)
|
|
|
|
|
- fmt.Println("类型:", typ)
|
|
|
|
|
- fmt.Println("描述:", desc)
|
|
|
|
|
|
|
+ if typ == "None" {
|
|
|
|
|
+ return nil, nil
|
|
|
|
|
+ }
|
|
|
|
|
+ if typ == "UNIT32" {
|
|
|
|
|
+ typ = "UINT32"
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
subNo, err := strconv.ParseUint(subNoStr, 10, 64)
|
|
subNo, err := strconv.ParseUint(subNoStr, 10, 64)
|
|
|
if err != nil {
|
|
if err != nil {
|