feat: 添加跨平台服务管理脚本设计文档
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
---
|
||||
author: opencode
|
||||
created_at: 2026-04-26
|
||||
purpose: 设计情绪博物馆项目的跨平台服务管理脚本,支持后端、前端、管理后台、小程序 H5 四个服务的启动、停止、重启、状态检查、日志查看等功能
|
||||
---
|
||||
|
||||
# 情绪博物馆 - 跨平台服务管理脚本设计
|
||||
|
||||
## 概述
|
||||
|
||||
创建一个 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]` - 调试信息
|
||||
|
||||
## 配置 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. 彩色终端输出
|
||||
Reference in New Issue
Block a user