From 95055fe955b4fd54962afb103f95ae346a99863b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=94=E5=BF=B5?= <12574910+72wo@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:38:23 +0800 Subject: [PATCH] =?UTF-8?q?```=20fix(logic):=20=E7=A7=BB=E9=99=A4main.go?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=A4=9A=E4=BD=99=E7=A9=BA=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除PprofWeb函数后的多余空行,保持代码整洁性 fix(fight): 修正effect_13.go中的效果应用对象 将效果应用从对方上下文改为正确的目标对象,修复技能效果逻辑 feat(middleware): 增强server.go中的自动化部署功能 - 添加下载链接格式校验,确保包含http/https协议 - 重构部署脚本,优化screen会话终止逻辑 - 改进下载过程,添加超时和重试机制 - 增强错误处理和日志输出 refactor(config): 更新server.go中的数据库查询方法 - 修改GetPort方法返回类型为gdb.List以提高兼容性 - 使用统一的DBM方法替代不同的数据库查询方式 ``` --- logic/main.go | 1 - logic/service/fight/effect/effect_13.go | 2 +- modules/base/middleware/server.go | 335 ++++++++++++++++-------- modules/config/service/server.go | 10 +- 4 files changed, 225 insertions(+), 123 deletions(-) diff --git a/logic/main.go b/logic/main.go index 1a59b14ad..84467575f 100644 --- a/logic/main.go +++ b/logic/main.go @@ -39,7 +39,6 @@ func PprofWeb() { // cleanup 优雅清理资源,根据业务需求实现 func cleanup() { log.Println("执行优雅清理资源...") - player.Mainplayer.Range(func(key uint32, value *player.Player) bool { value.Kick() diff --git a/logic/service/fight/effect/effect_13.go b/logic/service/fight/effect/effect_13.go index d5d2df4b1..f54b40892 100644 --- a/logic/service/fight/effect/effect_13.go +++ b/logic/service/fight/effect/effect_13.go @@ -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 } diff --git a/modules/base/middleware/server.go b/modules/base/middleware/server.go index 00e820f95..3b064792d 100644 --- a/modules/base/middleware/server.go +++ b/modules/base/middleware/server.go @@ -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 "系统未安装wget或curl" + 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 } diff --git a/modules/config/service/server.go b/modules/config/service/server.go index f547aea45..d3b08c2b8 100644 --- a/modules/config/service/server.go +++ b/modules/config/service/server.go @@ -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