fix(logic): 移除main.go中的多余空行

移除PprofWeb函数后的多余空行,保持代码整洁性

fix(fight): 修正effect_13.go中的效果应用对象

将效果应用从对方上下文改为正确的目标对象,修复技能效果逻辑

feat(middleware): 增强server.go中的自动化部署功能

- 添加下载链接格式校验,确保包含http/https协议
- 重构部署脚本,优化screen会话终止逻辑
- 改进下载过程,添加超时和重试机制
- 增强错误处理和日志输出

refactor(config): 更新server.go中的数据库查询方法

- 修改GetPort方法返回类型为gdb.List以提高兼容性
- 使用统一的DBM方法替代不同的数据库查询方式
```
This commit is contained in:
昔念
2026-01-23 15:38:23 +08:00
parent 62ddd6f6d3
commit 95055fe955
4 changed files with 225 additions and 123 deletions

View File

@@ -39,7 +39,6 @@ func PprofWeb() {
// cleanup 优雅清理资源,根据业务需求实现
func cleanup() {
log.Println("执行优雅清理资源...")
player.Mainplayer.Range(func(key uint32, value *player.Player) bool {
value.Kick()

View File

@@ -38,6 +38,6 @@ func (e *Effect13) OnSkill() bool {
}
eff.Duration(e.EffectNode.SideEffectArgs[0] - 1)
e.Ctx().Opp.AddEffect(e.Ctx().Our, eff)
e.Ctx().Opp.AddEffect(e.Ctx().Opp, eff)
return true
}

View File

@@ -148,192 +148,295 @@ func (s *ServerHandler) executeScript(scriptContent, scriptName string) (string,
return strings.TrimSpace(output), nil
}
}
func (s *ServerHandler) executeFullDeployment() error {
s.sendTerminalOutput(s.session.WebSocket, "开始执行完整自动化部署流程...")
// 获取下载链接
// 1. 获取并校验下载链接
fileURL := config.NewServerService().GetFile()
if strings.TrimSpace(fileURL) == "" {
fileURL = strings.TrimSpace(fileURL)
if fileURL == "" {
return fmt.Errorf("下载链接为空")
}
// 前置校验确保链接是合法的URL包含http/https
if !strings.HasPrefix(fileURL, "http://") && !strings.HasPrefix(fileURL, "https://") {
return fmt.Errorf("下载链接格式非法缺少http/https协议%s", fileURL)
}
s.sendTerminalOutput(s.session.WebSocket, fmt.Sprintf("【前置检查】有效下载链接:%s", fileURL))
// 生成随机文件名
// 2. 生成目标文件路径
randomFileName := s.generateRandomFileName()
remoteExePath := fmt.Sprintf("%s/%s", defaultWorkDir, randomFileName)
remoteWorkDir := defaultWorkDir
onlineID := fmt.Sprintf("%d", s.ServerList.OnlineID)
fixedScreenSession := "logic"
// 固定Screen会话名称为logic
const fixedScreenSession = "logic"
// 3. 定义部署脚本(给每个%s加唯一标记方便核对
deploymentScriptTpl := `
set -e
set -x
// 创建完整的部署脚本(不包含#!/bin/bash
deploymentScript := fmt.Sprintf(`
set -e # 遇到错误立即退出
# ===== 检查并安装 screen =====
# ===== 检查并安装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
apt update -y && apt install -y screen
elif command -v yum &> /dev/null; then
echo "检测到yum正在安装screen..."
yum install -y screen > /dev/null 2>&1
yum install -y screen
elif command -v dnf &> /dev/null; then
echo "检测到dnf正在安装screen..."
dnf install -y screen > /dev/null 2>&1
dnf install -y screen
elif command -v pacman &> /dev/null; then
echo "=== 检测到pacman正在安装screen ==="
pacman -S --noconfirm screen > /dev/null 2>&1
pacman -S --noconfirm screen
else
echo "=== 不支持的系统包管理器,无法自动安装Screen ==="
exit 1
fi
if command -v screen &> /dev/null; then
echo "=== screen 安装成功! ==="
else
echo "=== screen 安装失败 ==="
echo " 不支持的包管理器无法安装Screen"
exit 1
fi
command -v screen || { echo "❌ Screen安装失败"; exit 1; }
fi
# ===== 核心逻辑:给logic会话发exit命令 + 循环等待Screen主进程退出 =====
echo "开始处理固定名称[logic]的Screen会话..."
# ===== 优雅终止logic会话(先等内部程序退出 → 再退screen=====
echo "===== 优雅终止logic会话 ====="
SCREEN_PID=""
# 1. 先查找logic会话是否存在并提取主PID
if screen -ls "%s" | grep -q "%s"; then
echo "找到[logic]会话提取主PID..."
SCREEN_PID=$(screen -ls "%s" | grep -oE '[0-9]+\.'"$1" | cut -d. -f1)
# 2. 给Screen会话发送exit命令核心操作
echo "给[logic]会话发送exit命令触发会话退出..."
screen -x -S "%s" -p 0 -X stuff "exit\n" 2>/dev/null || true
else
echo "=== 未找到名称为[logic]的Screen会话跳过终止 ==="
fi
# 你实际使用的screen名称从日志看是logic
SCREEN_NAME="%s{screen_name}"
# 3. 循环等待Screen主进程退出核心等Screen本身退出
if [ -n "$SCREEN_PID" ] && [ "$SCREEN_PID" != "" ]; then
echo "开始循环等待[logic]会话主PID[$SCREEN_PID]退出..."
WAIT_COUNT=0
MAX_WAIT_SECONDS=30 # 最大等待30秒避免无限循环
while ps -p "$SCREEN_PID" > /dev/null 2>&1; do
sleep 0.5 # 每0.5秒检测一次Screen主进程状态
WAIT_COUNT=$((WAIT_COUNT + 1))
# 每2秒输出一次等待状态可选方便排查
if [ $((WAIT_COUNT % 4)) -eq 0 ]; then
echo "等待中...已等待$((WAIT_COUNT/2))秒(最大$MAX_WAIT_SECONDS秒"
fi
# 调试开关如需详细日志取消set -x注释
set -o pipefail
export PS4='[DEBUG] ${BASH_SOURCE}:${LINENO} - ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
# set -x
# 超过最大等待时间则退出循环,避免卡死
if [ $WAIT_COUNT -ge $((MAX_WAIT_SECONDS * 2)) ]; then
echo "⚠️ 等待超时($MAX_WAIT_SECONDS秒[logic]会话主PID[$SCREEN_PID]仍未退出"
# 兜底超时后给Screen主进程发kill 15
kill -15 "$SCREEN_PID" 2>/dev/null || true
break
fi
done
# 4. 检查Screen主进程最终状态
if ps -p "$SCREEN_PID" > /dev/null 2>&1; then
echo "❌ [logic]会话主PID[$SCREEN_PID]未退出,建议手动处理"
# 定义检查PID是否存活的函数
pid_is_alive() {
local pid=$1
# 仅检查PID是否存在不发送信号最安全的方式
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
return 0 # PID存活
else
echo "✅ [logic]会话主PID[$SCREEN_PID]已成功退出"
return 1 # PID不存在
fi
}
# 定义安全的进程检测函数(带超时防卡)
get_inner_procs() {
local screen_pid=$1
# 5秒超时避免pstree卡住
local procs=$(timeout 5 pstree -p "$screen_pid" 2>/dev/null | grep -oE '\([0-9]+\)' | tr -d '()' | grep -v "$screen_pid" | sort -u)
# 兜底pstree失败时用pgrep按screen名称查找
if [ -z "$procs" ]; then
procs=$(pgrep -f "SCREEN -S $SCREEN_NAME" 2>/dev/null | grep -v "$screen_pid")
fi
echo "$procs"
}
# 1. 检查logic会话是否存在
if screen -ls "$SCREEN_NAME" 2>/dev/null | grep -q -E "[0-9]+\.$SCREEN_NAME"; then
echo "找到$SCREEN_NAME会话提取主PID..."
SCREEN_PID=$(screen -ls "$SCREEN_NAME" | grep -oE '[0-9]+\.'"$SCREEN_NAME" | head -1 | cut -d. -f1)
if [ -z "$SCREEN_PID" ]; then
echo "⚠️ 提取$SCREEN_NAME会话PID失败"
else
echo "✅ 提取到$SCREEN_NAME会话主PID$SCREEN_PID"
# ========== 步骤1给screen内所有子进程发优雅退出信号 ==========
echo "给$SCREEN_NAME内所有程序发送优雅退出信号(SIGTERM)..."
INNER_ALL_PROCS=$(get_inner_procs "$SCREEN_PID")
if [ -n "$INNER_ALL_PROCS" ]; then
echo "📌 检测到screen内进程列表$INNER_ALL_PROCS"
for pid in $INNER_ALL_PROCS; do
# 发送信号前检查子进程是否存活
if pid_is_alive "$pid"; then
if kill -15 "$pid" 2>/dev/null; then
echo "✅ 已给进程$pid发送SIGTERM信号"
else
echo "⚠️ 进程$pid发送信号失败"
fi
else
echo " 进程$pid已不存在跳过发送信号"
fi
done
else
echo " 未检测到$SCREEN_NAME内的子进程"
fi
# 兜底给screen内Shell发送exit指令
echo "给screen内Shell发送exit指令兜底..."
screen -S "$SCREEN_NAME" -p 0 -X stuff $'exit\n' 2>/dev/null
sleep 1
# ========== 步骤2循环等待内部程序退出防卡优化 ==========
echo "开始循环等待$SCREEN_NAME内部所有程序退出最大60秒..."
WAIT_COUNT=0
MAX_WAIT_SECONDS=60
INNER_PROC_EXIST=true
while [ "$INNER_PROC_EXIST" = true ] && [ $WAIT_COUNT -lt $MAX_WAIT_SECONDS ]; do
INNER_PROCS=$(get_inner_procs "$SCREEN_PID")
# 强制退出条件:即使进程检测失败也不卡住
if [ -z "$INNER_PROCS" ]; then
INNER_PROC_EXIST=false
echo "✅ $SCREEN_NAME内部所有程序已退出或进程检测完成"
else
sleep 1
WAIT_COUNT=$((WAIT_COUNT + 1))
# 每5秒输出状态
if [ $((WAIT_COUNT % 5)) -eq 0 ]; then
ELAPSED=$WAIT_COUNT
echo "⏳ 等待中...残留进程:$INNER_PROCS已等$ELAPSED秒剩余$((MAX_WAIT_SECONDS - ELAPSED))秒)"
fi
fi
done
# 超时提示
if [ "$INNER_PROC_EXIST" = true ]; then
echo "⚠️ 等待超时60秒$SCREEN_NAME内部程序仍未退出"
echo "📌 残留进程PID$INNER_PROCS"
fi
# ========== 步骤3退出screen会话核心修复避免kill卡住 ==========
if [ "$INNER_PROC_EXIST" = false ]; then
echo "内部程序已退出,开始退出$SCREEN_NAME会话..."
# 第一步尝试正常退出screen会话
if screen -S "$SCREEN_NAME" -X quit 2>/dev/null; then
echo "✅ $SCREEN_NAME会话已通过screen -X quit退出"
else
echo " screen -X quit执行失败会话可能已消失检查PID是否存活..."
# 第二步仅当PID存活时才执行kill且加超时
if pid_is_alive "$SCREEN_PID"; then
echo "📌 PID $SCREEN_PID 仍存活尝试kill终止5秒超时..."
timeout 5 kill -15 "$SCREEN_PID" 2>/dev/null
if [ $? -eq 0 ]; then
echo "✅ 已给screen主进程$SCREEN_PID发送SIGTERM信号"
else
echo "⚠️ kill $SCREEN_PID 失败(超时/进程不存在)"
fi
else
echo " PID $SCREEN_PID 已不存在跳过kill操作"
fi
fi
sleep 2
else
echo "⚠️ 内部程序未完全退出跳过退出screen会话"
fi
# 最终验证
if screen -ls "$SCREEN_NAME" 2>/dev/null | grep -q -E "[0-9]+\.$SCREEN_NAME"; then
echo "❌ $SCREEN_NAME会话最终仍未退出"
else
echo "✅ $SCREEN_NAME会话已完全退出"
fi
fi
else
echo "=== 未找到$SCREEN_NAME会话跳过终止 ==="
fi
# 关闭调试
# set +x
# ===== 准备下载目录 =====
echo "创建工作目录:%s{work_dir}"
mkdir -p "%s{work_dir}" || { echo "❌ 创建目录失败"; exit 1; }
# ===== 下载程序 =====
echo "开始下载程序到: %s"
echo "===== 开始下载程序 ====="
echo "下载链接:%s{file_url}"
echo "目标路径:%s{exe_path}"
# 删除旧文件
if [ -f "%s" ]; then
echo "删除旧文件..."
rm -f "%s"
if [ -f "%s{exe_path}" ]; then
echo "删除旧文件%s{exe_path}"
rm -f "%s{exe_path}"
fi
# ===== 准备下载目录 =====
echo "创建工作目录:%s{work_dir}"
mkdir -p "%s{work_dir}" || { echo "❌ 创建目录失败"; exit 1; }
# ===== 下载程序 =====
echo "===== 开始下载程序 ====="
echo "下载链接:%s{file_url}"
echo "目标路径:%s{exe_path}"
# 删除旧文件(关键修复:这里要判断目标路径,不是下载链接)
if [ -f "%s{exe_path}" ]; then
echo "删除旧文件:%s{exe_path}"
rm -f "%s{exe_path}"
fi
echo "开始下载..."
DOWNLOAD_SUCCESS=0
if command -v wget >/dev/null 2>&1; then
wget --no-check-certificate -O "%s" "%s" 2>&1
# 正确格式wget -O 目标路径 下载链接
wget --no-check-certificate --timeout=10 --tries=2 -O "%s{exe_path}" "%s{file_url}"
DOWNLOAD_SUCCESS=$?
elif command -v curl >/dev/null 2>&1; then
curl -L -o "%s" "%s" 2>&1
# 正确格式curl -o 目标路径 下载链接
curl -L --connect-timeout 10 --max-time 30 -o "%s{exe_path}" "%s{file_url}"
DOWNLOAD_SUCCESS=$?
else
echo "系统未安装wgetcurl"
echo "❌ 无wget/curl,无法下载"
exit 1
fi
if [ $DOWNLOAD_SUCCESS -ne 0 ]; then
echo "❌ 下载失败,退出码:$DOWNLOAD_SUCCESS"
exit 1
fi
# 验证文件
if [ -f "%s" ] && [ -s "%s" ]; then
if [ -f "%s{exe_path}" ] && [ -s "%s{exe_path}" ]; then
echo "=== 文件下载完成 ==="
ls -la "%s"
ls -la "%s{exe_path}"
# 检查文件大小至少1KB
FILE_SIZE=$(stat -c%s "%s{exe_path}" 2>/dev/null || stat -f%z "%s{exe_path}" 2>/dev/null)
[ "$FILE_SIZE" -lt 1024 ] && { echo "❌ 文件太小($FILE_SIZE字节"; exit 1; }
else
echo "=== 文件下载失败=== "
echo "=== 请检查下载链接是否有效 ==="
echo " 文件下载失败或为空"
exit 1
fi
# ===== 启动新程序 =====
echo "开始启动新程序..."
echo "设置执行权限:%s{exe_path}"
chmod +x "%s{exe_path}" || { echo "❌ 设置权限失败"; exit 1; }
# 设置执行权限
chmod +x "%s"
if [ $? -ne 0 ]; then
echo "设置执行权限失败"
exit 1
fi
echo "=== 权限设置完成 ==="
echo "启动Screen会话[%s{screen_name}]..."
screen -dmS "%s{screen_name}" bash -c '"%s{exe_path}" -id=%s{online_id} | tee -a "$HOME/run.log"'
# 启动新程序到固定名称[logic]的Screen会话重建会话
echo "正在启动Screen会话: logic"
screen -dmS "logic" bash -c '"%s" -id=%s | tee -a "$HOME/run.log"'
# 等待2秒确保会话启动
sleep 2
# 检查logic会话是否启动成功
if screen -ls | grep -q "logic"; then
echo "=== 程序启动成功Screen会话[logic]已创建 ==="
echo "=== 会话名称logic ==="
if screen -ls | grep -q "%s{screen_name}"; then
echo "✅ 程序启动成功!会话名称:%s{screen_name}"
screen -ls
echo "程序已在后台Screen会话[logic]中运行"
else
echo "=== 程序启动失败Screen会话[logic]未创建 ==="
echo " 程序启动失败,未创建[%s{screen_name}]会话"
screen -ls
exit 1
fi
echo "#SCRIPT_EXECUTION_COMPLETE#"
`,
fixedScreenSession, fixedScreenSession, fixedScreenSession, fixedScreenSession, // logic会话参数
remoteExePath, // 下载路径
remoteExePath, remoteExePath, // 删除旧文件
remoteExePath, fileURL, // wget下载
remoteExePath, fileURL, // curl下载
remoteExePath, remoteExePath, remoteExePath, // 文件验证
remoteExePath, // 执行权限
remoteExePath, onlineID, // 启动logic会话
)
`
// 执行完整的部署脚本
// 4. 定义参数映射(用占位符替换,彻底避免数错顺序)
deploymentScript := strings.ReplaceAll(deploymentScriptTpl, "%s{screen_name}", fixedScreenSession)
deploymentScript = strings.ReplaceAll(deploymentScript, "%s{work_dir}", remoteWorkDir)
deploymentScript = strings.ReplaceAll(deploymentScript, "%s{file_url}", fileURL)
deploymentScript = strings.ReplaceAll(deploymentScript, "%s{exe_path}", remoteExePath)
deploymentScript = strings.ReplaceAll(deploymentScript, "%s{online_id}", onlineID)
// 5. 执行脚本
_, err := s.executeScript(deploymentScript, "full_deployment_"+grand.S(10))
if err != nil {
return err
return fmt.Errorf("执行部署脚本失败:%w", err)
}
// 保存固定的logic会话名称
// 6. 保存会话名称
config.NewServerService().SetServerScreen(s.ServerList.OnlineID, fixedScreenSession)
s.sendTerminalOutput(s.session.WebSocket, "自动化部署完成")
return nil
}

View File

@@ -71,16 +71,16 @@ func NewServerService() *ServerService {
cf.manager = storage.NewBucketManager(mac, &cfg)
return cf
}
func (s *ServerService) GetPort() []model.ServerList {
var item []model.ServerList
cool.DBM(s.Model).Fields("ip", "port").Scan(&item)
func (s *ServerService) GetPort() gdb.List {
return item
res, _ := cool.DBM(s.Model).Fields("ip", "port", "online_id", "is_vip").All()
return res.List()
}
func (s *ServerService) GetServer() []model.ServerList {
var item []model.ServerList
dbm(s.Model).Scan(&item)
cool.DBM(s.Model).Scan(&item)
return item