Files
happy-life-star/docs/superpowers/specs/2026-04-26-service-manager-design.md
T
peanut 646ab3d300 feat: 更新服务管理脚本优化设计文档
添加 10 项优化修复的设计说明:中文编码修复、动态可执行文件查找、
依赖等待逻辑修复、restart 支持 all、clean 增强、PID 严格验证、
进度条稳定、mini-program 补充配置。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 12:47:54 +08:00

14 KiB
Raw Blame History

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.xmlbuild_check 的修改时间,若 pom.xml 更新则重新构建
  • Node 服务:比较 package-lock.json(或 package.json)和 node_modules 的修改时间,若依赖文件更新则重新安装
  • 配置文件中新增 lock_file 字段指定锁文件路径

3. 服务启动顺序编排

根据 depends_on 字段构建依赖图,按拓扑排序启动:

  1. 先启动无依赖的服务(如 backend)
  2. 等待后端健康检查通过后,启动前端服务
  3. 并行启动无相互依赖的服务(如 web 和 web-admin

停止顺序:启动顺序的逆序(反向拓扑排序)

  1. 先停止无下游依赖的服务(如 mini-program → web-admin → web
  2. 最后停止根服务(backend
  3. 每个服务停止后等待 2 秒,确保端口释放

并发控制

  • 最大并发启动数:3(避免同时启动过多服务导致系统负载过高)
  • 失败策略:某个服务启动失败时,停止已启动的依赖服务,输出错误信息并终止流程

4. 健康检查

  • HTTP 检查: 向 http://localhost:{port}{health_check_path} 发送 GET 请求
    • Java 服务(/actuator/health):检查返回 JSON 中 status 字段是否为 UP
    • Node 前端服务(/):检查 HTTP 状态码是否为 2xx(不检查内容,因为 Vite 返回 HTML)
  • 端口检查(备选):使用 socket 检查端口是否监听,适用于无 HTTP 健康检查端点的服务
  • 超时控制: 默认等待 60 秒,每 2 秒重试一次
  • 日志输出: 显示等待进度 [=====> ] 50%

5. 日志聚合

  • 读取服务日志文件(如 logs/emotion-single-local.log
  • --follow 模式:类似 tail -f,实时输出新日志
  • --lines N:显示最近 N 行日志

6. 优雅停止

  1. 读取 {service.dir}/.pid 文件获取进程 ID
  2. 发送 SIGTERM(或 Windows 的 taskkill /PID {pid}
  3. 等待进程退出(最多 10 秒,每 0.5 秒检查一次)
  4. 如果未退出,发送 SIGKILL(或 taskkill /F /PID {pid}
  5. 删除 .pid 文件

7. 后台进程管理(关键设计)

问题mvn spring-boot:runnpm run dev 是前台阻塞命令,直接运行会阻塞主脚本。

解决方案:使用 subprocess.Popen 实现后台运行

  • 输出重定向:将 stdout 和 stderr 重定向到日志文件
    • Java: {service.dir}/logs/manage-{service}.log
    • Node: {service.dir}/logs/manage-{service}.log
  • 进程组创建
    • Windows: creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
    • Linux/macOS: start_new_session=True(等同于 os.setsid
  • 环境变量注入:通过 subprocess.Popenenv 参数传递
    • 合并当前环境变量 + 配置文件中定义的 env 字段
    • 配置文件的 env 覆盖系统环境变量

8. 日志聚合

  • 读取服务日志文件(如 logs/emotion-single-local.loglogs/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\nodejsC:\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 中不再等待"已加入启动队列但还未就绪"的服务,改为:

  1. 先启动依赖服务(如 backend
  2. 等待其健康检查通过
  3. 再启动依赖它的服务(如 web、web-admin

10.4 restart 支持 all 参数

restart_parserservice 参数改为 nargs="?",允许不传值。代码中处理 all 时触发全量重启。

10.5 clean 增强

清理范围扩展:

  • Node 服务: node_modulesdist.vite 目录
  • Java 服务: target 目录(优先使用 mvn cleanfallback 为直接删除)

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): 服务类型,仅支持 javanode
  • port (integer): 服务端口号(1-65535
  • start_cmd (string): 启动命令

可选字段(有默认值):

  • health_check_path (string): 默认 /
  • pid_file (string): 默认 .pid
  • depends_on (list): 默认 []
  • env (dict): 默认 {}
  • lock_file (string): 默认 null(不检测依赖更新)

条件必填字段

  • Java 服务 (type: java): 建议配置 build_cmdbuild_check
  • Node 服务 (type: node): 建议配置 install_cmdinstall_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

9. 彩色终端输出