# HANDOFF_服务监测 — 服务监测模块：检测项丰富化移植 + 移动端响应式优化 + 功能增强 + SSH sudo 自动检测 + 定时任务修复 + 根因排查彻底解决 + 功能增强（报告清理/通知修复/通知内容增强/报告缺失兜底告警）

> 最后更新：2026-08-12（阶段十四：功能增强 — 报告缺失兜底告警）｜ 分支：troubleshoot-ai-assistant ｜ 负责人：czj
> 专属交接文档：聚焦服务监测模块（service_monitor）的全部历史与当前状态

---

## 1. 我们在做什么

服务监测模块（service_monitor）是运行维护平台的子模块，提供目标服务器巡检、报告管理、定时任务、通知推送等功能。

**已完成轮次**：
- **阶段一**：补全缺失项 + 重构 MySQL/Redis 深度检测脚本（见 2.5 节）
- **阶段二**：移植安全合规深度检测 + 中间件连接检测模块（见 2.7-2.11 节）
- **阶段三**：移植 P2 级检测模块：系统类型识别 + 平台服务检测 + 配置IP检测 + Console配置检测（见 2.12-2.15 节）
- **阶段四**：移植 P3 级检测模块：文件权限检测 + 定时任务预期检测
- **阶段五**：移植剩余 P3 级检测模块：FastDFS功能验证 + Android设备检测 + 数据备份检测 + 配置日志导出检测 + 修复能力检测
- **阶段六**：巡检防重复触发优化 + 数据备份检测超时修复
- **阶段七**：模块清理 + 5.44 验证 + 移动端响应式样式优化
- **阶段八**：移动端响应式深度优化 — 新增 480px 超小屏幕断点 + 修正布局溢出问题
- **阶段九**：功能增强 — 报告对比 + 异常项趋势告警 + 导出 PDF/Excel
- **阶段十**：SSH sudo 自动检测 + Docker 权限适配 + 目标管理编辑功能补全
- **阶段十一**：定时任务调度器日志修复 + 任务触发验证
- **阶段十二**：根因排查彻底解决 — 定时任务死锁修复 + 9.89 麒麟系统适配 + 加密密钥固定
- **阶段十三**：功能增强 — 报告自动清理 + 钉钉通知修复 + 通知内容增强
- **阶段十四（本次）**：功能增强 — 报告缺失兜底告警

本轮会话完成了以下工作：

**阶段十四：报告缺失兜底告警**：
1. **报告缺失检测** — `report_service.py` 新增 `check_missing_reports()` 函数，检查每个目标最后报告时间，超过 `missing_days` 阈值的目标标记为缺失
2. **定时触发** — `schedule_service.py` 新增每天 09:30（Asia/Shanghai）定时任务，自动检测缺失报告并发送告警通知
3. **通知渠道** — 复用已配置的通知渠道（邮件/钉钉/企微），通过 `report_missing` 配置独立选择渠道
4. **前端配置页** — `Notification.vue` 新增"报告缺失告警"配置区（缺失天数阈值 + 渠道选择）
5. **统计页告警** — `Statistics.vue` 新增红色告警横幅，显示缺失目标列表（目标名 + 缺失天数 + 上次报告时间）
6. **API 端点** — `GET /api/service-monitor/notification/check-missing-reports` 返回缺失目标列表

**验证结果**：
- 218 个单元测试全绿 ✅
- vue-tsc 前端类型检查通过 ✅
- 前端配置项保存/加载正常 ✅

> **设计原则**：所有原版依赖 Windows 本地工具(pscp.exe/adb.exe)的模块，均改造为远程巡检检测（检测就绪状态/能力，不执行本地文件操作）；所有修复类操作均改为只检测不修复；SSH 用户非 root 时自动检测并启用 sudo 提权

---

## 2. 已经完成了什么

### 2.1 报告免登访问与操作鉴权 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_报告免登访问与操作鉴权.md`

**核心实现**：
- `report_service.py`：报告生成时自动生成 `access_token`（格式 `rpt_<32位随机hex>`）和 `access_token_expires_at`（7天有效期）
- `runner_service.py`：通知链接拼接 `?token=rpt_xxx` 参数
- `routes.py`：`page_report()` 支持 token 验证，验证通过后渲染报告页并标记 `token_auth=True`（访客模式）；`api_export_report()` 支持 token 导出
- `auth.py`：GET/POST `/login` 支持 `next` 参数，登录成功后跳转回原始页面
- `decorators.py`：`page_login_required` 重定向时携带 `next` 参数
- `login.html`：表单提交携带 `next` 参数，登录成功后跳转
- `report.html`：访客模式显示提示条、隐藏管理操作、导出链接携带 token
- `base.html`：访客模式下导航菜单点击跳转登录页，顶部显示"访客 + 登录按钮"

**验证结果**：
- 未登录点击通知链接 → 直接查看报告 ✅
- 访客模式点击其他模块 → 弹窗提示并跳转登录 ✅
- 登录后回跳到原始页面 ✅
- 218 个单元测试全绿 ✅

### 2.2 监测统计模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_监测统计模块.md`

**核心实现**：
- `statistics_service.py`（新增）：统计聚合服务，包含 5 个公开函数：
  - `get_overview()`：概览摘要（总报告数、异常率、最常异常项等）
  - `get_module_stats()`：模块健康统计
  - `get_item_trend()`：检测项趋势数据（时间序列 + 状态分布）
  - `get_abnormal_items()`：高频异常检测项 TOP 10
  - `get_item_keys_for_target()`：检测项列表（按模块分组，含 `has_numeric` 标记）
- `routes.py`：新增页面路由 `/service-monitor/statistics` + 5 个 API 端点
- `base.html`：侧边栏添加"📈 监测统计"菜单项
- `statistics.html`（新增）：完整统计页面，使用 ECharts 渲染图表
  - 筛选栏：目标选择器 + 日期范围（自定义）
  - 概览卡片：4 项摘要指标
  - 模块健康概览：堆叠条形图
  - 检测项趋势分析：模块/检测项两级联动选择器 + 折线图 + 饼图
  - 高频异常检测项：水平堆叠条形图

**验证结果**：
- 统计页面正常加载 ✅
- 各 API 返回正确数据结构 ✅
- 图表正常渲染 ✅
- 218 个单元测试全绿 ✅

### 2.3 检测项趋势图表显示问题修复 ✅

**问题文档**：`Docs/需求文档/服务监测/PRD_问题处理_检测项趋势图表显示问题.md`

**问题根因**：
- 部分检测项的值是状态文本（"正常"/"异常"）、yes/no、路径等，`_extract_numeric()` 无法提取数值
- 前端收到 `value_num=null` 后折线图无数据点，但未显示任何提示

**修复内容**：
- `statistics_service.py`：`get_item_keys_for_target()` 增加 `has_numeric` 字段，标记检测项是否有数值数据
- `statistics.html`：`fetchItemTrend()` 增加三种情况处理：
  - 无数据点：显示"暂无数据"
  - 有数据点但无数值：显示友好提示"该检测项为状态/文本类型，无法绘制数值趋势图，请查看右侧状态分布"，饼图正常渲染
  - 有数值数据：正常渲染折线图 + 饼图
- 检测项选择器标注：无数值能力的检测项标注 "(状态)" 后缀

**验证结果**：
- 选择 `CPU_USAGE` 等数值项，折线图正常 ✅
- 选择 `CPU_STATUS` 等状态项，显示友好提示 + 饼图正常 ✅
- 选择器中状态项标注 "(状态)" ✅

### 2.4 JavaScript 代码残留问题修复 ✅

**问题文档**：`Docs/需求文档/服务监测/PRD_问题处理_统计页面JS错误.md`

**问题根因**：
- 编辑 `fetchItemKeys` 函数时，旧代码尾部 4 行未被删除，残留在新函数之后
- 导致 `onModuleChange`、`fetchItemTrend`、`fetchAllData` 等函数被错误嵌套，全局作用域无法访问 `fetchAllData`

**修复内容**：
- 删除第 330-333 行、第 492-494 行的残留代码
- 补回被误删的 `onModuleChange` 函数体

**验证结果**：
- 花括号平衡（124 对）✅
- 9 个关键函数全部存在 ✅
- 查询按钮点击正常工作 ✅

### 2.5 检测项丰富化（参考目录移植 - 阶段一）✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_检测项丰富化移植.md`

**参考目录**：`platform-auto-test/临时目录/服务监测/`（老版 PowerShell + Shell 巡检工具）

**已完成内容**：

**1. display_names.py 补全缺失 KEY** ✅
- 新增 `VM_PGMajFAULT`（主缺页错误数）
- 原有 `DOCKER_DAEMON_CPU/MEM`、`NGINX_CONTAINER_LEVEL`、`NACOS_CONTAINER_LEVEL` 已存在

**2. 重构 23_mysql_depth.sh** ✅
- 从 `echo "KEY:value"` 改为 `output_result "KEY" "value"` 标准格式
- KEY 加 `MYSQL_DEPTH_` 前缀，display_names.py 新增 30 个 KEY
- 去掉硬编码密码，使用 `MYSQL_PASSWORD` 环境变量
- 容器名使用 `CONTAINERS[mysql]` 模糊匹配
- thresholds.py 新增 7 个深度阈值（连接使用率/慢查询/缓冲池命中率/主从延迟/死锁/锁等待/碎片表）

**3. 重构 25_redis_depth.sh** ✅
- 从 `echo "KEY:value"` 改为 `output_result "KEY" "value"` 标准格式
- KEY 加 `REDIS_DEPTH_` 前缀，display_names.py 新增 22 个 KEY
- 去掉硬编码密码，使用 `REDIS_PASSWORD` 环境变量
- 容器名使用 `CONTAINERS[redis]` 模糊匹配
- thresholds.py 新增 5 个深度阈值（内存碎片/缓存命中/阻塞客户端/拒绝连接/慢日志）

**验证结果**：218 个测试全绿 ✅ 已部署到 5.60 ✅

### 2.6 参考目录分析完成

参考目录有 18 类全新检测项，当前项目未实现：
- **P1**：MySQL深度/Redis深度/安全合规深度/中间件连接（4类）
- **P2**：系统类型识别/平台服务检测/配置IP检测/Console配置（4类）
- **P3**：文件权限/定时任务预期/FastDFS/Android/数据备份/日志导出/修复能力（7类）

**深度检测重构已完成（阶段一），P1 移植进行中（阶段二已完成 2/4）**

### 2.7 安全合规深度检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_检测项丰富化移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/modules/SecurityCheck.psm1`

**核心实现**：
- `46_security_deep.sh`（新增）：安全合规深度检测脚本，9 类安全检测
  - MySQL Root 访问范围：查询 `mysql.user` 表 root 用户的 `host` 字段，含 `%` 为严重
  - MySQL 空密码用户：查询 `authentication_string` 为空的用户
  - Redis 密码状态：检查 `requirepass` 配置，空或弱密码为严重
  - EMQX 默认密码：用 `admin:public` 测试 Dashboard API
  - Linux 空密码账户：解析 `/etc/shadow` 第 2 字段
  - SUID 文件：扫描系统 SUID 文件与白名单对比
  - Crontab：收集所有用户/系统级 crontab 与安全模式对比
  - SSH 暴力破解：分析 SSH 日志失败登录统计 IP 次数（>=50 次）
  - 异常端口：获取监听端口与平台端口白名单对比
- `display_names.py`：新增 20 个 `SECURITY_DEEP_*` KEY 映射
- `thresholds.py`：新增 6 个安全检测阈值
- `config.sh.template`：新增安全白名单配置（SUID 白名单、端口白名单、EMQX Dashboard 端口）

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅ **已部署到 5.60** ✅

### 2.8 中间件连接检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_检测项丰富化移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/lib/shell/middleware_check.sh` + `MiddlewareCheck.psm1`

**核心实现**：
- `39_middleware_conn.sh`（新增）：中间件连接检测脚本，4 类连接检测
  - Redis 连接：容器存在 + 端口监听 + `redis-cli ping` + 版本/内存/连接数
  - MySQL 连接：容器存在 + 端口监听 + `mysqladmin ping` + 版本
  - EMQX 连接：MQTT 端口监听 + Dashboard 端口 + API 状态 + 进程检查
  - FastDFS 连接：容器存在 + 网络模式 + trackerd/storaged 进程检查
- `display_names.py`：新增 16 个 `MW_CONN_*` KEY 映射
- `config.sh.template`：新增 EMQX 端口配置（MQTT 端口、Dashboard 端口）

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅ **已部署到 5.60** ✅

### 2.9 状态等级误判问题修复 ✅

**问题文档**：用户报告 Redis/MySQL/EMQX 连接状态"端口未监听"却显示"正常"

**问题根因**：
- LEVEL KEY 命名不一致：脚本输出 `MW_CONN_REDIS_LEVEL`，但 `_resolve_status` 查找 `MW_CONN_REDIS_STATUS_LEVEL`
- 状态词映射缺失：`"端口未监听"`、`"连接失败"` 等异常状态词不在 `_VALUE_STATUS_MAP` 中
- 优先级 1（LEVEL 字段）失败 → 优先级 3（文本映射）也失败 → 默认"正常"

**修复内容**：
- `39_middleware_conn.sh`：4 类连接的 LEVEL KEY 改为 `<STATUS_KEY>_LEVEL` 格式（8 处）
- `46_security_deep.sh`：7 类安全检测的 LEVEL KEY 改为 `<值KEY>_LEVEL` 格式（7 处）
- `display_names.py`：同步 11 个 LEVEL KEY 的显示名映射
- `parser.py`：`_VALUE_STATUS_MAP` 补充 11 个异常状态词（端口未监听/MQTT 端口未监听/连接失败/进程异常/未检测到容器/容器未运行/无密码/弱密码/使用默认密码/查询失败/无法读取）

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅ **已部署到 5.60** ✅

### 2.10 中间件端口检测逻辑重构 ✅

**问题文档**：部署后 5.44 检测仍报"端口未监听"

**问题根因**：
- 脚本在**容器内**用 `ss -tlnp | grep ':6379'` 检查端口
- 5.44 的容器镜像（精简版）**没有 `ss` 和 `netstat` 命令**
- `ss` 命令失败 stderr 被 `2>/dev/null` 吞掉，`port_listening` 变空 → 误判"端口未监听"

**修复内容**：
- `39_middleware_conn.sh` 重构检测逻辑：
  - Redis：`redis-cli ping` 优先（成功=运行中，NOAUTH=需密码认证，其他=连接失败）
  - MySQL：`mysqladmin ping` 优先（在容器内执行，连内部端口）
  - EMQX：宿主机 `ss` 检 MQTT 端口 + 容器内进程检查（官方镜像有 `ps`）
  - FastDFS：进程检查（不变，此前已正确处理 host 网络模式）

**验证结果**：
- Redis：运行中，版本 8.2.2，内存 2.23M，连接数 33 ✅
- MySQL：运行中，版本 8.0.28 ✅
- EMQX：运行中，MQTT 端口 1883 正常 ✅
- FastDFS：运行中，host 网络模式 ✅

### 2.11 5.44 目标凭据配置 ✅

**问题**：Redis/MySQL ping 连接失败（NOAUTH / Access denied）

**根因**：5.44 目标的 `credential_overrides` 为空，检测时无密码可用

**修复方式**：
- 通过 API 注入加密密码：`mysql_password` / `redis_password` = `dNrprU&2S`
- 密码使用 Fernet 加密存储，运行时解密注入 config.sh

**验证结果**：
- 凭据加密存储正确 ✅
- 解密后注入正确 ✅
- full 套件检测 442 项，4 正常 / 4 警告 / 11 严重 ✅

**额外发现的安全问题**（因密码配置后才检测出来）：
- MySQL Root 访问范围：`%,localhost` → **严重**（允许任意 IP）
- EMQX 默认密码：使用默认密码 → **严重**（admin:public）
- 异常端口数：57 个 → **严重**（大量非白名单端口）

### 2.12 系统类型识别模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P2级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/modules/ServerProfile.psm1`

**核心实现**：
- `50_system_profile.sh`（新增）：系统类型识别脚本，3 类识别
  - 平台类型：检测 `/data/services` 目录存在 → new，否则 → old
  - 容器类型：`docker ps` 容器名正则匹配（ujava/upython/upython_voice/unknown）
  - ujava 变体：检测 `/var/www/java/unifiedPlatform` 目录 → unified，否则 → meeting
- `display_names.py`：新增 6 个 `SYS_PROFILE_*` KEY 映射
- `check_modules.py`：注册 `50_system_profile` 模块

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅

### 2.13 平台服务检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P2级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/modules/ServiceCheck.psm1` + `check_server_health.sh`

**核心实现**：
- `51_service_check.sh`（新增）：平台服务检测脚本
  - 新平台 ujava 容器内服务：auth/gateway/system/meeting/mqtt/quartz/message（7 个 jar 进程匹配）
  - 新平台宿主机服务：extapi（jar 进程匹配）
  - 传统平台 ujava 容器服务：nginx + meeting
  - 传统平台宿主机服务：extapi（路径匹配 `/var/www/java/external-meeting-api`）
  - upython/upython_voice 端口监听：用进程检查代替 ss（避免容器内无 ss/netstat 误判）
- `display_names.py`：新增 20+ 个 `SVC_CHECK_*` KEY 映射
- `config.sh.template`：新增服务匹配模式 + 端口列表配置
- `check_modules.py`：注册 `51_service_check` 模块

**设计要点**：
- 服务匹配模式可配置化（`UJAVA_SERVICES` 等环境变量）
- 端口检测用进程检查代替 ss（吸取坑 9 教训）
- 内部自行检测平台/容器类型，不依赖 50 模块执行顺序

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅

### 2.14 配置IP检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P2级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/modules/ConfigIPCheck.psm1` + `lib/shell/config_check.sh`

**核心实现**：
- `52_config_ip_check.sh`（新增）：配置IP检测脚本
  - 新平台扫描 12 个配置目录（API 服务 + 前端 + 中间件）
  - 传统平台扫描 6 个配置目录
  - IP 提取：`grep -oE` 匹配 IP 正则
  - 允许列表：SERVER_IP + 127.0.0.1 + 172.17.0.1
  - 输出未授权 IP 明细（JSON 数组：文件路径 + 行号 + IP）
- `display_names.py`：新增 5 个 `CFG_IP_*` KEY 映射
- `config.sh.template`：新增 `SERVER_IP` 配置
- `check_modules.py`：注册 `52_config_ip_check` 模块

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅

### 2.15 Console配置检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P2级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/check_server_health.sh`（第1582-1708行）

**核心实现**：
- `53_config_console_check.sh`（新增）：Console配置检测脚本
  - 扫描配置文件中 `console[:=]true` 的行（不区分大小写）
  - **只检测不修复**（避免巡检工具自动改生产配置的风险）
  - 复用配置 IP 检测的扫描路径
  - 输出问题文件列表（JSON 数组）
- `display_names.py`：新增 5 个 `CFG_CONSOLE_*` KEY 映射
- `check_modules.py`：注册 `53_config_console_check` 模块

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅

### 2.16 文件权限检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/modules/FilePermission.psm1`

**核心实现**：
- `54_file_permission_check.sh`（新增）：文件权限检测脚本
  - 新平台：检查 12 个关键路径（run.sh 应可执行、rc.local 应可执行）
  - 传统平台：检查 7 个关键路径
  - 权限判定：`.sh` 文件和 `rc.local` 期望可执行（含 x 权限），配置文件期望不可执行
  - 兼容 GNU stat (`-c '%a'`) 和 BSD stat (`-f '%Lp'`)
  - **只检测不修复**
- `display_names.py`：新增 5 个 `FILE_PERM_*` KEY 映射
- `check_modules.py`：注册 `54_file_permission_check` 模块

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅ **已部署到 5.60** ✅

### 2.17 定时任务预期检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/check_server_health.sh`（第1718-1777行）

**核心实现**：
- `55_cron_expected.sh`（新增）：定时任务预期检测脚本
  - 通用预期：MySQL 每日自动备份脚本（`UbainsmysqlBakUp.sh`）
  - 新平台额外预期：ujava2 每3分钟守护启动脚本（`ujava2-startup.sh`）
  - 通过 `crontab -l` 获取当前定时任务，逐条 `grep -qF` 检查预期条目
  - **只检测不修改**
- `display_names.py`：新增 5 个 `CRON_EXPECTED_*` KEY 映射
- `check_modules.py`：注册 `55_cron_expected` 模块

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅ **已部署到 5.60** ✅

### 2.18 FastDFS功能验证检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/check_fdfs_arm.sh` + `check_fdfs_x86.sh`

**核心实现**：
- `56_fastdfs_check.sh`（新增）：FastDFS 功能验证检测脚本
  - 容器检测：自动检测 Tracker/Storage 容器名称
  - 端口检测：22122(Tracker) + 23000(Storage) + 8888(Nginx)
  - 进程检测：容器内 fdfs_tracked/fdfs_storaged 进程
  - 配置检测：/etc/fdfs/client.conf 存在性 + tracker_server 配置
  - 功能测试：创建测试文件 → 上传 → 下载 → 完整性验证（MD5） → 清理
  - HTTP 测试：8888 端口访问测试
  - **只检测不修复**
- `display_names.py`：新增 20 个 `FDFS_*` KEY 映射
- `check_modules.py`：注册 `56_fastdfs_check` 模块

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅

### 2.19 Android设备检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/modules/AndroidCheck.psm1`

**核心实现**：
- `57_android_check.sh`（新增）：Android 设备检测脚本
  - ADB 工具检测：检查系统 PATH + 常见安装位置
  - ADB 版本获取：`adb version`
  - ADB 服务状态：检查 adb server 是否运行
  - 设备连接检测：`adb devices` 获取已连接设备列表
  - 网络 ADB 能力：检查 adb tcpip 支持情况
  - **只检测不操作**（原版依赖 adb.exe + Windows 本地操作，改造为远程检测）
- `display_names.py`：新增 12 个 `ANDROID_*` KEY 映射
- `check_modules.py`：注册 `57_android_check` 模块

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅

### 2.20 数据备份检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/modules/DataBackup.psm1` + `dump_one_db.sh` + `list_user_dbs.sh`

**核心实现**：
- `58_data_backup_check.sh`（新增）：数据备份检测脚本
  - 备份脚本检测：检查常见备份脚本路径是否存在
  - MySQL 备份检测：检查备份目录 + 最近备份文件时间/大小
  - mysqldump 能力：检查宿主机或容器内 mysqldump 命令
  - 应用备份检测：检查应用备份目录
  - 定时备份任务：检查 crontab 中是否有备份任务
  - 磁盘空间检测：检查备份目录所在分区可用空间
  - **只检测不执行**（原版依赖 pscp.exe 下载到本地，改造为检测备份能力）
- `display_names.py`：新增 21 个 `BACKUP_*` KEY 映射
- `check_modules.py`：注册 `58_data_backup_check` 模块

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅

### 2.21 配置日志导出检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/modules/LogExport.psm1`

**核心实现**：
- `59_log_export_check.sh`（新增）：配置日志导出检测脚本
  - 配置文件检测：新平台 15 个配置路径 + 传统平台 8 个配置路径
  - 日志目录检测：新平台 13 个日志路径 + 传统平台 5 个日志路径
  - 关键日志大小：检查 ERROR 日志文件大小
  - 日志轮转配置：检查 logrotate.d 中是否有应用配置
  - 导出能力检测：检查 tar + gzip 命令可用性
  - **只检测不导出**（原版依赖 pscp.exe 下载到本地，改造为检测日志可获取性）
- `display_names.py`：新增 21 个 `LOGEXP_*` KEY 映射
- `check_modules.py`：注册 `59_log_export_check` 模块

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅

### 2.22 修复能力检测模块 ✅

**需求文档**：`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`

**参考来源**：`platform-auto-test/临时目录/服务监测/lib/shell/issue_handler.sh`

**核心实现**：
- `60_repair_capability_check.sh`（新增）：修复能力检测脚本
  - 修复脚本检测：检查 issue_handler.sh 等修复脚本是否存在
  - DNS 修复能力：检查 resolv.conf 可写性 + NetworkManager
  - NTP 修复能力：检查 chrony/ntp 服务 + 配置文件可写性
  - 端口修复能力：检查 firewalld/iptables 工具
  - 权限修复能力：检查 chmod/chown + 关键目录可写性
  - Docker 修复能力：检查 docker 命令 + 权限
  - 服务重启能力：检查 systemctl/service 命令
  - 日志清理能力：检查 logrotate/lsof/find 工具
  - **只检测不修复**（原版是交互式修复工具，改造为检测修复工具链就绪状态）
- `display_names.py`：新增 25 个 `REPAIR_*` KEY 映射
- `parser.py`：补充新模块状态词映射（yes/no/上传失败/下载失败等）
- `check_modules.py`：注册 `60_repair_capability_check` 模块

**验证结果**：218 个测试全绿 ✅ 语法检查通过 ✅

### 2.23 巡检防重复触发优化 ✅

**问题**：用户执行全量巡检后返回首页再次进来，又重新触发巡检

**问题根因**：
- `run.html:175` — `DOMContentLoaded` 无条件触发 `startInspection()`，页面每次加载都自动开始巡检
- 后端 `runner_service._runs` 按 `run_id` 索引，不按 `target_id` 索引，无法查询"该目标是否正在运行"
- 首页巡检按钮直接链接到 `/service-monitor/run/<id>`，每次都是全新页面导航

**修复内容**：

1. **后端 `runner_service.py`**：新增 `get_running_for_target(target_id)` 函数
   - 遍历 `_runs` 字典，查找 `target_id` 匹配且 `finished=False` 的运行记录
   - 返回 `{"run_id", "target_id", "suite", "done", "total", "current"}` 或 `None`

2. **后端 `routes.py`**：
   - 新增 `GET /api/service-monitor/run/status/<target_id>` API 端点
     - 返回 `{"running": true, ...}` 或 `{"running": false}`
   - SSE 流接口增加 409 防重检查：同一目标已有未完成巡检时返回 409 错误

3. **前端 `run.html`**：
   - `DOMContentLoaded` 改为先调用状态 API 检查
   - 如果已有巡检在运行：显示提示"该目标已有巡检正在运行 (x/y)，请等待完成或取消后重试"，不触发新巡检
   - 如果没有运行中巡检：正常调用 `startInspection()`

4. **前端 `index.html`**：
   - 巡检按钮增加 `checkBeforeRun(href, targetId)` 函数
   - 点击时先调用状态 API 检查，运行中则弹窗提示，否则正常跳转
   - 修复 `e.currentTarget` 在异步回调后为 null 导致的 TypeError

**验证结果**：218 个测试全绿 ✅ **已部署到 5.60** ✅

### 2.24 数据备份检测超时修复 ✅

**问题**：58_data_backup_check.sh 执行超过 5 分钟未完成

**问题根因**：
- `find "$backup_dir" -name "*.sql*"` 无深度限制（`-maxdepth`），在大型备份目录下遍历整个目录树
- `find "$app_backup_dir" -type f | wc -l` 统计所有文件数，目录大时很慢
- `find ... -printf` 是 GNU find 扩展，部分系统不支持

**修复内容**：
- 所有 `find` 命令添加 `-maxdepth 3` 限制搜索深度
- 兼容不支持 GNU `-printf` 的系统，用 `xargs ls -t` 备选方案查找最新文件

**验证结果**：语法检查通过 ✅ 218 个测试全绿 ✅ **已部署到 5.60** ✅

### 2.25 数据备份检测模块移除 ✅

**原因**：58_data_backup_check.sh 在实际运行中超时，且备份能力检测价值有限

**实现内容**：
- 删除 `58_data_backup_check.sh` 脚本文件（本地 + 5.60 服务器）
- 移除 `check_modules.py` 中 `58_data_backup_check` 模块注册
- 移除 `display_names.py` 中 21 个 `BACKUP_*` KEY 映射

**验证结果**：218 个测试全绿 ✅ **已部署到 5.60** ✅ full 套件模块数 43 → 42

### 2.26 FastDFS功能验证超时修复 ✅

**问题**：56_fastdfs_check.sh 执行超时，原脚本"功能测试"部分（上传→下载→MD5验证→清理）在异常情况下卡死

**问题根因**：
- `fdfs_upload_file` / `fdfs_download_file` 命令无超时机制
- 服务异常时会无限等待

**修复内容**：
- 重构 `56_fastdfs_check.sh`，移除上传下载功能测试，只保留状态检测
- 检测项：容器状态、端口检测（22122/23000/8888）、进程检测、配置文件检测、HTTP 探测
- HTTP 探测加 `--connect-timeout 3 --max-time 5` 超时保护
- 端口检测用宿主机 `ss` 替代容器内 `netstat`
- display_names.py 精简 KEY（移除上传/下载/完整性相关，保留 16 个）

**验证结果**：218 个测试全绿 ✅ **已部署到 5.60** ✅ 5.44 实测 11 项全正常

### 2.27 5.44 新模块实测验证 ✅

**验证方式**：在 5.44 服务器执行 full 套件巡检（报告 ID: 20260723_020607_63127b）

**验证结果**：
- 检测项总数：521（上次 510，新增 11 项）
- 正常 500，警告 8，严重 13
- 10 个新模块全部正常执行，无超时/报错

| 模块 | 结果 |
|------|------|
| 50 系统类型识别 | ✅ 3项全正常 |
| 51 平台服务检测 | ⚠️ 2项警告（MQTT/Message 未监听，真实状态） |
| 52 配置IP检测 | ⚠️ 1项警告（有未授权IP） |
| 53 Console配置检测 | ✅ 4项全正常 |
| 54 文件权限检测 | ✅ 4项全正常 |
| 55 定时任务预期检测 | ⚠️ 1项警告（定时任务缺失） |
| 56 FastDFS功能验证 | ✅ 11项全正常（修复后首次验证通过） |
| 57 Android设备检测 | ✅ 8项全正常 |
| 59 配置日志导出检测 | ⚠️ 1项警告（logrotate 未配置） |
| 60 修复能力检测 | ⚠️ 1项警告（修复脚本不存在） |

### 2.28 移动端响应式样式优化 ✅

**问题**：移动端排版在多个页面存在拥挤、溢出、文字竖向换行等问题

**优化内容**（9 个页面）：

| 页面 | 优化点 |
|------|--------|
| base.html | 顶部栏 padding 收窄、用户名溢出省略、退出按钮缩小、侧边栏菜单紧凑化 |
| index.html | 目标卡片 padding 收窄、巡检按钮纵向堆叠 |
| reports.html | 筛选栏纵向排列、报告行紧凑化、移动端隐藏【查看】按钮（点名称进入） |
| report.html | 报告头纵向排列、导出按钮全宽、异常项/明细行紧凑化、检测项状态文字 `white-space:nowrap` 防竖向换行 |
| statistics.html | 筛选栏纵向、概览卡片缩小、图表 padding 收窄、趋势筛选纵向 |
| schedule.html | 表格卡片化、操作按钮缩小、弹窗全宽、重复周期纵向 |
| targets.html | 表格卡片化、操作按钮缩小、弹窗全宽 |
| notification.html | 表单紧凑化、输入框缩小、保存按钮全宽 |
| run.html | 巡检卡片紧凑化、取消按钮全宽、模块列表字号缩小 |

**验证结果**：218 个测试全绿 ✅ **已部署到 5.60** ✅

### 2.29 移动端响应式深度优化 ✅

**问题**：上一轮优化仅覆盖 768px 断点，超小屏幕（<480px）仍存在溢出、触摸目标过小等问题

**本轮优化**：

| 问题类型 | 影响页面 | 修复方案 |
|---------|---------|---------|
| 缺少 480px 断点 | 全部 9 页面 | 新增 `@media (max-width: 480px)` 覆盖小屏手机 |
| statistics.html 筛选栏溢出 | statistics.html | 768px 断点改为完全纵向堆叠（原 `flex-wrap` 仍可溢出） |
| notification.html 端口输入框固定宽度 | notification.html | `flex: 0 0 120px` → 自适应，避免窄屏溢出 |
| report.html 模块标题溢出 | report.html | 添加 `word-break: break-word` |
| run.html 进度条高度未响应式 | run.html | 三级递减 12→10→8px |

**各页面 480px 断点优化详情**：

| 页面 | 优化内容 |
|------|--------|
| base.html | 顶栏高度/字号缩小、用户名截断宽度收紧、汉堡按钮/侧边栏紧凑化 |
| index.html | 卡片间距/padding/icon 收窄、section-head 纵向堆叠、目标管理按钮全宽 |
| reports.html | 筛选下拉框/报告行/复选框/批量栏全部缩小 |
| report.html | 汇总卡片字号缩小、模块标题 word-break、异常项/检测项行紧凑化、进程表字号递减 |
| statistics.html | 筛选栏及趋势选择器完全纵向堆叠、概览卡片/图表/padding 全面缩小 |
| schedule.html | 表格行/操作按钮/弹窗/星期按钮/时间选择器紧凑化 |
| targets.html | 表格行/操作按钮/弹窗紧凑化 |
| notification.html | 开关/输入框/标签字号缩小、端口输入框自适应 |
| run.html | 进度条高度响应式（12→10→8px）、巡检卡片/模块列表字号递减、完成横幅长文字 word-break |

**验证结果**：218 个测试全绿 ✅ **已部署到 5.60** ✅

### 2.30 SSH sudo 自动检测 ✅

**问题来源**：用户在 9.89 服务器上执行巡检时，Docker 容器显示为"未运行"，但实际有 12 个容器在运行。根本原因是 SSH 用户 `openkylin` 不在 docker 组，巡检脚本直接执行 `docker ps` 因权限拒绝失败。

**核心实现**：

**1. `executor.py` — SSHExecutor sudo 自动检测**：
- `_detect_sudo_need()` 方法：SSH 连接后自动检测
  - 先执行 `whoami` 检查当前用户是否为 root
  - 如果是 root → `use_sudo = False`
  - 如果不是 root → 测试 `docker ps` 是否可直接执行
  - 如果 `docker ps` 失败 → 测试 `sudo -n docker ps`
  - 如果 `sudo -n docker ps` 成功 → `use_sudo = True`
  - 如果 sudo 需要密码 → 调用 `_try_configure_passwordless_sudo()`
- `_try_configure_passwordless_sudo()` 方法：
  - 用 SSH 密码通过 `sudo -S` 写入 `/etc/sudoers.d/<username>`
  - 验证写入后免密 sudo 是否生效
  - 失败时给出明确配置提示
- `_exec()` 方法：根据 `use_sudo` 自动用 `sudo bash -c` 包裹命令
- 修复关键 bug：初始检测逻辑只测 `sudo -n whoami`（用户免密 sudo），未检测 Docker 命令权限。修正为直接测试 `docker ps`

**2. `target_service.py` — use_sudo 字段支持**：
- `make_executor()` 从 target 数据读取 `use_sudo` 字段
- `create_target()` / `update_target()` 支持 `use_sudo` 字段
- `list_targets()` / `_build_view()` 返回 `use_sudo` 状态

**验证结果**：直接测试 9.89 服务器，`sudo docker ps` 正常列出 12 个容器 ✅ 脚本逻辑在 9.89 上实测通过 ✅

### 2.31 Docker 检测脚本 sudo 适配 ✅

**核心实现**：

**`20_docker_basic.sh` — 自动适配 sudo**：
- 新增 `_detect_docker_sudo()` 函数：
  - 先尝试 `docker ps`
  - 失败则尝试 `sudo -n docker ps`
  - 设置 `DOCKER_CMD` 为 `docker` 或 `sudo docker`
- 所有 docker 命令使用 `$DOCKER_CMD` 变量
- 每个检测函数开头增加 `if [ -z "$DOCKER_CMD" ]` 保护
- 修复初始 bug：`_detect_docker_sudo()` 中 `$DOCKER_CMD` 变量未初始化就被引用

**验证结果**：在 9.89 上用修复后脚本实际测试，输出 `DOCKER_SERVICE_STATUS:运行中`，`CONTAINER_TOTAL:12`，`CONTAINER_RUNNING:11` ✅

### 2.32 目标管理编辑功能补全 ✅

**问题来源**：用户之前已新增目标但无法编辑，`openEdit()` 函数为空（仅 TODO 注释）。

**核心实现**：

**1. `routes.py` — 新增 GET 单个目标 API**：
- `GET /api/service-monitor/targets/<target_id>`：返回单个目标的对外视图（脱敏）
- 管理员权限校验

**2. `targets.html` — 完整编辑交互**：
- `openEdit(id)` 函数：通过 API 获取目标数据并回填表单
- 密码字段不回填（留空表示不修改）
- `use_sudo` 复选框回填
- 保存时区分新增（POST）和编辑（PUT）

**验证结果**：API 语法检查通过 ✅ 前端逻辑完整 ✅

### 2.33 5.60 Docker 部署环境修复 ✅

**背景**：部署 SSH sudo 自动检测功能时，发现 5.60 服务存在多个问题，一一修复。

**修复内容**：

| 问题 | 原因 | 修复 |
|------|------|------|
| systemd 服务不断重启（9300+次） | Docker 容器与 systemd 抢占 8088 端口 | `sudo systemctl stop/disable troubleshoot`，统一用 Docker 容器 |
| 容器内代码未更新 | 代码在镜像构建时 COPY，非 volume 挂载 | 重建 Docker 镜像：`docker compose up -d --build` |
| 前端 403 Forbidden | volume 空目录覆盖 dist | `cp -r frontend/dist/* /opt/troubleshoot/dist/` |
| 搜索索引文件错误 | `搜索索引.json` 被误创建为目录 | 删除目录，复制正确的 JSON 文件 |
| 通知 API 500 错误 | 重建后 MONITOR_ENC_KEY 变更，旧密文无法解密 | 清除 notifications.json 中无法解密的加密字段 |
| 搜索向量.json 挂载失败 | 宿主机文件为空，镜像内路径不存在 | 注释 docker-compose.yml 中搜索向量的 volume 挂载 |

**当前部署状态**：
- 容器运行：健康（healthy）✅
- 前端：正常访问 ✅
- 通知 API：已修复，需重新配置钉钉 secret ✅
- 定时任务：2 个任务运行中 ✅
- 搜索模式：TF-IDF ✅

### 2.34 报告缺失兜底告警 ✅

**需求背景**：当定时任务因各种原因（调度器异常、目标不可达、凭据失效等）未如期生成报告时，需要主动发现并告警，避免"静默失联"。

**核心实现**：

**1. `notification_service.py` — 配置与发送**：
- `report_missing` 配置结构：`{ enabled: bool, missing_days: int, channels: string[] }`
- `send_missing_report_notification()` 函数：接收缺失目标列表，通过已配置的渠道发送告警消息
- 邮件告警：表格格式列出目标名、缺失天数、上次报告时间
- 钉钉告警：Markdown 格式，红色标题 + 目标列表
- 企微告警：Markdown 格式，类似钉钉

**2. `report_service.py` — 缺失检测**：
- `check_missing_reports()` 函数：遍历所有目标，查询每个目标的最后报告时间
- 比较当前时间与最后报告时间，超过 `missing_days` 阈值的目标标记为缺失
- 返回缺失目标列表（`target_name`, `target_id`, `last_report_at`, `missing_days`）

**3. `schedule_service.py` — 定时触发**：
- 新增 `MISSING_REPORT_CHECK_CRON = "30 1 * * *"`（UTC 01:30 = Asia/Shanghai 09:30）
- 调度器启动时注册 `check_missing_reports_job()` 定时任务
- 每天 09:30 自动检测并发送告警

**4. `routes.py` — API 端点**：
- `GET /api/service-monitor/notification/check-missing-reports`：返回缺失目标列表
- 管理员权限校验

**5. 前端 — 通知配置页（Notification.vue）**：
- 新增"📭 报告缺失告警"配置卡片
- el-switch 控制启用/禁用
- el-input-number 设置缺失天数阈值（1~14 天）
- 渠道选择器（邮件/钉钉/企微）复用已有配置

**6. 前端 — 统计页告警横幅（Statistics.vue）**：
- 页面加载时自动调用 `checkMissingReports()` API
- 有缺失目标时显示红色告警卡片
- 展示缺失目标名、缺失天数、上次报告时间

**验证结果**：
- 218 个单元测试全绿 ✅
- vue-tsc 前端类型检查通过 ✅
- 通知配置页保存/加载正常 ✅

---

## 3. 当前卡在哪

**无卡点！** 本轮会话已完成所有问题的根因排查和彻底修复 + 报告缺失兜底告警功能。

定时任务功能已验证正常：
- 5.44 全量巡检成功（525 项检测）✅
- 调度器正常触发 ✅
- 死锁已解除（`_schedules_lock` → `RLock`）✅
- 加密密钥已固定（`MONITOR_ENC_KEY` 写入 `.env`）✅

**待处理（非阻塞）**：
1. 钉钉通知需要重新配置 secret（访问通知配置页面重新填写）
2. 通知配置页面会加载失败，因为钉钉 secret 用旧密钥加密，需先在页面清除旧值并重新填写

定时任务功能已验证正常：
- 5.44 全量巡检成功（525 项检测）✅
- 调度器正常触发 ✅
- 死锁已解除（`_schedules_lock` → `RLock`）✅
- 加密密钥已固定（`MONITOR_ENC_KEY` 写入 `.env`）✅

**待处理（非阻塞）**：
1. 钉钉通知需要重新配置 secret（访问通知配置页面重新填写）
2. 通知配置页面会加载失败，因为钉钉 secret 用旧密钥加密，需先在页面清除旧值并重新填写

---

## 4. 下一步计划

| 优先级 | 任务 | 说明 |
|--------|------|------|
| ~~P1~~ | ~~排查定时任务巡检执行失败~~ | ✅ 已完成 — 根因是 RLock 死锁 + 凭据解密失败，已彻底修复 |
| ~~P1~~ | ~~验证 9.89 巡检~~ | ✅ 已完成 — 快速巡检 74 项正常执行 |
| ~~P1~~ | ~~固定 MONITOR_ENC_KEY~~ | ✅ 已完成 — 写入 .env，避免重建后凭据解密失败 |
| ~~P1~~ | ~~报告自动清理~~ | ✅ 已完成 — 巡检完成后自动清理过期报告（保留 14 天） |
| ~~P1~~ | ~~钉钉通知修复~~ | ✅ 已完成 — 通知配置页面恢复正常，secret 已重新加密 |
| ~~P1~~ | ~~通知内容增强~~ | ✅ 已完成 — 钉钉消息包含巡检摘要 + 异常项 + 报告链接 |
| ~~P1~~ | ~~报告缺失兜底告警~~ | ✅ 已完成 — 每天 09:30 检测缺失报告并通知 |
| **P2** | **测试 9.89 全量巡检** | 手动触发 9.89 full 套件，验证全部 42 个模块 |
| 持续 | 更新 HANDOFF_服务监测文档 | 每次会话结束前更新进度 |

---

## 4.1 阶段九进度：功能增强

### ✅ P1：报告对比功能（已完成）

**实现内容**：
- `compare_service.py`：对比服务，5 类差异分类（新增异常/恢复正常/值变化/新增项/消失项）
- API：`GET /api/service-monitor/reports/{id1}/compare/{id2}`
- 导出 API：`GET /api/service-monitor/reports/{id1}/compare/{id2}/export?format=md|html`
- `compare.html`：对比结果页，差异按模块分组展示，可折叠
- `report.html`：新增"对比历史报告"按钮 + 报告选择弹窗
- 值变化阈值：默认 > 20%
- 数值提取：正则匹配，支持带单位的值（如 45%、100MB）

### ✅ P1：异常项趋势告警（已完成）

**实现内容**：
- `report_service.py`：`check_consecutive_abnormal()` 函数
- `_get_abnormal_items()`：提取异常检测项（支持仅严重/警告+严重）
- `_is_consecutive_abnormal()`：连续性检查
- 冷却期机制：`_ALERT_COOLDOWN` 内存字典，24 小时冷却
- `runner_service.py`：集成告警检查，报告完成后自动触发
- `_send_consecutive_alert()`：告警通知发送（邮件/钉钉/企微）
- `notification.html`：新增"连续异常告警"配置区
- 配置字段：`alert_on_consecutive.enabled/threshold/alert_level/channels`

### ✅ P2：导出 PDF/Excel 格式（已完成）

**实现内容**：
- `report_service.py`：`export_excel()` 函数（使用 openpyxl）
  - Sheet 1：汇总信息（目标、套件、时间、统计）
  - Sheet 2：检测明细（全量表格 + 条件格式）
  - 状态列颜色标记（正常=绿、警告=黄、严重=红）
  - 表头冻结
- `report_service.py`：`export_pdf()` 函数
  - 使用 weasyprint 将 HTML 转 PDF
  - 降级策略：weasyprint 不可用时返回可打印 HTML
  - `_build_report_html()`：打印友好 HTML 模板
  - @page 分页规则 + 中文字体支持
- `routes.py`：`api_export_report()` 增加 `pdf` 和 `excel` 格式
- `report.html`：导出菜单新增"导出 PDF"、"导出 Excel"
- PDF 检测：通过 `%PDF` 魔数判断，降级时添加打印按钮

---

## 5. 踩过的坑（绝对不要再踩）

### 坑 1：JavaScript 编辑时代码残留

**坑**：使用 Edit 工具替换函数时，`old_string` 未精确匹配完整范围，只替换了函数开头，尾部代码残留。导致后续函数定义在错误的闭包内，全局作用域无法访问。

**避免方法**：
- 编辑前完整阅读原代码，确保 `old_string` 匹配完整范围
- 编辑后检查花括号平衡
- 本地测试后再部署

### 坑 2：状态类检测项折线图空白无提示

**坑**：`_extract_numeric()` 对非数值（"正常"/"异常"/"yes"/"no"）返回 `(None, value)`，前端收到 `value_num=null` 后折线图无数据点，但未显示任何提示。用户以为"图表坏了"。

**避免方法**：
- 前端处理数据前检查数值情况，无数值时显示友好提示
- 饼图使用 `status` 字段，不依赖数值

### 坑 3：检测项选择器选项过多难找

**坑**：419 个检测项平铺在一个下拉框，用户难以找到目标项。

**避免方法**：
- 用两级联动：第一级选模块，第二级选检测项
- 检测项选择器按模块分组（optgroup）
- 标注无趋势能力的检测项

### 坑 4：部署后健康检查误报

**坑**：`upload_to_server.py` 结尾的 `[FAIL] Service may not be running` 是误报。服务启动需要加载索引（357 条），sleep 3 秒不够。

**避免方法**：
- 用 `curl http://192.168.5.60:8088/api/health` 权威验证
- 不要信赖部署脚本的自动检查

### 坑 5：Shell 脚本硬编码密码

**坑**：参考目录的 `23_mysql_depth.sh` 和 `25_redis_depth.sh` 硬编码了密码（`MYSQL_PASSWORD="dNrprU&2S"`），直接复制到项目会泄露凭据。

**避免方法**：
- 使用 `MYSQL_PASSWORD` 环境变量（通过 config.sh 注入）
- 容器名使用 `CONTAINERS[name]` 模糊匹配而非硬编码
- 所有输出使用 `output_result "KEY" "value"` 标准函数

### 坑 6：PowerShell 移植到 Bash 的差异

**坑**：参考目录是 PowerShell 模块，直接翻译到 Bash 时需注意：
- PowerShell 的 `@()` 数组 → Bash 用空格分隔字符串 + `for` 循环
- PowerShell 的 `$Config.Key` → Bash 用 `${CONTAINERS[key]}` 关联数组
- PowerShell 的 `Invoke-WebRequest` → Bash 用 `curl`
- PowerShell 的 `Select-Object` → Bash 用 `awk`/`cut`
- PowerShell 的 `-match` → Bash 用 `grep -E`

**避免方法**：
- 移植时只保留核心检测逻辑，简化边缘功能
- 容器名用 `grep -iE` 模糊匹配替代精确名
- 凭据用 `__占位符__` + executor 渲染替代硬编码

### 坑 7：部署路径与验证方式

**坑**：部署后想验证新模块，发现：
- `/tmp/check_modules/lib/` 目录为空 —— 这是**检测运行时**临时工作目录，脚本只在运行巡检时才上传到这里
- `/data/web/service_monitor/assets/` 不存在 —— 实际部署路径是 `/opt/troubleshoot/web/service_monitor/`
- 统计 API `item-keys` 返回空 —— 因为统计页面基于**历史报告数据**聚合，新模块没运行过就没有数据
- 登录接口 `/login` 返回 415 —— 后端期望 JSON 格式（`Content-Type: application/json`），不是 form-urlencoded

**避免方法**：
- 部署路径：`/opt/troubleshoot/web/service_monitor/`（不是 /data/web）
- 验证模块注册：SSH 检查 `check_modules.py` 包含新模块 ID
- 验证脚本：SSH 检查 `assets/system/` 或 `assets/service/` 下文件存在 + `bash -n` 语法检查
- 触发实际运行：必须通过 Web 界面 SSE 流式接口 `/api/service-monitor/run/stream`（需管理员权限）
- Windows 没有 `sshpass`，用 paramiko（项目已依赖）执行 SSH 命令

### 坑 8：LEVEL KEY 命名不一致导致状态误判

**坑**：检测脚本输出 `MW_CONN_REDIS_LEVEL`，但 `_resolve_status` 查找 `MW_CONN_REDIS_STATUS_LEVEL`。LEVEL 字段对不上 → 文本映射也缺失 → 默认"正常"。明明"端口未监听"却显示"正常"。

**避免方法**：
- LEVEL KEY 必须严格遵循 `<值KEY>_LEVEL` 格式（`f"{key}_LEVEL"` 精确匹配）
- 文本状态词映射作为双重保险，补充常见异常词
- 新模块开发时先确认已有模块的命名约定

### 坑 9：容器内无 ss/netstat 导致端口检测误判

**坑**：脚本在容器内用 `ss -tlnp | grep ':6379'` 检查端口。但精简镜像（Alpine/slim）没装 `iproute2`，`ss` 命令失败 stderr 被 `2>/dev/null` 吞掉，结果变量为空 ≠ "yes" → 误判"端口未监听"。

**避免方法**：
- 端口检测**不要依赖容器内的 ss/netstat**
- 优先用**服务自带工具**（redis-cli ping / mysqladmin ping / curl API）
- 端口检测用**宿主机 ss**（适用于有端口映射的服务）
- 进程检查用 `ps aux`（几乎所有镜像都有）

### 坑 10：目标凭据未配置导致安全检测失效

**坑**：46_security_deep.sh 检测 Redis 密码状态时，因 `REDIS_PASSWORD` 未配置，`redis-cli INFO` 报 `NOAUTH`，脚本误判"未检测到容器"或"查询失败"。实际 Redis 有密码保护，但密码没配进系统，安全漏洞检测不出来。

**避免方法**：
- 添加远程目标时必须配置数据库密码（credential_overrides）
- 密码通过 API 加密存储，config.sh 模板用 `__REDIS_PASSWORD__` 占位符
- 部署后验证：跑一次 full 套件，确认安全模块能输出"已设置"而非"查询失败"

### 坑 11：定时任务并发执行导致 run_id 冲突

**坑**：两个定时任务在同一秒触发（如都在 09:00），`run_id` 使用时间戳+"sched"后缀生成，导致 `run_id` 完全相同。第二个任务覆盖第一个的工作目录和 `_runs` 字典，只生成一份报告。

**避免方法**：
- `run_id` 必须加入 uuid 或其他唯一标识（已修复：使用 `target_id[:4]` + uuid[:6]）
- 并发执行场景必须确保资源隔离（工作目录、内存状态）

### 坑 12：Docker 容器与 systemd 服务端口冲突

**坑**：Docker 容器和 systemd 服务同时运行抢占 8088 端口，systemd 每 6 秒重启一次，累计重启 9300+ 次。定时任务因服务不稳定无法执行。

**原因**：部署脚本只更新宿主机文件并重启 systemd 服务，没检查 Docker 容器是否已在运行。

**避免方法**：
```bash
# 部署前先检查
sudo docker ps | grep troubleshoot  # 如果有容器在运行
sudo systemctl disable troubleshoot  # 禁用 systemd
```

### 坑 13：Docker 镜像内代码未随部署更新

**坑**：部署脚本只更新宿主机 `/opt/troubleshoot/web/` 下的文件，但 Docker 镜像构建时 `COPY` 代码到容器内，容器内代码仍是旧版本。

**原因**：代码文件不在 volume 挂载范围内，必须重建镜像才能更新。

**避免方法**：
```bash
# 代码更新后必须重建镜像
sudo rsync -av --delete /opt/troubleshoot/web/ /data/third_party/monitor-platform/skill/code/web/
cd /data/third_party/monitor-platform && sudo docker compose up -d --build
```

### 坑 14：volume 挂载文件类型不匹配导致容器启动失败

**坑**：`搜索索引.json` 被误创建为目录，docker-compose 挂载时报错 `not a directory: Are you trying to mount a directory onto a file`。

**原因**：宿主机上 `/opt/troubleshoot/data/搜索索引.json` 是目录，挂载到容器内也是目录，与镜像内期望的文件类型冲突。

**避免方法**：
```bash
# 重建前检查挂载文件类型
sudo file /opt/troubleshoot/data/搜索索引.json  # 应输出：JSON text data
sudo rm -rf /opt/troubleshoot/data/搜索索引.json  # 如果是目录则删除
sudo cp /data/.../搜索索引.json /opt/troubleshoot/data/  # 复制正确的文件
```

### 坑 15：通知配置解密失败导致 API 500

**坑**：`notifications.json` 中钉钉 secret 用旧密钥加密（`enc:gAAAAA...`），重建容器后 `MONITOR_ENC_KEY` 变更，解密失败导致通知 API 返回 500。

**原因**：Fernet 加密密钥每次容器启动可能不同（如果 `MONITOR_ENC_KEY` 未固定）。

**避免方法**：在 `docker-compose.yml` 中设置固定的 `MONITOR_ENC_KEY` 环境变量。

### 坑 16：sudo 检测逻辑只测用户权限没测命令权限

**坑**：最初只检测 `sudo -n whoami`（用户是否有免密 sudo），没检测 Docker 命令权限。9.89 上 openkylin 有免密 sudo 但不在 docker 组，`docker ps` 仍失败。

**原因**：想当然认为"有 sudo 权限就能执行所有命令"。

**避免方法**：检测要具体到命令本身。正确逻辑：先测 `docker ps`，失败再测 `sudo -n docker ps`。

### 坑 17：定时任务 end_date 过期导致不触发

**坑**：`schedules.json` 中 5.44 定时任务配置了 `end_date: 2026-07-22`，8 月 5 日任务已过期。APScheduler 在注册时会设置 `trigger.end_date`，过期任务不会触发。

**避免方法**：
- 创建定时任务时，如果不想要失效日期，设置为 `null` 而不是设置一个短期日期
- 部署后验证：用 `cat schedules.json` 确认 end_date 合理

### 坑 18：service_monitor 日志不在 app.log 中

**坑**：`schedule_service.py`、`runner_service.py` 等使用 `logging.getLogger("service_monitor.xxx")`，这个 logger 不在 `troubleshoot` 命名空间下，日志不会输出到 app.log。导致定时任务执行过程完全不可追踪。

**原因**：项目日志系统通过 `utils/logger.py` 的 `get_logger()` 初始化根 logger（`troubleshoot`），子 logger 必须以 `troubleshoot.` 为前缀才能继承 handler。

**避免方法**：
- 所有 service 模块使用 `from utils.logger import get_logger; logger = get_logger("service_monitor.xxx")`
- 或者改为 `logging.getLogger("troubleshoot.service_monitor.xxx")`

### 坑 19：Docker COPY 文件需重建镜像

**坑**：代码文件在 Docker 构建时 `COPY` 到容器内，`docker compose restart` 不会更新容器内代码。修改代码后必须 `docker compose up -d --build` 重建镜像。

**原因**：docker compose restart 只是重启容器，不会重新构建镜像。

**避免方法**：
```bash
# 代码更新后必须重建镜像
sudo docker compose up -d --build
# 或：先 down 再 up --build
sudo docker compose down && sudo docker compose up -d --build
```

### 坑 20：_schedules_lock 死锁（致命 Bug）

**坑**：`_update_current_status()` 持有 `_schedules_lock` 后调用 `_save_schedules()` 又获取同一个 `threading.Lock()`。Python 的 `Lock()` 不可重入，同一线程重复获取 → 死锁！定时任务触发后 forever 卡在状态更新处。

**现象**：APScheduler 日志显示"触发成功"，但 `run_inspection_sync()` 内部日志完全没有输出，状态永远不更新。

**避免方法**：所有可能被嵌套调用的锁必须使用 `threading.RLock()`（可重入锁）。写完 `_save_schedules` 后检查所有调用它的函数是否已经持有锁。

### 坑 21：加密密钥未固定导致凭据解密失败

**坑**：`.env` 中 `MONITOR_ENC_KEY=` 为空，容器重启时 Fernet 随机生成新密钥。旧密文（targets.json 中的 SSH 密码、notifications.json 中的钉钉 secret）无法解密，导致巡检在 `make_executor()`/`resolve_credentials()` 阶段静默失败。

**现象**：后台线程异常被吞掉，日志中看不到任何错误，只有通过 `python -c 'import ...'` 直接调用才能看到 `ValueError: 凭据解密失败，密钥不匹配`。

**避免方法**：
- 在 `.env` 中设置固定的 `MONITOR_ENC_KEY`（`cryptography.fernet.Fernet.generate_key()` 生成）
- 代码部署时检查 `.env` 中是否已配置该变量
- 所有 `decrypt_password()` 调用要确保异常被记录到日志而非静默吞掉

### 坑 22：SFTP 上传用 sudo 创建目录导致权限问题

**坑**：`use_sudo=True` 时，`setup()` 通过 `sudo bash -c 'mkdir -p ...'` 创建工作目录，目录属于 root。后续 SFTP 以普通用户身份上传文件 → Permission denied。

**避免方法**：SFTP 上传前的工作目录创建**不能用 sudo**。setup() 中直接调用 `ssh.exec_command("mkdir -p ...")` 而非 `_exec()`（后者会自动加 sudo 前缀）。

### 坑 23：workdir_base 路径拼接错误

**坑**：`workdir_base="/home/openkylin"` 时，`f"{base}_{run_id}"` 拼接成 `/home/openkylin_test_xxx` 而非 `/home/openkylin/check_modules_xxx`。

**避免方法**：检测 workdir_base 是否包含 `/`，如果是完整路径则追加 `/check_modules_<run_id>` 子目录。

### 坑 24：后台线程异常被静默吞掉

**坑**：`_run_job_body()` 在后台线程中调用 `run_inspection_sync()`，当凭据解密失败抛出异常时，虽然在 `try/except Exception as e` 中（按理由应该被捕获），但实际上因为 `_update_current_status("running")` 在内部调用了 `_save_schedules()` 导致**死锁卡住**，异常永远不会被抛到 try/except。

**避免方法**：关键路径的状态更新要在启动后台耗时任务**之前**执行，避免锁竞争。或者状态更新不持有锁去调用其他也可能获取锁的函数。

---

## 6. Git 提交记录

| 日期 | Commit | 说明 |
|------|--------|------|
| **2026-08-12** | - | **feat(monitor): 报告缺失兜底告警（阶段十四，待提交）** |
| **2026-08-05** | - | **fix(monitor): 定时任务死锁修复 + 9.89 麒麟系统适配 + 加密密钥固定（阶段十二，待提交）** |
| **2026-08-05** | `65b25c4d` | **feat(monitor): SSH sudo 自动检测 + Docker 权限适配** |
| 2026-07-24 | `3cb1cf62` | fix(service-monitor): 修复定时任务并发执行导致 run_id 冲突 |
| 2026-07-23 | `7b0e7dcd` | feat(service-monitor): 导出 PDF/Excel 格式 |
| 2026-07-23 | `069f9fa2` | feat(service-monitor): 报告对比功能 + 异常项趋势告警 |
| 2026-07-23 | `8e7a5b3a` | style(service-monitor): 移动端响应式深度优化-新增480px超小屏幕断点 |
| 2026-07-23 | `dfe01cae` | feat(service-monitor): P3级检测模块补全+巡检防重复触发+移动端响应式优化 |
| 2026-07-22 | `664d318d` | feat(service-monitor): P3级移植-文件权限检测/定时任务预期检测 |
| 2026-07-22 | `dc42cffe` | feat(service-monitor): P2级移植-系统类型识别/平台服务检测/配置IP检测/Console配置检测 |
| 2026-07-22 | `c0200115` | feat(service-monitor): 新增安全合规/中间件检测模块并修复状态等级误判 |
| 2026-07-14 | `3f48af4c` | feat(service-monitor): 报告免登访问 + 监测统计模块 + 多项修复 |

**最近提交详情（c0200115）**：

```
feat(service-monitor): 新增安全合规/中间件检测模块并修复状态等级误判

检测项丰富化移植（阶段二）：
- 新增 39_middleware_conn.sh 中间件连接检测
- 新增 46_security_deep.sh 安全合规深度检测（9类）
- check_modules.py 注册 2 个新模块
- display_names.py 追加 36 个 KEY 映射
- thresholds.py 追加 6 个安全阈值
- config.sh.template 追加安全白名单 + EMQX 端口配置

状态等级误判修复：
- 39/46 脚本 LEVEL KEY 对齐为 <值KEY>_LEVEL 格式
- 23_mysql_depth / 25_redis_depth 重构为 output_result 格式
- parser.py _VALUE_STATUS_MAP 补充 11 个异常状态词映射

中间件端口检测逻辑重构：
- 改用服务自带工具 ping 优先（redis-cli/mysqladmin/curl API）
- 避免容器内无 ss/netstat 导致误判"端口未监听"

服务管理模块（附带）：
- routes/service_manage.py 新增授权/升级/信息页面路由
- templates/service_manage/ 新增 4 个模板

文档：更新 HANDOFF_服务监测.md + 检测项丰富化 PRD
```

**分支状态**：已推送到 `origin/troubleshoot-ai-assistant`，待合并到 master。

---

## 7. 关键文件与命令速查

### 本次修改文件（阶段一：检测项丰富化）

| 文件 | 改动 |
|------|------|
| `service_monitor/utils/display_names.py` | 补全 `VM_PGMajFAULT`；新增 `MYSQL_DEPTH_*` 30 个 KEY、`REDIS_DEPTH_*` 22 个 KEY（共 52 条） |
| `service_monitor/utils/thresholds.py` | 新增 MySQL 深度阈值 7 个、Redis 深度阈值 5 个 |
| `service_monitor/assets/service/23_mysql_depth.sh` | 重构为 output_result 格式，30 个检测项，加 MYSQL_DEPTH_ 前缀 |
| `service_monitor/assets/service/25_redis_depth.sh` | 重构为 output_result 格式，22 个检测项，加 REDIS_DEPTH_ 前缀 |

### 本次修改文件（阶段二：新检测模块移植）

| 文件 | 改动 |
|------|------|
| `service_monitor/assets/system/46_security_deep.sh` | 新建 - 安全合规深度检测脚本（9 类安全检测） |
| `service_monitor/assets/service/39_middleware_conn.sh` | 新建 - 中间件连接检测脚本（4 类连接检测），修订 - 端口检测逻辑重构（服务 ping 优先） |
| `service_monitor/assets/config.sh.template` | 追加安全白名单配置（SUID/端口）+ EMQX 端口配置 |
| `service_monitor/utils/check_modules.py` | 追加 2 个 CheckModule 条目（46_security_deep、39_middleware_conn） |
| `service_monitor/utils/display_names.py` | 追加 36 个 KEY 映射（`SECURITY_DEEP_*` 20 个 + `MW_CONN_*` 16 个） |
| `service_monitor/utils/thresholds.py` | 追加 6 个安全检测阈值 |
| `service_monitor/utils/parser.py` | 追加 11 个异常状态词映射（`_VALUE_STATUS_MAP`） |

### 本次修改文件（阶段三：P2 级移植）

| 文件 | 改动 |
|------|------|
| `service_monitor/assets/system/50_system_profile.sh` | 新建 - 系统类型识别脚本（6 个检测项） |
| `service_monitor/assets/service/51_service_check.sh` | 新建 - 平台服务检测脚本（~20 个检测项） |
| `service_monitor/assets/system/52_config_ip_check.sh` | 新建 - 配置IP检测脚本（5 个检测项） |
| `service_monitor/assets/system/53_config_console_check.sh` | 新建 - Console配置检测脚本（5 个检测项） |
| `service_monitor/utils/check_modules.py` | 追加 4 个 CheckModule 条目（50/51/52/53） |
| `service_monitor/utils/display_names.py` | 追加 35 个 KEY 映射（`SYS_PROFILE_*` 6 个 + `SVC_CHECK_*` 20+ 个 + `CFG_IP_*` 5 个 + `CFG_CONSOLE_*` 5 个） |
| `service_monitor/utils/parser.py` | 追加 2 个状态词映射（监听/未监听） |
| `service_monitor/assets/config.sh.template` | 追加服务匹配模式 + 端口列表 + SERVER_IP 配置 |

### 本次修改文件（阶段五：P3 级补全）

| 文件 | 改动 |
|------|------|
| `service_monitor/assets/service/56_fastdfs_check.sh` | 新建 - FastDFS功能验证检测脚本（上传/下载/完整性测试） |
| `service_monitor/assets/service/57_android_check.sh` | 新建 - Android设备检测脚本（adb可用性+设备连接状态） |
| `service_monitor/assets/system/58_data_backup_check.sh` | 新建 - 数据备份检测脚本（备份脚本+备份时间+备份能力） |
| `service_monitor/assets/system/59_log_export_check.sh` | 新建 - 配置日志导出检测脚本（配置文件+日志目录+导出能力） |
| `service_monitor/assets/system/60_repair_capability_check.sh` | 新建 - 修复能力检测脚本（修复工具链就绪状态） |
| `service_monitor/utils/check_modules.py` | 追加 5 个 CheckModule 条目（56/57/58/59/60） |
| `service_monitor/utils/display_names.py` | 追加 99 个 KEY 映射（FDFS/ANDROID/BACKUP/LOGEXP/REPAIR） |
| `service_monitor/utils/parser.py` | 追加新模块状态词映射 |

### 本次修改文件（阶段八：移动端响应式深度优化）

| 文件 | 改动 |
|------|------|
| `templates/service_monitor/base.html` | 新增 480px 断点：顶栏/侧边栏/用户名紧凑化 |
| `templates/service_monitor/index.html` | 新增 480px 断点：卡片/按钮缩小，目标管理按钮全宽 |
| `templates/service_monitor/reports.html` | 新增 480px 断点：筛选框/报告行/批量栏紧凑化 |
| `templates/service_monitor/report.html` | 新增 480px 断点 + 模块标题 `word-break` + 异常项/检测项紧凑化 |
| `templates/service_monitor/statistics.html` | 修正 768px 筛选栏纵向堆叠 + 新增 480px 断点 |
| `templates/service_monitor/schedule.html` | 新增 480px 断点：弹窗/星期按钮/时间选择器紧凑化 |
| `templates/service_monitor/targets.html` | 新增 480px 断点：表格行/操作按钮/弹窗紧凑化 |
| `templates/service_monitor/notification.html` | 修正 768px 端口输入框自适应 + 新增 480px 断点 |
| `templates/service_monitor/run.html` | 新增进度条响应式高度（12→10→8px）+ 480px 断点 |

### 本次修改文件（阶段九：报告对比 + 连续异常告警）

| 文件 | 改动 |
|------|------|
| `service_monitor/services/compare_service.py` | **新增** — 对比服务（5 类差异分类 + 导出 Markdown/HTML） |
| `service_monitor/services/report_service.py` | 新增连续异常检测函数（check_consecutive_abnormal + 冷却期） |
| `service_monitor/services/runner_service.py` | 集成连续异常告警 + 告警消息发送（邮件/钉钉/企微） |
| `service_monitor/services/__init__.py` | 导出 compare_service |
| `service_monitor/routes.py` | 新增对比/导出 API 路由 + 对比页面路由 |
| `templates/service_monitor/compare.html` | **新增** — 对比结果页模板 |
| `templates/service_monitor/report.html` | 新增"对比历史报告"按钮 + 报告选择弹窗 |
| `templates/service_monitor/notification.html` | 新增"连续异常告警"配置区 |

### 本次修改文件（阶段九-P2：导出 PDF/Excel）

| 文件 | 改动 |
|------|------|
| `service_monitor/services/report_service.py` | 新增 export_excel()、export_pdf()、_build_report_html() |
| `service_monitor/routes.py` | api_export_report() 增加 pdf/excel 格式，重构避免重复代码 |
| `templates/service_monitor/report.html` | 导出菜单新增"导出 PDF"、"导出 Excel" |

### 本次修改文件（阶段十：SSH sudo 自动检测 + Docker 权限适配）

| 文件 | 改动 |
|------|------|
| `service_monitor/utils/executor.py` | SSHExecutor 新增 sudo 自动检测（+150 行）：`_detect_sudo_need()` + `_try_configure_passwordless_sudo()` |
| `service_monitor/services/target_service.py` | `make_executor()` 等支持 `use_sudo` 字段（+10 行） |
| `service_monitor/routes.py` | 新增 `GET /api/service-monitor/targets/<target_id>` API（+12 行） |
| `service_monitor/assets/service/20_docker_basic.sh` | 新增 `_detect_docker_sudo()` 函数，所有 docker 命令使用 `$DOCKER_CMD` 变量（+69 行） |
| `templates/service_monitor/targets.html` | 补充编辑回填功能 + sudo 提权选项（+37 行） |

**5.60 部署环境修复**：
| `docker-compose.yml` | 注释搜索向量 volume 挂载 |
| `/opt/troubleshoot/data/搜索索引.json` | 修复目录→文件 |
| `/opt/troubleshoot/data/notifications.json` | 清除无效加密字段 |
| `systemd` | 禁用 troubleshoot 服务，统一用 Docker 容器 |

### 本次修改文件（阶段十一：定时任务调度器日志修复 + 任务触发验证）

| 文件 | 改动 |
|------|------|
| `service_monitor/services/schedule_service.py` | Logger 改用 `get_logger()`；添加 APScheduler 事件监听（JOB_EXECUTED/ERROR/MISSED）；添加详细调试日志（调度器启动、任务注册、触发、执行全过程） |
| `service_monitor/services/runner_service.py` | Logger 改用 `get_logger()`；`run_inspection_sync()` 添加详细调试日志（执行器创建、SSH 连通、模块执行、报告保存全过程）；巡检完成后自动调用 `cleanup_expired()` 清理过期报告 |
| `HANDOFF_服务监测.md` | 更新阶段十一/十二/十三进度 |

### 本次修改文件（阶段十二：根因排查彻底解决）

| 文件 | 改动 |
|------|------|
| `service_monitor/services/schedule_service.py` | `_schedules_lock` 从 `Lock()` 改为 `RLock()`，解除死锁 |
| `service_monitor/utils/executor.py` | `_remote_workdir()` 修复路径拼接（含 `/` 时追加 `/check_modules_<run_id>` 子目录）；`setup()` 改用用户权限创建目录（直接 `ssh.exec_command` 而非 `_exec`）；`_upload_file()` 自动创建父目录；SSHExecutor 新增 `workdir_base` 参数 |
| `service_monitor/services/target_service.py` | `make_executor()` 传递 `workdir_base`；`create_target()`/`update_target()`/`_build_view()` 支持 `workdir_base` 字段 |
| `templates/service_monitor/targets.html` | 前端新增"工作目录"配置项（`f-workdir-base` 输入框） |
| `.env` | `MONITOR_ENC_KEY` 设置为固定值 `Dy_CMi2ugIPjst38xfuqMbTpqXIzjdybJneu5xSsARs=` |
| `service_monitor/data/notifications.json` | 清除加密的钉钉 secret（旧密钥加密，新密钥无法解密） |
| `service_monitor/data/targets.json` | 清除所有加密的 `password_enc` 和 `credential_overrides`，重新配置 |

**5.60 配置变更**：
- `.env` 文件写入固定 `MONITOR_ENC_KEY`
- 9.89 目标配置：`use_sudo=True` + `workdir_base=/home/openkylin`
- 5.44 目标配置：重新设置 SSH 密码（root/Ubains@123）和 MySQ/Redis 密码

### 本次修改文件（阶段十三：报告清理 + 通知修复 + 通知内容增强）

| 文件 | 改动 |
|------|------|
| `service_monitor/services/runner_service.py` | 巡检完成后调用 `report_service.cleanup_expired()` 清理过期报告 |
| `service_monitor/services/notification_service.py` | 钉钉消息增强：增加执行时间、目标类型、总项数行；调整 Markdown 布局 |

**当前通知消息示例**：
```
✅ **【巡检报告】新统一平台5.44**

📊 **汇总：** 正常 505 / 警告 8 / 严重 12
🕐 **时间：** 2026-08-06 09:11:37
🎯 **目标：** 新统一平台5.44 ｜ full巡检

📊 总项数：525
(异常项如有则展示，最多5严重+3警告)
📎 [查看完整报告](链接)
```

### 本次修改文件（阶段十四：报告缺失兜底告警）

| 文件 | 改动 |
|------|------|
| `service_monitor/services/notification_service.py` | 新增 `report_missing` 配置结构 + `send_missing_report_notification()` 发送函数（邮件/钉钉/企微 3 渠道） |
| `service_monitor/services/report_service.py` | 新增 `check_missing_reports()` 检测函数（遍历目标，比较最后报告时间） |
| `service_monitor/services/schedule_service.py` | 新增每天 09:30 定时任务 `check_missing_reports_job()` |
| `service_monitor/routes.py` | 新增 `GET /api/service-monitor/notification/check-missing-reports` API 端点 |
| `frontend/src/api/service-monitor/notification.ts` | 新增 `checkMissingReports()` API 客户端函数 |
| `frontend/src/views/service-monitor/Notification.vue` | 新增"报告缺失告警"配置区（阈值 + 渠道选择） |
| `frontend/src/views/service-monitor/Statistics.vue` | 新增报告缺失告警红色横幅 + 缺失目标列表 |

### 新增文档

| 文件 | 说明 |
|------|------|
| `Docs/需求文档/服务监测/PRD_需求文档_检测项丰富化移植.md` | 检测项丰富化需求文档 |
| `Docs/需求文档/服务监测/PRD_需求文档_报告对比功能.md` | 报告对比功能需求文档 |
| `Docs/需求文档/服务监测/PRD_需求文档_异常项趋势告警.md` | 异常项趋势告警需求文档 |
| `Docs/需求文档/服务监测/PRD_计划执行_报告对比功能.md` | 报告对比功能计划执行文档 |
| `Docs/需求文档/服务监测/PRD_计划执行_异常项趋势告警.md` | 异常项趋势告警计划执行文档 |

### 参考目录

```
platform-auto-test/临时目录/服务监测/
├── lib/shell/          # Shell 检测脚本（10个）
├── modules/            # PowerShell 模块（20个）
├── check_mysql_deep.sh # MySQL深度检测参考
├── check_redis.sh      # Redis深度检测参考
├── check_server_health.sh  # 综合健康检查参考
└── SecurityCheck.psm1  # 安全合规检测参考（下一阶段移植目标）
```

### 常用命令

```bash
# 本地启动
python skill/code/web/server.py

# 单元测试
cd skill/code && python -m pytest -v

# 部署到 5.60（用 ! 前缀执行）
! cd deploy && SSH_PASSWORD='***' python upload_to_server.py

# 健康检查
curl -s http://192.168.5.60:8088/api/health

# 测试报告免登访问
curl -s "http://192.168.5.60:8088/service-monitor/report/<id>?token=rpt_xxx"

# SSH 验证部署文件（Windows 用 paramiko，见坑 7）
# 部署路径：/opt/troubleshoot/web/service_monitor/
# 语法检查：bash -n <script.sh>
```

### 服务器部署路径速查

| 路径 | 说明 |
|------|------|
| `/opt/troubleshoot/web/` | 应用部署根目录 |
| `/opt/troubleshoot/web/service_monitor/` | 服务监测子包 |
| `/opt/troubleshoot/web/service_monitor/assets/system/` | 系统类检测脚本（如 46_security_deep.sh） |
| `/opt/troubleshoot/web/service_monitor/assets/service/` | 服务类检测脚本（如 39_middleware_conn.sh） |
| `/tmp/check_modules/lib/` | **运行时**临时目录（巡检执行时才生成，勿手动查） |

### 未提交文件

- **服务管理模块**（非本次开发，应单独提交）：
  - `Docs/需求文档/服务管理/HANDOFF_服务管理.md`
  - `Docs/需求文档/服务管理/PRD_需求文档_服务管理模块.md`
  - `Docs/需求文档/服务管理/PRD_计划执行_服务管理模块.md`
  - `skill/code/web/routes/service_manage.py`
  - `skill/code/web/templates/service_manage/`
- **其他**：
  - `skill/code/web/users.json`（仅登录统计更新）
  - `HANDOFF_服务监测.md`（本轮待提交）

> 注：
> - 检测项丰富化移植（阶段一至四）已全部提交并部署到 5.60
> - 5.44 目标的 `credential_overrides`（MySQL/Redis 密码）已通过 API 写入 5.60 服务器的 `targets.json`（加密），不在代码仓库内