---
author: claude
created_at: 2026-06-27
purpose: 本地服务管理规则优化实现计划
---

# 本地服务管理规则优化实现计划

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 优化 dev-services.py，强制使用脚本管理本地服务，固定前端/H5 端口从 5178 开始累加，并在无必须重启的变更时禁止 restart

**Architecture:** 在 dev-services.py 中内置固定前端端口映射并强制使用，修改各前端项目 vite 配置和 env 文件同步端口，新增 restart 命令热加载保护，更新 CLAUDE.md 规则

**Tech Stack:** Python, Vite, UniApp, Spring Boot

---

## 文件结构

**修改：**
- `dev-services.py` - 添加固定端口映射、强制端口分配、热加载保护
- `web/vite.config.ts` - 端口改为 5178
- `web/.env.development` - 添加 `VITE_PORT=5178`
- `web-admin/vite.config.ts` - 端口改为 5179
- `web-admin/.env.development` - `VITE_APP_PORT` 改为 5179
- `mini-program/vite.config.js` - 端口改为 5180
- `mini-program/.env.development` - 添加 `VITE_PORT=5180`
- `life-script/vite.config.js` - 端口改为 5181
- `life-script/.env.development` - 添加 `VITE_PORT=5181`
- `CLAUDE.md` - 更新本地服务管理规则

---

### Task 1: 修改 dev-services.py 固定前端端口并保护端口分配

**Files:**
- Modify: `dev-services.py`

- [ ] **Step 1: 在常量区添加固定前端端口映射**

找到 `DEFAULT_PORTS` 定义（约第 58-68 行），在其后添加：

```python
# 前端/H5 服务固定端口映射（按项目目录名匹配）
FIXED_FRONTEND_PORTS = {
    "web": 5178,
    "web-admin": 5179,
    "mini-program": 5180,
    "life-script": 5181,
}
```

- [ ] **Step 2: 修改 `_update_service_port()` 函数以支持 Vite 端口**

当前 `_update_service_port()` 已处理 `--port` 和 `--server.port` 参数，还需要处理 Vite 的 `server.port` 配置更新。函数本身不需要修改，因为端口值更新后，`assign_unique_ports()` 会通过 `_service_has_explicit_port()` 判断。

但需要注意：对于 Vite/UniApp/Taro 服务，如果 `start_cmd` 中没有 `--port` 参数（当前 UniApp 的 `dev:h5` 脚本就没有），需要在启动命令前注入 `VITE_PORT` 环境变量或在命令中追加 `--port` 参数。

修改 `_update_service_port()` 函数，使其在更新 `start_cmd` 时，如果命令中没有 `--port` 参数，则在命令末尾追加 `--port {port}`：

修改前：
```python
def _update_service_port(svc: Service, port: int):
    old_port = svc.port
    if old_port == port:
        return

    svc.port = port
    svc.health_url = re.sub(r":\d+", f":{port}", svc.health_url, count=1)
    updated = []
    skip_next = False
    for index, part in enumerate(svc.start_cmd):
        if skip_next:
            skip_next = False
            continue
        if part == "--port" and index + 1 < len(svc.start_cmd):
            updated.extend([part, str(port)])
            skip_next = True
        elif part.startswith("--port="):
            updated.append(f"--port={port}")
        elif f"--server.port={old_port}" in part:
            updated.append(part.replace(f"--server.port={old_port}", f"--server.port={port}"))
        else:
            updated.append(part)
    svc.start_cmd = updated
```

修改后：
```python
def _update_service_port(svc: Service, port: int):
    old_port = svc.port
    if old_port == port:
        return

    svc.port = port
    svc.health_url = re.sub(r":\d+", f":{port}", svc.health_url, count=1)
    updated = []
    skip_next = False
    has_port_arg = False
    for index, part in enumerate(svc.start_cmd):
        if skip_next:
            skip_next = False
            continue
        if part == "--port":
            has_port_arg = True
            if index + 1 < len(svc.start_cmd):
                updated.extend([part, str(port)])
                skip_next = True
        elif part.startswith("--port="):
            has_port_arg = True
            updated.append(f"--port={port}")
        elif f"--server.port={old_port}" in part:
            updated.append(part.replace(f"--server.port={old_port}", f"--server.port={port}"))
        else:
            updated.append(part)

    # Vite/UniApp/Taro/Next 服务如果没有 --port 参数，追加 --port
    if not has_port_arg and svc.project_type in {"vite", "uniapp", "taro", "next"}:
        updated.extend(["--port", str(port)])

    svc.start_cmd = updated
```

- [ ] **Step 3: 重写 `assign_unique_ports()` 强制前端固定端口**

修改前：
```python
def assign_unique_ports(services: list[Service]) -> list[Service]:
    reserved = {svc.port for svc in services if _service_has_explicit_port(svc)}
    used = set()
    for svc in services:
        port = svc.port
        if port in used or (port in reserved and not _service_has_explicit_port(svc)):
            while port in used or port in reserved:
                port += 1
            log(f"{svc.name} 端口 {svc.port} 与其他服务冲突，自动调整为 {port}", "WARN")
            _update_service_port(svc, port)
        used.add(svc.port)
    return services
```

修改后：
```python
def assign_unique_ports(services: list[Service]) -> list[Service]:
    # 按目录名匹配固定前端端口
    dir_name_to_service = {}
    for svc in services:
        dir_name = svc.project_dir.name
        if dir_name in FIXED_FRONTEND_PORTS:
            dir_name_to_service[dir_name] = svc

    # 先为固定前端服务分配端口
    for dir_name, svc in dir_name_to_service.items():
        fixed_port = FIXED_FRONTEND_PORTS[dir_name]
        if svc.port != fixed_port:
            log(f"{svc.name} 使用固定前端端口 {fixed_port}", "INFO")
            _update_service_port(svc, fixed_port)

    # 检查固定端口冲突
    used_ports = {}
    for svc in services:
        port = svc.port
        if port in used_ports:
            other = used_ports[port]
            log(f"端口冲突: {svc.name} ({port}) 与 {other.name} ({port}) 冲突", "ERROR")
            sys.exit(1)
        used_ports[port] = svc

    # 非固定前端服务按原有逻辑处理冲突
    reserved = {svc.port for svc in services}
    for svc in services:
        dir_name = svc.project_dir.name
        if dir_name in FIXED_FRONTEND_PORTS:
            continue  # 固定前端服务已处理

        port = svc.port
        if port in used_ports and used_ports[port] is not svc:
            while port in used_ports:
                port += 1
            log(f"{svc.name} 端口 {svc.port} 与其他服务冲突，自动调整为 {port}", "WARN")
            _update_service_port(svc, port)

    return services
```

- [ ] **Step 4: 验证 dev-services.py 语法**

```bash
cd "G:/IdeaProjects/emotion-museun"
python dev-services.py discover
```

预期输出：成功列出所有发现的服务，且前端服务端口为 5178-5181

- [ ] **Step 5: 提交**

```bash
git add dev-services.py
git commit -m "feat: dev-services.py 强制前端服务使用固定端口

- 新增 FIXED_FRONTEND_PORTS 映射
- 前端/H5 端口从 5178 开始固定分配
- 端口冲突时不再自动递增前端服务端口，改为报错"
```

---

### Task 2: 为 dev-services.py 添加 restart 热加载保护

**Files:**
- Modify: `dev-services.py`

- [ ] **Step 1: 添加"必须重启"文件模式常量**

在 `RESTART_REQUIRED_PATTERNS` 常量区（与 `FIXED_FRONTEND_PORTS` 一起）添加：

```python
# 必须重启才能生效的文件模式
RESTART_REQUIRED_PATTERNS = [
    re.compile(r'vite\.config\.(ts|js|mjs|cjs)$'),
    re.compile(r'tsconfig\.json$'),
    re.compile(r'\.env(\.[^/]*)?$'),
    re.compile(r'package\.json$'),
    re.compile(r'application.*\.ya?ml$'),
    re.compile(r'pom\.xml$'),
    re.compile(r'build\.gradle(\.kts)?$'),
]
```

注意：需要确保文件顶部已导入 `re` 和 `sys`。

- [ ] **Step 2: 添加 `_should_restart_for_changes()` 函数**

在 `assign_unique_ports()` 函数附近添加：

```python
def _should_restart_for_changes(service_dir: Path) -> tuple[bool, list[str]]:
    """
    检查指定服务目录下是否有必须重启才能生效的变更。
    返回: (是否需要重启, 已修改文件列表)
    """
    try:
        result = subprocess.run(
            ["git", "diff", "--name-only", "--", str(service_dir)],
            capture_output=True,
            text=True,
            cwd=SCRIPT_DIR,
            check=True,
        )
        changed_files = [line.strip() for line in result.stdout.splitlines() if line.strip()]
    except subprocess.CalledProcessError:
        # 非 git 仓库或 git 命令失败，默认允许重启
        return True, []
    except FileNotFoundError:
        return True, []

    if not changed_files:
        return False, []

    for file_path in changed_files:
        for pattern in RESTART_REQUIRED_PATTERNS:
            if pattern.search(file_path):
                return True, changed_files

    return False, changed_files
```

- [ ] **Step 3: 修改 restart 命令解析支持 --force**

找到 `main()` 函数中的 `restart` 子命令解析部分（约文件末尾），添加 `--force` 参数。

当前 restart 解析可能类似：
```python
restart_parser = subparsers.add_parser("restart", help="重启所有或指定服务")
restart_parser.add_argument("service", nargs="?", help="指定服务名")
```

修改后：
```python
restart_parser = subparsers.add_parser("restart", help="重启所有或指定服务")
restart_parser.add_argument("service", nargs="?", help="指定服务名")
restart_parser.add_argument(
    "--force",
    action="store_true",
    help="强制重启，忽略热加载保护"
)
```

- [ ] **Step 4: 在 restart 执行前加入热加载保护**

找到 `cmd_restart()` 函数，在其开头添加检测逻辑。

假设 `cmd_restart()` 函数签名当前为：
```python
def cmd_restart(args):
    services = discover_services()
    services = _filter_services(services, args.service)
```

修改后：
```python
def cmd_restart(args):
    services = discover_services()
    services = _filter_services(services, args.service)

    if not args.force:
        blocked = []
        for svc in services:
            should_restart, changed_files = _should_restart_for_changes(svc.project_dir)
            if not should_restart:
                if changed_files:
                    blocked.append((svc.name, changed_files))
                else:
                    blocked.append((svc.name, []))

        if blocked:
            log("检测到以下服务无需重启（当前修改支持热加载）：", "WARN")
            for name, files in blocked:
                if files:
                    log(f"  - {name}: 修改文件 {', '.join(files)}", "WARN")
                else:
                    log(f"  - {name}: 未检测到代码变更", "WARN")
            log("如需强制重启，请使用: python dev-services.py restart --force", "WARN")
            sys.exit(0)

    # 原有 restart 逻辑继续...
```

注意：实际代码中 `cmd_restart()` 的函数名和参数可能不同，请根据实际代码调整。

- [ ] **Step 5: 验证热加载保护**

1. 修改一个 `.vue` 文件（例如 `mini-program/src/pages/main/ScriptView.vue`）
2. 运行：
   ```bash
   python dev-services.py restart mini-program
   ```
   预期：提示无需重启，并建议使用 `--force`
3. 运行：
   ```bash
   python dev-services.py restart mini-program --force
   ```
   预期：强制重启

- [ ] **Step 6: 提交**

```bash
git add dev-services.py
git commit -m "feat: dev-services.py restart 命令新增热加载保护

- 无必须重启的变更时禁止 restart
- 支持 --force 参数强制重启
- 避免无意义重启前端热加载服务"
```

---

### Task 3: 同步各前端项目端口配置

**Files:**
- Modify: `web/vite.config.ts`, `web/.env.development`
- Modify: `web-admin/vite.config.ts`, `web-admin/.env.development`
- Modify: `mini-program/vite.config.js`, `mini-program/.env.development`
- Modify: `life-script/vite.config.js`, `life-script/.env.development`

- [ ] **Step 1: 修改 web/ 端口为 5178**

修改 `web/vite.config.ts` 中的 `server.port`：

```typescript
server: {
  port: 5178,
  // ... 其他配置
}
```

修改/创建 `web/.env.development`，添加：
```
VITE_PORT=5178
```

- [ ] **Step 2: 修改 web-admin/ 端口为 5179**

修改 `web-admin/vite.config.ts` 中的 `server.port`：

```typescript
server: {
  port: 5179,
  // ... 其他配置
}
```

修改 `web-admin/.env.development`，将 `VITE_APP_PORT` 改为：
```
VITE_APP_PORT=5179
```

- [ ] **Step 3: 修改 mini-program/ 端口为 5180**

修改 `mini-program/vite.config.js` 中的 `server.port`：

```javascript
server: {
  port: 5180,
  // ... 其他配置
}
```

修改/创建 `mini-program/.env.development`，添加：
```
VITE_PORT=5180
```

- [ ] **Step 4: 修改 life-script/ 端口为 5181**

修改 `life-script/vite.config.js` 中的 `server.port`：

```javascript
server: {
  port: 5181,
  // ... 其他配置
}
```

修改/创建 `life-script/.env.development`，添加：
```
VITE_PORT=5181
```

- [ ] **Step 5: 验证端口配置**

```bash
cd "G:/IdeaProjects/emotion-museun"
python dev-services.py discover
```

预期输出：
- web 服务端口为 5178
- web-admin 服务端口为 5179
- mini-program 服务端口为 5180
- life-script 服务端口为 5181

- [ ] **Step 6: 提交**

```bash
git add web/ web-admin/ mini-program/ life-script/
git commit -m "chore: 统一前端/H5 服务固定端口

- web: 5178
- web-admin: 5179
- mini-program: 5180
- life-script: 5181"
```

---

### Task 4: 更新 CLAUDE.md 规则文档

**Files:**
- Modify: `CLAUDE.md`

- [ ] **Step 1: 添加/更新本地服务管理规则章节**

在 `CLAUDE.md` 中找到现有的"本地服务管理规则"或"热加载规则"章节，合并更新为统一的"本地服务管理规则（强制）"：

```markdown
## 本地服务管理规则（强制）

**启动、重启、停止本地前后端服务时，必须使用项目根目录下的 `dev-services.py` 脚本。**

### 强制要求

- ✅ 必须使用：`python dev-services.py [start|stop|restart|status|discover] [服务名]`
- ❌ 禁止直接使用 `npm run dev` / `pnpm run dev` 启动前端
- ❌ 禁止直接使用 `npm run dev:h5` 启动小程序 H5
- ❌ 禁止直接使用 `mvn spring-boot:run` 启动后端
- ❌ 禁止无意义重启支持热加载的服务

### 前端/H5 固定端口

| 服务 | 目录 | 固定端口 |
|---|---|---|
| web | `web/` | 5178 |
| web-admin | `web-admin/` | 5179 |
| mini-program | `mini-program/` | 5180 |
| life-script | `life-script/` | 5181 |

### 热加载规则

1. **不需要重启的场景**（热加载自动生效）
   - 前端：修改 `.vue`、`.tsx`、`.ts`、`.js`、`.css`、`.scss` 等源码文件
   - 样式、模板、静态资源等修改

2. **需要重启的场景**
   - 前端：修改 `vite.config.ts`、`tsconfig.json`、`.env`、`package.json`、别名配置等
   - 后端：按现有后端规则处理（本地不启动后端）

3. **禁止无意义重启**
   - 执行 `python dev-services.py restart` 时，脚本会自动检测是否有必须重启才能生效的变更
   - 如果只有支持热加载的源码文件变更，脚本会拒绝重启并提示：
     > 当前修改只涉及支持热加载的源码文件，无需重启。如需强制重启，请使用 `python dev-services.py restart --force`
   - 确需强制重启时，使用 `--force` 参数
```

- [ ] **Step 2: 删除冲突的旧规则**

检查 CLAUDE.md 中是否有与上述新规则冲突的旧规则（如旧的前端端口说明、热加载规则），删除或更新它们，确保文档内部一致。

- [ ] **Step 3: 提交**

```bash
git add CLAUDE.md
git commit -m "docs: 更新本地服务管理规则

- 强制使用 dev-services.py
- 明确前端/H5 固定端口
- 新增禁止无意义重启规则"
```

---

### Task 5: 最终验证

**Files:**
- 无需修改文件，仅做验证

- [ ] **Step 1: 验证 dev-services.py 发现服务端口正确**

```bash
cd "G:/IdeaProjects/emotion-museun"
python dev-services.py discover
```

预期输出：
- web 端口 5178
- web-admin 端口 5179
- mini-program 端口 5180
- life-script 端口 5181
- 后端服务保持原有端口

- [ ] **Step 2: 验证热加载保护**

1. 修改 `mini-program/src/pages/main/ScriptView.vue` 中任意一处（例如加一个空行）
2. 运行：
   ```bash
   python dev-services.py restart mini-program
   ```
   预期：提示无需重启
3. 运行：
   ```bash
   python dev-services.py restart mini-program --force
   ```
   预期：强制重启

- [ ] **Step 3: 验证必须重启的场景**

1. 修改 `mini-program/vite.config.js` 中任意一处
2. 运行：
   ```bash
   python dev-services.py restart mini-program
   ```
   预期：正常允许重启

- [ ] **Step 4: 验证端口冲突检测**

1. 手动启动一个占用 5178 端口的进程（例如另一个 node 服务）
2. 运行：
   ```bash
   python dev-services.py start web
   ```
   预期：报错，提示端口 5178 被占用
3. 终止占用进程

- [ ] **Step 5: 检查 CLAUDE.md 一致性**

快速浏览 `CLAUDE.md`，确认：
- 没有重复的本地服务管理规则
- 前端端口说明与新的固定端口表一致
- 热加载规则与 dev-services.py 行为一致

- [ ] **Step 6: 提交验证修复（如有）**

如果在验证中修复了问题：

```bash
git add dev-services.py CLAUDE.md
git commit -m "fix: 修复本地服务管理规则验证中的问题

[描述具体修复]"
```

如果全部通过，无需额外提交。

---

## 自审检查

- **Spec 覆盖：**
  - ✅ dev-services.py 固定前端端口 → Task 1
  - ✅ 热加载保护 → Task 2
  - ✅ 前端项目端口配置同步 → Task 3
  - ✅ CLAUDE.md 规则更新 → Task 4
  - ✅ 验证 → Task 5
- **无占位符：** 计划中没有 TBD/TODO
- **类型一致：** `FIXED_FRONTEND_PORTS`、`RESTART_REQUIRED_PATTERNS`、`_should_restart_for_changes` 命名和用法一致
