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 }