fix: rebuild MES as compilable go-zero+ent backend (renamed bj_power_mes), restore 3 workstation projects from pristine original, align naming; all Go projects go build clean
This commit is contained in:
+121
-90
@@ -1,8 +1,7 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"flag"
|
||||
@@ -11,17 +10,19 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"bj_power_mes/internal/handler"
|
||||
"bj_power_mes/internal/middleware"
|
||||
"bj_power_mes/internal/upload"
|
||||
|
||||
xhttp "bj_power_mes/common/httpx"
|
||||
xlog "bj_power_mes/common/logx"
|
||||
"bj_power_mes/constants"
|
||||
"bj_power_mes/ent/workorder"
|
||||
"bj_power_mes/internal/config"
|
||||
"bj_power_mes/internal/svc"
|
||||
|
||||
@@ -35,13 +36,28 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const AppName = "bj_power_mes"
|
||||
//go:generate go install -v github.com/josephspurrier/goversioninfo/cmd/goversioninfo
|
||||
//go:generate goversioninfo -icon=app.ico -manifest=app.manifest -64 -o=app.syso
|
||||
|
||||
const AppName = "back_cover_pms"
|
||||
|
||||
var configFile = flag.String("f", "etc/bj_power_mes-api.yaml", "the config file")
|
||||
|
||||
var AppPath, _ = filepath.Abs(os.Args[0])
|
||||
// AppPath 优先用 os.Executable(),正确处理通过快捷方式 / PATH 启动的情况
|
||||
// 开机自启时 os.Args[0] 可能不是绝对路径
|
||||
var AppPath = func() string {
|
||||
if exe, err := os.Executable(); err == nil {
|
||||
if abs, err := filepath.Abs(exe); err == nil {
|
||||
return abs
|
||||
}
|
||||
}
|
||||
abs, _ := filepath.Abs(os.Args[0])
|
||||
return abs
|
||||
}()
|
||||
|
||||
var baseUrl string
|
||||
var svcCtx *svc.ServiceContext
|
||||
var server *rest.Server
|
||||
|
||||
//go:embed public
|
||||
var public embed.FS
|
||||
@@ -50,9 +66,18 @@ var public embed.FS
|
||||
var icon []byte
|
||||
|
||||
func main() {
|
||||
// 切换工作目录到 exe 所在目录
|
||||
// 开机自启时 CWD 默认是 system32,会导致 etc/bj_power_mes-api.yaml / logs 等相对路径失效
|
||||
if exe, err := os.Executable(); err == nil {
|
||||
if dir := filepath.Dir(exe); dir != "" {
|
||||
_ = os.Chdir(dir)
|
||||
}
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// 业务影�:所有时间显示使用北京时� carbon.SetDefault(carbon.Default{
|
||||
// carbon 默认配置
|
||||
carbon.SetDefault(carbon.Default{
|
||||
Layout: carbon.DateTimeLayout,
|
||||
Timezone: carbon.PRC,
|
||||
Locale: "zh-CN",
|
||||
@@ -61,13 +86,13 @@ func main() {
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(*configFile, &c)
|
||||
|
||||
baseUrl = fmt.Sprintf("http://127.0.0.1:%d", c.Port)
|
||||
|
||||
logger := xlog.NewLogger(c.Logger)
|
||||
xlog.SetDefault(logger)
|
||||
logx.SetWriter(logger)
|
||||
|
||||
// 业务影å“�:防æ¢å�Œæ—¶å�¯åŠ¨å¤šä¸ªå®žä¾‹ï¼Œé�¿å…�端å�£å†²çª�å’?PLC 连接冲çª�
|
||||
mutex, err := createMutex(AppName)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrAlreadyExists) {
|
||||
@@ -79,81 +104,113 @@ func main() {
|
||||
}
|
||||
defer procCloseHandle.Call(mutex)
|
||||
|
||||
// 业务影�:首次�行时自动创建桌�和开始��快�方� createShortcuts()
|
||||
svcCtx = svc.NewServiceContext(c)
|
||||
|
||||
// 业务影å“�:自动写入注册表开机å�¯åŠ¨ï¼Œæ— éœ€ç”¨æˆ·æ‰‹åŠ¨å‹¾é€? if err := setAutoStart(AppName, AppPath); err != nil {
|
||||
slog.Warn("开机�动设置失� " + err.Error())
|
||||
sub, _ := fs.Sub(public, "public")
|
||||
spaHandler := xhttp.NewNotFoundHandler(http.FS(sub))
|
||||
|
||||
// 静态文件:检测图片
|
||||
uploadDir := svcCtx.Config.Upload.Dir
|
||||
if uploadDir == "" {
|
||||
uploadDir = "./uploads"
|
||||
}
|
||||
uploadFS := http.Dir(uploadDir)
|
||||
|
||||
// 业务影�:�始化数�库连接�PLC 连接
|
||||
svcCtx := svc.NewServiceContext(c)
|
||||
notFoundHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/uploads/") {
|
||||
http.StripPrefix("/uploads/", http.FileServer(uploadFS)).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
spaHandler.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
spaHandler := xhttp.NewNotFoundHandler(http.FS(mustSub(public, "public")))
|
||||
|
||||
server := rest.MustNewServer(
|
||||
server = rest.MustNewServer(
|
||||
c.RestConf,
|
||||
rest.WithCors("*"),
|
||||
rest.WithNotFoundHandler(spaHandler),
|
||||
rest.WithNotFoundHandler(notFoundHandler),
|
||||
)
|
||||
logx.DisableStat()
|
||||
logx.SetWriter(logger)
|
||||
logx.SetWriter(logger) //重新设置日志输出
|
||||
|
||||
defer server.Stop()
|
||||
|
||||
//server.Use(middleware.CheckPermission(svcCtx))
|
||||
|
||||
handler.RegisterHandlers(server, svcCtx)
|
||||
|
||||
// 写æ“�作审计ä¸é—´ä»¶ï¼ˆå…œåº•覆盖系统管ç�†ç±» CRUD ç‰æ‰€æœ‰å†™æŽ¥å�£ï¼? server.Use(middleware.WriteAudit(svcCtx))
|
||||
// 注册 SSE 路由
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/sse",
|
||||
Handler: svcCtx.SSEHandler.Serve,
|
||||
}, rest.WithTimeout(time.Hour*60))
|
||||
|
||||
// 业务影å“�:统一错误å“�åº”æ ¼å¼�,å‰�端ä¸�用处ç�†å¤šç§�é”™è¯¯æ ¼å¼? httpx.SetValidator(xhttp.NewValidator())
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost,
|
||||
Path: "//document/upload",
|
||||
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
upload.HandleInspectionUpload(svcCtx, w, r)
|
||||
},
|
||||
}, rest.WithMaxBytes(10*1024*1024))
|
||||
|
||||
// 设置自定义 Validator
|
||||
httpx.SetValidator(xhttp.NewValidator())
|
||||
// 设置自定义返回处理
|
||||
httpx.SetOkHandler(xhttp.OkJsonCtx)
|
||||
// 设置自定义错误处理
|
||||
httpx.SetErrorHandlerCtx(xhttp.ErrorCtx)
|
||||
|
||||
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
|
||||
slog.Info(fmt.Sprintf("Starting server at %s:%d...", c.Host, c.Port))
|
||||
|
||||
go upload.StartCleanup(svcCtx)
|
||||
go server.Start()
|
||||
|
||||
// ç‰å¾…æœ�务器å�¯åŠ¨å®Œæˆ�å�Žå†�打开æµ�览器,é�¿å…� 404
|
||||
go func() {
|
||||
for i := 0; i < 50; i++ {
|
||||
resp, err := http.Get(baseUrl)
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
_ = browser.OpenURL(baseUrl)
|
||||
return
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
slog.Warn("server �动超时,跳过自动打开�览�)
|
||||
}()
|
||||
_ = browser.OpenURL(baseUrl)
|
||||
|
||||
// 业务影å“�:Windows å�Žå�°è¿�è¡Œï¼Œé€šè¿‡æ‰˜ç›˜å›¾æ ‡æŽ§åˆ¶é€€å‡?打开网页
|
||||
// 启动系统托盘
|
||||
systray.Run(onReady, func() {
|
||||
slog.Info("onExit")
|
||||
if server != nil {
|
||||
server.Stop()
|
||||
}
|
||||
os.Exit(0)
|
||||
})
|
||||
}
|
||||
|
||||
func mustSub(fsFS embed.FS, dir string) fs.FS {
|
||||
sub, err := fs.Sub(fsFS, dir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
func checkCanShutdown() bool {
|
||||
if svcCtx == nil {
|
||||
return true
|
||||
}
|
||||
return sub
|
||||
count, err := svcCtx.EntClient.WorkOrder.Query().
|
||||
Where(workorder.StatusIn(constants.WorkOrderStatus_InProgress, constants.WorkOrderStatus_Pausing)).
|
||||
Count(context.Background())
|
||||
if err != nil {
|
||||
slog.Warn("check shutdown: query failed", "error", err)
|
||||
return true
|
||||
}
|
||||
if count > 0 {
|
||||
MessageBox("警告", fmt.Sprintf("当前有 %d 个工单正在执行(IN_PROGRESS/PAUSING),请先暂停或完成工单后再退出程序。", count))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// onReady 系统托盘:用户�打开网页�切�开机自�(需管�员��)�退出程�func onReady() {
|
||||
func onReady() {
|
||||
systray.SetIcon(icon)
|
||||
systray.SetTemplateIcon(icon, icon)
|
||||
systray.SetTitle("北京电力管控系统")
|
||||
systray.SetTooltip("北京电力管控系统")
|
||||
|
||||
systray.SetTitle("单元管控系统")
|
||||
systray.SetTooltip("单元管控系统")
|
||||
mOpen := systray.AddMenuItem("打开管理页面", "")
|
||||
systray.AddSeparator()
|
||||
mAutoStart := systray.AddMenuItem("开机��, "")
|
||||
mQuit := systray.AddMenuItem("退�, "退出��)
|
||||
|
||||
// 业务影�:读�注册表��上次的开机�动设置状� if isAutoStartEnabled(AppName) {
|
||||
mAutoStart := systray.AddMenuItem("开机启动", "")
|
||||
if isAutoStartEnabled(AppName) {
|
||||
mAutoStart.Check()
|
||||
} else {
|
||||
mAutoStart.Uncheck()
|
||||
}
|
||||
mQuit := systray.AddMenuItem("退出", "退出服务")
|
||||
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
@@ -163,11 +220,16 @@ func mustSub(fsFS embed.FS, dir string) fs.FS {
|
||||
select {
|
||||
case s := <-signalChan:
|
||||
slog.Info("receive signal: " + s.String())
|
||||
systray.Quit()
|
||||
if checkCanShutdown() {
|
||||
systray.Quit()
|
||||
}
|
||||
return
|
||||
case <-mQuit.ClickedCh:
|
||||
systray.Quit()
|
||||
return
|
||||
if checkCanShutdown() {
|
||||
systray.Quit()
|
||||
return
|
||||
}
|
||||
// 不能退出时继续监听托盘菜单,否则再次右键点退出没反应
|
||||
case <-mOpen.ClickedCh:
|
||||
err := browser.OpenURL(baseUrl)
|
||||
if err != nil {
|
||||
@@ -175,64 +237,33 @@ func mustSub(fsFS embed.FS, dir string) fs.FS {
|
||||
}
|
||||
case <-mAutoStart.ClickedCh:
|
||||
if mAutoStart.Checked() {
|
||||
if err := removeAutoStart(AppName); err != nil {
|
||||
err := removeAutoStart(AppName)
|
||||
if err != nil {
|
||||
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
||||
MessageBox("错误", "请用管ç�†å‘˜æ�ƒé™�è¿�行æ¤ç¨‹åº�ã€?)
|
||||
MessageBox("错误", "请用管理员权限运行此程序。")
|
||||
} else {
|
||||
MessageBox("错误", err.Error())
|
||||
}
|
||||
slog.Error("设置失败�" + err.Error())
|
||||
slog.Error("设置失败: " + err.Error())
|
||||
} else {
|
||||
mAutoStart.Uncheck()
|
||||
slog.Info("已��功�消自动�行�)
|
||||
slog.Info("已经成功取消自动运行。")
|
||||
}
|
||||
} else {
|
||||
if err := setAutoStart(AppName, AppPath); err != nil {
|
||||
err := setAutoStart(AppName, AppPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
||||
MessageBox("错误", "请用管ç�†å‘˜æ�ƒé™�è¿�行æ¤ç¨‹åº�ã€?)
|
||||
MessageBox("错误", "请用管理员权限运行此程序。")
|
||||
} else {
|
||||
MessageBox("错误", err.Error())
|
||||
slog.Error("设置失败: " + err.Error())
|
||||
}
|
||||
slog.Error("设置失败�" + err.Error())
|
||||
} else {
|
||||
mAutoStart.Check()
|
||||
slog.Info("已��功设置为自动�行�)
|
||||
slog.Info("已经成功设置为自动运行。")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// createShortcuts 首次è¿�行时自动创建桌é�¢å’Œå¼€å§‹è�œå�•å¿«æ�·æ–¹å¼�ã€?// 如果快æ�·æ–¹å¼�å·²å˜åœ¨åˆ™è·³è¿‡ï¼Œåˆ›å»ºå¤±è´¥ä¸�阻塞程åº�å�¯åЍã€?func createShortcuts() {
|
||||
shortcutName := "北京电力管控系统"
|
||||
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
exeDir := filepath.Dir(exePath)
|
||||
iconPath := filepath.Join(exeDir, "app.ico")
|
||||
|
||||
// 桌�快�方�
|
||||
desktopDir, _ := os.UserHomeDir()
|
||||
desktopPath := filepath.Join(desktopDir, "Desktop", shortcutName+".lnk")
|
||||
makeShortcut(desktopPath, exePath, exeDir, iconPath)
|
||||
|
||||
// 开始��快�方� startMenuPath := filepath.Join(os.Getenv("APPDATA"),
|
||||
"Microsoft", "Windows", "Start Menu", "Programs", shortcutName+".lnk")
|
||||
makeShortcut(startMenuPath, exePath, exeDir, iconPath)
|
||||
}
|
||||
|
||||
// makeShortcut 通过 PowerShell 创建 .lnk å¿«æ�·æ–¹å¼�ã€?// 如果文件已å˜åœ¨åˆ™è·³è¿‡ï¼Œåˆ›å»ºå¤±è´¥ä¸�阻塞ã€?func makeShortcut(shortcutPath, targetPath, workDir, iconPath string) {
|
||||
if _, err := os.Stat(shortcutPath); err == nil {
|
||||
return // å·²å˜åœ¨ï¼Œè·³è¿‡
|
||||
}
|
||||
|
||||
script := fmt.Sprintf(
|
||||
`$s=(New-Object -ComObject WScript.Shell).CreateShortcut('%s');$s.TargetPath='%s';$s.WorkingDirectory='%s';$s.IconLocation='%s';$s.Save()`,
|
||||
shortcutPath, targetPath, workDir, iconPath,
|
||||
)
|
||||
|
||||
cmd := exec.Command("powershell", "-ExecutionPolicy", "Bypass", "-NoProfile", "-Command", script)
|
||||
_ = cmd.Run() // 创建失败�阻塞��}
|
||||
Reference in New Issue
Block a user