# Server Manager 项目总结与复刻提示词 本文档用于从零复刻当前 `server_manager` 项目。复刻目标是还原源码、配置模板、运行脚本、打包脚本、更新机制和主要交互行为;`src/dist/`、`output/*.exe` 等二进制构建产物不需要手写,应通过脚本重新生成。 ## 当前项目总结 `Server Manager` 是一个跨平台的 Erlang 游戏服务器管理工具。 - Windows 侧提供 PyQt6 图形界面,支持打开服务器项目、维护项目配置、编译代码、创建服务器、管理本地/远程节点、查看日志和自动更新。 - Linux/Windows 都提供命令行入口,支持 `list`、`create`、`start`、`stop`、`connect`、`compile`、`regen`、`interactive` 等命令。 - 工具面向一个 Erlang/rebar3 游戏服务器工程,约定服务器工程根目录包含 `run/`、`config/`、`rebar3` 等内容。 - 每个服务器实例位于 `<服务器项目根>/run/__s/`,实例差异配置写入 `config/kv.config`,启动前根据模板生成 `config/sys.config`。 - 项目级工具配置位于 `<服务器项目根>/.server_manager/config/`,首次打开项目时从软件自带模板目录同步 `default.kv` 和 `sys_*.config.example`。 - 应用级配置位于 `%LOCALAPPDATA%/ServerManager` 或 `~/.config/ServerManager`,用于保存最近项目列表和应用日志。 - 打包使用 PyInstaller 目录模式生成 `src/dist/main/main.exe`,Inno Setup 生成 `output/ServerManager_Setup.exe`。 - 实际 GUI/CLI 主入口是 `src/server_manager_gui.py`;根目录 `main.py` 是一个 Tkinter 自动更新集成示例/遗留入口。 - 更新机制支持 `version.json` 清单、全量安装包更新,以及基于 `bsdiff4` 的 `main.exe` 增量补丁;替换 exe 时由独立 `mini_updater.exe` 等待主进程退出后完成替换并重启。 - 日志查看支持单文件加载、玩家协议日志、日期归档日志、ripgrep 全局搜索、正文高亮、匹配行过滤和 less 风格快捷键。 ## 核心文件树 ```text server_manager/ ├── README.md ├── RELEASE.md ├── PROJECT_REPLICATION_PROMPT.md ├── version.json ├── main.py ├── updater.py ├── mini_updater.py ├── release.py ├── release.bat ├── pack_all.bat ├── pack_local_test.py ├── pack_local_test.bat ├── build_installer.iss ├── rg.exe # 可选,ripgrep Windows 可执行文件 ├── output/ │ ├── config/ │ │ ├── default.kv │ │ ├── sys_game.config.example │ │ ├── sys_login.config.example │ │ ├── sys_center.config.example │ │ ├── sys_cross.config.example │ │ └── sys_client.config.example │ ├── patches/ │ ├── version.json │ └── ServerManager_Setup.exe # 构建产物 ├── output_local/ # 本地测试包产物,通常忽略 └── src/ ├── app_config.py ├── server_manager_gui.py ├── server_manager_cli.py ├── server_commands.py ├── server_creator.py ├── log_viewer_tab.py ├── log_viewer_pane.py ├── rg_search.py ├── updater.py ├── config.json ├── requirements.txt ├── run.bat ├── run.sh ├── build.bat ├── build_portable.bat ├── build_linux.sh └── icon/ ├── icon.png ├── icon.ico └── convert_icon.py ``` ## 完整复刻提示词 将下面提示词交给代码生成模型,即可要求其从零生成当前项目的等价实现。 ```text 你是资深 Python 桌面工具工程师。请从零创建一个名为 Server Manager 的项目,目标是复刻一个用于 Erlang/rebar3 游戏服务器工程的跨平台管理工具。输出完整仓库源码、配置模板、运行脚本、打包脚本和文档。不要手工生成 exe、dist、installer 这类二进制产物;这些产物必须由脚本重新构建。 一、项目目标 1. 实现一个 Python 3.8+ 项目。 2. Windows 下提供 PyQt6 GUI;Windows/Linux 下提供 CLI。 3. GUI 无参数启动;命令行带任意参数时进入 CLI,行为与 src/server_manager_cli.py 一致。 4. 工具管理一个外部 Erlang 游戏服务器项目,项目根目录通常含 run、config、rebar3、apps 等。 5. 首次打开服务器项目时,将软件自带模板 output/config 下的 default.kv 与 sys_*.config.example 同步到 <项目根>/.server_manager/config/。 6. 所有项目级配置保存在 <项目根>/.server_manager/config/tool.config;应用级配置和日志保存在 %LOCALAPPDATA%/ServerManager 或 ~/.config/ServerManager。 7. 支持服务器创建、启动、快速启动、停止、remsh 连接、编译、配置编辑、sys.config 重新生成、本地/远程服务器列表、节点状态批量检查、日志查看、日志搜索、自动更新、打包发版。 二、技术栈与依赖 1. Python 3.8+。 2. GUI: PyQt6。 3. 打包: pyinstaller。 4. 数据库访问: pymysql。 5. 更新下载: requests,缺失时可回退 urllib。 6. 增量更新: bsdiff4。 7. 日志全局搜索: 优先使用 ripgrep,可执行文件名 rg.exe 或 rg;也支持 RG_PATH 环境变量。 8. Windows 安装包: Inno Setup 6。 9. 被管理服务器依赖 Erlang/OTP、rebar3;Linux 后台启动优先 screen,其次 tmux,最后后台进程。 三、仓库文件 请创建如下文件: 根目录: - README.md:写明项目介绍、安装、运行、配置、打包、version.json 更新说明、CLI 参数和注意事项。 - RELEASE.md:写明本地测试包、正式发版、增量更新、ripgrep 放置方式。 - version.json:版本清单,当前版本为 1.0.46,字段包含 version、update_url、release_notes、full_installer_url、delta_updates。 - main.py:Tkinter 示例/遗留入口,LOCAL_VERSION = "1.0.46",演示 updater 自动更新集成;真实桌面工具入口仍以 src/server_manager_gui.py 为准。 - updater.py:保留一个 Tkinter/独立入口可用的无感更新模块,兼容 delta 与 delta_updates。 - mini_updater.py:独立进程外更新器,等待主进程 PID 退出后替换 main.exe 并重启。 - release.py:发版脚本,负责更新 build_installer.iss、main.py、version.json,生成 delta_updates,打包后可生成 bsdiff4 patch 并填 new_exe_sha256。 - release.bat:一键正式发版脚本,顺序为 PyInstaller -> release.py 版本预处理 -> Inno Setup -> release.py --post-build。 - pack_all.bat:正式打包但不改版本号,生成 output/ServerManager_Setup.exe,并复制 version.json 到 output/。 - pack_local_test.py 与 pack_local_test.bat:生成 output_local/ServerManager_Setup_LOCAL.exe,不改版本号、不覆盖 output/。 - build_installer.iss:Inno Setup 脚本,正式版 AppName=ServerManager,测试版 AppName=ServerManager (本地测试),安装 src/dist/main/*、src/dist/mini_updater.exe、version.json、output/config/*。 - .gitignore:忽略 output_local/,可注明 release_backup/ 可选忽略。 - rg.exe:可选,不需要生成二进制;文档说明放置在根目录即可被打包复制。 src 目录: - requirements.txt:包含 PyQt6>=6.4.0、pyinstaller>=6.0.0、pymysql>=1.0.0、requests>=2.28.0、bsdiff4>=1.2.0。 - app_config.py:应用级配置目录、app_config.json、最近项目列表、应用日志目录。 - server_manager_gui.py:PyQt6 主 GUI 和主入口;无参数 GUI,有参数转发 CLI。 - server_manager_cli.py:命令行逻辑。 - server_commands.py:服务器命令、配置模板同步、节点状态、启动/停止/remsh、sys.config 生成、远程服务器 RPC 查询、日志路径和 trace RPC。 - server_creator.py:服务器创建、kv.config 生成、配置项定义、ID 范围、端口默认值。 - log_viewer_tab.py:日志查看顶级页签,含“日志搜索”和“全局搜索”两个子页。 - log_viewer_pane.py:可嵌入或独立窗口的日志正文检索组件。 - rg_search.py:ripgrep 封装。 - updater.py:GUI 使用的自动更新工具函数。 - config.json:旧版/示例配置,可包含 workspace、database、server、erlang、commands、login_db。 - run.bat 与 run.sh:源码/打包入口自动选择脚本。 - build.bat、build_portable.bat、build_linux.sh:PyInstaller 打包脚本。 - icon/convert_icon.py、icon/icon.png、icon/icon.ico:图标资源和转换脚本。 output/config 目录: - default.kv:默认配置和配置项定义。 - sys_game.config.example、sys_login.config.example、sys_center.config.example、sys_cross.config.example、sys_client.config.example:Erlang sys.config 模板,使用 ${key} 占位符。 四、version.json 内容 生成根目录 version.json,内容结构如下: { "version": "1.0.46", "update_url": "http://172.18.180.94:3000/api/file?path=server_manager/output/version.json", "release_notes": "优化ui", "full_installer_url": "http://172.18.180.94:3000/api/download?path=server_manager/output/ServerManager_Setup.exe", "delta_updates": { "1.0.45": { "patch_url": "http://172.18.180.94:3000/api/download?path=server_manager/output/patches/v1.0.45_to_v1.0.46.patch", "new_exe_sha256": "" } } } 五、配置体系 1. 项目级配置目录固定为 <项目根>/.server_manager/config。 2. 软件自带模板目录: - 打包后:main.exe 同级的 config/。 - 源码运行:仓库根目录 output/config/。 3. 受管模板文件: - default.kv - sys_center.config.example - sys_client.config.example - sys_cross.config.example - sys_game.config.example - sys_login.config.example 4. 打开项目时必须确保 .server_manager/config 存在并同步上述模板;保留用户自己的 tool.config。 5. 配置合并优先级从低到高: - .server_manager/config/default.kv - .server_manager/config/tool.config - run_dir/config/tool.config 或 run_dir/tool.config - run_dir//config/kv.config 6. 合并配置后强制用当前机器 IP 覆盖 ip 和 game_host。 7. tool.config 是 key=value 文件,至少写入: - server_root、run_dir - r25_path - db_host、db_port、db_user、db_pass - login_db_name、login_db_server_id、login_db_host、login_db_port、login_db_user、login_db_pass - prefix、default_server_id、server_type、server_name、login_node、center_node、cookie 8. 应用级配置: - Windows: %LOCALAPPDATA%/ServerManager/app_config.json - Linux/macOS: ~/.config/ServerManager/app_config.json - 保存最近 10 个有效项目,判断有效项目时需存在 .server_manager/config/default.kv 或旧式 config/default.kv。 - 应用日志写到同目录 logs/,日志文件名 server_manager_YYYYMMDD.log 或 server_manager_cli_YYYYMMDD.log,保留最近 7 天。 六、default.kv 要求 default.kv 必须含以下默认值和配置定义: - prefix=ddxq2 - server_id=1 - server_type=game_server - server_name=自己名字_1 - ip= - game_host= - tcp_port=18001 - http_port=19001 - login_node=ddxq2_login_server_s900@172.16.14.122 - center_node=ddxq2_center_server_s1@172.16.14.122 - db_host=172.16.12.220 - db_port=3306 - db_user=ddxqadmin - db_pass=ddxq@44168deabe63 - db_save_time=300000 - db_save_count=10000 - log_save_time=120000 - log_save_count=1000 - log_dir=run/server/log - role_log=run/server/log - logger_level=debug - open_time={{2025,1,1},{10,0,0}} - merge_server_ids=[] - merge_server_time={{1970,1,1},{0,0,0}} - last_merge_server_ids=[] - game_tcp_port=18001 - login_host=172.16.14.122 - login_http_port=19900 - is_develop=true - is_inner=true - auto_reload=true - gm_auth=true - risk_control_server_ip=msg.4399sy.com - risk_control_server_post=5978 - game_id_min=100, game_id_max=9999, game_id_default=100 - login_id_min=11, login_id_max=99, login_id_default=10 - center_id_min=1, center_id_max=10, center_id_default=1 - cross_id_min=10000, cross_id_max=20000, cross_id_default=10000 - client_id_min=1, client_id_max=10000, client_id_default=1 - login_db_name= - login_db_server_id=900 - login_db_host=172.16.12.220 - login_db_port=3306 - login_db_user=ddxqadmin - login_db_pass=ddxq@44168deabe63 - cookie=ddxq2-node 必须配置项定义: - required_keys_base=prefix,server_id,server_type,ip,db_host,db_user,db_pass,db_port,log_dir,db_game_name,auto_reload - required_keys_game=server_name,game_host,tcp_port,http_port,open_time,login_node,center_node,db_log_name - required_keys_login=tcp_port - required_keys_center=open_time,login_node,db_log_name - required_keys_cross=open_time,center_node,db_log_name - required_keys_client=tcp_port,game_host,login_host,login_http_port,login_node 配置项定义使用 config_keys_base_*、config_keys_game_*、config_keys_login_*、config_keys_center_*、config_keys_cross_*、config_keys_client_*;编辑对话框可选项使用 editable_config_*。至少包含当前项目中的这些 key:prefix、server_id、server_type、ip、db_host、db_port、db_user、db_pass、db_save_time、db_save_count、log_save_time、log_save_count、log_dir、logger_level、is_develop、is_inner、auto_reload、gm_auth、server_name、game_host、tcp_port、http_port、login_node、center_node、open_time、merge_server_ids、merge_server_time、last_merge_server_ids、risk_control_server_ip、risk_control_server_post、role_log、game_tcp_port、login_host、login_http_port。 七、sys.config 模板要求 1. 每种服务器类型都有一个 sys_*.config.example。 2. 模板中使用 ${key} 占位符,至少覆盖: ${auto_reload}, ${center_node}, ${db_game_name}, ${db_host}, ${db_log_name}, ${db_pass}, ${db_port}, ${db_save_count}, ${db_save_time}, ${db_user}, ${game_host}, ${game_tcp_port}, ${gm_auth}, ${http_port}, ${is_develop}, ${is_inner}, ${log_dir}, ${log_save_count}, ${log_save_time}, ${logger_level}, ${login_host}, ${login_http_port}, ${login_node}, ${merge_server_ids}, ${open_time}, ${prefix}, ${risk_control_server_ip}, ${risk_control_server_post}, ${role_log}, ${server_id}, ${server_name}, ${server_type}, ${tcp_port}。 3. 生成 sys.config 时必须根据 server_type 选择模板: - game_server -> sys_game.config.example - login_server -> sys_login.config.example - client_server -> sys_client.config.example - center_server -> sys_center.config.example - cross_server -> sys_cross.config.example 4. 生成 sys.config 前需要将 log_dir 和 role_log 归一化为 run//。如果 default.kv 中是 run/server/log,则实际应变为 run//log。 5. 若模板中仍有未替换占位符,要输出警告但不要崩溃。 八、服务器创建逻辑 1. 支持服务器类型: - game -> game_server,显示名“游戏服” - login -> login_server,显示名“登录服” - client -> client_server,显示名“客户端测试服” - center -> center_server,显示名“中心服” - cross -> cross_server,显示名“跨服” 2. 目录名格式:__s,例如 ddxq2_game_s100。 3. 服务器目录://config/。 4. 差异配置文件:kv.config,只写入当前实例的差异配置。 5. 创建后立即调用 generate_start_config 生成 sys.config。 6. 默认端口: - tcp_port = 18000 + server_id - http_port = 19000 + server_id 7. 默认游戏服显示名:_,GUI 中可由 server_name 前缀 + ID 动态生成。 8. 数据库名: - db_game_name=__s - db_log_name=__log_s,登录服不写 db_log_name 9. game 额外写入 server_name、game_host、tcp_port、http_port、open_time、role_log。 10. login 写入 tcp_port。 11. center 写入 open_time、login_node。 12. cross 写入 open_time、center_node。 13. client 写入 tcp_port、game_host、game_tcp_port、login_host、login_http_port、login_node。 14. auto_reload 作为布尔复选项,写成 true/false。 15. GUI 创建前检查同类型同 ID 是否已存在;已存在时警告,覆盖只在二次确认后允许。 九、命令构建与服务器运行 1. 平台检测: - Windows: REBAR3_CMD = rebar3.cmd - Linux: REBAR3_CMD = ./rebar3 2. 如果配置了 Erlang 根目录 r25_path: - Windows 使用 /bin/erl.exe 与 escript.exe - Linux 使用 /bin/erl 与 escript 3. rebar3 命令: - Windows 如配置了 Erlang 路径,用 "" "/rebar3",否则 rebar3.cmd。 - Linux 如配置了 Erlang 路径,用 "" "/rebar3",否则 ./rebar3。 4. rebar profile: - login_server -> login_server_dev, app login_server - client_server -> client_server_dev, app client_server - 其他 -> game_server_dev, app game_server 5. 标准启动命令: rebar3 as shell --eval -1,"application:ensure_all_started()." --setcookie --name --config "" 6. 快速启动命令: erl -pa <_build profile ebin paths> -smp enable -setcookie -name -config "" -eval "application:ensure_all_started()." 7. 启动工作目录统一为 server_root,不要切换到 run/;日志路径由 sys.config 指向 run//log。 8. stop 命令用 erl -noshell -name stop_@ -setcookie -eval "rpc:call('', init, stop, [])" -s c q,参数列表方式执行,避免 shell 引号问题。 9. remsh 命令用 erl -setcookie -name remsh_@ -remsh 。 10. 节点状态使用 net_adm:ping;批量状态检查需要缓存 NodeStatusCache,减少重复查询。 11. 可实现 EPMD 快速查询作为优化;至少提供 check_node_status 和 check_nodes_status。 12. Windows 新窗口启动用 cmd/start 或 powershell/cmd 方案,并记录窗口标题,停止服务器后尝试关闭由本工具启动的服务窗口和 remsh 窗口。 13. Linux 后台启动优先 screen,其次 tmux,否则后台进程。 十、CLI 规格 创建 server_manager_cli.py,命令行参数: 全局参数: - --root / -r:服务器根目录 - --config / -c:指定配置文件 - --cookie:覆盖 Erlang cookie 子命令: - list:列出所有服务器,按 game/cross/login/client/center/other 分组。 - create:创建服务器。 - --type/-t 必填,choices game/login/center/cross/client - --id/-i 必填 int - --prefix/-p - --db-host、--db-port、--db-user、--db-pass - --login-node、--center-node、--server-name - --tcp-port、--http-port - --open-time - --overwrite - start: - --server/-s 必填 - --quick/-q 表示不用 rebar3,直接 erl - --background/-b 表示后台运行;默认前台 - stop:--server/-s 必填 - connect:--server/-s 必填,--ip 可选 - compile: - --target/-t choices all/code/proto/table/tbllog,默认 all - --type choices game/login,默认 game - regen:--server/-s 必填,重新生成 sys.config - interactive / i:交互式菜单 CLI 初始化时自动检测 server_root: 1. 命令行 --root 优先。 2. 读取 tool.config 的 server_root/run_dir 次之。 3. 当前工作目录、脚本目录、上级目录中查找 apps 与 config。 4. 加载配置后执行 run_startup_migrations。 十一、GUI 规格 GUI 使用 PyQt6。主窗口标题为 Server Manager v,最小尺寸 1000x750,深色主题: - 背景 #181b22 - 面板 #242932 - 边框 #3a4352 - 主色 #4f8cff - 成功 #25c889 - 危险 #ff5c6a - 文本 #e8eef7 / #aab4c3 主窗口行为: 1. 无项目启动时显示欢迎页。 2. 菜单栏: - 文件:打开项目、最近的项目、关闭项目、退出。 - 帮助:检查更新、关于。 3. 状态栏显示运行状态、版本号、系统时间。 4. 打开项目成功后显示主项目内容:上方主标签页,下方控制台输出区。 5. 主标签页: - 控制台 - 创建服务器 - 服务器管理 - 日志查看 - 工具设置 6. 控制台输出区只在“控制台”页显示;其他页隐藏。 7. 顶部角落显示本机 IP,点击可复制。 8. 首次打开项目后初始化节点状态缓存。 GUI 图标: 使用代码内嵌简洁线性 SVG 图标渲染为 QIcon,至少支持 calendar、plus、server、log、settings、code、bolt、shield、table、database、file、trash、play、stop、rocket、network、user、branch、download、folder、check、link、edit、refresh、save、external、eye、eye_off。 控制台页: 1. 分三组卡片: - 编译与构建:编译代码、快速编译、编译协议、编译Table、编译Tbllog、清理编译。 - 服务器运行:运行服务器、快速启动、连接节点、查看账号、停止服务器、服务器清档。 - 版本控制:SVN 更新。 2. 编译命令: - svn update - rebar3 as game_server_dev compile - rebar3 protobuf compile -a game_server - rebar3 cache compile -a game_server - rebar3 tbllog compile - rebar3 clean - 快速编译:escript shell/es/emake.escript 1 && escript shell/es/emake.escript 2 3. 运行/快速启动前检查节点是否已在线,在线则询问是否 remsh 连接。 4. 停止服务器要异步执行,进度输出到控制台,成功后更新状态缓存。 5. 清档功能:停止服务器后连接 MySQL,删除游戏库和日志库,需要明确危险提示。 6. 查看账号功能:连接数据库展示玩家账号相关数据,结果表格支持数字排序。 创建服务器页: 1. 卡片布局包含“服务器配置”“数据库配置”“节点配置”。 2. 服务器类型下拉:游戏服务器、登录服务器、客户端服务器、中心服务器、跨服服务器。 3. 按类型动态显示字段: - game:server_name、tcp_port、http_port、auto_reload、open_time、login_node、center_node - login:tcp_port、auto_reload - center:auto_reload、open_time、login_node - cross:auto_reload、open_time、center_node - client:连接服务器ID、连接服务器端口、登录服地址、登录服HTTP端口、auto_reload、login_node 4. ID 范围从 default.kv 的 _id_min/max/default 读取,越界时自动修正并提示。 5. 服务器 ID 改变时自动更新服务器名与默认端口。 6. 数据库和节点支持“使用默认配置”复选框,默认勾选并禁用手工输入。 7. 点击创建前展示确认信息,再调用 server_creator.create_server。 服务器管理页: 1. 顶部显示运行目录。 2. 两个子标签: - 本地服务器:扫描 run_dir,按 game/cross/login/client/center/other 分类显示;每项显示状态图标、类型图标、是否有 kv.config。 - 远程服务器:从登录服节点 RPC 加载远程服务器列表,表格列为服务器ID、服务器名称、服务器节点,支持过滤和排序。 3. 本地操作:刷新列表、刷新状态、远程连接、编辑配置、重新生成配置、删除服务器。 4. 远程操作:从登录服加载、刷新状态、搜索过滤、双击或按钮 remsh 连接。 5. 远程列表通过登录服节点 rpc:call 获取 server_temp_info/server_info_lib 数据;输出需解析 server_id、server_name、server_node。 6. 状态刷新使用批量 check_nodes_status,并写入 NodeStatusCache。 7. 编辑配置打开 KvConfigEditDialog,读取 kv.config,允许添加/删除 default.kv 中声明的可编辑配置项;保存后重新生成 sys.config。 日志查看页: 1. 顶级“日志查看”内有两个子页签: - 日志搜索:加载单个日志文件。 - 全局搜索:用 ripgrep 在当前服务器 log 目录递归搜索。 2. 日志搜索支持: - 选择服务器目录。 - 日志类型:服务器日志 / 玩家协议(tag_log)。 - 服务器日志预设:error.log、debug.log、http_info.log、http_error.log、info.log、http.log。 - 自定义日志文件名。 - 玩家协议按角色 ID 生成 network_codec-.log。 - 指定归档日期,文件名变为 .YYYYMMDD。 - 加载日志、开启玩家协议、关闭玩家协议、打开文件夹、独立窗口、清空、自动滚动。 3. 日志文件路径规则: - 无日期://log// - 有日期://log//.YYYYMMDD - 玩家协议 subdir=tag_log 4. 单文件读取最大约 3MB,编码依次尝试 utf-8、utf-8-sig、gbk、latin-1。 5. 开启/关闭玩家协议通过 rpc_role_gs_trace_network 调 role_gs:trace_network/1 或 trace_network_close/1。 6. 全局搜索要求找到 rg,否则提示用户放置 rg.exe 或设置 RG_PATH;搜索结果在独立窗口打开。 日志正文组件: 1. LogViewerPane 包含搜索行和只读 QPlainTextEdit。 2. 支持区分大小写、仅显示匹配行、正则表达、全词匹配。 3. 检测到 rg 时,过滤、高亮、上/下一个都尽量与 ripgrep 一致;否则回退 Python re。 4. 支持快捷键: - Enter/F3/n:下一个 - Shift+F3/Shift+N:上一个 - g:文件开头 - Shift+G:文件末尾 - / 或 Ctrl+F:聚焦搜索框 5. 使用 rg --json 时要将 UTF-8 byte offsets 转为 Qt UTF-16 光标位置。 工具设置页: 1. 表单分组: - 项目目录配置:服务器根目录、运行目录,均有浏览按钮。 - 数据库配置:host、port、user、password,密码框带眼睛图标。 - 服务器默认配置:prefix、默认服务器ID、server_type、server_name、默认 TCP/HTTP 端口。 - Erlang 与节点配置:Erlang 路径、Erlang 版本、登录服节点、中心服节点、Cookie。 2. 提供“检查版本”按钮,比对系统 erl 和配置路径 erl 的 OTP 版本。 3. 登录服节点、中心服节点提供“测试连接”,使用 net_adm:ping。 4. “填充默认值”从 default.kv 填入表单。 5. “保存配置”写入 tool.config;若 Erlang 路径变化,询问是否删除 server_root/_build 并执行 rebar3 as game_server_dev compile。 欢迎页: 1. 无项目时显示欢迎页,提供打开项目、最近项目、定位项目、帮助入口。 2. 最近项目来自应用级 app_config.json,只显示仍存在且含 default.kv 的项目。 更新机制: 1. src/updater.py 提供: - get_current_version - get_version_info - version_less - fetch_update_manifest - get_delta_for_current - download_file - clean_up_old_version - apply_delta_patch - run_installer_and_exit 2. 打包后 version.json 优先从 main.exe 同目录读取,其次从 _MEIPASS。 3. GUI 启动 1.5 秒后后台检查 update_url;远端 version 与本地不同则询问是否更新。 4. 当前版本有 delta_updates 且 patch_url、new_exe_sha256 完整时优先增量,否则下载 full_installer_url 或 download_url。 5. 下载时显示 QProgressDialog;可取消。 6. 增量使用 bsdiff4.patch(当前 main.exe, patch) 生成临时新 exe,校验 sha256,通过 mini_updater.exe 替换并重启。 7. 全量更新下载安装包后用 /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS 静默安装并退出。 8. mini_updater.py 只依赖标准库,参数: - --pid - --install-dir - --new-exe-path - --target-exe-name 等待 pid 退出,重命名旧 exe 为 .old,移动新 exe,cwd 为安装目录启动新程序。 打包脚本: 1. src/build.bat: - 安装 PyQt6、pyinstaller、pymysql、Pillow。 - 调 icon/convert_icon.py。 - 清理 dist/build/spec。 - PyInstaller onedir console 模式,name=main,入口 server_manager_gui.py。 - add-data: ../version.json、config.json、server_commands.py、server_creator.py、icon。 - hidden-import: PyQt6.QtWidgets/Core/Gui、pymysql、server_commands、server_creator、server_manager_cli、updater、app_config、bsdiff4、requests。 - collect-all PyQt6 与 bsdiff4。 - 若根目录有 rg.exe,复制到 dist/main/rg.exe。 - 复制 ../output/config 到 dist/main/config。 - 再用 PyInstaller onefile windowed 打包 ../mini_updater.py 到 dist/mini_updater.exe。 2. src/build_linux.sh 与 build.bat 等价,但使用 python3、Linux add-data 冒号分隔,输出 dist/main/main。 3. src/build_portable.bat 提供单文件便携版尝试,失败回退 onedir。 4. build_installer.iss: - 正式输出 output/ServerManager_Setup.exe。 - 本地测试 /DLOCAL_TEST=1 输出 output_local/ServerManager_Setup_LOCAL.exe,独立 AppId。 - 安装 src/dist/main/* 到 {app},安装 mini_updater.exe、version.json、output/config/*。 - 创建开始菜单/可选桌面快捷方式。 - 安装完成后运行 main.exe。 5. release.py: - get_current_version 以 output/version.json 为准读取旧版本。 - set_version_pre(new_version, notes):更新 build_installer.iss 的 MyAppVersion、main.py 的 LOCAL_VERSION、version.json 的 version/release_notes/delta_updates,并同步到 src/dist/main/version.json 和 _internal/version.json。 - set_version_post(old_exe):复制 version.json 到 output;若提供旧版 main.exe 且安装 bsdiff4,生成 output/patches/v_to_v.patch 并填 new_exe_sha256。 入口脚本: 1. src/run.bat: - 自动计算 SERVER_ROOT 为脚本目录向上两级。 - 优先使用 SERVER_MANAGER_MAIN、同目录 main.exe、dist/main/main.exe。 - 无参数进入 interactive。 - gui 启动 GUI。 - 其他参数转发 CLI,并附加 --root SERVER_ROOT。 2. src/run.sh 同理,优先 SERVER_MANAGER_MAIN、同目录 main、dist/main/main。 十二、核心代码复刻细则 下面是必须复刻的核心代码结构、关键函数签名和关键实现逻辑。生成代码时可以按这些骨架实现,允许适度重构,但外部行为、配置文件路径、命令格式、函数职责必须保持一致。 1. app_config.py 核心代码 必须提供这些函数: - get_app_config_dir() -> Path - get_app_config_path() -> Path - load_app_config() -> Dict[str, Any] - save_app_config(data: Dict[str, Any]) -> None - get_recent_projects() -> List[str] - save_recent_project(project_path: str) -> None - get_app_log_dir() -> Path 实现要点: - Windows 使用 os.environ["LOCALAPPDATA"] 或用户主目录下的 ServerManager。 - 非 Windows 使用 Path.home() / ".config" / "ServerManager"。 - load_app_config 读取 JSON,异常或格式错误返回 {}。 - save_app_config 使用 json.dump(..., ensure_ascii=False, indent=2)。 - get_recent_projects 从 app_config.json 的 recent_projects 读取,去重,只保留存在且 project_has_default_kv_for_manager(path) 为 True 的项目,最多 10 个。 - save_recent_project 将 resolve 后的路径插到列表首位。 伪代码: def get_app_config_dir(): if sys.platform == "win32": base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~") root = Path(base) / "ServerManager" else: root = Path.home() / ".config" / "ServerManager" root.mkdir(parents=True, exist_ok=True) return root def get_recent_projects(): data = load_app_config() raw = data.get("recent_projects") or [] result = [] for item in raw[:15]: p = Path(str(item).strip()) if p.exists() and str(p) not in result and project_has_default_kv_for_manager(p): result.append(str(p)) if len(result) >= 10: break return result 2. server_commands.py 配置与模板同步核心代码 必须定义: - IS_WINDOWS = platform.system() == "Windows" - IS_LINUX = platform.system() == "Linux" - REBAR3_CMD = "rebar3.cmd" if IS_WINDOWS else "./rebar3" - MANAGED_CONFIG_TEMPLATE_FILES = ("default.kv", "sys_center.config.example", "sys_client.config.example", "sys_cross.config.example", "sys_game.config.example", "sys_login.config.example") 必须提供这些函数: - get_server_manager_config_dir(server_root: Union[str, Path]) -> Path - get_bundled_tool_config_template_dir() -> Path - sync_server_manager_config_templates(project_root: Path) -> Tuple[bool, str] - ensure_server_manager_config(project_root: Path) -> Tuple[bool, str] - ensure_and_get_server_manager_config_dir(project_root: Path) -> Tuple[Optional[Path], str] - project_has_default_kv_for_manager(project_root: Union[str, Path]) -> bool - read_config_file(file_path: str) -> Dict[str, str] - write_config_file(file_path: str, config: Dict[str, str]) -> None - read_merged_config(server_root: str, server_dir: str = None, run_dir: str = None) -> Dict[str, str] 实现要点: - get_server_manager_config_dir 返回 Path(server_root).resolve() / ".server_manager" / "config"。 - get_bundled_tool_config_template_dir 打包后返回 Path(sys.executable).parent / "config",源码运行返回 Path(__file__).parent.parent / "output" / "config"。 - sync_server_manager_config_templates 必须检查模板目录存在、default.kv 存在、每个 MANAGED_CONFIG_TEMPLATE_FILES 都存在;逐个 copy2 到项目 .server_manager/config。 - read_config_file 读取 key=value,忽略空行、# 注释、% 注释;去除空白和首尾双引号。 - read_merged_config 合并顺序为 default.kv -> tool.config -> run_dir tool.config -> kv.config,最后强制 merged["ip"] 和 merged["game_host"] 为 get_local_ip()。 read_merged_config 伪代码: def read_merged_config(server_root, server_dir=None, run_dir=None): cfg_dir = get_server_manager_config_dir(server_root) merged = {} if (cfg_dir / "default.kv").exists(): merged.update(read_config_file(str(cfg_dir / "default.kv"))) elif (cfg_dir / "default.config").exists(): merged.update(read_config_file(str(cfg_dir / "default.config"))) if (cfg_dir / "tool.config").exists(): merged.update(read_config_file(str(cfg_dir / "tool.config"))) if run_dir: for p in (Path(run_dir) / "config" / "tool.config", Path(run_dir) / "tool.config"): if p.exists(): merged.update(read_config_file(str(p))) break if server_dir: kv = (Path(run_dir) if run_dir else Path(server_root) / "run") / server_dir / "config" / "kv.config" if kv.exists(): merged.update(read_config_file(str(kv))) local_ip = get_local_ip() merged["ip"] = local_ip merged["game_host"] = local_ip return merged 3. server_commands.py 命令生成核心代码 必须提供这些函数: - get_local_ip() -> str - get_erl_cmd(erl_path: str = None) -> str - get_escript_cmd(erl_path: str = None) -> str - get_rebar3_cmd(server_root: str, erl_path: str = None) -> str - get_ebin_paths(server_root: str, profile: str = "game_server_dev") -> List[str] - get_rebar_profile(server_type: str) -> Tuple[str, str] - build_start_command_by_config(server_root: str, merged_config: Dict[str, str], cookie: str, use_rebar: bool = True, erl_path: str = None) -> str - build_start_command(server_root: str, server_name: str, cookie: str, use_rebar: bool = True, erl_path: str = None) -> str - build_stop_command(server_name: str, cookie: str, local_host: str = None, erl_path: str = None) -> List[str] - build_remsh_command(server_name: str, cookie: str, target_ip: str = None, erl_path: str = None) -> str - subprocess_args_to_display(args: List[str]) -> str 命令格式必须符合: - login_server 使用 profile login_server_dev,app login_server。 - client_server 使用 profile client_server_dev,app client_server。 - game/center/cross 使用 profile game_server_dev,app game_server。 - rebar3 启动: as shell --eval -1,"application:ensure_all_started()." --setcookie --name --config "" - 快速 erl 启动: "" -pa "" -pa "" ... -smp enable -setcookie -name -config "" -eval "application:ensure_all_started()." - stop 必须返回参数列表: [erl, "-noshell", "-name", "stop_@", "-setcookie", cookie, "-eval", "rpc:call('', init, stop, [])", "-s", "c", "q"] build_start_command_by_config 伪代码: def build_start_command_by_config(server_root, merged_config, cookie, use_rebar=True, erl_path=None): server_dir = merged_config.get("_server_dir", "") config_file = merged_config.get("_config_file", "") game_host = merged_config.get("game_host", get_local_ip()) node_name = merged_config.get("node_name", f"{server_dir}@{game_host}").replace('"', '').replace("'", "") server_type = merged_config.get("server_type", "game_server").strip('"').strip("'") profile, app_name = get_rebar_profile(server_type) if use_rebar: rebar3 = get_rebar3_cmd(server_root, erl_path) return f'{rebar3} as {profile} shell --eval -1,"application:ensure_all_started({app_name})." --setcookie {cookie} --name {node_name} --config "{config_file}"' erl = get_erl_cmd(erl_path) pa_args = " ".join(f'-pa "{p}"' for p in get_ebin_paths(server_root, profile)) return f'"{erl}" {pa_args} -smp enable -setcookie {cookie} -name {node_name} -config "{config_file}" -eval "application:ensure_all_started({app_name})."' 4. server_commands.py sys.config 生成核心代码 必须提供: - replace_placeholders(template_content: str, config: Dict[str, str]) -> str - get_template_file(config_path: Path, server_type: str) -> Optional[Path] - build_replace_map(config: Dict[str, str]) -> Dict[str, str] - generate_start_config(server_root: str, server_dir: str) -> Optional[Dict[str, str]] generate_start_config 必须: - 定位 kv.config: /run//config/kv.config。 - 读取 read_merged_config(server_root, server_dir)。 - 按 server_type 选择 sys_*.config.example。 - 将 log_dir 和 role_log 归一化为 run//。 - 替换所有 ${key}。 - 写入 /run//config/sys.config。 - 在 merged_config 中添加内部字段 _server_dir 和 _config_file。 核心伪代码: def _normalize_run_log_path(raw, default_tail): raw = (raw or "").strip('"').strip("'").strip() if raw.startswith("run/"): parts = raw.split("/", 2) tail = parts[2] if len(parts) > 2 and parts[2] else default_tail else: tail = raw or default_tail return f"run/{server_dir}/{tail}" merged_config["log_dir"] = _normalize_run_log_path(merged_config.get("log_dir", "log"), "log") if "role_log" in merged_config: merged_config["role_log"] = _normalize_run_log_path(merged_config.get("role_log", ""), "log") replace_map = build_replace_map(merged_config) output = template_content for key, value in replace_map.items(): output = output.replace(f"${{{key}}}", str(value)) unreplaced = re.findall(r"\$\{(\w+)\}", output) if unreplaced: print(f"[警告] 配置文件中有未替换的占位符: {set(unreplaced)}") sys_config_file.write_text(output, encoding="utf-8") build_replace_map 必须为缺失字段提供默认值: - prefix=ddxq2 - server_id=1 - server_type=game_server - tcp_port=18001 - http_port=19001 - log_dir=log - merge_server_ids=[] - merge_server_time={{1970,1,1},{0,0,0}} - last_merge_server_ids=[] - open_time={{2025,1,1},{10,0,0}} - auto_reload=true - ip/game_host 使用 get_local_ip() 5. server_commands.py 迁移与列表核心代码 必须提供: - run_startup_migrations(server_root, logger_func=None) -> Dict[str, Any] - get_config_list(run_dir: str) -> List[str] - get_server_list(run_dir: str) -> Dict[str, List[str]] - flatten_server_dirs(run_dir: str) -> List[str] 迁移状态: - 状态文件为 <项目根>/.server_manager/migrations.json。 - 迁移 ID 至少包含 sys_config_log_dir_v2 与 sys_config_role_log_v1。 - 每个迁移只执行一次,执行内容是为所有包含 config/kv.config 的服务器重新 generate_start_config。 get_server_list 分类规则: - 含 _game_s 或 _game_ -> game - 含 _cross_s 或 _cross_ -> cross - 含 _login_s 或 _login_ -> login - 含 _client_s 或 _client_ -> client - 含 _center_s 或 _center_ -> center - 其他有 config 目录的 -> other 6. server_commands.py 节点状态与远程 RPC 核心代码 必须提供: - class NodeStatusCache - get(node_name) -> Optional[bool] - set(node_name, online) - set_online(node_name) - set_offline(node_name) - batch_set(results) - get_all() - clear() - is_initialized() - set_initialized(value=True) - get_node_status_cache() -> NodeStatusCache - check_node_status(server_name, cookie, target_ip=None, erl_path=None) -> bool - check_nodes_status(server_names, cookie, target_ip=None, erl_path=None) -> Dict[str, bool] - query_remote_servers_from_login_rpc(login_node, cookie, erl_path=None, timeout=120) -> List[Dict[str, Any]] - rpc_role_gs_trace_network(server_name, cookie, role_id, enable, target_ip=None, erl_path=None) -> Tuple[int, str, str, str] 节点状态实现要求: - check_node_status 内部可调用 check_nodes_status。 - check_nodes_status 优先尝试 epmd 快速查询,再用 erl net_adm:ping 批量检测;即使不实现 epmd 优化,也必须提供批量接口和超时保护。 - 本地临时节点名使用 sm_ping_@ 或类似名称,避免重复。 - 启动 erl 前尽量执行 epmd -daemon。 远程服务器 RPC 要点: - 先 ping 登录服节点,失败则给出清晰错误。 - 通过临时 Erlang 脚本 file:script/1 调用登录服节点。 - 输出格式为: SM_COUNT数量 server_idserver_name_base64server_node - Python 侧解析 base64 得到 UTF-8 server_name。 - 返回字段至少包含 server_id、server_name、server_node、running=False。 玩家协议 trace RPC: - enable=True 调 role_gs:trace_network(RoleId)。 - enable=False 调 role_gs:trace_network_close(RoleId)。 - 返回 returncode、stdout、stderr、完整命令行。 7. server_commands.py 日志路径核心代码 必须提供: - resolve_log_file_path(run_dir, server_dir, base_filename, date_yyyymmdd=None, subdir="") -> Path - read_text_file_best_effort(path: Path, max_bytes: int = 3145728) -> Tuple[str, Optional[str]] 路径规则: log_root = Path(run_dir) / server_dir / "log" if subdir: log_root = log_root / subdir if date_yyyymmdd 是 8 位数字: name = f"{base_filename}.{date_yyyymmdd}" else: name = base_filename return log_root / name 读取规则: - 文件不存在、非普通文件、超过 max_bytes 都返回 ("", 错误说明)。 - 编码依次尝试 utf-8、utf-8-sig、gbk、latin-1。 - 都失败则 utf-8 errors="replace"。 8. server_creator.py 核心代码 必须定义: - SERVER_TYPE_MAP = {"game": "game_server", "login": "login_server", "client": "client_server", "center": "center_server", "cross": "cross_server"} - SERVER_TYPE_NAMES = {"game": "游戏服", "login": "登录服", "client": "客户端测试服", "center": "中心服", "cross": "跨服"} - BASE_REQUIRED_KEYS、SERVER_TYPE_REQUIRED_KEYS、BASE_CONFIG_KEYS、SERVER_TYPE_SPECIFIC_KEYS。 必须提供: - get_required_keys_from_kv(server_root, server_type) - get_config_keys_from_kv(server_root, server_type) - get_editable_config_from_default_kv(server_root) - get_allowed_config_keys(server_type, server_root="") - get_all_valid_keys(server_root="") - get_required_keys(server_type, server_root="") - get_server_type_name(server_type) - extract_template_placeholders_with_comments(server_root, server_type) - get_template_config_keys(server_root, server_type) - get_server_type_str(server_type) - generate_server_dir_name(prefix, server_type, server_id) - get_existing_server_ids(run_dir, server_type, prefix) - build_kv_config(...) - create_server(...) - get_id_range_from_config(default_kv, server_type) - get_default_port(server_type, server_id) - get_default_server_name(prefix, server_id) - build_confirmation_message(...) build_kv_config 必须生成差异配置: server_type_str = SERVER_TYPE_MAP.get(server_type, "game_server") type_prefix = {"game": "game", "login": "login", "center": "center", "cross": "cross", "client": "client"}[server_type] db_game_name = f"{prefix}_{type_prefix}_s{server_id}" db_log_name = f"{prefix}_{type_prefix}_log_s{server_id}" if server_type != "login" else "" diff_config = { "prefix": prefix, "server_id": str(server_id), "server_type": server_type_str, "ip": ip, "db_host": db_host, "db_user": db_user, "db_pass": db_pass, "db_port": str(db_port), "log_dir": "log", "db_game_name": db_game_name, } 类型字段: - game:写 server_name、game_host、tcp_port、http_port、open_time、role_log=run//log。 - login:写 tcp_port。 - center:写 open_time。 - cross:写 open_time。 - client:写 game_host、tcp_port、game_tcp_port、login_host、login_http_port。 - game/client/center 有 login_node 时写 login_node。 - game/cross 有 center_node 时写 center_node。 create_server 必须: - 从 read_merged_config(server_root) 取 ip,取不到用 get_local_ip。 - run_dir 为空时使用 Path(server_root) / "run"。 - 创建 //config。 - 如果 kv.config 已存在且 overwrite=False,返回 False。 - 写入 kv.config。 - 调 generate_start_config(server_root, server_dir)。 - 返回 (True, "服务器 创建成功", merged_config) 或 (False, 错误, None)。 9. server_manager_cli.py 核心代码 必须实现 class ServerManagerCLI: - __init__(self, server_root: str = None, config_path: str = None) - _read_tool_config_value(self, detected_root: str, key: str) -> str - _read_configured_server_root(self, detected_root: str) -> str - _read_configured_run_dir(self, detected_root: str) -> str - _detect_server_root(self) -> str - _find_server_root(self) -> Path - _load_config(self, config_path: str = None) - list_servers(self) -> dict - create_server_cmd(...) - start_server(self, server_name: str, use_rebar: bool = True, foreground: bool = False) -> bool - stop_server(self, server_name: str) -> bool - connect_server(self, server_name: str, target_ip: str = None) -> bool - regenerate_config(self, server_name: str) -> bool - compile(self, target: str = "all", server_type: str = "game") -> bool - interactive_menu(self) CLI 初始化行为: - detected_root = _detect_server_root() - configured_root = _read_configured_server_root(detected_root) - server_root 优先级:参数 server_root > configured_root > detected_root - run_dir 优先级:tool.config 的 run_dir > server_root/run - config_path = get_server_manager_config_dir(server_root) - 加载配置后设置 prefix、db_host、db_port、db_user、db_pass、cookie。 - 执行 run_startup_migrations(server_root)。 compile 命令格式: - all:依次 code、proto、table、tbllog。 - code: as game_server_dev compile 或 login_server_dev compile。 - proto: protobuf compile -a game_server。 - table: cache compile -a game_server。 - tbllog: tbllog compile。 10. server_manager_gui.py Config 核心代码 必须实现 _empty_config_data 与 class Config。 _empty_config_data 返回结构: { "workspace": {"server_root": "", "run_dir": ""}, "database": {"host": "", "user": "", "password": "", "port": 3306}, "server": { "prefix": "ddxq2", "login_server_node": "", "center_server_node": "", "default_server_id": "1", "server_type": "game_server", "server_name": "", "ip": get_local_ip(), "game_host": get_local_ip(), "cookie": "ddxq2-node", }, "erlang": {"r25_path": "", "erl": "erl", "werl": "werl", "escript": "escript"}, "login_db": {"name": "", "server_id": 900, "host": "", "port": 0, "user": "", "password": ""}, "commands": {"rebar": REBAR3_CMD, "svn": "svn"}, } Config 必须: - __init__(project_root=None):无项目时 config_error="未打开项目"。 - open_project(project_root):调用 ensure_and_get_server_manager_config_dir;成功后设置 tool_dir、tool_config_path、data。 - _load_tool_config:读 key=value。 - _save_tool_config:按固定分组写 tool.config。 - _load_default_kv(server_root):读 .server_manager/config/default.kv。 - _load_config:tool.config 为空时从 default.kv 补齐;首次运行自动创建 run_dir;需要保存补齐后的 tool.config。 - save:写 tool.config 前刷新 ip/game_host 为 get_local_ip()。 - get/set 支持嵌套 key。 - has_project/is_config_complete/is_configured/get_missing_config。 11. server_manager_gui.py GUI 类结构 必须保留这些核心类和职责: - NoWheelSpinBox、NoWheelComboBox、NoWheelDateTimeEdit:禁用滚轮误操作。 - ServerIdTableItem、NumericTableWidgetItem:表格数字排序。 - CopyableIpLabel:点击复制 IP。 - CommandRunner(QThread):执行 shell 命令,分批发 output_signal,结束发 finished_signal。 - NodeStatusCheckWorker(QThread):批量检查节点状态。 - GroupedNodeStatusCheckWorker(QThread):按 cookie 分组检查节点状态。 - StopServerWorker(QThread):停止节点、轮询离线、关闭本工具启动的窗口。 - OutputConsole(QTextEdit):彩色追加输出,支持独立 ConsoleWindow。 - CommandTab:编译、启动、remsh、停止、清档、查看账号、SVN。 - ClearDatabaseDialog/ClearDatabaseThread:清档。 - ViewAccountsDialog:查账号。 - ServerSelectDialog:本地服务器选择,含状态刷新。 - RemoteConnectDialog:本地/远程节点连接选择。 - KvConfigEditDialog:kv.config 编辑与 sys.config 重新生成。 - CreateServerTab:创建服务器。 - ServerManageTab:本地/远程服务器管理。 - ConfigTab:工具设置。 - DefaultKvTab:default.kv 编辑。 - UpdateCheckWorker/UpdateDownloadWorker:自动更新后台工作线程。 - WelcomePage:无项目欢迎页。 - MainWindow:主窗口、菜单、欢迎页/项目页切换、更新检查、项目迁移、状态缓存。 MainWindow 必须: - 初始化深色样式。 - 使用 QStackedWidget 管理 WelcomePage 与项目内容。 - 菜单栏 setNativeMenuBar(False)。 - 文件菜单含打开项目、最近的项目、关闭项目、退出。 - 帮助菜单含检查更新、关于。 - 项目内容使用 QSplitter 垂直分割主标签页与控制台。 - 主标签页添加:控制台、创建服务器、服务器管理、日志查看、工具设置。 - 切到服务器管理页时从缓存刷新状态;切到日志查看页时刷新服务器列表。 - closeEvent 中停止还在运行的 QThread,避免退出崩溃。 server_manager_gui.py main() 行为: - 调 clean_up_old_version。 - 抑制 PyInstaller 退出时的临时目录告警。 - 如果 len(sys.argv) > 1,调用 _run_cli_from_argv(),将参数交给 server_manager_cli.main。 - 无参数创建 QApplication、配置 frozen Qt 运行时、显示 MainWindow。 12. log_viewer_pane.py 与 rg_search.py 核心代码 rg_search.py 必须提供: - find_rg_executable() -> Optional[Path] - rg_match_spans_qt(full_text, pattern, case_sensitive, use_regex, whole_word, max_spans=3000) - compile_python_span_pattern(pattern, case_sensitive, use_regex, whole_word) - rg_filter_lines(full_text, pattern, case_sensitive, use_regex, whole_word) - rg_search_directory(search_root, pattern, case_sensitive, use_regex, whole_word, glob_globs=None) find_rg_executable 查找顺序: - RG_PATH 环境变量。 - 打包后 main.exe 同目录的 rg.exe/rg。 - 源码仓库根目录的 rg.exe/rg。 - PATH 中的 rg。 rg_match_spans_qt 要点: - 调 rg --json --color never。 - 非大小写敏感加 -i。 - use_regex=False 用 --fixed-strings;whole_word=True 加 -w。 - use_regex=True 且 whole_word=True 时 pattern 包装为 \b(?:pattern)\b。 - 从 rg JSON 的 absolute_offset 和 submatches start/end 得到 UTF-8 byte offsets。 - 将 UTF-8 byte offsets 转成 Qt QTextCursor 使用的 UTF-16 偏移: qt_pos = len(prefix.encode("utf-16-le")) // 2。 LogViewerPane 必须: - 保存 self._full_text、self._current_match_range、spans cache。 - UI 包含搜索输入、区分大小写、仅显示匹配行、正则表达、全词匹配、上一个、下一个、rg 提示、只读 QPlainTextEdit。 - set_full_text(text, reset_search=True) 设置全文并可重置搜索选项。 - _apply_filter_or_full:勾选仅显示匹配行时优先 rg_filter_lines,失败回退 Python re。 - _match_spans_in_plain:优先 rg_match_spans_qt,失败回退 Python re,最多 3000 个匹配。 - find_next/find_prev:循环跳转匹配。 - _highlight_matches:当前匹配用亮色,其余匹配用暗黄色。 - 快捷键 n、Shift+N、g、Shift+G、/、F3、Shift+F3、Ctrl+F。 13. log_viewer_tab.py 核心代码 必须提供: - _run_dir_from_config(config) -> str - _refresh_server_combo(combo, config) -> bool - class LogLoadTab - class LogSearchTab - class LogViewerTab LogLoadTab 行为: - 预设日志名:error.log、debug.log、http_info.log、http_error.log、info.log、http.log。 - 两种模式:服务器日志、玩家协议(tag_log)。 - 玩家协议文件名 network_codec-.log,role_id 必须纯数字。 - 日期归档文件名 .YYYYMMDD。 - load_log 调 resolve_log_file_path 和 read_text_file_best_effort;成功显示到内嵌 QPlainTextEdit,并可打开 LogViewerStandaloneDialog。 - open_log_directory Windows 用 os.startfile,其他平台用 xdg-open。 - _prompt_trace 只允许 game 类型服务器,输入 role_id 后调 rpc_role_gs_trace_network。 LogSearchTab 行为: - 必须检测 rg,找不到时 QMessageBox 提示。 - 搜索根目录为 //log。 - 调 rg_search_directory;结果用 LogViewerStandaloneDialog 打开。 - 与 LogLoadTab 的服务器下拉互相同步。 14. updater.py 与 mini_updater.py 核心代码 src/updater.py 必须实现: - _version_json_path:打包后优先 main.exe 同目录 version.json,再 sys._MEIPASS/version.json;源码运行用项目根 version.json。 - get_current_version:读取 version 字段,失败返回 0.0.0。 - get_version_info:读取完整 JSON,失败返回 {"version": "0.0.0", "release_notes": ""}。 - _parse_version/version_less:将版本字符串转 int tuple 比较。 - fetch_update_manifest:HTTP GET update_url,兼容 full_installer_url 与旧 download_url。 - get_delta_for_current:从 manifest["delta_updates"][current_version] 取 patch_url/new_exe_sha256。 - download_file:requests stream 下载,progress_callback(percent)。 - apply_delta_patch:bsdiff4.patch 当前 exe + patch,校验 sha256,写 TEMP/ServerManager_new.exe,调用 apply_update_and_restart。 - run_installer_and_exit:Windows 下静默参数启动安装包,frozen 时 os._exit(0)。 mini_updater.py 必须: - argparse 参数 --pid、--install-dir、--new-exe-path、--target-exe-name。 - is_process_alive Windows 用 ctypes OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION),Unix 用 os.kill(pid, 0)。 - 每 0.5 秒等待主进程退出。 - 删除旧的 target.exe.old。 - os.rename(target.exe, target.exe.old)。 - shutil.move(new_exe_path, target.exe)。 - subprocess.Popen([target_exe_full], cwd=install_dir, close_fds=True, creationflags=DETACHED_PROCESS)。 - 错误写入 install_dir/logs/update_error.log。 15. release.py 核心代码 必须提供: - read_text/write_text - read_version_from_json - get_current_version:只以 output/version.json 为准。 - set_version_pre(new_version, release_notes=None) -> str - set_version_post(old_exe_path=None) -> None - _copy_version_to_output() set_version_pre 必须: - 读取根目录 version.json。 - old_version 从 output/version.json 读取,读不到直接失败。 - 正则更新 build_installer.iss 中 #define MyAppVersion。 - 正则更新 main.py 中 LOCAL_VERSION。 - version.json 设置 version、release_notes。 - full_installer_url 固定指向 ServerManager_Setup.exe。 - delta_updates 只保留 old_version -> new_version 一项,patch_url 格式为 output/patches/v_to_v.patch。 - 如果 src/dist/main/_internal/version.json 或 src/dist/main/version.json 存在,则同步复制。 set_version_post 必须: - 找新 exe:src/dist/main/main.exe,找不到再找 src/dist/main.exe。 - 没有 old_exe_path 时只复制 version.json 到 output。 - 有 old_exe_path 且 bsdiff4 可用时生成 output/patches/v_to_v.patch。 - 计算新 exe sha256,填回 version.json 的 delta_updates[old_version].new_exe_sha256。 - 最后复制 version.json 到 output。 16. server_commands.py 新窗口启动与窗口追踪细则 必须提供: - run_cmd_in_new_window(cmd: str, working_dir: str, window_title: str = "Server", erl_path: str = None) -> str - run_cmd_in_terminal_linux(cmd: str, working_dir: str, window_title: str = "Server", erl_path: str = None) -> str - register_launched_window(server_name: str, kind: str, window_title: str, launch_info: str = "") -> None - get_launched_windows(server_name: str) -> List[Dict[str, Any]] - close_launched_windows(server_name: str) -> List[Tuple[Dict[str, Any], bool, str]] - run_cmd_foreground(cmd: str, working_dir: str, window_title: str = "Server") -> subprocess.Popen Windows 新窗口启动必须这样实现: - 创建临时 .bat 文件。 - bat 内容包含: - `@echo off` - `title ` - `cd /d ""` - `set ESCRIPT_EMULATOR=erl` - 打印命令分隔线和命令内容 - 执行原始 cmd - 使用 `subprocess.Popen(["cmd", "/k", bat_path], creationflags=0x00000010, cwd=working_dir)` 启动新控制台。 - 返回 `winpid:`,用于后续精确关闭窗口。 - 如果失败,兜底使用 `start cmd /k ""`,返回 bat_path。 Linux 新窗口/后台启动: - 创建临时 .sh,内容包含 cd、`export ESCRIPT_EMULATOR=erl`、打印命令、执行 cmd。 - chmod 755。 - 如果有 screen:`screen -dmS bash