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

384 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
author: Peanut
created_at: 2026-04-26
updated_at: 2026-04-26
purpose: 设计并优化情绪博物馆跨平台服务管理脚本,修复硬编码路径、中文乱码、依赖等待逻辑等问题
---
# 情绪博物馆 - 跨平台服务管理脚本设计
## 概述
创建一个 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
```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 接口
```bash
# 启动服务(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` 字段构建依赖图,按拓扑排序启动:
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:run``npm 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.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 控制台中文乱码:
```python
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` 中不再等待"已加入启动队列但还未就绪"的服务,改为:
1. 先启动依赖服务(如 backend)
2. 等待其健康检查通过
3. 再启动依赖它的服务(如 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``node`
- `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_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+ (前端依赖)
## 测试方案
### 单元测试
- 测试配置解析
- 测试依赖图拓扑排序
- 测试跨平台命令生成
- 测试健康检查逻辑
### 集成测试
- 启动单个服务并验证健康检查
- 启动所有服务并验证依赖顺序
- 停止服务并验证进程清理
- 重启服务并验证状态恢复
### 手动测试
```bash
# 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. 彩色终端输出