增加分布式更新方法

This commit is contained in:
2025-10-27 19:07:48 +08:00
parent 10c349b82f
commit c48c85f075
15 changed files with 345 additions and 112 deletions

4
.gitignore vendored
View File

@@ -18,4 +18,6 @@ temp.yaml
bin
**/config/config.yaml
v1.0.0/
manifest/config/local.yaml
config/local.yaml
main.exe~
download/

View File

@@ -2,6 +2,7 @@ package cmd
import (
"context"
"fmt"
"time"
"github.com/ayflying/p2p/internal/consts"
@@ -13,6 +14,7 @@ import (
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gtimer"
"github.com/gogf/gf/v2/util/grand"
)
func init() {
@@ -47,7 +49,24 @@ var (
})
addr := g.Cfg().MustGet(ctx, "ws.address").String()
ws := parser.GetOpt("ws", addr).String()
//port := parser.GetOpt("port", 0).Int()
if ws == "" {
listVar := g.Cfg().MustGet(ctx, "p2p.list")
var p2pItem []struct {
Host string `json:"host"`
Port int `json:"port"`
SSL bool `json:"ssl"`
Ws string `json:"ws"`
}
listVar.Scan(&p2pItem)
key := grand.Intn(len(p2pItem) - 1)
wsData := p2pItem[key]
ws = fmt.Sprintf("ws://%s:%d/ws", wsData.Host, wsData.Port)
}
port := parser.GetOpt("port", 0).Int()
if port > 0 {
s.SetPort(port)
}
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
@@ -66,8 +85,6 @@ var (
}
})
//s.SetPort(port)
// 延迟启动
gtimer.SetTimeout(ctx, time.Second*5, func(ctx context.Context) {
g.Log().Debug(ctx, "开始执行客户端")
@@ -75,12 +92,6 @@ var (
err = service.P2P().Start(ws)
g.Log().Debugf(ctx, "当前监听端口:%v", s.GetListenedPort())
//addrs, _ := net.InterfaceAddrs()
//for _, addr := range addrs {
// if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil {
// g.Log().Infof(ctx, "访问地址:http://%v:%d", ipnet.IP.String(), s.GetListenedPort())
// }
//}
})

View File

@@ -74,9 +74,6 @@ var (
case "client":
// 获取客户端模式所需的参数
g.Log().Debug(ctx, "开始执行client")
//addrs := []string{"/ip4/127.0.0.1/tcp/51888", "/ip4/192.168.50.173/tcp/51888"}
//addr := "/ip4/192.168.50.173/tcp/51888/p2p/12D3KooWJKBB9bF9MjqgsFYUUsPBG249FDq7a3ZdaYc9iw8G78JQ"
//addrs := "WyIvaXA0LzEyNy4wLjAuMS90Y3AvNTE4ODgiLCIvaXA0LzE5Mi4xNjguNTAuMTczL3RjcC81MTg4OCJd"
wsStr := "ws://192.168.50.173:51888/ws"
err = service.P2P().Start(wsStr)
case "dht":

View File

@@ -41,13 +41,14 @@ var (
// 拼接操作系统和架构格式OS_ARCH
platform := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)
var versionFile = make(map[string]string)
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("server_update", name, version, updatePlatform)
updateFilePath := path.Join(rootDir, name, version, updatePlatform)
var obj bytes.Buffer
g.Log().Debugf(ctx, "读取目录成功:%v", v)
@@ -70,14 +71,14 @@ var (
// 写入文件版本文件
fileByte := gjson.MustEncode(versionFile)
service.S3().PutObject(ctx, bytes.NewReader(fileByte), path.Join("server_update", name, "version.json"))
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("server_update", name, "version.json"))
versionUrl := service.S3().GetCdnUrl(path.Join(rootDir, name))
listVar := g.Cfg().MustGet(ctx, "p2p.list")
var p2pItem []struct {
Host string `json:"host"`
@@ -97,6 +98,7 @@ var (
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)

View File

@@ -2,73 +2,66 @@ package system
import (
"context"
"log"
"os"
"os/exec"
"path/filepath"
"time"
"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.Dump(fileSha)
g.Dump(getRunFile)
g.Log().Debugf(ctx, "当前文件哈希值:%v", fileSha)
go func() {
log.Println("5秒后开始重启...")
time.Sleep(5 * time.Second)
versionUrl, _ := url.JoinPath(req.Url, "version.json")
resp, err := g.Client().Get(ctx, versionUrl)
var version map[string]string
gjson.DecodeTo(resp.ReadAll(), &version)
if err = restartSelf(); err != nil {
log.Fatalf("重启失败:%v", err)
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
}
}
}()
//更新文件
err = service.System().Update(ctx)
type DataType struct {
File []byte `json:"file"`
Name string `json:"name"`
}
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)
return
}
// restartSelf 实现 Windows 平台下的程序自重启
func restartSelf() error {
// 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 // 理论上不会执行到这里
}

View File

@@ -8,4 +8,5 @@ import (
_ "github.com/ayflying/p2p/internal/logic/os"
_ "github.com/ayflying/p2p/internal/logic/p2p"
_ "github.com/ayflying/p2p/internal/logic/s3"
_ "github.com/ayflying/p2p/internal/logic/system"
)

View File

@@ -5,13 +5,14 @@ import (
"encoding/json"
"fmt"
"net"
"path"
"strconv"
"time"
"github.com/ayflying/p2p/internal/service"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gtimer"
"github.com/gogf/gf/v2/util/grand"
@@ -420,18 +421,23 @@ func (s *sP2P) receiveGatewayMessages(ctx context.Context) {
glog.Errorf(ctx, "网关错误: %s", data.Error)
case MsgUpdate: //更新节点信息
var msgData struct {
Server string `json:"server"`
Version string `json:"version"`
Files []struct {
File []byte `json:"file"`
Name string `json:"name"`
} `json:"files"`
}
//var msgData *dataType
json.Unmarshal(msg.Data, &msgData)
for _, v := range msgData.Files {
err = gfile.PutBytes(path.Join("download", v.Name), v.File)
}
// 更新器路径(假设与主程序同目录)
//updaterPath := filepath.Join(filepath.Dir(selfPath), "updater.exe")
g.Log().Infof(ctx, "更新节点信息: %v", data)
// 调用不同系统的更新服务
service.OS().Update(msgData.Version, msgData.Server)
//// 调用不同系统的更新服务
//service.OS().Update(msgData.Version, msgData.Server)
}
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"net"
"strings"
"time"
@@ -66,14 +65,14 @@ func (s *sP2P) GatewayStart(ctx context.Context, group *ghttp.RouterGroup) (err
_, data, _err := ws.ReadMessage()
if _err != nil {
//g.Log().Errorf(ctx, "读取消息失败: %v", err)
//s.sendError(ws, err.Error())
//s.SendError(ws, err.Error())
break
}
var msg GatewayMessage
var msg *GatewayMessage
if err = json.Unmarshal(data, &msg); err != nil {
//g.Log().Error(ctx, "消息格式错误")
s.sendError(ws, "消息格式错误")
s.SendError(ws, "消息格式错误")
continue
}
@@ -96,7 +95,7 @@ func (s *sP2P) GatewayStart(ctx context.Context, group *ghttp.RouterGroup) (err
}
// 处理注册请求
func (s *sP2P) handleRegister(ctx context.Context, conn *websocket.Conn, msg GatewayMessage) {
func (s *sP2P) handleRegister(ctx context.Context, conn *websocket.Conn, msg *GatewayMessage) {
if msg.From == "" {
g.Log().Error(ctx, "客户端ID不能为空")
return
@@ -108,22 +107,22 @@ func (s *sP2P) handleRegister(ctx context.Context, conn *websocket.Conn, msg Gat
}
if err := json.Unmarshal(msg.Data, &data); err != nil {
s.sendError(conn, "注册数据格式错误")
s.SendError(conn, "注册数据格式错误")
return
}
// 追加公网ip
publicIp, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
ParseIP := net.ParseIP(publicIp)
var ipType string
if ParseIP.To4() != nil {
ipType = "ip4"
} else {
ipType = "ip6"
}
port2 := 53533
data.Addrs = append(data.Addrs, fmt.Sprintf("/%s/%s/tcp/%d", ipType, publicIp, port2))
data.Addrs = append(data.Addrs, fmt.Sprintf("/%s/%s/udp/%d/quic-v1", ipType, publicIp, port2))
//// 追加公网ip
//publicIp, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
//ParseIP := net.ParseIP(publicIp)
//var ipType string
//if ParseIP.To4() != nil {
// ipType = "ip4"
//} else {
// ipType = "ip6"
//}
//port2 := 53533
//data.Addrs = append(data.Addrs, fmt.Sprintf("/%s/%s/tcp/%d", ipType, publicIp, port2))
//data.Addrs = append(data.Addrs, fmt.Sprintf("/%s/%s/udp/%d/quic-v1", ipType, publicIp, port2))
// 过滤回环地址
data.Addrs = s.filterLoopbackAddrs(data.Addrs)
@@ -144,12 +143,12 @@ func (s *sP2P) handleRegister(ctx context.Context, conn *websocket.Conn, msg Gat
glog.Infof(ctx, "客户端 ip=%s,%s 注册成功PeerID: %s", conn.RemoteAddr(), msg.From, data.PeerID)
// 发送注册成功响应
err := s.sendMessage(conn, GatewayMessage{
err := s.sendMessage(conn, &GatewayMessage{
Type: MsgTypeRegisterAck,
Data: json.RawMessage(`{"success": true, "message": "注册成功"}`),
})
if err != nil {
s.sendError(conn, err.Error())
s.SendError(conn, err.Error())
}
}
@@ -178,15 +177,41 @@ func (s *sP2P) cleanupClients(ctx context.Context) {
}
// 发送错误消息
func (s *sP2P) sendError(conn *websocket.Conn, errMsg string) {
s.sendMessage(conn, GatewayMessage{
func (s *sP2P) SendError(conn *websocket.Conn, errMsg string) {
s.sendMessage(conn, &GatewayMessage{
Type: "error",
Data: json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, errMsg)),
})
}
// SendAll 发送消息给所有客户端
func (s *sP2P) SendAll(typ string, data any) (err error) {
for _, client := range s.Clients {
conn := client.Conn
err = s.sendMessage(conn, &GatewayMessage{
Type: MsgType(typ),
Data: gjson.MustEncode(data),
})
if err != nil {
g.Log().Error(gctx.New(), err)
}
}
return
}
// Send 发送消息给指定客户端
func (s *sP2P) Send(conn *websocket.Conn, typ string, data any) (err error) {
err = s.sendMessage(conn, &GatewayMessage{
Type: MsgType(typ),
Data: gjson.MustEncode(data),
})
return
}
// 发送消息
func (s *sP2P) sendMessage(conn *websocket.Conn, msg GatewayMessage) error {
func (s *sP2P) sendMessage(conn *websocket.Conn, msg *GatewayMessage) error {
data, err := json.Marshal(msg)
if err != nil {
glog.Errorf(gctx.New(), "序列化消息失败: %v", err)
@@ -196,9 +221,9 @@ func (s *sP2P) sendMessage(conn *websocket.Conn, msg GatewayMessage) error {
}
// 处理发现请求
func (s *sP2P) handleDiscover(ctx context.Context, conn *websocket.Conn, msg GatewayMessage) {
func (s *sP2P) handleDiscover(ctx context.Context, conn *websocket.Conn, msg *GatewayMessage) {
if msg.From == "" {
s.sendError(conn, "消息缺少发送方IDfrom")
s.SendError(conn, "消息缺少发送方IDfrom")
return
}
@@ -207,12 +232,12 @@ func (s *sP2P) handleDiscover(ctx context.Context, conn *websocket.Conn, msg Gat
}
if err := json.Unmarshal(msg.Data, &data); err != nil {
s.sendError(conn, "发现请求格式错误需包含target_id")
s.SendError(conn, "发现请求格式错误需包含target_id")
return
}
if data.TargetID == "" {
s.sendError(conn, "目标ID不能为空")
s.SendError(conn, "目标ID不能为空")
return
}
@@ -225,7 +250,7 @@ func (s *sP2P) handleDiscover(ctx context.Context, conn *websocket.Conn, msg Gat
s.lock.RUnlock()
//if !fromExists {
// s.sendError(conn, "请先注册")
// s.SendError(conn, "请先注册")
// return
//}
@@ -236,11 +261,10 @@ func (s *sP2P) handleDiscover(ctx context.Context, conn *websocket.Conn, msg Gat
if targetClient == nil {
// 目标不存在
s.sendMessage(conn, GatewayMessage{
s.sendMessage(conn, &GatewayMessage{
Type: MsgTypeDiscoverAck,
From: "gateway",
To: msg.From,
//Data: json.RawMessage(`{"found": false}`),
Data: gjson.MustEncode(g.Map{
"found": false,
}),
@@ -249,7 +273,7 @@ func (s *sP2P) handleDiscover(ctx context.Context, conn *websocket.Conn, msg Gat
}
// 向请求方发送目标信息
s.sendMessage(conn, GatewayMessage{
s.sendMessage(conn, &GatewayMessage{
Type: MsgTypeDiscoverAck,
From: "gateway", // 发送方是网关
To: msg.From, // 接收方是原请求方
@@ -262,7 +286,7 @@ func (s *sP2P) handleDiscover(ctx context.Context, conn *websocket.Conn, msg Gat
})
// 向目标方发送打洞请求(协调时机)
s.sendMessage(targetClient.Conn, GatewayMessage{
s.sendMessage(targetClient.Conn, &GatewayMessage{
Type: MsgTypePunchRequest,
From: msg.From, // 发送方是原请求方
To: data.TargetID, // 接收方是目标方

View File

@@ -27,7 +27,7 @@ var (
// 常量定义
const (
ProtocolID string = "/ay"
DefaultPort = 51888
//DefaultPort = 51888
)
var (

View File

@@ -58,7 +58,10 @@ func New(_name ...string) *sS3 {
if len(_name) > 0 {
name = _name[0]
} else {
getName, _ := g.Cfg("local").Get(gctx.New(), "s3.type")
getName, err := g.Cfg("local").Get(gctx.New(), "s3.type")
if err != nil {
return nil
}
name = getName.String()
}

View File

@@ -0,0 +1,17 @@
package system
import (
"github.com/ayflying/p2p/internal/service"
)
type sSystem struct{}
func New() *sSystem {
return &sSystem{}
}
func init() {
service.RegisterSystem(New())
}
func (system *sSystem) Init() {}

View File

@@ -0,0 +1,98 @@
package system
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"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"
)
func (s *sSystem) Update(ctx context.Context) (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)
gz := path.Join("download", platform+".gz")
err = gcompress.UnGzipFile(gz, runFile)
go func() {
log.Println("5秒后开始重启...")
time.Sleep(5 * time.Second)
if err = service.System().RestartSelf(); err != nil {
log.Fatalf("重启失败:%v", err)
}
}()
return
}
// RestartSelf 实现 Windows 平台下的程序自重启
func (s *sSystem) RestartSelf() error {
// 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 重命名正在运行的程序文件(如 p2p.exe → p2p.exe~
func (s *sSystem) RenameRunningFile(exePath string) (string, error) {
// 目标备份文件名p2p.exe → p2p.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
}

View File

@@ -9,6 +9,7 @@ import (
"context"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gorilla/websocket"
"github.com/libp2p/go-libp2p/core/host"
)
@@ -28,6 +29,12 @@ type (
// FindFromDHT 从 DHT 查找数据(从网络节点获取)
FindFromDHT(ctx context.Context, key string) (string, error)
GatewayStart(ctx context.Context, group *ghttp.RouterGroup) (err error)
// 发送错误消息
SendError(conn *websocket.Conn, errMsg string)
// SendAll 发送消息给所有客户端
SendAll(typ string, data any) (err error)
// Send 发送消息给指定客户端
Send(conn *websocket.Conn, typ string, data any) (err error)
// 只获取IPv4公网IP过滤IPv6结果
GetIPv4PublicIP() (string, error)
}

View File

@@ -0,0 +1,36 @@
// ================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// You can delete these comments if you wish manually maintain this interface file.
// ================================================================================
package service
import (
"context"
)
type (
ISystem interface {
Init()
Update(ctx context.Context) (err error)
// RestartSelf 实现 Windows 平台下的程序自重启
RestartSelf() error
// RenameRunningFile 重命名正在运行的程序文件(如 p2p.exe → p2p.exe~
RenameRunningFile(exePath string) (string, error)
}
)
var (
localSystem ISystem
)
func System() ISystem {
if localSystem == nil {
panic("implement not found for interface ISystem, forgot register?")
}
return localSystem
}
func RegisterSystem(i ISystem) {
localSystem = i
}

36
main.go
View File

@@ -1,9 +1,15 @@
package main
import (
"fmt"
"os"
_ "github.com/ayflying/p2p/internal/logic"
_ "github.com/ayflying/p2p/internal/packed"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
//步骤1加载驱动
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
@@ -17,9 +23,39 @@ var (
)
func main() {
g.Log().Infof(ctx, "启动文件最后修改时间:%v", gtime.New(gfile.MTime(gcmd.GetArg(0).String())).String())
g.Dump("v1.0.0.2")
if ok := gfile.Exists("runtime"); !ok {
gfile.Mkdir("runtime")
}
//daili()
cmd.Main.Run(ctx)
}
func daili() {
// 读取HTTP代理环境变量小写/大写通常都兼容,部分系统可能用大写)
httpProxy := os.Getenv("http_proxy")
if httpProxy == "" {
httpProxy = os.Getenv("HTTP_PROXY")
}
// 读取HTTPS代理环境变量
httpsProxy := os.Getenv("https_proxy")
if httpsProxy == "" {
httpsProxy = os.Getenv("HTTPS_PROXY")
}
// 读取无代理列表(不使用代理的域名/IP
noProxy := os.Getenv("no_proxy")
if noProxy == "" {
noProxy = os.Getenv("NO_PROXY")
}
fmt.Printf("HTTP 代理: %s\n", httpProxy)
fmt.Printf("HTTPS 代理: %s\n", httpsProxy)
fmt.Printf("无代理列表: %s\n", noProxy)
}