174 lines
4.1 KiB
Go
174 lines
4.1 KiB
Go
package handler
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"time"
|
||
)
|
||
|
||
// UpdateHandler 系统更新处理器
|
||
type UpdateHandler struct {
|
||
httpClient *http.Client
|
||
currentVersion string
|
||
}
|
||
|
||
// NewUpdateHandler 创建系统更新处理器
|
||
func NewUpdateHandler(currentVersion string) *UpdateHandler {
|
||
return &UpdateHandler{
|
||
httpClient: &http.Client{
|
||
Timeout: 10 * time.Second,
|
||
},
|
||
currentVersion: currentVersion,
|
||
}
|
||
}
|
||
|
||
// GitHubRelease GitHub 发布响应结构
|
||
type GitHubRelease struct {
|
||
TagName string `json:"tag_name"`
|
||
Name string `json:"name"`
|
||
Body string `json:"body"`
|
||
PublishedAt string `json:"published_at"`
|
||
HtmlURL string `json:"html_url"`
|
||
Assets []struct {
|
||
Name string `json:"name"`
|
||
DownloadURL string `json:"browser_download_url"`
|
||
Size int64 `json:"size"`
|
||
} `json:"assets"`
|
||
}
|
||
|
||
// CheckUpdateResponse 检查更新响应
|
||
type CheckUpdateResponse struct {
|
||
HasUpdate bool `json:"has_update"`
|
||
LatestVersion string `json:"latest_version"`
|
||
CurrentVersion string `json:"current_version"`
|
||
ReleaseNotes string `json:"release_notes"`
|
||
DownloadURL string `json:"download_url"`
|
||
PublishedAt string `json:"published_at"`
|
||
Error string `json:"error,omitempty"`
|
||
}
|
||
|
||
// CheckUpdate 检查更新
|
||
// @Summary 检查系统更新
|
||
// @Tags System
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Success 200 {object} CheckUpdateResponse
|
||
// @Router /api/v1/system/update/check [get]
|
||
func (h *UpdateHandler) CheckUpdate() (*CheckUpdateResponse, error) {
|
||
// Gitea Releases API
|
||
repo := "zkcoi/meshray"
|
||
url := fmt.Sprintf("https://git.zkcoi.com/api/v1/repos/%s/releases/latest", repo)
|
||
|
||
resp, err := h.httpClient.Get(url)
|
||
if err != nil {
|
||
return &CheckUpdateResponse{
|
||
HasUpdate: false,
|
||
CurrentVersion: h.currentVersion,
|
||
Error: "检查更新失败:" + err.Error(),
|
||
}, nil
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return &CheckUpdateResponse{
|
||
HasUpdate: false,
|
||
CurrentVersion: h.currentVersion,
|
||
Error: "读取响应失败:" + err.Error(),
|
||
}, nil
|
||
}
|
||
|
||
var release GitHubRelease
|
||
if err := json.Unmarshal(body, &release); err != nil {
|
||
return &CheckUpdateResponse{
|
||
HasUpdate: false,
|
||
CurrentVersion: h.currentVersion,
|
||
Error: "解析响应失败:" + err.Error(),
|
||
}, nil
|
||
}
|
||
|
||
// 移除版本号前缀的 'v'
|
||
latestVersion := release.TagName
|
||
if len(latestVersion) > 0 && latestVersion[0] == 'v' {
|
||
latestVersion = latestVersion[1:]
|
||
}
|
||
|
||
currentVersion := h.currentVersion
|
||
if len(currentVersion) > 0 && currentVersion[0] == 'v' {
|
||
currentVersion = currentVersion[1:]
|
||
}
|
||
|
||
// 比较版本号
|
||
hasUpdate := compareVersions(latestVersion, currentVersion) > 0
|
||
|
||
downloadURL := release.HtmlURL
|
||
if len(release.Assets) > 0 {
|
||
// 优先选择 Windows 可执行文件
|
||
for _, asset := range release.Assets {
|
||
if asset.Name == "meshray.exe" {
|
||
downloadURL = asset.DownloadURL
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
return &CheckUpdateResponse{
|
||
HasUpdate: hasUpdate,
|
||
LatestVersion: release.TagName,
|
||
CurrentVersion: h.currentVersion,
|
||
ReleaseNotes: release.Body,
|
||
DownloadURL: downloadURL,
|
||
PublishedAt: release.PublishedAt,
|
||
}, nil
|
||
}
|
||
|
||
// compareVersions 比较版本号
|
||
// 返回:1 (v1 > v2), 0 (v1 == v2), -1 (v1 < v2)
|
||
func compareVersions(v1, v2 string) int {
|
||
if v1 == v2 {
|
||
return 0
|
||
}
|
||
|
||
// 简单版本号比较(格式:major.minor.patch)
|
||
parts1 := parseVersion(v1)
|
||
parts2 := parseVersion(v2)
|
||
|
||
for i := 0; i < len(parts1) && i < len(parts2); i++ {
|
||
if parts1[i] > parts2[i] {
|
||
return 1
|
||
} else if parts1[i] < parts2[i] {
|
||
return -1
|
||
}
|
||
}
|
||
|
||
// 如果前面都相同,比较长度
|
||
if len(parts1) > len(parts2) {
|
||
return 1
|
||
}
|
||
return -1
|
||
}
|
||
|
||
// parseVersion 解析版本号字符串为整数数组
|
||
func parseVersion(version string) []int {
|
||
var parts []int
|
||
current := 0
|
||
|
||
for i, ch := range version {
|
||
if ch == '.' {
|
||
parts = append(parts, current)
|
||
current = 0
|
||
} else if ch >= '0' && ch <= '9' {
|
||
current = current*10 + int(ch-'0')
|
||
}
|
||
|
||
// 处理最后一个字符
|
||
if i == len(version)-1 {
|
||
parts = append(parts, current)
|
||
}
|
||
}
|
||
|
||
return parts
|
||
}
|