# HANDOFF — 服务监测模块实施进度

> 最后更新：2026-07-27 ｜ 分支：troubleshoot-ai-assistant ｜ 模块：service-monitor
> 状态：**定时任务修复完成 + Docker 容器化方案设计完成 + Vue 前端迁移规划完成，待部署**

---

## 1. 我们在做什么

为运维平台落地「服务监测」模块：监控本机/远程服务器系统资源与服务状态，输出巡检报告。
**只做监测不做修复**（修复接口预留，点击返回"开发中"）。

需求与计划文档：
- `Docs/需求文档/服务监测/PRD_需求文档_服务监测模块.md`
- `Docs/需求文档/服务监测/PRD_计划执行_服务监测模块.md`

### 关键决策（已与用户对齐）
- 代码**完全隔离**：所有代码进 `skill/code/web/service_monitor/` 子包，不动 `routes/`/`services/`/`utils/`/`config.json`/`container.py`，对外仅 `server.py` 一行注册
- 本期**核心子集**：system(01/02/03/04) + Docker-basic + MySQL-basic + Redis-basic
- bash 模块**搬进项目**当静态资源 + config 外置模板
- 容器名**模糊匹配**（不同服务器容器名不一致，用 grep -iE 模式发现）
- 远程 SSH 用 **paramiko**；SSH 密码 **Fernet 加密存储**
- 进度反馈 **SSE**；报告默认保留 **14 天**可配置
- **快速巡检 / 全量巡检** 两套件
- **角色控制**：普通用户仅查看（隐藏操作入口），管理员可操作

---

## 2. 已完成（全部 7 阶段）

### ✅ 阶段一：子包骨架 + bash 资产搬迁
- 建子包目录 `service_monitor/{__init__,routes,services/,utils/,assets/,data/,tests/}`
- 搬迁核心 bash 模块到 `assets/`：common.sh + config.sh.template + system/{01,02,03,04} + service/{20,22,24}
- 改造模块头部 `LIB_DIR="${LIB_DIR:-/tmp/check_modules}"`（7个模块，支持注入）
- config.sh.template：容器名改匹配模式、密码改占位符、阈值可覆盖、新增 resolve_container
- 端到端本地执行验证通过（KEY:VALUE 输出正常）

### ✅ 阶段二：utils 层
- `paths.py` — 模块私有路径常量 + ensure_dirs()
- `crypto.py` — Fernet 加密/解密（MONITOR_ENC_KEY > 派生自 SECRET_KEY > 兜底）
- `check_modules.py` — CheckModule 清单 + get_suite() + render_config()（shlex.quote 防注入）
- `parser.py` — 移植 Parse-ModuleResult（过滤脏行 + KEY:VALUE 解析 + 状态判定，优先 *_LEVEL）
- `thresholds.py` — 阈值表 + judge_status()（数值/百分比比较，严重/警告分级）
- `display_names.py` — KEY→中文显示名（核心子集）
- `executor.py` — BaseExecutor/LocalExecutor(subprocess)/SSHExecutor(paramiko)

### ✅ 阶段三：services 层
- `target_service.py` — 目标 CRUD + 连通性测试 + make_executor + resolve_credentials（解密）
- `runner_service.py` — 巡检编排（生成器 yield SSE 事件）+ cancel_run + get_run_status
- `report_service.py` — 报告 save/get/list/delete/cleanup_expired/export_md/export_json

### ✅ 阶段四：routes 层 + 接入 + 删旧占位
- `routes.py` — 16 条路由（4页面 + 12API），角色控制完整
- 删除旧占位 `routes/service_monitor.py` + `templates/service_monitor.html`
- `server.py` 已有 `from service_monitor import bp`（之前占位就改好了）

### ✅ 阶段五：前端模板（UI 已与用户确认）
- `index.html` — 目标卡片 + 报告列表（普通用户隐藏操作按钮）
- `targets.html` — 目标 CRUD 弹窗 + 连通性测试
- `run.html` — 进度条 + 模块清单 + SSE 实时反馈 + 完成跳报告
- `report.html` — 汇总卡片 + 异常置顶 + 分模块折叠 + 导出MD/JSON + 修复预留按钮

### ✅ 阶段六：测试
- 模块私有测试 55 用例（parser/crypto/check_modules/target_service/report_service/routes_sm）
- **全套 218 测试全绿**（原 163 + 新增 55，未破坏现有测试）
- `pytest.ini` 新增 testpath `web/service_monitor/tests`
- 子包 conftest 注入 tmp 数据目录隔离

### ✅ 阶段七：部署与依赖
- `requirements.txt` 加 paramiko + cryptography
- `.env.example` 加 MONITOR_ENC_KEY 说明（含生成命令）
- `deploy/upload_to_server.py` 加 `RECURSIVE_DIRS_TO_UPLOAD` + `_upload_dir_recursive()`（递归上传 service_monitor，排除 tests/data/__pycache__）
- `.gitignore` 加服务监测运行时数据忽略（targets.json/reports/*.json，保留 .gitkeep）
- `CLAUDE.md` 更新项目结构说明
- 本机 test_client 冒烟全通过（页面200/权限403/API正常）

---

## 3. 待办（部署）

代码完成，**尚未部署到 5.60**。部署步骤：

```bash
# 1. 服务器需装依赖（paramiko 已在 deploy 用，cryptography 需确认）
#    服务器执行：pip install paramiko cryptography
#    （或确认 paramiko 已装则 cryptography 作为其依赖已存在）

# 2. 上传代码（用 ! 前缀在会话内执行，SSH_PASSWORD 环境变量）
! cd deploy && SSH_PASSWORD='***' python upload_to_server.py

# 3. 权威验证
cd deploy && python verify_deployment.py

# 4. 手动验证服务监测
#    - 浏览器访问 http://192.168.5.60:8088/service-monitor
#    - 本地目标快速巡检 → 看报告
#    - （可选）新增远程目标测连通 + 远程巡检
```

---

## 4. 任务进度

| # | 阶段 | 状态 |
|---|------|------|
| 1 | 子包骨架 + bash 资产搬迁 | ✅ |
| 2 | utils 层 | ✅ |
| 3 | services 层 | ✅ |
| 4 | routes 层 + 接入 + 删旧占位 | ✅ |
| 5 | 前端模板 | ✅ |
| 6 | 测试（55 用例，全套 218 绿） | ✅ |
| 7 | 部署与依赖 | ✅ 代码完成，待部署 |

---

## 5. 踩过的坑 / 注意事项

1. **模块头部 LIB_DIR 写死**：原脚本 `LIB_DIR="/tmp/check_modules"` 覆盖环境变量，改 `${LIB_DIR:-...}` 才能注入
2. **common.sh 加载路径**：source `$LIB_DIR/lib/config.sh`，executor 必须布置成 `<workdir>/lib/` 结构
3. **MySQL/Redis 模块已内置模糊匹配**：`grep -i "${CONTAINERS[mysql]}"`，config 容器名值直接当模式用
4. **config 凭据占位符**：模板里 `MYSQL_PASSWORD=__MYSQL_PASSWORD__`（不加引号），渲染时用 shlex.quote 输出安全引用；若模板自带引号会双引号套单引号出错
5. **shlex.quote 空值**：返回 `''`（空字符串带引号），bash 合法
6. **测试 fixture session 覆盖**：user/admin 不能共用同一 client 实例（session 互相覆盖），改为各自独立 test_client()
7. **测试断言 `'password' not in json`**：太严，`has_password` 字段含子串；改为检查不返回 `password_enc`/`password` 明文字段
8. **upload 递归上传**：原 `DIRS_TO_UPLOAD` 只上传一层文件，service_monitor 多层子目录需 `_upload_dir_recursive`；排除 tests/data/__pycache__
9. **运行时 data 目录**：服务器首次部署无 data/，靠 `ensure_dirs()` 自动创建（mkdir parents=True）
10. **Blueprint 端点名**：`service-monitor.xxx`（横线），url_for 用全名
11. **Docker basic 的 check_container_status**：用精确名 `grep -q "^${name}$"`，模式下可能匹配不到逐容器项；本期可接受（还有 service 状态/资源等通用项）

---

## 6. 关键文件速查

### 新增子包
```
skill/code/web/service_monitor/
├── __init__.py              # from .routes import bp
├── routes.py               # 16 条路由
├── services/{__init__,target_service,runner_service,report_service}.py
├── utils/{__init__,paths,crypto,check_modules,parser,thresholds,display_names,executor}.py
├── assets/{config.sh.template, common.sh, system/{01,02,03,04}*.sh, service/{20,22,24}*.sh}
├── data/{.gitkeep, reports/.gitkeep}    # 运行时数据，gitignore
└── tests/{__init__,conftest,test_parser,test_crypto,test_check_modules,test_target_service,test_report_service,test_routes_sm}.py
templates/service_monitor/{index,targets,run,report}.html
```

### 修改的全局文件（仅这些）
- `server.py` — `from service_monitor import bp as service_monitor_bp`（占位时就改好）
- `skill/code/requirements.txt` — +paramiko +cryptography
- `skill/code/pytest.ini` — testpaths 加 web/service_monitor/tests
- `deploy/upload_to_server.py` — +RECURSIVE_DIRS_TO_UPLOAD + _upload_dir_recursive
- `.env.example` — +MONITOR_ENC_KEY
- `.gitignore` — +服务监测运行时数据忽略
- `CLAUDE.md` — 项目结构说明

### 已删除
- `skill/code/web/routes/service_monitor.py`（旧占位路由）
- `skill/code/web/templates/service_monitor.html`（旧占位模板）

---

## 8. 2026-07-27 会话进度追加

### 8.1 完成的工作

#### 定时任务并发执行失败修复 ✅

**问题**：5.44 服务器定时任务（工作日 8:40）不执行，5.202 的 9:00 正常。

**根因**：
- APScheduler `misfire_grace_time` 默认 1 秒，调度线程稍有延迟就跳过任务
- `_execute_scheduled_job` 同步阻塞 APScheduler 线程
- `schedules.json` 并发写入无锁保护

**修复**（`schedule_service.py` 5 处）：
1. 新增 `_schedules_lock` 锁
2. `_save_schedules()` 加锁
3. `update_run_status()` 读-改-写整体加锁
4. `_add_job()` 添加 `misfire_grace_time=3600`、`coalesce=True`、`max_instances=1`
5. `_execute_scheduled_job` 改为 `threading.Thread` 后台执行

**验证**：218 测试全绿 ✅

**文档**：
- `Docs/需求文档/服务监测/PRD_问题处理_定时任务并发执行失败.md`
- `Docs/需求文档/服务监测/PRD_计划执行_定时任务并发执行失败修复.md`

#### Docker 容器化方案设计 ✅

**用户需求**：
- 部署目录：`/data/third_party/monitor-platform/`
- 单容器部署（nginx + Flask + SQLite）
- 前端更新不需要容器重启，后端更新需要容器重启
- 数据库采用轻量级 SQLite
- 5.60 上有其他服务容器，必须隔离不互相影响

**已完成文件**：
- `Dockerfile` — python:3.11-slim，单进程，CRLF 自动修复
- `.dockerignore` — 排除测试/运行时数据/文档
- `docker-compose.yml` — 单容器 + volume + 资源限制（1G/1CPU）
- `deploy/docker_deploy.sh` — 5.60 部署脚本

**隔离保障**：
- 不挂载 docker.sock
- 容器名 `troubleshoot`，端口 8088
- volume 路径 `/opt/troubleshoot/` 独占前缀

#### 搜索引擎路径适配 ✅

- `utils/paths.py` 新增 `SEARCH_INDEX_DIR` 常量
- `search_engine.py` 改用统一路径

#### Vue 前端迁移规划 ✅

**文档**：`Docs/需求文档/Flask模板迁移Vue前端任务清单.md`

**范围**：三大模块共 18 个页面
- 问题排查助手：3 个页面
- 服务监测：10 个页面
- 服务管理：4 个页面

**技术选型**：Vue 3 + Vite + Element Plus + Pinia + TypeScript + ECharts

**预估工时**：17-21 天（约 3-4 周）

**建议执行顺序**：先容器化部署落地，再并行推进 Vue 重构

### 8.2 待执行任务

| 优先级 | 任务 | 说明 |
|--------|------|------|
| **P0** | 部署容器化版本到 5.60 | 需先确认 Docker 已安装，执行 `docker_deploy.sh` |
| **P0** | 更新 5.44 定时任务 end_date | 当前 end_date=2026-07-22 已过期 |
| P1 | 修复 APScheduler day_of_week 约定 bug | `from_crontab('1-5')` 实际是周二到周六 |
| P2 | SQLite 迁移 | 替换 JSON 文件存储 |
| P2 | Vue 前端开发 | 按任务清单执行 |

### 8.3 遗留问题

1. **5.44 定时任务 end_date 已过期**（2026-07-22），需通过 API 更新
2. **APScheduler day_of_week 约定 bug**：`CronTrigger.from_crontab('1-5')` 按 APScheduler 约定是周二到周六，非周一到周五，需改为 `CronTrigger(day_of_week='0-4')`
3. **容器化尚未部署**：需在 5.60 上执行部署脚本验证

### 8.4 本次新增文件

| 文件 | 说明 |
|------|------|
| `Dockerfile` | 重写：python:3.11-slim + 单进程 |
| `docker-compose.yml` | 重写：单容器 + volume + 资源限制 |
| `.dockerignore` | 更新：排除 tests/data 等 |
| `deploy/docker_deploy.sh` | 新增：5.60 部署脚本 |
| `Docs/需求文档/服务监测/PRD_问题处理_定时任务并发执行失败.md` | 问题处理文档 |
| `Docs/需求文档/服务监测/PRD_计划执行_定时任务并发执行失败修复.md` | 计划执行文档 |
| `Docs/需求文档/Flask模板迁移Vue前端任务清单.md` | Vue 迁移任务清单 |
