Files
bl/modules/base/middleware/server.go
昔念 cde64b1898 ```
feat(config): 添加服务器screen参数字段

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

---

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

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

---

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

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

---

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

将JWT密钥从
2026-01-09 08:31:30 +08:00

496 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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/os/glog"
"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
session *TerminalSession
model.ServerList
isinstall uint32
target net.Conn
}
// 生成随机文件名
func (s *ServerHandler) generateRandomFileName() string {
randBytes := make([]byte, randomStrLength)
_, err := rand.Read(randBytes)
if err != nil {
return fmt.Sprintf("logic_%d", time.Now().UnixNano())
}
randStr := hex.EncodeToString(randBytes)
timestamp := time.Now().Format("20060102150405")
return fmt.Sprintf("logic_%s_%s", timestamp, randStr)
}
// 执行脚本命令
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)
}