重新整理物联网服务,分离业务逻辑。

This commit is contained in:
BBIT-Kai
2026-01-05 11:54:33 +08:00
parent e2982b141d
commit 892cb2494e
53 changed files with 558 additions and 26 deletions
+132
View File
@@ -0,0 +1,132 @@
package main
import (
"flag"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"sentinel/pkg/device"
"sentinel/pkg/log"
"sentinel/pkg/net"
"strconv"
"time"
)
func main() {
// 定义命令行参数
version := flag.String("version", "", "current version of main program")
flag.Parse()
if *version == "" {
// updater 视角:-1 表示“未知版本”,一定触发更新检测
*version = "0"
log.Println("[updater] --version not provided, fallback to -1")
fmt.Println("[updater] 主程序版本号:", *version)
}
deviceID := device.GetDeviceID()
fmt.Printf("[updater] 当前设备id: %s\n", deviceID)
versionInt, err := strconv.Atoi(*version)
if err != nil {
log.Println("[updater] invalid version:", *version)
versionInt = 0
}
if err := RunUpdate(deviceID, versionInt); err != nil {
log.Fatalf("[updater] 更新失败: %v", err)
}
fmt.Println("[updater] 更新程序结束")
}
func RunUpdate(deviceID string, version int) error {
// 1. 检查更新
info, err := api.CheckUpdate(deviceID)
if err != nil {
fmt.Println("[updater] 请求错误,请检查网络")
return err
}
fmt.Println("[updater] 新版本:", info.Version)
fmt.Println("[updater] 新内容:", info.Notes)
fmt.Println("[updater] 下载地址:", info.DownloadURL)
// 获取主程序路径
selfPath, err := os.Executable()
if err != nil {
return err
}
selfDir := filepath.Dir(selfPath)
starter := newMainProgramStarter()
targetExe := filepath.Join(selfDir, starter.GetMainName())
// 2. 对比版本号,没有新版本则直接启动原程序
if info.Version <= version {
fmt.Println("[updater] 暂未发现新版本,启动原程序")
if err := starter.Start(targetExe); err != nil {
return err
}
os.Exit(0)
}
// 3. 有新版本则先备份到 ./tmp/old_app/
backupDir := filepath.Join(selfDir, "tmp", "old_app")
_ = os.MkdirAll(backupDir, 0755)
backupFile := filepath.Join(backupDir, "main_"+strconv.Itoa(version)+".bak")
if err := os.Rename(targetExe, backupFile); err != nil {
fmt.Println("[updater] 备份失败,但继续更新:", err)
}
// 4. 下载新版本到 ./tmp
tmpDir := filepath.Join(selfDir, "tmp")
_ = os.MkdirAll(tmpDir, 0755)
u, err := url.Parse(info.DownloadURL)
if err != nil {
return err
}
base := filepath.Base(u.Path)
ext := filepath.Ext(base)
tmpFile, err := os.CreateTemp(tmpDir, "main_*"+ext)
if err != nil {
return err
}
defer tmpFile.Close()
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Get(info.DownloadURL)
if err != nil {
return err
}
defer resp.Body.Close()
if _, err := io.Copy(tmpFile, resp.Body); err != nil {
return err
}
// 5. 重命名新文件到 ./main.exe
tmpFile.Close() // 关闭临时文件才能重命名
maxRetry := 20
for i := 0; i < maxRetry; i++ {
err := os.Rename(tmpFile.Name(), targetExe)
if err == nil {
break
}
fmt.Println("[updater] 文件被占用,等待 500ms 再尝试...")
time.Sleep(500 * time.Millisecond)
if i == maxRetry-1 {
return fmt.Errorf("替换失败: %w", err)
}
}
// 6. 启动主程序,同时完全退出自己
if err := starter.Start(targetExe); err != nil {
return err
}
fmt.Printf("[updater] 更新完成,新程序已启动,退出更新程序")
os.Exit(0)
return nil
}
+7
View File
@@ -0,0 +1,7 @@
package main
import "sentinel/pkg/platform"
func newMainProgramStarter() platform.MainProgramStarter {
return &mainProgramStarter{}
}
+27
View File
@@ -0,0 +1,27 @@
//go:build linux
// +build linux
package main
import (
"os"
"os/exec"
)
type mainProgramStarter struct{}
func (s *mainProgramStarter) GetMainName() string {
return "main"
}
func (s *mainProgramStarter) Start(targetExe string) error {
if err := os.Chmod(targetExe, 0755); err != nil {
return err
}
cmd := exec.Command(targetExe)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Linux 下:先保持最简单,保证能跑
return cmd.Start()
}
+29
View File
@@ -0,0 +1,29 @@
//go:build windows
// +build windows
package main
import (
"os"
"os/exec"
"syscall"
)
type mainProgramStarter struct{}
func (s *mainProgramStarter) GetMainName() string {
return "main.exe"
}
func (s *mainProgramStarter) Start(targetExe string) error {
cmd := exec.Command(targetExe)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Windows:新进程组,脱离 Ctrl+C / 父进程
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
}
return cmd.Start()
}