添加 10 项优化修复的设计说明:中文编码修复、动态可执行文件查找、 依赖等待逻辑修复、restart 支持 all、clean 增强、PID 严格验证、 进度条稳定、mini-program 补充配置。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
14 KiB
author, created_at, updated_at, purpose
| author | created_at | updated_at | purpose |
|---|---|---|---|
| Peanut | 2026-04-26 | 2026-04-26 | 设计并优化情绪博物馆跨平台服务管理脚本,修复硬编码路径、中文乱码、依赖等待逻辑等问题 |
情绪博物馆 - 跨平台服务管理脚本设计
概述
创建一个 Python 脚本 (manage.py) 及配套的 YAML 配置文件 (manage.conf.yaml),用于统一管理情绪博物馆项目的多个服务。支持跨平台运行(Windows / Linux / macOS),提供服务编排、依赖管理、健康检查、日志聚合等企业级功能。
架构设计
文件结构
项目根目录/
├── manage.py # 主入口脚本 (CLI)
├── manage.conf.yaml # 服务配置文件
└── tools/
└── service_manager.py # 核心服务管理模块
模块职责
| 模块 | 职责 | 依赖 |
|---|---|---|
manage.py |
CLI 入口,参数解析,命令分发 | argparse, tools.service_manager |
manage.conf.yaml |
服务定义、端口、启动命令、依赖关系 | 无 |
tools/service_manager.py |
进程管理、健康检查、日志读取、依赖安装 | psutil, subprocess, requests, pyyaml |
服务配置
manage.conf.yaml
# 服务配置文件
# 定义所有可管理的服务及其属性
services:
backend:
name: "后端服务"
dir: "backend-single"
type: "java"
port: 19089
health_check_path: "/actuator/health"
start_cmd: "mvn spring-boot:run"
build_cmd: "mvn clean package -DskipTests"
build_check: "target/emotion-single-1.0.0.jar"
lock_file: "pom.xml" # 用于检测是否需要重新构建
pid_file: ".pid" # 存储在服务目录内: {service.dir}/.pid
log_file: "logs/emotion-single-local.log"
depends_on: []
env:
SPRING_PROFILES_ACTIVE: "local"
web:
name: "用户前端"
dir: "web"
type: "node"
port: 5173
health_check_path: "/"
start_cmd: "npm run dev"
install_cmd: "npm install"
install_check: "node_modules"
lock_file: "package-lock.json" # 用于检测是否需要重新安装
pid_file: ".pid" # 存储在服务目录内: {service.dir}/.pid
depends_on: ["backend"]
web-admin:
name: "管理后台"
dir: "web-admin"
type: "node"
port: 5174
health_check_path: "/"
start_cmd: "npm run dev"
install_cmd: "npm install"
install_check: "node_modules"
lock_file: "package-lock.json" # 用于检测是否需要重新安装
pid_file: ".pid" # 存储在服务目录内: {service.dir}/.pid
depends_on: ["backend"]
mini-program:
name: "小程序 H5"
dir: "mini-program"
type: "node"
port: 5175
health_check_path: "/"
start_cmd: "npm run dev:h5"
install_cmd: "npm install"
install_check: "node_modules"
lock_file: "package-lock.json" # 用于检测是否需要重新安装
pid_file: ".pid" # 存储在服务目录内: {service.dir}/.pid
depends_on: ["backend"]
命令设计
CLI 接口
# 启动服务(service 参数可选,默认为 all)
python manage.py start # 启动所有服务(按依赖顺序)
python manage.py start backend # 启动后端
python manage.py start web # 启动用户前端
# 停止服务(service 参数可选,默认为 all)
python manage.py stop # 停止所有服务(逆依赖顺序)
python manage.py stop backend # 停止后端
python manage.py stop all # 显式停止所有服务
# 重启服务(必须指定服务名称)
python manage.py restart backend # 重启后端
python manage.py restart all # 重启所有服务
# 查看状态
python manage.py status # 显示所有服务状态
# 查看日志(必须指定服务名称)
python manage.py logs backend # 查看后端日志
python manage.py logs backend --follow # 实时跟踪日志
python manage.py logs backend --lines 100 # 查看最近 100 行
# 显示访问地址
python manage.py info # 显示所有服务访问地址
# 其他命令
python manage.py clean backend # 清理后端构建产物
python manage.py setup web # 安装前端依赖
输出格式
╔══════════════════════════════════════════════════════════════╗
║ 情绪博物馆 - 服务管理器 ║
╚══════════════════════════════════════════════════════════════╝
[INFO] 启动服务: 后端服务 (backend)
[INFO] 检查依赖...
[INFO] 依赖检查通过
[INFO] 启动进程: mvn spring-boot:run
[INFO] 等待服务就绪...
[INFO] ✅ 后端服务 已启动 (PID: 12345, 端口: 19089)
──────────────────────────────────────────────────────────────
服务地址:
🔌 后端 API: http://localhost:19089/api
📊 WebSocket: ws://localhost:19089/ws
──────────────────────────────────────────────────────────────
核心功能实现
1. 跨平台进程管理
- Windows: 使用
subprocess.CREATE_NEW_PROCESS_GROUP+taskkill - Linux/macOS: 使用
os.setsid+kill(SIGTERM → 超时 → SIGKILL) - PID 管理: 每个服务目录创建
.pid文件,记录进程 ID
2. 依赖检测与安装
启动前检查:
- Java 服务:检查
build_check路径是否存在,不存在则执行build_cmd - Node 服务:检查
install_check路径是否存在,不存在则执行install_cmd
依赖更新检测:
- Java 服务:比较
pom.xml和build_check的修改时间,若pom.xml更新则重新构建 - Node 服务:比较
package-lock.json(或package.json)和node_modules的修改时间,若依赖文件更新则重新安装 - 配置文件中新增
lock_file字段指定锁文件路径
3. 服务启动顺序编排
根据 depends_on 字段构建依赖图,按拓扑排序启动:
- 先启动无依赖的服务(如 backend)
- 等待后端健康检查通过后,启动前端服务
- 并行启动无相互依赖的服务(如 web 和 web-admin)
停止顺序:启动顺序的逆序(反向拓扑排序)
- 先停止无下游依赖的服务(如 mini-program → web-admin → web)
- 最后停止根服务(backend)
- 每个服务停止后等待 2 秒,确保端口释放
并发控制:
- 最大并发启动数:3(避免同时启动过多服务导致系统负载过高)
- 失败策略:某个服务启动失败时,停止已启动的依赖服务,输出错误信息并终止流程
4. 健康检查
- HTTP 检查: 向
http://localhost:{port}{health_check_path}发送 GET 请求- Java 服务(
/actuator/health):检查返回 JSON 中status字段是否为UP - Node 前端服务(
/):检查 HTTP 状态码是否为 2xx(不检查内容,因为 Vite 返回 HTML)
- Java 服务(
- 端口检查(备选):使用
socket检查端口是否监听,适用于无 HTTP 健康检查端点的服务 - 超时控制: 默认等待 60 秒,每 2 秒重试一次
- 日志输出: 显示等待进度
[=====> ] 50%
5. 日志聚合
- 读取服务日志文件(如
logs/emotion-single-local.log) --follow模式:类似tail -f,实时输出新日志--lines N:显示最近 N 行日志
6. 优雅停止
- 读取
{service.dir}/.pid文件获取进程 ID - 发送 SIGTERM(或 Windows 的
taskkill /PID {pid}) - 等待进程退出(最多 10 秒,每 0.5 秒检查一次)
- 如果未退出,发送 SIGKILL(或
taskkill /F /PID {pid}) - 删除
.pid文件
7. 后台进程管理(关键设计)
问题:mvn spring-boot:run 和 npm run dev 是前台阻塞命令,直接运行会阻塞主脚本。
解决方案:使用 subprocess.Popen 实现后台运行
- 输出重定向:将 stdout 和 stderr 重定向到日志文件
- Java:
{service.dir}/logs/manage-{service}.log - Node:
{service.dir}/logs/manage-{service}.log
- Java:
- 进程组创建:
- Windows:
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP - Linux/macOS:
start_new_session=True(等同于os.setsid)
- Windows:
- 环境变量注入:通过
subprocess.Popen的env参数传递- 合并当前环境变量 + 配置文件中定义的
env字段 - 配置文件的
env覆盖系统环境变量
- 合并当前环境变量 + 配置文件中定义的
8. 日志聚合
- 读取服务日志文件(如
logs/emotion-single-local.log或logs/manage-{service}.log) --follow模式:跨平台实现方案- 使用文件偏移量轮询(非平台特定的
tail -f) - 每 0.5 秒检查文件大小变化,读取新增内容
- 支持 Ctrl+C 优雅退出
- 使用文件偏移量轮询(非平台特定的
--lines N:从文件末尾向前读取 N 行(使用seek到文件末尾,向前扫描换行符)
9. 彩色终端输出
- 使用
colorama实现跨平台彩色输出 - 颜色规范:
- 绿色
[INFO]- 正常信息 - 黄色
[WARN]- 警告信息 - 红色
[ERROR]- 错误信息 - 蓝色
[SECTION]- 分隔标题 - 青色
[DEBUG]- 调试信息
- 绿色
10. 优化修复(2026-04-26)
10.1 中文编码修复
在 manage.py 入口添加 UTF-8 编码配置,修复 Windows 控制台中文乱码:
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
10.2 动态可执行文件查找
移除所有硬编码路径,新增 _find_executable() 方法动态查找 Node/Maven/npm 路径:
- Node:
shutil.which('node.exe')→F:\Program Files\nodejs→C:\Program Files\nodejs→%APPDATA%\npm - npm:
shutil.which('npm.cmd')→ 同 Node 路径 - Maven:
shutil.which('mvn.cmd')→shutil.which('mvn.bat')→ 常见 Maven 安装路径
10.3 依赖等待逻辑修复
_start_all 中不再等待"已加入启动队列但还未就绪"的服务,改为:
- 先启动依赖服务(如 backend)
- 等待其健康检查通过
- 再启动依赖它的服务(如 web、web-admin)
10.4 restart 支持 all 参数
将 restart_parser 的 service 参数改为 nargs="?",允许不传值。代码中处理 all 时触发全量重启。
10.5 clean 增强
清理范围扩展:
- Node 服务:
node_modules、dist、.vite目录 - Java 服务:
target目录(优先使用mvn clean,fallback 为直接删除)
10.6 PID 严格验证
不仅检查 PID 存在,还检查进程的 cwd(工作目录)是否匹配服务目录,防止误判复用 PID 的其他进程。
10.7 进度条显示稳定
进度更新增加节流:仅在百分比变化 >= 10% 时刷新终端输出,避免频繁抖动。
10.8 mini-program 补充 log_file 配置
在 manage.conf.yaml 中为 mini-program 服务添加 log_file 字段。
配置 Schema 校验
配置文件加载后进行严格校验:
必填字段(每个服务必须包含):
name(string): 服务显示名称dir(string): 服务目录路径(相对于项目根目录)type(string): 服务类型,仅支持java或nodeport(integer): 服务端口号(1-65535)start_cmd(string): 启动命令
可选字段(有默认值):
health_check_path(string): 默认/pid_file(string): 默认.piddepends_on(list): 默认[]env(dict): 默认{}lock_file(string): 默认null(不检测依赖更新)
条件必填字段:
- Java 服务 (
type: java): 建议配置build_cmd和build_check - Node 服务 (
type: node): 建议配置install_cmd和install_check
校验失败行为:输出具体错误信息(如 backend: port 必须是整数),终止脚本执行
错误处理
| 场景 | 处理方式 |
|---|---|
| 服务已运行 | 提示服务已在运行,显示 PID 和端口 |
| 端口被占用 | 检查是否为同一服务,否则报错提示 |
| 依赖安装失败 | 输出错误日志,终止启动流程 |
| 健康检查超时 | 提示超时,建议查看日志排查 |
| 进程异常退出 | 记录退出码,提示查看日志 |
| 配置文件缺失 | 使用默认配置或报错 |
| Python 依赖缺失 | 提示运行 pip install -r requirements.txt |
| 配置字段缺失 | 启动时校验配置,缺少必填字段(name, dir, type, port, start_cmd)则报错退出 |
| 未知配置字段 | 忽略未知字段,输出警告日志 |
技术依赖
Python 依赖 (requirements.txt)
psutil>=5.9.0
pyyaml>=6.0
requests>=2.28.0
colorama>=0.4.6
系统依赖
- Python 3.8+
- Java 17+ (后端服务)
- Node.js 18+ (前端服务)
- Maven 3.6+ (后端构建)
- npm 9+ (前端依赖)
测试方案
单元测试
- 测试配置解析
- 测试依赖图拓扑排序
- 测试跨平台命令生成
- 测试健康检查逻辑
集成测试
- 启动单个服务并验证健康检查
- 启动所有服务并验证依赖顺序
- 停止服务并验证进程清理
- 重启服务并验证状态恢复
手动测试
# Windows 测试
python manage.py start all
python manage.py status
python manage.py stop all
# Linux/macOS 测试
python3 manage.py start backend
python3 manage.py logs backend --follow
python3 manage.py restart web