From 42404bb8e552a662007b470f90c821e255d0c0b9 Mon Sep 17 00:00:00 2001 From: ayflying Date: Thu, 30 Oct 2025 17:39:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E4=BA=8E=E5=90=AF=E5=8A=A8=E5=90=8E?= =?UTF-8?q?=E9=80=9A=E8=BF=87github=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/logic/system/model.go | 87 ++++++++++++++++++++++++++ internal/logic/system/system.go | 9 +++ internal/logic/system/update.go | 106 ++++++++++++++++++++++++++++++++ internal/service/system.go | 1 + 4 files changed, 203 insertions(+) create mode 100644 internal/logic/system/model.go diff --git a/internal/logic/system/model.go b/internal/logic/system/model.go new file mode 100644 index 0000000..59fa7da --- /dev/null +++ b/internal/logic/system/model.go @@ -0,0 +1,87 @@ +package system + +import "time" + +type T struct { + Url string `json:"url"` + AssetsUrl string `json:"assets_url"` + UploadUrl string `json:"upload_url"` + HtmlUrl string `json:"html_url"` + Id int `json:"id"` + Author *Author `json:"author"` + NodeId string `json:"node_id"` + TagName string `json:"tag_name"` + TargetCommitish string `json:"target_commitish"` + Name string `json:"name"` + Draft bool `json:"draft"` + Immutable bool `json:"immutable"` + Prerelease bool `json:"prerelease"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PublishedAt time.Time `json:"published_at"` + Assets []*Assets + TarballUrl string `json:"tarball_url"` + ZipballUrl string `json:"zipball_url"` + Body string `json:"body"` +} + +type Author struct { + Login string `json:"login"` + Id int `json:"id"` + NodeId string `json:"node_id"` + AvatarUrl string `json:"avatar_url"` + GravatarId string `json:"gravatar_id"` + Url string `json:"url"` + HtmlUrl string `json:"html_url"` + FollowersUrl string `json:"followers_url"` + FollowingUrl string `json:"following_url"` + GistsUrl string `json:"gists_url"` + StarredUrl string `json:"starred_url"` + SubscriptionsUrl string `json:"subscriptions_url"` + OrganizationsUrl string `json:"organizations_url"` + ReposUrl string `json:"repos_url"` + EventsUrl string `json:"events_url"` + ReceivedEventsUrl string `json:"received_events_url"` + Type string `json:"type"` + UserViewType string `json:"user_view_type"` + SiteAdmin bool `json:"site_admin"` +} + +type Assets struct { + Url string `json:"url"` + Id int `json:"id"` + NodeId string `json:"node_id"` + Name string `json:"name"` + Label string `json:"label"` + Uploader *Uploader `json:"uploader"` + ContentType string `json:"content_type"` + State string `json:"state"` + Size int `json:"size"` + Digest string `json:"digest"` + DownloadCount int `json:"download_count"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + BrowserDownloadUrl string `json:"browser_download_url"` +} + +type Uploader struct { + Login string `json:"login"` + Id int `json:"id"` + NodeId string `json:"node_id"` + AvatarUrl string `json:"avatar_url"` + GravatarId string `json:"gravatar_id"` + Url string `json:"url"` + HtmlUrl string `json:"html_url"` + FollowersUrl string `json:"followers_url"` + FollowingUrl string `json:"following_url"` + GistsUrl string `json:"gists_url"` + StarredUrl string `json:"starred_url"` + SubscriptionsUrl string `json:"subscriptions_url"` + OrganizationsUrl string `json:"organizations_url"` + ReposUrl string `json:"repos_url"` + EventsUrl string `json:"events_url"` + ReceivedEventsUrl string `json:"received_events_url"` + Type string `json:"type"` + UserViewType string `json:"user_view_type"` + SiteAdmin bool `json:"site_admin"` +} diff --git a/internal/logic/system/system.go b/internal/logic/system/system.go index 8dcf66e..7dbdf95 100644 --- a/internal/logic/system/system.go +++ b/internal/logic/system/system.go @@ -2,6 +2,8 @@ package system import ( "github.com/ayflying/p2p/internal/service" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" ) type sSystem struct{} @@ -12,6 +14,13 @@ func New() *sSystem { func init() { service.RegisterSystem(New()) + + getDev, _ := g.Cfg().GetWithEnv(gctx.New(), "dev") + if !getDev.Bool() { + service.System().CheckUpdate() + } else { + g.Log().Debugf(gctx.New(), "开发模式,不检查更新") + } } func (system *sSystem) Init() {} diff --git a/internal/logic/system/update.go b/internal/logic/system/update.go index 8118fd0..8f156b2 100644 --- a/internal/logic/system/update.go +++ b/internal/logic/system/update.go @@ -2,21 +2,52 @@ package system import ( "context" + "encoding/json" "fmt" "log" + "net/http" "os" "os/exec" "path" "path/filepath" "runtime" + "strings" "time" "github.com/ayflying/p2p/internal/service" "github.com/gogf/gf/v2/encoding/gcompress" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" ) +// 本地版本号(建议从编译参数注入,如 -ldflags "-X main.version=v0.1.3") +var localVersion = "v0.0.0" + +// 对应 GitHub API 响应的核心字段(按需精简) +type GitHubRelease struct { + Url string `json:"url"` + AssetsUrl string `json:"assets_url"` + UploadUrl string `json:"upload_url"` + HtmlUrl string `json:"html_url"` + Id int `json:"id"` + TagName string `json:"tag_name"` + Assets []*Assets `json:"assets"` + NodeId string `json:"node_id"` + TargetCommitish string `json:"target_commitish"` + Name string `json:"name"` + Draft bool `json:"draft"` + Immutable bool `json:"immutable"` + Prerelease bool `json:"prerelease"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PublishedAt time.Time `json:"published_at"` + TarballUrl string `json:"tarball_url"` + ZipballUrl string `json:"zipball_url"` + Body string `json:"body"` +} + func (s *sSystem) Update(ctx context.Context) (err error) { //拼接操作系统和架构(格式:OS_ARCH) platform := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH) @@ -96,3 +127,78 @@ func (s *sSystem) RenameRunningFile(exePath string) (string, error) { } return backupPath, nil } + +// 简化版版本对比(仅适用于 vX.Y.Z 格式) +func (s *sSystem) isNewVersion(local, latest string) bool { + // 移除前缀 "v",按 "." 分割成数字切片 + localParts := strings.Split(strings.TrimPrefix(local, "v"), ".") + latestParts := strings.Split(strings.TrimPrefix(latest, "v"), ".") + + // 逐段对比版本号(如 0.1.3 vs 0.1.4 → 后者更新) + for i := 0; i < len(localParts) && i < len(latestParts); i++ { + if localParts[i] < latestParts[i] { + return true + } else if localParts[i] > latestParts[i] { + return false + } + } + // 若前缀相同,长度更长的版本更新(如 0.1 vs 0.1.1) + return len(localParts) < len(latestParts) +} + +func (s *sSystem) getLatestVersion() (string, []*Assets, error) { + apiURL := "https://api.github.com/repos/ayflying/p2p/releases/latest" + resp, err := http.Get(apiURL) + if err != nil { + return "", nil, fmt.Errorf("请求失败:%v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", nil, fmt.Errorf("API 响应错误:%d", resp.StatusCode) + } + + var release GitHubRelease + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return "", nil, fmt.Errorf("解析响应失败:%v", err) + } + + return release.TagName, release.Assets, nil +} + +func (s *sSystem) CheckUpdate() { + ctx := gctx.New() + latestVersion, assets, err := s.getLatestVersion() + if err != nil { + fmt.Printf("检查更新失败:%v\n", err) + return + } + + localVersion = gfile.GetContents("download/version.txt") + + if s.isNewVersion(localVersion, latestVersion) { + g.Log().Printf(ctx, "发现新版本:%s(当前版本:%s)", latestVersion, localVersion) + //拼接操作系统和架构(格式:OS_ARCH) + platform := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH) + //name := fmt.Sprintf("p2p_%s_%s.tar.gz", latestVersion, platform) + fmt.Println("下载链接:") + for _, asset := range assets { + if strings.Contains(fmt.Sprintf("_%s.", asset.Name), platform) { + fmt.Printf("- %s\n", asset.BrowserDownloadUrl) + + fileDownload, err2 := g.Client().Get(ctx, asset.BrowserDownloadUrl) + if err2 != nil { + return + } + //filename := gfile.Name() + err = gfile.PutBytes(path.Join("download", asset.Name), fileDownload.ReadAll()) + + // 保存最新版本号到文件 + gfile.PutContents("download/version.txt", latestVersion) + break + } + } + } else { + fmt.Printf("当前已是最新版本:%s\n", localVersion) + } +} diff --git a/internal/service/system.go b/internal/service/system.go index 4567f95..4b9fb3f 100644 --- a/internal/service/system.go +++ b/internal/service/system.go @@ -17,6 +17,7 @@ type ( RestartSelf() error // RenameRunningFile 重命名正在运行的程序文件(如 message.exe → message.exe~) RenameRunningFile(exePath string) (string, error) + CheckUpdate() } )