feat(config): 添加服务器screen参数字段

添加OldScreen字段用于存储服务器的screen会话名称

---

fix(fight): 修复攻击值结构体格式

格式化AttackValue结构体字段对齐,并添加Offensive字段用于存储攻击力

---

feat(fight): 计算并存储技能攻击力

在技能计算过程中添加攻击力计算并存储到AttackValue结构体中

---

fix(base): 修正JWT密钥配置

将JWT密钥从
This commit is contained in:
2026-01-09 08:31:30 +08:00
parent 54e0649313
commit cde64b1898
10 changed files with 525 additions and 436 deletions

View File

@@ -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)
}