去掉本地的更新逻辑,是有分离后的更新库

This commit is contained in:
2025-10-31 15:50:59 +08:00
parent 8b943b0cca
commit 22b4402737
8 changed files with 1 additions and 567 deletions

View File

@@ -1,11 +1 @@
package v1 package v1
import "github.com/gogf/gf/v2/frame/g"
type UpdateReq struct {
g.Meta `path:"/system/update" tags:"system" method:"get" sm:"更新服务端"`
Url string `json:"url" dc:"更新地址"`
Version string `json:"version" dc:"当前版本"`
}
type UpdateRes struct {
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/ayflying/p2p/internal/consts" "github.com/ayflying/p2p/internal/consts"
"github.com/ayflying/p2p/internal/controller/p2p" "github.com/ayflying/p2p/internal/controller/p2p"
"github.com/ayflying/p2p/internal/controller/system"
"github.com/ayflying/p2p/internal/service" "github.com/ayflying/p2p/internal/service"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/net/ghttp"
@@ -18,7 +17,7 @@ import (
) )
func init() { func init() {
err := Main.AddCommand(&Main, &Debug, &Update) err := Main.AddCommand(&Main, &Debug)
if err != nil { if err != nil {
g.Log().Error(gctx.GetInitCtx(), err) g.Log().Error(gctx.GetInitCtx(), err)
return return
@@ -69,7 +68,6 @@ var (
group.Middleware(ghttp.MiddlewareHandlerResponse) group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind( group.Bind(
p2p.NewV1(), p2p.NewV1(),
system.NewV1(),
) )
}) })

View File

@@ -1,109 +0,0 @@
package cmd
import (
"bytes"
"context"
"fmt"
"path"
"runtime"
systemV1 "github.com/ayflying/p2p/api/system/v1"
"github.com/ayflying/p2p/internal/service"
"github.com/gogf/gf/v2/crypto/gsha1"
"github.com/gogf/gf/v2/encoding/gcompress"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gfile"
)
var (
Update = gcmd.Command{
Name: "update",
Usage: "update",
Brief: "更新版本",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
g.Log().Info(ctx, "准备上传更新文件")
//加载编辑配置文件
g.Cfg("hack").GetAdapter().(*gcfg.AdapterFile).SetFileName("hack/config.yaml")
//获取文件名
getName, err := g.Cfg("hack").Get(ctx, "gfcli.build.name")
name := getName.String()
getPath, err := g.Cfg("hack").Get(ctx, "gfcli.build.path")
pathMain := getPath.String()
//获取版本号
getVersion, err := g.Cfg("hack").Get(ctx, "gfcli.build.version")
version := getVersion.String()
// 拼接操作系统和架构格式OS_ARCH
platform := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)
rootDir := "server_update"
var versionFile = make(map[string]string)
var filePath = path.Join(pathMain, version, platform, name)
dirList, _ := gfile.ScanDir(path.Join(pathMain, version), "*", false)
for _, v := range dirList {
updatePlatform := gfile.Name(v)
updateFilePath := path.Join(rootDir, name, version, updatePlatform)
var obj bytes.Buffer
g.Log().Debugf(ctx, "读取目录成功:%v", v)
fileMian := path.Join(v, name)
g.Log().Debugf(ctx, "判断当前文件是否存在:%v", fileMian)
if gfile.IsFile(fileMian) {
// 写入文件哈希
versionFile[updatePlatform] = gsha1.MustEncryptFile(fileMian)
err = gcompress.GzipPathWriter(fileMian, &obj)
service.S3().PutObject(ctx, &obj, updateFilePath+".gz")
g.Log().Debugf(ctx, "成功上传文件到:%v", updateFilePath+".gz")
}
if gfile.IsFile(fileMian + ".exe") {
// 写入文件哈希
versionFile[updatePlatform] = gsha1.MustEncryptFile(fileMian + ".exe")
err = gcompress.GzipPathWriter(fileMian+".exe", &obj)
service.S3().PutObject(ctx, &obj, updateFilePath+".gz")
g.Log().Debugf(ctx, "成功上传文件到:%v", updateFilePath+".gz")
}
// 写入文件版本文件
fileByte := gjson.MustEncode(versionFile)
service.S3().PutObject(ctx, bytes.NewReader(fileByte), path.Join(rootDir, name, "version.json"))
if err != nil {
g.Log().Error(ctx, err)
}
}
g.Log().Debugf(ctx, "当前获取到的地址为:%v", filePath)
versionUrl := service.S3().GetCdnUrl(path.Join(rootDir, name))
listVar := g.Cfg().MustGet(ctx, "message.list")
var p2pItem []struct {
Host string `json:"host"`
Port int `json:"port"`
SSL bool `json:"ssl"`
Ws string `json:"ws"`
}
listVar.Scan(&p2pItem)
for _, v := range p2pItem {
url := "http"
if v.SSL == true {
url = "https"
}
url = fmt.Sprintf("%s://%s:%d/system/update", url, v.Host, v.Port)
g.Log().Debugf(ctx, "开始上传到服务器:%v,file=%v", url, versionUrl)
_, err := g.Client().Get(ctx, url, systemV1.UpdateReq{
Url: versionUrl,
Version: version,
})
if err != nil {
g.Log().Error(ctx, err)
}
}
return
}}
)

View File

@@ -4,12 +4,4 @@
package system package system
import (
"github.com/ayflying/p2p/api/system"
)
type ControllerV1 struct{} type ControllerV1 struct{}
func NewV1() system.ISystemV1 {
return &ControllerV1{}
}

View File

@@ -1,70 +0,0 @@
package system
import (
"context"
"net/url"
"path"
"github.com/ayflying/p2p/api/system/v1"
"github.com/ayflying/p2p/internal/service"
"github.com/gogf/gf/v2/crypto/gsha1"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gfile"
)
func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
getRunFile := gcmd.GetArg(0).String()
fileSha, err := gsha1.EncryptFile(getRunFile)
g.Log().Debugf(ctx, "当前文件哈希值:%v", fileSha)
versionUrl, _ := url.JoinPath(req.Url, "version.json")
resp, err := g.Client().Get(ctx, versionUrl)
var version map[string]string
gjson.DecodeTo(resp.ReadAll(), &version)
for k, _ := range version {
//downloadUrl, _ := url.QueryUnescape(v)
downloadUrl, _ := url.JoinPath(req.Url, req.Version, k+".gz")
fileByte, err2 := g.Client().Get(ctx, downloadUrl)
if err2 != nil {
g.Log().Error(ctx, err2)
continue
}
putFile := path.Join("download", gfile.Basename(downloadUrl))
err2 = gfile.PutBytes(putFile, fileByte.ReadAll())
if err2 != nil {
g.Log().Error(ctx, err2)
continue
}
}
type DataType struct {
File []byte `json:"file"`
Name string `json:"name"`
}
//var GatewayMessage *message.GatewayMessage
var msgData = struct {
Files []*DataType `json:"files"`
}{}
msgData.Files = []*DataType{}
files, _ := gfile.ScanDir("download", "*.gz")
for _, v := range files {
msgData.Files = append(msgData.Files, &DataType{
File: gfile.GetBytes(v),
Name: gfile.Basename(v),
})
}
service.P2P().SendAll("update", msgData)
//更新自己的文件
//err = service.System().Update(ctx)
return
}

View File

@@ -1,64 +0,0 @@
package system
import "time"
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"`
}

View File

@@ -3,7 +3,6 @@ package system
import ( import (
"context" "context"
"github.com/ayflying/p2p/internal/service"
updateGithub "github.com/ayflying/update-github-release" updateGithub "github.com/ayflying/update-github-release"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcron" "github.com/gogf/gf/v2/os/gcron"
@@ -17,7 +16,6 @@ func New() *sSystem {
} }
func init() { func init() {
service.RegisterSystem(New())
getDev, _ := g.Cfg().GetWithEnv(gctx.New(), "dev") getDev, _ := g.Cfg().GetWithEnv(gctx.New(), "dev")
if !getDev.Bool() { if !getDev.Bool() {

View File

@@ -1,301 +0,0 @@
package system
import (
"archive/tar"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"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/net/ghttp"
"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"
const versionFile = "version.txt"
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, gzFile string) (err error) {
//拼接操作系统和架构格式OS_ARCH
platform := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)
runFile := gcmd.GetArg(0).String()
oldFile, err := service.System().RenameRunningFile(runFile)
g.Log().Debugf(ctx, "执行文件改名为%v", oldFile)
if gzFile == "" {
gzFile = path.Join("download", platform+".gz")
}
//结束后删除压缩包
defer gfile.RemoveFile(gzFile)
ext := gfile.Ext(gzFile)
if ext == ".zip" {
g.Log().Debugf(ctx, "zip解压%v到%v", gzFile, gfile.Dir(runFile))
err = gcompress.UnZipFile(gzFile, gfile.Dir(runFile))
} else {
g.Log().Debugf(ctx, "gzip解压%v到%v", gzFile, gfile.Dir(runFile))
err = s.UnTarGz(gzFile, gfile.Dir(runFile))
}
if err != nil {
return
}
//修改文件权限为755
err = gfile.Chmod(runFile, 0755)
go func() {
log.Println("5秒后开始重启...")
time.Sleep(5 * time.Second)
if err = service.System().RestartSelf(); err != nil {
log.Fatalf("重启失败:%v", err)
}
}()
return
}
// UnTarGz 解压tar.gz文件到指定目录
func (s *sSystem) UnTarGz(tarGzFileName, targetDir string) (err error) {
// 打开tar.gz文件
file, err := os.Open(tarGzFileName)
if err != nil {
return err
}
defer file.Close()
// 创建gzip reader
gzr, err := gzip.NewReader(file)
if err != nil {
return err
}
defer gzr.Close()
// 创建tar reader
tr := tar.NewReader(gzr)
// 遍历tar中的每个文件
for {
hdr, err := tr.Next()
if err == io.EOF {
// 到达文件末尾,退出循环
break
}
if err != nil {
return err
}
// 构建解压后的文件路径
targetPath := targetDir + string(os.PathSeparator) + hdr.Name
// 如果是目录,创建目录
if hdr.Typeflag == tar.TypeDir {
err := os.MkdirAll(targetPath, 0755)
if err != nil {
return err
}
continue
}
// 如果是文件,创建文件并写入内容
outFile, err := os.Create(targetPath)
if err != nil {
return err
}
defer outFile.Close()
_, err = io.Copy(outFile, tr)
if err != nil {
return err
}
}
return
}
// RestartSelf 实现 Windows 平台下的程序自重启
func (s *sSystem) RestartSelf() error {
ctx := gctx.New()
// 判断是否为linux平台
if runtime.GOOS == "linux" {
err := ghttp.RestartAllServer(ctx, os.Args[0])
if err != nil {
g.Log().Errorf(ctx, "重启失败:%v", err)
}
return err
}
// 1. 获取当前程序的绝对路径
exePath, err := os.Executable()
if err != nil {
return err
}
// 处理路径中的符号链接(确保路径正确)
exePath, err = filepath.EvalSymlinks(exePath)
if err != nil {
return err
}
// 2. 获取命令行参数os.Args[0] 是程序名,实际参数从 os.Args[1:] 开始)
args := os.Args[1:]
// 3. 构建新进程命令(路径为当前程序,参数为原参数)
cmd := exec.Command(exePath, args...)
// 设置新进程的工作目录与当前进程一致
cmd.Dir, err = os.Getwd()
if err != nil {
return err
}
// 新进程的输出继承当前进程的标准输出(可选,根据需求调整)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
// 4. 启动新进程非阻塞Start() 后立即返回)
if err := cmd.Start(); err != nil {
return err
}
// 5. 新进程启动成功后,退出当前进程
os.Exit(0)
return nil // 理论上不会执行到这里
}
// RenameRunningFile 重命名正在运行的程序文件(如 message.exe → message.exe~
func (s *sSystem) RenameRunningFile(exePath string) (string, error) {
// 目标备份文件名message.exe → message.exe~
backupPath := exePath + "~"
// 先删除已存在的备份文件(若有)
if _, err := os.Stat(backupPath); err == nil {
if err := os.Remove(backupPath); err != nil {
return "", fmt.Errorf("删除旧备份文件失败: %v", err)
}
}
// 重命名正在运行的 exe 文件
// 关键Windows 允许对锁定的文件执行重命名操作
if err := os.Rename(exePath, backupPath); err != nil {
return "", fmt.Errorf("重命名运行中文件失败: %v", err)
}
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() (err error) {
ctx := gctx.New()
latestVersion, assets, err := s.getLatestVersion()
if err != nil {
fmt.Printf("检查更新失败:%v\n", err)
return
}
localVersion = gfile.GetContents(versionFile)
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
}
updateFile := path.Join("download", asset.Name)
err = gfile.PutBytes(updateFile, fileDownload.ReadAll())
err = s.Update(ctx, updateFile)
if err != nil {
return
}
// 保存最新版本号到文件
gfile.PutContents(versionFile, latestVersion)
break
}
}
} else {
fmt.Printf("当前已是最新版本:%s\n", localVersion)
}
return
}