增加更新重启逻辑

This commit is contained in:
2025-10-17 17:03:19 +08:00
parent 7184defc50
commit 248f9a28e7
24 changed files with 604 additions and 65 deletions

View File

@@ -0,0 +1,25 @@
# CLI tool, only in development environment.
# https://goframe.org/docs/cli
gfcli:
build:
name: "update"
arch: "amd64"
system: "windows"
mod: "none"
# packSrc: "resource,manifest"
version: "v1.0.0"
# output: "./bin/"
# extra: -trimpath -ldflags="-s -w -H=windowsgui"
extra: -trimpath -ldflags="-s -w"
# cgo: true
dumpEnv: true
gen:
dao:
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
descriptionTag: true
docker:
build: "-a amd64 -s linux -p temp -ew"
tagPrefixes:
- my.image.pub/my-app

View File

@@ -0,0 +1,20 @@
# Install/Update to the latest CLI tool.
.PHONY: cli
cli:
@set -e; \
wget -O gf \
https://github.com/gogf/gf/releases/latest/download/gf_$(shell go env GOOS)_$(shell go env GOARCH) && \
chmod +x gf && \
./gf install -y && \
rm ./gf
# Check and install CLI tool.
.PHONY: cli.install
cli.install:
@set -e; \
gf -v > /dev/null 2>&1 || if [[ "$?" -ne "0" ]]; then \
echo "GoFame CLI is not installed, start proceeding auto installation..."; \
make cli; \
fi;

75
build/update/hack/hack.mk Normal file
View File

@@ -0,0 +1,75 @@
.DEFAULT_GOAL := build
# Update GoFrame and its CLI to latest stable version.
.PHONY: up
up: cli.install
@gf up -a
# Build binary using configuration from hack/config.yaml.
.PHONY: build
build: cli.install
@gf build -ew
# Parse api and generate controller/sdk.
.PHONY: ctrl
ctrl: cli.install
@gf gen ctrl
# Generate Go files for DAO/DO/Entity.
.PHONY: dao
dao: cli.install
@gf gen dao
# Parse current project go files and generate enums go file.
.PHONY: enums
enums: cli.install
@gf gen enums
# Generate Go files for Service.
.PHONY: service
service: cli.install
@gf gen service
# Build docker image.
.PHONY: image
image: cli.install
$(eval _TAG = $(shell git rev-parse --short HEAD))
ifneq (, $(shell git status --porcelain 2>/dev/null))
$(eval _TAG = $(_TAG).dirty)
endif
$(eval _TAG = $(if ${TAG}, ${TAG}, $(_TAG)))
$(eval _PUSH = $(if ${PUSH}, ${PUSH}, ))
@gf docker ${_PUSH} -tn $(DOCKER_NAME):${_TAG};
# Build docker image and automatically push to docker repo.
.PHONY: image.push
image.push: cli.install
@make image PUSH=-p;
# Deploy image and yaml to current kubectl environment.
.PHONY: deploy
deploy: cli.install
$(eval _TAG = $(if ${TAG}, ${TAG}, develop))
@set -e; \
mkdir -p $(ROOT_DIR)/temp/kustomize;\
cd $(ROOT_DIR)/manifest/deploy/kustomize/overlays/${_ENV};\
kustomize build > $(ROOT_DIR)/temp/kustomize.yaml;\
kubectl apply -f $(ROOT_DIR)/temp/kustomize.yaml; \
if [ $(DEPLOY_NAME) != "" ]; then \
kubectl patch -n $(NAMESPACE) deployment/$(DEPLOY_NAME) -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"$(shell date +%s)\"}}}}}"; \
fi;
# Parsing protobuf files and generating go files.
.PHONY: pb
pb: cli.install
@gf gen pb
# Generate protobuf files for database tables.
.PHONY: pbentity
pbentity: cli.install
@gf gen pbentity

183
build/update/main.go Normal file
View File

@@ -0,0 +1,183 @@
package main
import (
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
)
var (
updateServer = "https://your-server.com/update"
)
func main() {
if len(os.Args) < 3 {
fmt.Println("用法: updater <主程序路径> <当前版本> <更新服务器URL>")
os.Exit(1)
}
mainExePath := os.Args[1]
currentVersion := os.Args[2]
updateServer = os.Args[3]
fmt.Println("开始更新程序...")
// 1. 等待主程序完全退出
if err := waitForMainExit(mainExePath); err != nil {
fmt.Printf("等待主程序退出失败: %v\n", err)
pauseAndExit()
}
// 2. 获取最新版本信息
updateInfo, err := getLatestVersionInfo(currentVersion)
if err != nil {
fmt.Printf("获取更新信息失败: %v\n", err)
pauseAndExit()
}
// 3. 下载新版本
newExePath, err := downloadNewVersion(updateInfo.DownloadURL)
if err != nil {
fmt.Printf("下载更新失败: %v\n", err)
pauseAndExit()
}
// 4. 替换主程序
if err := replaceMainExe(mainExePath, newExePath); err != nil {
fmt.Printf("替换程序失败: %v\n", err)
pauseAndExit()
}
// 5. 重启主程序
if err := restartMainProgram(mainExePath); err != nil {
fmt.Printf("重启程序失败: %v\n", err)
pauseAndExit()
}
fmt.Println("更新完成")
}
// 等待主程序退出
func waitForMainExit(mainPath string) error {
// 获取主程序文件名
mainName := filepath.Base(mainPath)
for {
// 检查是否还有同名进程在运行
if !isProcessRunning(mainName) {
return nil
}
time.Sleep(300 * time.Millisecond)
}
}
// 检查进程是否在运行
func isProcessRunning(name string) bool {
// Windows下通过tasklist命令检查进程
cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("IMAGENAME eq %s", name))
_, err := cmd.Output()
if err != nil {
return false
}
return true
}
// 获取最新版本信息
func getLatestVersionInfo(currentVersion string) (UpdateInfo, error) {
var info UpdateInfo
resp, err := http.Get(fmt.Sprintf("%s/latest?current=%s", updateServer, currentVersion))
if err != nil {
return info, err
}
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return info, err
}
return info, nil
}
// 下载新版本
func downloadNewVersion(url string) (string, error) {
tempFile := filepath.Join(os.TempDir(), "main_new.exe")
out, err := os.Create(tempFile)
if err != nil {
return "", err
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
// 下载并计算哈希值
hash := sha256.New()
writer := io.MultiWriter(out, hash)
if _, err := io.Copy(writer, resp.Body); err != nil {
os.Remove(tempFile)
return "", err
}
// 这里可以添加哈希校验逻辑
return tempFile, nil
}
// 替换主程序文件
func replaceMainExe(oldPath, newPath string) error {
// 备份旧版本
backupPath := oldPath + ".bak"
if err := os.Remove(backupPath); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.Rename(oldPath, backupPath); err != nil {
return err
}
// 移动新版本到目标路径
if err := os.Rename(newPath, oldPath); err != nil {
// 替换失败,恢复备份
os.Rename(backupPath, oldPath)
return err
}
// 替换成功,删除备份
os.Remove(backupPath)
return nil
}
// 重启主程序
func restartMainProgram(mainPath string) error {
cmd := exec.Command(mainPath)
// 在后台启动主程序
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: false}
return cmd.Start()
}
// 暂停并退出(给用户看错误信息)
func pauseAndExit() {
fmt.Println("按任意键退出...")
var input string
fmt.Scanln(&input)
os.Exit(1)
}
// 更新信息结构体
type UpdateInfo struct {
Version string `json:"version"`
DownloadURL string `json:"download_url"`
SHA256 string `json:"sha256"`
}