7 Commits

8 changed files with 305 additions and 8 deletions

1
.gitignore vendored
View File

@@ -16,7 +16,6 @@ manifest/output/
temp/ temp/
temp.yaml temp.yaml
bin bin
**/config/config.yaml
v1.0.0/ v1.0.0/
config/local.yaml config/local.yaml
main.exe~ main.exe~

View File

@@ -456,7 +456,7 @@ func (s *sP2P) receiveGatewayMessages(ctx context.Context) {
g.Log().Info(ctx, "文件接收完成") g.Log().Info(ctx, "文件接收完成")
// 开始覆盖文件与重启 // 开始覆盖文件与重启
err = service.System().Update(ctx) err = service.System().Update(ctx, "")
//// 调用不同系统的更新服务 //// 调用不同系统的更新服务
//service.OS().Update(msgData.Version, msgData.Server) //service.OS().Update(msgData.Version, msgData.Server)

View File

@@ -188,9 +188,10 @@ func (s *sP2P) removeDuplicates(strs []string) []string {
return result return result
} }
const privKeyPath = "runtime/p2p.key"
// 生成固定密钥(核心:通过固定种子生成相同密钥) // 生成固定密钥(核心:通过固定种子生成相同密钥)
func (s *sP2P) generateFixedKey() (crypto.PrivKey, error) { func (s *sP2P) generateFixedKey() (crypto.PrivKey, error) {
privKeyPath := "runtime/message.key"
if ok := gfile.Exists(privKeyPath); ok { if ok := gfile.Exists(privKeyPath); ok {
// 从文件读取密钥 // 从文件读取密钥
keyBytes := gfile.GetBytes(privKeyPath) keyBytes := gfile.GetBytes(privKeyPath)

View File

@@ -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"`
}

View File

@@ -1,7 +1,12 @@
package system package system
import ( import (
"context"
"github.com/ayflying/p2p/internal/service" "github.com/ayflying/p2p/internal/service"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcron"
"github.com/gogf/gf/v2/os/gctx"
) )
type sSystem struct{} type sSystem struct{}
@@ -12,6 +17,24 @@ func New() *sSystem {
func init() { func init() {
service.RegisterSystem(New()) service.RegisterSystem(New())
getDev, _ := g.Cfg().GetWithEnv(gctx.New(), "dev")
if !getDev.Bool() {
// 每天0点检查更新
gcron.Add(gctx.New(), "0 0 0 * * *", func(ctx context.Context) {
err := service.System().CheckUpdate()
if err != nil {
g.Log().Errorf(ctx, "检查更新失败:%v", err)
}
})
err := service.System().CheckUpdate()
if err != nil {
g.Log().Errorf(gctx.New(), "检查更新失败:%v", err)
}
} else {
g.Log().Debugf(gctx.New(), "开发模式,不检查更新")
}
} }
func (system *sSystem) Init() {} func (s *sSystem) Init() {}

View File

@@ -2,30 +2,79 @@ package system
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"log" "log"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"time" "time"
"github.com/ayflying/p2p/internal/service" "github.com/ayflying/p2p/internal/service"
"github.com/gogf/gf/v2/encoding/gcompress" "github.com/gogf/gf/v2/encoding/gcompress"
"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/os/gcmd" "github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gfile"
) )
func (s *sSystem) Update(ctx context.Context) (err error) { // 本地版本号(建议从编译参数注入,如 -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 //拼接操作系统和架构格式OS_ARCH
platform := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH) platform := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)
runFile := gcmd.GetArg(0).String() runFile := gcmd.GetArg(0).String()
oldFile, err := service.System().RenameRunningFile(runFile) oldFile, err := service.System().RenameRunningFile(runFile)
g.Log().Debugf(ctx, "执行文件改名为%v", oldFile) g.Log().Debugf(ctx, "执行文件改名为%v", oldFile)
gz := path.Join("download", platform+".gz") if gzFile == "" {
err = gcompress.UnGzipFile(gz, runFile) 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, runFile)
err = gcompress.UnGzipFile(gzFile, runFile)
}
if err != nil {
return
}
go func() { go func() {
log.Println("5秒后开始重启...") log.Println("5秒后开始重启...")
@@ -40,6 +89,16 @@ func (s *sSystem) Update(ctx context.Context) (err error) {
// RestartSelf 实现 Windows 平台下的程序自重启 // RestartSelf 实现 Windows 平台下的程序自重启
func (s *sSystem) RestartSelf() error { 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. 获取当前程序的绝对路径 // 1. 获取当前程序的绝对路径
exePath, err := os.Executable() exePath, err := os.Executable()
if err != nil { if err != nil {
@@ -96,3 +155,84 @@ func (s *sSystem) RenameRunningFile(exePath string) (string, error) {
} }
return backupPath, nil 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
}

View File

@@ -12,11 +12,12 @@ import (
type ( type (
ISystem interface { ISystem interface {
Init() Init()
Update(ctx context.Context) (err error) Update(ctx context.Context, gzFile string) (err error)
// RestartSelf 实现 Windows 平台下的程序自重启 // RestartSelf 实现 Windows 平台下的程序自重启
RestartSelf() error RestartSelf() error
// RenameRunningFile 重命名正在运行的程序文件(如 message.exe → message.exe~ // RenameRunningFile 重命名正在运行的程序文件(如 message.exe → message.exe~
RenameRunningFile(exePath string) (string, error) RenameRunningFile(exePath string) (string, error)
CheckUpdate() (err error)
} }
) )

View File

@@ -0,0 +1,46 @@
module:
server: true
client: true
# https://goframe.org/docs/web/server-config-file-template
server:
address: "51888"
# openapiPath: "/api.json"
# swaggerPath: "/swagger"
dumpRouterMap: false
graceful: true
# https://goframe.org/docs/core/glog-config
logger:
level : "all"
stdout: true
# https://goframe.org/docs/core/gdb-config-file
database:
default:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
redis:
default:
address: "ay.cname.com:6379"
db: 15
pass: "12345678"
cache:
address: "ay.cname.com:6379"
db: 15
pass: "12345678"
p2p:
list:
# - host: "192.168.50.173"
# port: 51888
# ssl: false
# ws: ws
- host: "ay.cname.com"
port: 51888
ssl: false
ws: ws