193 lines
5.6 KiB
Go
193 lines
5.6 KiB
Go
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()
|
||
}
|