diff --git a/common/cool/coolconfig/config.go b/common/cool/coolconfig/config.go index 1fcd1e1d..6a9fff82 100644 --- a/common/cool/coolconfig/config.go +++ b/common/cool/coolconfig/config.go @@ -40,8 +40,9 @@ type ServerList struct { WeatherRate uint8 `gorm:"default:0;comment:'天气概率'" json:"weather_rate"` //服务器属主Desc - Owner uint32 `gorm:"comment:'服务器属主'" json:"owner"` - Desc string `gorm:"comment:'服务器描述'" json:"desc"` + Owner uint32 `gorm:"comment:'服务器属主'" json:"owner"` + Desc string `gorm:"comment:'服务器描述'" json:"desc"` + OldScreen string `gorm:"comment:'服务器screen参数'" json:"old_screen"` } // OSS相关配置 diff --git a/logic/service/fight/info/info.go b/logic/service/fight/info/info.go index 9fd436ba..42233819 100644 --- a/logic/service/fight/info/info.go +++ b/logic/service/fight/info/info.go @@ -99,6 +99,7 @@ func NewAttackValue(userid uint32) *AttackValue { 0, [20]int8{}, [6]int8{}, + 0, } } @@ -118,7 +119,8 @@ type AttackValue struct { IsCritical uint32 `json:"isCritical" fieldDescription:"是否暴击"` Status [20]int8 //精灵的状态 // 攻击,防御,特供,特防,速度,命中 - Prop [6]int8 + Prop [6]int8 + Offensive float32 // OwnerMaxShield uint32 `json:"ownerMaxShield" fieldDescription:"我方最大护盾"` // OwnerCurrentShield uint32 `json:"ownerCurrentShield" fieldDescription:"我方当前护盾"` } diff --git a/logic/service/fight/input/fight.go b/logic/service/fight/input/fight.go index 6035a0e6..fcee2ec5 100644 --- a/logic/service/fight/input/fight.go +++ b/logic/service/fight/input/fight.go @@ -9,6 +9,7 @@ import ( "blazing/logic/service/fight/info" "github.com/alpacahq/alpacadecimal" + "github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/grand" ) @@ -318,6 +319,7 @@ func (our *Input) CalculatePower(deftype *Input, skill *info.SkillEntity) alpaca var typeRate alpacadecimal.Decimal //fmt.Println(skill.Type().ID, deftype.CurrentPet.Type().ID) t, _ := element.Calculator.GetOffensiveMultiplier(skill.GetType().ID, deftype.CurrentPet.GetType().ID) + our.AttackValue.Offensive = gconv.Float32(t) typeRate = alpacadecimal.NewFromFloat(t) // 8. DmgBindLv: 使对方受到的伤害值等于等级; 默认: 0 diff --git a/manifest/config/config.yaml b/manifest/config/config.yaml index 5a01d064..d3c9c898 100644 --- a/manifest/config/config.yaml +++ b/manifest/config/config.yaml @@ -76,7 +76,7 @@ modules: base: jwt: sso: false - secret: "cool-base88776655" + secret: "sun-base88776655" token: expire: 10 # 2*3600 refreshExpire: 1296000 # 24*3600*15 diff --git a/modules/base/middleware/authority.go b/modules/base/middleware/authority.go index 359adb88..60548149 100644 --- a/modules/base/middleware/authority.go +++ b/modules/base/middleware/authority.go @@ -43,6 +43,9 @@ func BaseAuthorityMiddleware(r *ghttp.Request) { } tokenString := r.GetHeader("Authorization") + if tokenString == "" { + tokenString = r.URL.Query().Get("Authorization") + } token, err := jwt.ParseWithClaims(tokenString, &cool.Claims{}, func(token *jwt.Token) (interface{}, error) { return []byte(config.Config.Jwt.Secret), nil diff --git a/modules/base/middleware/middleware.go b/modules/base/middleware/middleware.go index 8d1c54da..8dede040 100644 --- a/modules/base/middleware/middleware.go +++ b/modules/base/middleware/middleware.go @@ -1,13 +1,16 @@ package middleware import ( + "blazing/cool" "blazing/modules/base/config" + "blazing/modules/config/service" "fmt" "net/http" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/util/gconv" + "github.com/golang-jwt/jwt/v4" "github.com/lxzan/gws" ) @@ -42,61 +45,33 @@ func init() { if config.Config.Middleware.Log.Enable { g.Server().BindMiddleware("/admin/*", BaseLog) } - g.Server().BindHandler("/ws/*", func(r *ghttp.Request) { - urls := r.URL.Query().Get("port") - fmt.Println(urls) - tt := new(Handler) - tt.port = gconv.Int(urls) - upgrader := gws.NewUpgrader(tt, &gws.ServerOption{ - //CompressEnabled: true, - - // 在querystring里面传入用户名 - // 把Sec-WebSocket-Key作为连接的key - - // 刷新页面的时候, 会触发上一个连接的OnClose/OnError事件, 这时候需要对比key并删除map里存储的连接 - Authorize: func(rt *http.Request, session gws.SessionStorage) bool { - //r.s\\\ - // r.Get("t") - //admin := cool.GetAdmin(r.Context()) - // var name = r.URL.Query().Get("name") - // if name == "" { - // return false - // } - // t, _ := service.NewBaseSysUserService().Person(admin.UserID) - - //Loger.Debug(context.TODO(), t.Mimi) - // session.Store("name", t.Mimi) - //session.Store("key", r.Header.Get("Sec-WebSocket-Key")) - return true - }, - }) - - socket, err := upgrader.Upgrade(r.Response.Writer, r.Request) - if err != nil { - fmt.Println(err) - return - } - // ants.Submit(func() { - // socket.ReadLoop() - // }) - // ants.Submit(func() { socket.ReadLoop() }) - go socket.ReadLoop() - - }) g.Server().BindHandler("/server/*", func(r *ghttp.Request) { tt := new(ServerHandler) + id := gconv.Uint32(r.URL.Query().Get("id")) + tt.ServerList = service.NewServerService().GetServerByID(id) + tt.isinstall = gconv.Uint32(r.URL.Query().Get("isinstall")) upgrader := gws.NewUpgrader(tt, &gws.ServerOption{ Authorize: func(rt *http.Request, session gws.SessionStorage) bool { - //r.s\\\ - // r.Get("t") - // admin := cool.GetAdmin(rt.Context()) - // if admin.UserId != 10001 { - // return false - // } + + tokenString := r.URL.Query().Get("Authorization") + token, err := jwt.ParseWithClaims(tokenString, &cool.Claims{}, func(token *jwt.Token) (interface{}, error) { + + return []byte(config.Config.Jwt.Secret), nil + }) + if err != nil { + return false + } + if !token.Valid { + return false + } + admin := token.Claims.(*cool.Claims) + if admin.UserId != 10001 { + return false + } // var name = r.URL.Query().Get("name") // if name == "" { diff --git a/modules/base/middleware/server.go b/modules/base/middleware/server.go index a678c417..bb1e4f84 100644 --- a/modules/base/middleware/server.go +++ b/modules/base/middleware/server.go @@ -1,97 +1,495 @@ package middleware import ( + "blazing/modules/config/model" + config "blazing/modules/config/service" + "bufio" "context" + "crypto/rand" + "encoding/hex" "encoding/json" + "fmt" + "io" "net" + "strings" + "sync/atomic" "time" - "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/glog" - "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/grand" "github.com/lxzan/gws" + "golang.org/x/crypto/ssh" ) +const ( + PingInterval = 10 * time.Second + defaultWorkDir = "$HOME" // 全环境兼容 + randomStrLength = 4 // 缩短随机串长度 + cmdTimeout = 120 * time.Second // 延长超时(适配Screen安装+下载) +) + +// SSHConfig SSH连接配置 +type SSHConfig struct { + ServerIP string `json:"server_ip"` + Port string `json:"port"` + Username string `json:"username"` + Password string `json:"password"` +} + +// WebSSHMessage 消息结构 +type WebSSHMessage struct { + Type string `json:"type"` + Payload string `json:"payload"` +} + +// TerminalSession 会话结构体(优化通道) +type TerminalSession struct { + WebSocket *gws.Conn + SSHClient *ssh.Client + SSHSession *ssh.Session + Stdin io.WriteCloser + Stdout io.Reader + Ready bool + outputBuf chan string // 增大通道缓存 + closed atomic.Bool +} + type ServerHandler struct { gws.BuiltinEventHandler - port int - target net.Conn + session *TerminalSession + model.ServerList + isinstall uint32 + target net.Conn } -func (c *ServerHandler) OnOpen(socket *gws.Conn) { - // target, err := net.Dial("tcp", "127.0.0.1:"+gconv.String(c.port)) - - // if err != nil { - // glog.Debug(context.Background(), "连接失败") - // } - // c.target = target - // //errChan := make(chan error, 2) - // if c.target == nil { - // return - // } - // go func(conn net.Conn, socket *gws.Conn) { - // reader := bufio.NewReader(conn) - // LOOP: - // for { - - // // select { - // // default: - // packlen, err := reader.Peek(4) - - // if err != nil { - // socket.WriteClose(1000, nil) - // break LOOP - // } - - // length := int32(binary.BigEndian.Uint32(packlen)) - - // data := make([]byte, length) - // io.ReadFull(reader, data) - - // //pack_Ver := data[4] //因为包体已经解析,所以这里直接取0 - // // var pack = make([]byte, length) - - // socket.WriteMessage(gws.OpcodeBinary, data) - // //t.event.RecvServerHandler(pack) - // // client.OnReceiveBase(client, pack, length) - - // } - // }(c.target, socket) - // //err = <-errChan - // if err != io.EOF { - // log.Println("proxy error:", err) - - // } -} - -func (c *ServerHandler) OnPing(socket *gws.Conn, payload []byte) { - _ = socket.SetDeadline(time.Now().Add(2 * PingInterval)) - _ = socket.WritePong(nil) -} - -func (c *ServerHandler) OnPong(socket *gws.Conn, payload []byte) {} - -func (c *ServerHandler) OnMessage(socket *gws.Conn, gwsmessage *gws.Message) { - //socket.WriteMessage(gwsmessage.Opcode, gwsmessage.Bytes()) - - for i := 0; i < 4; i++ { - jsondata := g.Map{"type": 1, "message": "hel " + gconv.String(i)} - json, _ := json.Marshal(jsondata) - socket.WriteMessage(gwsmessage.Opcode, json) +// 生成随机文件名 +func (s *ServerHandler) generateRandomFileName() string { + randBytes := make([]byte, randomStrLength) + _, err := rand.Read(randBytes) + if err != nil { + return fmt.Sprintf("logic_%d", time.Now().UnixNano()) } - jsondata := g.Map{"type": 0, "message": "hel"} - json, _ := json.Marshal(jsondata) - socket.WriteMessage(gwsmessage.Opcode, json) - //fmt.Println(gwsmessage.Bytes()) - + randStr := hex.EncodeToString(randBytes) + timestamp := time.Now().Format("20060102150405") + return fmt.Sprintf("logic_%s_%s", timestamp, randStr) } -func (c *ServerHandler) OnClose(socket *gws.Conn, err error) { - - glog.Debug(context.Background(), "断开连接") - if c.target != nil { - c.target.Close() - +// 执行脚本命令 +func (s *ServerHandler) executeScript(scriptContent, scriptName string) (string, error) { + if s.session == nil || s.session.closed.Load() || !s.session.Ready { + return "", fmt.Errorf("session not ready") } + // 生成临时脚本路径 + scriptPath := fmt.Sprintf("/tmp/%s.sh", scriptName) + + // 直接将脚本内容写入文件 + writeScriptCmd := fmt.Sprintf("cat > '%s' << 'DEPLOYMENT_SCRIPT_END'\n%s\nDEPLOYMENT_SCRIPT_END\n", scriptPath, scriptContent) + _, err := s.session.Stdin.Write([]byte(writeScriptCmd)) + if err != nil { + return "", err + } + + // 等待一会儿确保脚本写入完成 + time.Sleep(time.Second) + + // 设置执行权限 + _, err = s.session.Stdin.Write([]byte(fmt.Sprintf("chmod +x %s\n", scriptPath))) + if err != nil { + return "", err + } + + // 等待权限设置完成 + time.Sleep(time.Second) + + // 执行脚本并将输出发送到WebSocket(在命令中直接删除脚本文件) + executeCmd := fmt.Sprintf("bash %s 2>&1; rm -f %s\n", scriptPath, scriptPath) + _, err = s.session.Stdin.Write([]byte(executeCmd)) + if err != nil { + return "", err + } + + // 读取脚本执行输出 + output := "" + done := make(chan bool) + + go func() { + defer func() { + if r := recover(); r != nil { + glog.Error(context.Background(), "Script execution goroutine panic:", r) + } + }() + + scanner := bufio.NewScanner(s.session.Stdout) + for scanner.Scan() { + line := scanner.Text() + + // 检测到脚本执行完成标记则退出 + if strings.Contains(line, "#SCRIPT_EXECUTION_COMPLETE#") { + break + } + + // 忽略一些可能导致连接断开的输出 + if strings.Contains(line, "logout") || strings.Contains(line, "exit") { + continue + } + + output += line + "\n" + s.sendTerminalOutput(s.session.WebSocket, line) + } + done <- true + }() + + // 等待脚本执行完成或超时 + select { + case <-done: + return strings.TrimSpace(output), nil + case <-time.After(cmdTimeout): + return strings.TrimSpace(output), nil + } +} + +// 执行完整的自动化部署流程 +func (s *ServerHandler) executeFullDeployment() error { + s.sendTerminalOutput(s.session.WebSocket, "开始执行完整自动化部署流程...") + + // 获取下载链接 + fileURL := config.NewServerService().GetFile() + if strings.TrimSpace(fileURL) == "" { + return fmt.Errorf("下载链接为空") + } + + // 生成随机文件名 + randomFileName := s.generateRandomFileName() + remoteExePath := fmt.Sprintf("%s/%s", defaultWorkDir, randomFileName) + onlineID := fmt.Sprintf("%d", s.ServerList.OnlineID) + + // 创建完整的部署脚本(不包含#!/bin/bash) + deploymentScript := fmt.Sprintf(` + +set -e # 遇到错误立即退出 + +# ===== 检查并安装 screen ===== +echo "检查Screen是否已安装..." +if command -v screen &> /dev/null; then + echo "=== Screen已安装,跳过 ===" +else + echo "=== Screen未安装,开始安装 ===" + + if command -v apt &> /dev/null; then + echo "检测到apt,正在更新软件包列表..." + apt update -y > /dev/null 2>&1 + echo "正在安装screen..." + apt install -y screen > /dev/null 2>&1 + elif command -v yum &> /dev/null; then + echo "检测到yum,正在安装screen..." + yum install -y screen > /dev/null 2>&1 + elif command -v dnf &> /dev/null; then + echo "检测到dnf,正在安装screen..." + dnf install -y screen > /dev/null 2>&1 + elif command -v pacman &> /dev/null; then + echo "检测到pacman,正在安装screen..." + pacman -S --noconfirm screen > /dev/null 2>&1 + else + echo "=== 不支持的系统包管理器,无法自动安装Screen ===" + exit 1 + fi + + if command -v screen &> /dev/null; then + echo "=== screen 安装成功! ===" + else + echo "=== screen 安装失败 ===" + exit 1 + fi +fi + +# ===== 停止旧Screen会话 ===== +if [ -n "%s" ] && [ "%s" != "" ]; then + echo "检查并停止旧会话: %s" + if screen -ls | grep -q "%s"; then + echo "发现旧会话,正在停止: %s" + screen -S %s -X quit 2>/dev/null || true + pkill -f "%s" 2>/dev/null || true + echo "=== 旧会话已停止 ===" + else + echo "=== 旧会话不存在,无需停止 ===" + fi +else + echo "无旧Screen会话,跳过停止" +fi + +# ===== 下载程序 ===== +echo "开始下载程序到: %s" + +# 删除旧文件 +if [ -f "%s" ]; then + echo "删除旧文件..." + rm -f "%s" +fi + +echo "开始下载..." + +if command -v wget >/dev/null 2>&1; then + wget --no-check-certificate -O "%s" "%s" 2>&1 +elif command -v curl >/dev/null 2>&1; then + curl -L -o "%s" "%s" 2>&1 +else + echo "系统未安装wget或curl" + exit 1 +fi + +# 验证文件 +if [ -f "%s" ] && [ -s "%s" ]; then + echo "=== 文件下载完成 ===" + ls -la "%s" +else + echo "=== 文件下载失败!=== " + echo "=== 请检查下载链接是否有效 ===" + exit 1 +fi + +# ===== 启动新程序 ===== +echo "开始启动新程序..." + +# 设置执行权限 +chmod +x "%s" +if [ $? -ne 0 ]; then + echo "设置执行权限失败" + exit 1 +fi +echo "=== 权限设置完成 ===" + +# 启动Screen会话 +echo "正在启动Screen会话: %s" +screen -dmS "%s" bash -c '"%s" -id=%s | tee -a "$HOME/run.log"' + +# 等待一段时间确保会话启动 +sleep 3 + +# 检查会话是否存在 +if screen -ls | grep -q "%s"; then + echo "=== 程序启动成功:Screen会话已创建 ===" + echo "=== 会话名称:%s ===" + screen -ls + echo "程序已在后台Screen会话中运行" +else + echo "=== 程序启动失败:Screen会话未创建 ===" + screen -ls + exit 1 +fi + +echo "#SCRIPT_EXECUTION_COMPLETE#" +`, + s.ServerList.OldScreen, s.ServerList.OldScreen, s.ServerList.OldScreen, + s.ServerList.OldScreen, s.ServerList.OldScreen, s.ServerList.OldScreen, + s.ServerList.OldScreen, + remoteExePath, + remoteExePath, remoteExePath, + remoteExePath, fileURL, + remoteExePath, fileURL, + remoteExePath, remoteExePath, remoteExePath, + remoteExePath, + randomFileName, randomFileName, remoteExePath, onlineID, + randomFileName, randomFileName) + + // 执行完整的部署脚本 + _, err := s.executeScript(deploymentScript, "full_deployment_"+grand.S(10)) + if err != nil { + return err + } + + // 保存会话名称 + config.NewServerService().SetServerScreen(s.ServerList.OnlineID, randomFileName) + s.sendTerminalOutput(s.session.WebSocket, "自动化部署完成") + + return nil +} + +// executeCommand 执行单个命令并返回输出 +func (s *ServerHandler) executeCommand(command string) (string, error) { + return s.executeScript(command, "cmd_"+grand.S(8)) +} + +// ---------------- 主流程:OnOpen(严格顺序执行) ---------------- +func (s *ServerHandler) OnOpen(socket *gws.Conn) { + // 1. 建立SSH连接 + sshConfig := &ssh.ClientConfig{ + User: s.ServerList.Account, + Auth: []ssh.AuthMethod{ssh.Password(s.ServerList.Password)}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Timeout: 30 * time.Second, + } + + // 连接服务器(重试) + var sshClient *ssh.Client + var err error + for retry := 0; retry < 2; retry++ { + sshClient, err = ssh.Dial("tcp", s.ServerList.LoginAddr, sshConfig) + if err == nil { + break + } + time.Sleep(3 * time.Second) + } + if err != nil { + s.sendError(socket, "SSH连接失败:"+err.Error()) + return + } + + // 创建会话(重试) + var session *ssh.Session + for retry := 0; retry < 2; retry++ { + session, err = sshClient.NewSession() + if err == nil { + break + } + time.Sleep(3 * time.Second) + } + if err != nil { + session.Close() + sshClient.Close() + s.sendError(socket, "创建SSH会话失败:"+err.Error()) + return + } + + // 配置PTY(通用配置) + modes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 115200, + ssh.TTY_OP_OSPEED: 115200, + } + err = session.RequestPty("vt100", 100, 80, modes) + if err != nil { + session.Close() + sshClient.Close() + s.sendError(socket, "PTY请求失败:"+err.Error()) + return + } + + // 获取Stdin/Stdout + stdin, err := session.StdinPipe() + if err != nil { + session.Close() + sshClient.Close() + s.sendError(socket, "获取Stdin失败:"+err.Error()) + return + } + stdout, err := session.StdoutPipe() + if err != nil { + session.Close() + sshClient.Close() + s.sendError(socket, "获取Stdout失败:"+err.Error()) + return + } + + // 启动Shell + err = session.Shell() + if err != nil { + session.Close() + sshClient.Close() + s.sendError(socket, "启动Shell失败:"+err.Error()) + return + } + + // 初始化会话 + s.session = &TerminalSession{ + WebSocket: socket, + SSHClient: sshClient, + SSHSession: session, + Stdin: stdin, + Stdout: stdout, + Ready: true, + outputBuf: make(chan string, 2048), // 增大缓存 + closed: atomic.Bool{}, + } + + // 启动输出转发协程 + s.startOutputForwarding() + + s.sendSuccess(socket, "SSH连接成功,会话已就绪") + + // ---------------- 严格按顺序执行自动化部署 ---------------- + if s.isinstall == 1 { + // 执行完整的自动化部署脚本 + err := s.executeFullDeployment() + if err != nil { + s.sendError(socket, "自动化部署失败: "+err.Error()) + return + } + + s.sendSuccess(socket, "自动化部署完成") + } +} + +// startOutputForwarding 启动输出转发协程 +func (s *ServerHandler) startOutputForwarding() { + if s.session == nil { + return + } + + go func() { + defer func() { + if r := recover(); r != nil { + glog.Error(context.Background(), "Output forwarding goroutine panic:", r) + } + }() + + scanner := bufio.NewScanner(s.session.Stdout) + for scanner.Scan() { + line := scanner.Text() + s.sendTerminalOutput(s.session.WebSocket, line+"\r\n") + } + + if err := scanner.Err(); err != nil && s.session != nil { + glog.Error(context.Background(), "读取 SSH 输出失败:", err) + s.sendError(s.session.WebSocket, "读取 SSH 输出失败: "+err.Error()) + } + }() +} + +// ---------------- 基础函数 ---------------- +func (s *ServerHandler) OnMessage(socket *gws.Conn, gwsmessage *gws.Message) { + if s.session == nil || s.session.Stdin == nil { + s.sendError(socket, "SSH 会话未建立") + return + } + + _, err := s.session.Stdin.Write([]byte(gwsmessage.Data.Bytes())) + if err != nil { + s.sendError(socket, "写入消息失败:"+err.Error()) + } +} + +func (s *ServerHandler) OnClose(socket *gws.Conn, err error) { + glog.Debug(context.Background(), "连接断开:", err) + if s.session != nil { + s.session.closed.Store(true) + if s.session.SSHSession != nil { + s.session.SSHSession.Close() + } + if s.session.SSHClient != nil { + s.session.SSHClient.Close() + } + s.session = nil + } +} + +// 发送终端输出 +func (s *ServerHandler) sendTerminalOutput(ws *gws.Conn, output string) { + msg := WebSSHMessage{Type: "output", Payload: output} + data, _ := json.Marshal(msg) + ws.WriteMessage(gws.OpcodeText, data) +} + +// 发送成功消息 +func (s *ServerHandler) sendSuccess(ws *gws.Conn, msg string) { + res := WebSSHMessage{Type: "success", Payload: msg} + data, _ := json.Marshal(res) + ws.WriteMessage(gws.OpcodeText, data) +} + +// 发送错误消息 +func (s *ServerHandler) sendError(ws *gws.Conn, msg string) { + res := WebSSHMessage{Type: "error", Payload: msg} + data, _ := json.Marshal(res) + ws.WriteMessage(gws.OpcodeText, data) } diff --git a/modules/base/middleware/websocket.go b/modules/base/middleware/websocket.go deleted file mode 100644 index 0ae4aeb4..00000000 --- a/modules/base/middleware/websocket.go +++ /dev/null @@ -1,306 +0,0 @@ -package middleware - -import ( - "bufio" - "context" - "encoding/json" - "fmt" - "io" - "sync" - "time" - - "github.com/gogf/gf/v2/os/glog" - "github.com/lxzan/gws" - "golang.org/x/crypto/ssh" -) - -const PingInterval = 10 * time.Second - -// SSHConfig SSH 连接配置 -type SSHConfig struct { - ServerIP string `json:"server_ip"` - Port string `json:"port"` - Username string `json:"username"` - Password string `json:"password"` -} - -// WebSSHMessage WebSSH 消息结构 -type WebSSHMessage struct { - Type string `json:"type"` // "connect", "command", "resize", "disconnect" - Payload string `json:"payload"` // JSON encoded data -} - -// TerminalSession 终端会话 -type TerminalSession struct { - WebSocket *gws.Conn - SSHClient *ssh.Client - SSHSession *ssh.Session - Stdin io.WriteCloser - Stdout io.Reader - Mutex sync.Mutex -} - -type Handler struct { - gws.BuiltinEventHandler - session *TerminalSession // 保存当前会话 -} - -// OnOpen 连接建立时的处理 -func (c *Handler) OnOpen(socket *gws.Conn) { - _ = socket.SetReadDeadline(time.Now().Add(60 * time.Second)) - glog.Info(context.Background(), "WebSSH WebSocket 连接建立") -} - -// OnMessage 处理收到的消息 -func (c *Handler) OnMessage(socket *gws.Conn, message *gws.Message) { - defer message.Close() - - // 解析消息 - var webSSHMsg WebSSHMessage - err := json.Unmarshal(message.Data, &webSSHMsg) - if err != nil { - glog.Error(context.Background(), "解析 WebSSH 消息失败:", err) - c.sendError(socket, "无效的消息格式: "+err.Error()) - return - } - - switch webSSHMsg.Type { - case "connect": - c.handleConnect(socket, webSSHMsg.Payload) - case "command": - c.handleCommand(socket, webSSHMsg.Payload) - case "resize": - c.handleResize(socket, webSSHMsg.Payload) - case "disconnect": - c.handleDisconnect(socket) - default: - glog.Warning(context.Background(), "未知的 WebSSH 消息类型:", webSSHMsg.Type) - } -} - -// handleConnect 处理连接请求 -func (c *Handler) handleConnect(socket *gws.Conn, payload string) { - var config SSHConfig - err := json.Unmarshal([]byte(payload), &config) - if err != nil { - glog.Error(context.Background(), "解析 SSH 配置失败:", err) - c.sendError(socket, "无效的 SSH 配置: "+err.Error()) - return - } - - // 设置默认端口 - if config.Port == "" { - config.Port = "22" - } - - // SSH 客户端配置 - sshConfig := &ssh.ClientConfig{ - User: config.Username, - Auth: []ssh.AuthMethod{ - ssh.Password(config.Password), - }, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 生产环境应使用更安全的验证 - Timeout: 30 * time.Second, - } - - // 连接到 SSH 服务器 - addr := fmt.Sprintf("%s:%s", config.ServerIP, config.Port) - sshClient, err := ssh.Dial("tcp", addr, sshConfig) - if err != nil { - glog.Error(context.Background(), "SSH 连接失败:", err) - c.sendError(socket, "SSH 连接失败: "+err.Error()) - return - } - - // 创建 SSH 会话 - session, err := sshClient.NewSession() - if err != nil { - glog.Error(context.Background(), "创建 SSH 会话失败:", err) - sshClient.Close() - c.sendError(socket, "创建 SSH 会话失败: "+err.Error()) - return - } - - // 设置终端 - modes := ssh.TerminalModes{ - ssh.ECHO: 1, - ssh.TTY_OP_ISPEED: 14400, - ssh.TTY_OP_OSPEED: 14400, - } - - err = session.RequestPty("xterm", 80, 40, modes) - if err != nil { - glog.Error(context.Background(), "请求 PTY 失败:", err) - session.Close() - sshClient.Close() - c.sendError(socket, "请求 PTY 失败: "+err.Error()) - return - } - - // 获取标准输入输出 - stdin, err := session.StdinPipe() - if err != nil { - glog.Error(context.Background(), "获取 SSH 输入管道失败:", err) - session.Close() - sshClient.Close() - c.sendError(socket, "获取 SSH 输入管道失败: "+err.Error()) - return - } - - stdout, err := session.StdoutPipe() - if err != nil { - glog.Error(context.Background(), "获取 SSH 输出管道失败:", err) - session.Close() - sshClient.Close() - c.sendError(socket, "获取 SSH 输出管道失败: "+err.Error()) - return - } - - // 启动会话 - err = session.Shell() - if err != nil { - glog.Error(context.Background(), "启动 SSH Shell 失败:", err) - session.Close() - sshClient.Close() - c.sendError(socket, "启动 SSH Shell 失败: "+err.Error()) - return - } - - // 保存会话信息 - c.session = &TerminalSession{ - WebSocket: socket, - SSHClient: sshClient, - SSHSession: session, - Stdin: stdin, - Stdout: stdout, - } - - // 启动输出转发协程 - c.startOutputForwarding() - - c.sendSuccess(socket, "SSH 连接成功") -} - -// startOutputForwarding 启动输出转发协程 -func (c *Handler) startOutputForwarding() { - if c.session == nil { - return - } - - go func() { - scanner := bufio.NewScanner(c.session.Stdout) - for scanner.Scan() { - line := scanner.Text() - c.sendTerminalOutput(c.session.WebSocket, line+"\r\n") - } - - if err := scanner.Err(); err != nil && c.session != nil { - glog.Error(context.Background(), "读取 SSH 输出失败:", err) - c.sendError(c.session.WebSocket, "读取 SSH 输出失败: "+err.Error()) - } - }() -} - -// handleCommand 处理命令输入 -func (c *Handler) handleCommand(socket *gws.Conn, payload string) { - if c.session == nil || c.session.Stdin == nil { - c.sendError(socket, "SSH 会话未建立") - return - } - - _, err := c.session.Stdin.Write([]byte(payload)) - if err != nil { - glog.Error(context.Background(), "写入 SSH 命令失败:", err) - c.sendError(socket, "写入 SSH 命令失败: "+err.Error()) - } -} - -// handleResize 处理终端大小调整 -func (c *Handler) handleResize(socket *gws.Conn, payload string) { - if c.session == nil || c.session.SSHSession == nil { - c.sendError(socket, "SSH 会话未建立") - return - } - - // 解析终端大小 - var size struct { - Width int `json:"width"` - Height int `json:"height"` - } - err := json.Unmarshal([]byte(payload), &size) - if err != nil { - c.sendError(socket, "无效的终端大小参数") - return - } - - // 调整终端大小 - err = c.session.SSHSession.WindowChange(size.Height, size.Width) - if err != nil { - glog.Error(context.Background(), "调整终端大小失败:", err) - c.sendError(socket, "调整终端大小失败: "+err.Error()) - return - } - - c.sendSuccess(socket, "终端大小已调整") -} - -// handleDisconnect 处理断开连接 -func (c *Handler) handleDisconnect(socket *gws.Conn) { - c.cleanup() - c.sendSuccess(socket, "已断开连接") -} - -// cleanup 清理会话资源 -func (c *Handler) cleanup() { - if c.session != nil { - if c.session.SSHSession != nil { - c.session.SSHSession.Close() - } - if c.session.SSHClient != nil { - c.session.SSHClient.Close() - } - c.session = nil - } -} - -// sendTerminalOutput 发送终端输出到 WebSocket -func (c *Handler) sendTerminalOutput(ws *gws.Conn, output string) { - msg := WebSSHMessage{ - Type: "output", - Payload: output, - } - data, _ := json.Marshal(msg) - ws.WriteMessage(gws.OpcodeText, data) -} - -// sendSuccess 发送成功消息 -func (c *Handler) sendSuccess(ws *gws.Conn, message string) { - msg := WebSSHMessage{ - Type: "success", - Payload: message, - } - data, _ := json.Marshal(msg) - ws.WriteMessage(gws.OpcodeText, data) -} - -// sendError 发送错误消息 -func (c *Handler) sendError(ws *gws.Conn, message string) { - msg := WebSSHMessage{ - Type: "error", - Payload: message, - } - data, _ := json.Marshal(msg) - ws.WriteMessage(gws.OpcodeText, data) -} - -func (c *Handler) OnPing(socket *gws.Conn, payload []byte) { - _ = socket.SetDeadline(time.Now().Add(2 * PingInterval)) - _ = socket.WritePong(nil) -} - -func (c *Handler) OnPong(socket *gws.Conn, payload []byte) {} - -func (c *Handler) OnClose(socket *gws.Conn, err error) { - glog.Debug(context.Background(), "WebSSH 连接断开:", err) - c.cleanup() -} diff --git a/modules/config/service/server.go b/modules/config/service/server.go index 9810c09d..8a726c96 100644 --- a/modules/config/service/server.go +++ b/modules/config/service/server.go @@ -78,7 +78,19 @@ func (s *ServerService) GetServer() []model.ServerList { return item } -func (s *ServerService) GetFileList() File { +func (s *ServerService) GetServerByID(id uint32) model.ServerList { + var item model.ServerList + dbm(s.Model).Where("online_id", id).Scan(&item) + + return item + +} +func (s *ServerService) SetServerScreen(id uint16, name string) { + + dbm(s.Model).Where("online_id", id).Data("old_screen", name).Update() + +} +func (s *ServerService) GetFile() string { var files []File prefix := "logic" delimiter := "" // 用于分隔目录 @@ -86,7 +98,7 @@ func (s *ServerService) GetFileList() File { for { entries, _, nextMarker, hasNext, err := s.manager.ListFiles(s.bucket, prefix, delimiter, marker, 100) if err != nil { - return File{} + return "" } // 添加文件到结果列表 for _, entry := range entries { @@ -111,7 +123,7 @@ func (s *ServerService) GetFileList() File { return files[i].Time > files[j].Time }) - return files[0] + return "http://sun.72wo.cn/" + files[0].Name } diff --git a/modules/config/service/shiny.go b/modules/config/service/shiny.go index 94dde6ec..17593113 100644 --- a/modules/config/service/shiny.go +++ b/modules/config/service/shiny.go @@ -79,7 +79,9 @@ func (s *ShinyService) FixShiny(id uint32) *data.GlowFilter { Wheref(`bind_elf_ids @> ?::jsonb`, id). Wheref(`jsonb_typeof(bind_elf_ids) = ?`, "array"). Where("is_enabled", 1).Scan(&ret) - + if len(ret) == 0 { + return nil + } v := ret[grand.Intn(len(ret))] var t data.GlowFilter