package main import ( "bufio" "context" "crypto/subtle" "embed" "flag" "fmt" "io/fs" "net/http" "os" "os/exec" "runtime" "strings" "time" "bj_power_mes/common/httpx" xlog "bj_power_mes/common/logx" "bj_power_mes/internal/config" "bj_power_mes/internal/db" "bj_power_mes/internal/handler" "bj_power_mes/internal/logic" "bj_power_mes/internal/svc" "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/rest" ) var configFile = flag.String("f", "etc/bj_power_mes-api.yaml", "the config file") //go:embed public var public embed.FS // openBrowser 调用系统默认浏览器打开 url func openBrowser(url string) { var cmd *exec.Cmd switch runtime.GOOS { case "windows": cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url) case "darwin": cmd = exec.Command("open", url) default: cmd = exec.Command("xdg-open", url) } _ = cmd.Start() } // promptPassword 从终端读取一行输入(不隐藏回显,用于运维终端操作) func promptPassword(msg string) string { fmt.Print(msg) line, _ := bufio.NewReader(os.Stdin).ReadString('\n') return strings.TrimRight(line, "\r\n") } // adminGate 管理员密码门禁:敏感命令行操作必须输入与配置一致的密码才放行; // 校验失败会记录日志并终止,绝不继续执行。 func adminGate(c config.Config) bool { pwd := promptPassword("请输入管理员密码以执行 [" + flag.Arg(0) + "]: ") if subtle.ConstantTimeCompare([]byte(pwd), []byte(c.Cli.AdminPassword)) != 1 { logx.Errorf("敏感命令 [%s] 因管理员密码校验失败被终止", flag.Arg(0)) fmt.Println("管理员密码不正确,已终止该操作。") return false } logx.Infof("敏感命令 [%s] 已通过管理员密码校验", flag.Arg(0)) return true } func main() { flag.Parse() var c config.Config conf.MustLoad(*configFile, &c) // 日志按天存储:logs/bj_power_mes-2026-01-02.log,保留 30 天 logger := xlog.NewLogger(c.Logger) xlog.SetDefault(logger) logx.SetWriter(logger) logx.DisableStat() defer func() { _ = logger.Close() }() // 命令行子命令:DDL/数据操作一律显式触发 + 管理员密码门禁,不随服务启动自动改库 cmd := flag.Arg(0) switch cmd { case "migrate": if !adminGate(c) { os.Exit(1) } if err := db.EnsureDB(c.Database); err != nil { logx.Errorf("ensure database failed: %v", err) os.Exit(1) } if err := db.AutoMigrate(c.Database); err != nil { logx.Errorf("migrate failed: %v", err) os.Exit(1) } logx.Info("migrate 完成:数据库结构迁移成功(只增/补列,未删除任何数据)。") fmt.Println("migrate: 数据库结构迁移成功(只增/补列,未删除任何数据)。") return case "reset-all": fmt.Println("reset-all: 即将清空并重建全部表,此操作不可恢复!") if !adminGate(c) { os.Exit(1) } if err := db.EnsureDB(c.Database); err != nil { logx.Errorf("ensure database failed: %v", err) os.Exit(1) } if err := db.DropAllTables(c.Database); err != nil { logx.Errorf("reset-all drop failed: %v", err) os.Exit(1) } entClient, _ := db.MustNewDB(c.Database) defer entClient.Close() if err := entClient.Schema.Create(context.Background()); err != nil { logx.Errorf("reset-all create failed: %v", err) os.Exit(1) } if err := logic.New(&svc.ServiceContext{EntClient: entClient}).Seed(context.Background()); err != nil { logx.Errorf("reset-all seed failed: %v", err) os.Exit(1) } logx.Info("reset-all 完成:已清空并重建全部表,并写入基础数据。") fmt.Println("reset-all: 已清空并重建全部表,并写入基础数据。") return case "seed": if !adminGate(c) { os.Exit(1) } if err := db.EnsureDB(c.Database); err != nil { logx.Errorf("ensure database failed: %v", err) os.Exit(1) } entClient, sqlDB := db.MustNewDB(c.Database) defer entClient.Close() defer sqlDB.Close() if err := entClient.Schema.Create(context.Background()); err != nil { logx.Errorf("seed ensure schema failed: %v", err) os.Exit(1) } if err := logic.New(&svc.ServiceContext{EntClient: entClient}).Seed(context.Background()); err != nil { logx.Errorf("seed failed: %v", err) os.Exit(1) } logx.Info("seed 完成:基础数据已对齐。") fmt.Println("seed: 基础数据已对齐。") return case "", "serve": // 正常启动服务 default: fmt.Println("未知命令: " + cmd + ";可用命令: migrate / reset-all / seed / serve") os.Exit(1) } svcCtx := svc.NewServiceContext(c) // 启动时对齐基础数据(角色、admin 账号、菜单、工序模板、工位派工);本次不再自动建表 if err := logic.New(svcCtx).Seed(context.Background()); err != nil { logx.Errorf("Seed failed: %v", err) } // 内嵌前端(public/)SPA sub, _ := fs.Sub(public, "public") spaHandler := httpx.NewNotFoundHandler(http.FS(sub)) server := rest.MustNewServer( c.RestConf, rest.WithCors("*"), rest.WithNotFoundHandler(http.HandlerFunc(spaHandler.ServeHTTP)), ) defer server.Stop() // 鉴权/系统管理 + 生产业务 + 内部 API/看板 handler.RegisterRoutes(server, svcCtx) handler.RegisterProductionRoutes(server, svcCtx) fmt.Printf("Starting bj_power_mes server at %s:%d...\n", c.Host, c.Port) logx.Infof("Starting bj_power_mes server at %s:%d...", c.Host, c.Port) // 服务起来后自动打开浏览器访问前端 host := c.Host if host == "0.0.0.0" || host == "" { host = "127.0.0.1" } go func() { time.Sleep(time.Second) openBrowser(fmt.Sprintf("http://%s:%d", host, c.Port)) }() server.Start() }