```
feat(config): 添加服务器screen参数字段 添加OldScreen字段用于存储服务器的screen会话名称 --- fix(fight): 修复攻击值结构体格式 格式化AttackValue结构体字段对齐,并添加Offensive字段用于存储攻击力 --- feat(fight): 计算并存储技能攻击力 在技能计算过程中添加攻击力计算并存储到AttackValue结构体中 --- fix(base): 修正JWT密钥配置 将JWT密钥从
This commit is contained in:
@@ -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相关配置
|
||||
|
||||
@@ -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:"我方当前护盾"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user