Files
AUTO-MAS-test/Go_Updater/api/client.go
AoXuan 23b3691a13 feat(version): 优化版本比较和显示逻辑
- 修改版本比较逻辑,优先比较主版本号,然后是次版本号和修订号
- 增加对 beta版本的特殊处理,正式版比 beta 版更新
- 优化版本显示格式,移除不必要的 "v" 前缀
- 在主界面和日志中使用优化后的版本显示格式
2025-08-04 00:12:36 +08:00

313 lines
8.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
// MirrorResponse 表示 MirrorChyan API 的响应结构
type MirrorResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
VersionName string `json:"version_name"`
VersionNumber int `json:"version_number"`
URL string `json:"url,omitempty"`
SHA256 string `json:"sha256,omitempty"`
Channel string `json:"channel"`
OS string `json:"os"`
Arch string `json:"arch"`
UpdateType string `json:"update_type,omitempty"`
ReleaseNote string `json:"release_note"`
FileSize int64 `json:"filesize,omitempty"`
} `json:"data"`
}
// UpdateCheckParams 表示更新检查的参数
type UpdateCheckParams struct {
ResourceID string
CurrentVersion string
Channel string
UserAgent string
}
// MirrorClient 定义 Mirror API 客户端的接口方法
type MirrorClient interface {
CheckUpdate(params UpdateCheckParams) (*MirrorResponse, error)
IsUpdateAvailable(response *MirrorResponse, currentVersion string) bool
GetDownloadURL(versionName string) string
}
// Client 实现 MirrorClient 接口
type Client struct {
httpClient *http.Client
baseURL string
downloadURL string
}
// NewClient 创建新的 Mirror API 客户端
func NewClient() *Client {
return &Client{
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
baseURL: "https://mirrorchyan.com/api/resources",
downloadURL: "http://221.236.27.82:10197/d/AUTO_MAA",
}
}
// CheckUpdate 调用 MirrorChyan API 检查更新
func (c *Client) CheckUpdate(params UpdateCheckParams) (*MirrorResponse, error) {
// 构建 API URL
apiURL := fmt.Sprintf("%s/%s/latest", c.baseURL, params.ResourceID)
// 解析 URL 并添加查询参数
u, err := url.Parse(apiURL)
if err != nil {
return nil, fmt.Errorf("解析 API URL 失败: %w", err)
}
// 添加查询参数
q := u.Query()
q.Set("current_version", params.CurrentVersion)
q.Set("channel", params.Channel)
q.Set("os", "") // 跨平台为空
q.Set("arch", "") // 跨平台为空
u.RawQuery = q.Encode()
// 创建 HTTP 请求
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, fmt.Errorf("创建 HTTP 请求失败: %w", err)
}
// 设置 User-Agent 头
if params.UserAgent != "" {
req.Header.Set("User-Agent", params.UserAgent)
} else {
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36")
}
// 发送 HTTP 请求
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("发送 HTTP 请求失败: %w", err)
}
defer resp.Body.Close()
// 检查 HTTP 状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API 返回非 200 状态码: %d", resp.StatusCode)
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应体失败: %w", err)
}
// 解析 JSON 响应
var mirrorResp MirrorResponse
if err := json.Unmarshal(body, &mirrorResp); err != nil {
return nil, fmt.Errorf("解析 JSON 响应失败: %w", err)
}
return &mirrorResp, nil
}
// IsUpdateAvailable 比较当前版本与 API 响应中的最新版本
func (c *Client) IsUpdateAvailable(response *MirrorResponse, currentVersion string) bool {
// 检查 API 响应是否成功
if response.Code != 0 {
return false
}
// 从响应中获取最新版本
latestVersion := response.Data.VersionName
if latestVersion == "" {
return false
}
// 转换版本格式以便比较
currentVersionNormalized := c.normalizeVersionForComparison(currentVersion)
latestVersionNormalized := c.normalizeVersionForComparison(latestVersion)
// 调试输出
// fmt.Printf("Current: %s -> %s\n", currentVersion, currentVersionNormalized)
// fmt.Printf("Latest: %s -> %s\n", latestVersion, latestVersionNormalized)
// fmt.Printf("Compare result: %d\n", compareVersions(currentVersionNormalized, latestVersionNormalized))
// 使用语义版本比较
return compareVersions(currentVersionNormalized, latestVersionNormalized) < 0
}
// normalizeVersionForComparison 将不同版本格式转换为可比较格式
func (c *Client) normalizeVersionForComparison(version string) string {
// 处理 AUTO_MAA 版本格式: "4.4.1.3" -> "v4.4.1-beta3"
if !strings.HasPrefix(version, "v") && strings.Count(version, ".") == 3 {
parts := strings.Split(version, ".")
if len(parts) == 4 {
major, minor, patch, beta := parts[0], parts[1], parts[2], parts[3]
if beta == "0" {
return fmt.Sprintf("v%s.%s.%s", major, minor, patch)
} else {
return fmt.Sprintf("v%s.%s.%s-beta%s", major, minor, patch, beta)
}
}
}
// 如果已经是标准格式则直接返回
return version
}
// compareVersions 比较两个语义版本字符串
// 返回值: -1 如果 v1 < v2, 0 如果 v1 == v2, 1 如果 v1 > v2
func compareVersions(v1, v2 string) int {
// 通过移除 'v' 前缀来标准化版本
v1 = normalizeVersion(v1)
v2 = normalizeVersion(v2)
// 解析版本组件
parts1 := parseVersionParts(v1)
parts2 := parseVersionParts(v2)
// 比较前三个组件 (major.minor.patch)
for i := 0; i < 3; i++ {
var p1, p2 int
if i < len(parts1) {
p1 = parts1[i]
}
if i < len(parts2) {
p2 = parts2[i]
}
if p1 < p2 {
return -1
} else if p1 > p2 {
return 1
}
}
// 如果前三个组件相同比较beta版本号
var beta1, beta2 int
if len(parts1) > 3 {
beta1 = parts1[3]
}
if len(parts2) > 3 {
beta2 = parts2[3]
}
// 特殊处理beta版本比较
// - 如果一个是正式版(beta=0)另一个是beta版(beta>0),正式版更新
// - 如果都是beta版比较beta版本号
if beta1 == 0 && beta2 > 0 {
return 1 // 正式版比beta版更新
}
if beta1 > 0 && beta2 == 0 {
return -1 // beta版比正式版旧
}
// 都是正式版或都是beta版直接比较
if beta1 < beta2 {
return -1
} else if beta1 > beta2 {
return 1
}
return 0
}
// normalizeVersion 移除 'v' 前缀并处理常见版本格式
func normalizeVersion(version string) string {
if len(version) > 0 && (version[0] == 'v' || version[0] == 'V') {
return version[1:]
}
return version
}
// parseVersionParts 将版本字符串解析为数字组件包括beta版本号
func parseVersionParts(version string) []int {
if version == "" {
return []int{0}
}
parts := make([]int, 0, 4)
current := 0
// 先检查是否包含 -beta
betaIndex := strings.Index(version, "-beta")
var mainVersion, betaVersion string
if betaIndex != -1 {
mainVersion = version[:betaIndex]
betaVersion = version[betaIndex+5:] // 跳过 "-beta"
} else {
mainVersion = version
betaVersion = ""
}
// 解析主版本号 (major.minor.patch)
for _, char := range mainVersion {
if char >= '0' && char <= '9' {
current = current*10 + int(char-'0')
} else if char == '.' {
parts = append(parts, current)
current = 0
} else {
// 遇到非数字非点字符,停止解析
break
}
}
// 添加最后一个主版本组件
parts = append(parts, current)
// 确保至少有 3 个组件 (major.minor.patch)
for len(parts) < 3 {
parts = append(parts, 0)
}
// 解析beta版本号
if betaVersion != "" {
// 跳过可能的点号
if strings.HasPrefix(betaVersion, ".") {
betaVersion = betaVersion[1:]
}
betaNum := 0
for _, char := range betaVersion {
if char >= '0' && char <= '9' {
betaNum = betaNum*10 + int(char-'0')
} else {
break
}
}
// Beta版本号保持正数但在比较时会特殊处理
parts = append(parts, betaNum)
} else {
// 非beta版本添加0作为beta版本号
parts = append(parts, 0)
}
return parts
}
// GetDownloadURL 根据版本名生成下载站的下载 URL
func (c *Client) GetDownloadURL(versionName string) string {
// 将版本名转换为文件名格式
// 例如: "v4.4.0" -> "AUTO_MAA_v4.4.0.zip"
// 例如: "v4.4.1-beta3" -> "AUTO_MAA_v4.4.1-beta.3.zip"
filename := fmt.Sprintf("AUTO_MAA_%s.zip", versionName)
// 处理 beta 版本: 将 "beta3" 转换为 "beta.3"
if strings.Contains(filename, "-beta") && !strings.Contains(filename, "-beta.") {
filename = strings.Replace(filename, "-beta", "-beta.", 1)
}
return fmt.Sprintf("%s/%s", c.downloadURL, filename)
}