# HANDOFF — 离线排查文档下载 + 巡检修复

> 最后更新：2026-07-29 16:30 ｜ 分支：troubleshoot-ai-assistant ｜ 负责人：czj

---

## 1. 我们在做什么

### 1.1 Docker 容器巡检环境隔离修复 + 离线排查增强

Docker 容器化部署下，本级巡检脚本在容器内执行获取的是容器虚拟环境数据，已实施完整修复方案（方案 C：本级巡检走 SSH 到宿主机）。同时完善了离线排查模式，增加文档查看/下载功能。

### 1.2 本次会话完成的功能

| 子任务 | 状态 | 说明 |
|--------|------|------|
| 本级巡检 SSH 到宿主机 | ✅ 已提交 | LocalExecutor SSH 模式 |
| 巡检 sudo 提权 | ✅ 已部署 | 非 root 用户用 `sudo bash -c '...'` 执行 |
| 巡检 items_count=0 修复 | ✅ 已部署 | `sudo export` 无效，改用 `sudo bash -c` |
| SSE "连接中断" 修复 | ✅ 已部署 | 连续 3 次 onerror 才判定断开 + 兼容 event 字段 |
| 版本号前后端分开 | ✅ 已部署 | backendVersion / frontendVersion |
| 操作日志返回首页按钮 | ✅ 已部署 | AppNavbar 添加 homePath prop |
| 项目名称下拉关闭 | ✅ 已部署 | 改为普通输入框 |
| AI 输出隐藏项目名称 | ✅ 已部署 | 匹配案例去掉 [项目名] 前缀 |
| "问题排查助手"名称隐藏 | ✅ 已部署 | 统一显示"运行维护平台" |
| MySQL/Redis 默认密码 | ✅ 已部署 | 环境变量注入巡检 config.sh |
| 离线排查文档查看/下载 | ✅ 已部署 | `/api/troubleshoot/record/<id>` + 下载 |

---

## 2. 已经完成了什么

### 2.1 本级巡检 SSH 到宿主机 ✅

| 子任务 | 状态 | 说明 |
|--------|------|------|
| `get_docker_host_ip()` 函数 | ✅ | 三级优先：环境变量 → ip route 网关 → host.docker.internal |
| `LocalExecutor` SSH 模式 | ✅ | 有 LOCAL_SSH_PASSWORD/KEY 时走 SSH，否则回退本地 subprocess |
| LocalExecutor sudo 提权 | ✅ | 非 root 用户自动用 `sudo bash -c '...'` |
| 本级目标警告字段 | ✅ | `list_targets` / `_build_view` 返回 warning 字段 |
| 禁止创建 localhost 目标 | ✅ | `create_target` 拦截 localhost/127.0.0.1/::1/host.docker.internal |
| 前端目标列表警告列 | ✅ | Targets.vue 新增"状态/警告"列 |
| docker-compose.yml | ✅ | 添加 `extra_hosts` + `LOCAL_SSH_*` + `DOCKER_HOST_IP` 环境变量 |
| .env 配置 | ✅ | `LOCAL_SSH_USER=ubains`, `LOCAL_SSH_PASSWORD=Ubains@123` |

### 2.2 SSE "连接中断" 修复 ✅

| 问题 | 原因 | 解决方案 |
|------|------|----------|
| SSE 过早报错 | `onerror` 一触发就关闭连接 | 增加 `maxErrorCount=3`，连续 3 次才判定断开 |
| 事件字段不匹配 | 后端发 `data.event`，前端用 `data.type` | 改用 `data.event \|\| data.type` |
| 巡检 items_count=0 | `sudo export` 无效 | 改用 `sudo bash -c '原始命令'` |
| setup() 未调用 | SSH 模式下 `self._workdir` 未同步 | `setup()` 中同步赋值 `_workdir` |

### 2.3 离线排查文档查看/下载 ✅

| 子任务 | 状态 | 说明 |
|--------|------|------|
| `SearchEngine.get_record_by_id()` | ✅ | 按 ID 返回单条记录完整数据 |
| `GET /api/troubleshoot/record/<id>` | ✅ | 返回记录完整内容（含 full_text） |
| `GET /api/troubleshoot/record/<id>/download` | ✅ | 下载为 .md 文件 |
| `format_matched_cases` 增加 record_id | ✅ | 前端据此构造查看/下载链接 |
| `build_offline_response` 增加查看详情链接 | ✅ | 离线文本中显示文档链接 |
| 前端匹配案例「查看详情」按钮 | ✅ | 弹窗展示完整 Markdown 文档 |
| 前端「下载文档」按钮 | ✅ | 弹窗内下载 .md 文件 |

### 2.4 前端 UI 优化 ✅

| 修改 | 说明 |
|------|------|
| 项目名称输入框 | 去掉 el-autocomplete 下拉联想，改为普通输入 |
| 匹配案例 | 去掉 `[项目名]` 前缀 |
| 模块名称 | "问题排查助手" → "运行维护平台" |
| 登录页标题 | "问题排查助手" → "运行维护平台" |
| 页脚 | "问题排查助手" → "运行维护平台" |
| 门户卡片 | "运行维护平台" → "问题排查" |
| 操作日志返回按钮 | AppNavbar 添加 homePath prop，模块名可点击 |

---

## 3. 当前卡在哪

**无卡点**。所有功能已部署到 5.60 并验证通过。

---

## 4. 下一步计划

| 优先级 | 任务 | 说明 |
|--------|------|------|
| **P1** | 产品维护手册集成 | 用户提供维护手册清单后，按产品名称自动推荐下载链接 |
| **P2** | 验证全量巡检（42 模块） | 确认全量巡检端到端完成无中断 |
| **P3** | 向量搜索模式验证 | 当前 TF-IDF 模式，向量文件待部署 |
| **P4** | SSH 密钥认证支持 | SSHExecutor 只支持密码认证，key_filename 待扩展 |

### 产品维护手册需求

用户希望根据产品名称自动推荐维护手册下载（类似华为 Q&A 文档形式）。需要用户提供：
1. 产品维护手册清单（产品名称、系统类型、文件名）
2. 手册文件存放位置（项目内 `Docs/维护手册/` 或外部文档系统）

---

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

### 坑 1：`sudo export` 无效

**坑**：`LocalExecutor._exec()` 对非 root 用户加 `sudo` 前缀，但 `sudo export LIB_DIR=...` 无效（export 是 shell 内置命令）。
**避免方法**：改用 `sudo bash -c '原始命令'`。

### 坑 2：SSE `onerror` 过早关闭连接

**坑**：EventSource 的 `onerror` 在连接建立过程、短暂波动都会触发，一触发就关闭连接并报"连接中断"。但后端巡检实际还在运行并生成报告。
**避免方法**：连续 3 次 `onerror` 才判定为真正断开，收到正常消息时重置计数。

### 坑 3：SSE 事件字段不匹配

**坑**：后端发 `{"event": "progress"}`，前端用 `data.type` 查找处理器，导致所有事件被忽略。
**避免方法**：改用 `data.event || data.type` 兼容两种格式。

### 坑 4：LocalExecutor.setup() 未同步 _workdir

**坑**：SSH 模式下 `setup()` 返回了 SSH 执行器的 workdir，但没有赋值给 `self._workdir`，导致 `run_module()` 报 `setup() 未调用`。
**避免方法**：`self._workdir = self._get_ssh_executor().setup()`。

### 坑 5：root 用户禁止 SSH 密码登录

**坑**：宿主机 `PermitRootLogin prohibit-password`，root 只能密钥登录。
**避免方法**：改用 ubains 用户 + sudo 提权。

### 坑 6：SSE 流式接口 session 脱离请求上下文

**坑**：在 `analyze_stream()` 的 `generate()` 生成器内部访问 `session.get('user', {})` 时，生成器延迟执行已脱离请求上下文，导致 `RuntimeError: Working outside of request context`。
**避免方法**：在请求上下文内提前捕获 session 数据。

### 坑 7：admin 账号被错误密码锁定

**坑**：多次错误登录尝试后，admin 账号被内存中的 `login_attempts` 锁定。
**避免方法**：测试登录时使用正确密码；锁定后重启容器清空内存状态。

---

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

### 本次新增/修改文件

| 文件 | 说明 |
|------|------|
| `skill/code/web/service_monitor/utils/executor.py` | LocalExecutor SSH 模式 + sudo 提权 + _workdir 同步 |
| `skill/code/web/service_monitor/utils/check_modules.py` | render_config 支持环境变量默认密码 |
| `skill/code/web/search_engine.py` | 新增 `get_record_by_id()` |
| `skill/code/web/routes/troubleshoot.py` | 新增记录查看/下载 API + format_matched_cases 增加 record_id |
| `skill/code/web/services/ai_service.py` | build_offline_response 增加查看详情链接 |
| `frontend/src/utils/sse.ts` | SSE 错误处理优化 + event 字段兼容 |
| `frontend/src/components/layout/AppNavbar.vue` | 添加 homePath prop |
| `frontend/src/views/troubleshoot/Index.vue` | 文档弹窗 + 下载 + UI 优化 |
| `frontend/src/views/troubleshoot/Logs.vue` | 传入 homePath |
| `frontend/src/views/Login.vue` | 标题改为"运行维护平台" |
| `frontend/src/views/Platform.vue` | 卡片名称调整 |
| `frontend/src/router/index.ts` | 路由标题调整 |
| `frontend/src/types/troubleshoot.ts` | MatchedCase 增加 record_id |
| `frontend/vite.config.ts` | 构建期注入前端版本号 |
| `frontend/src/components/common/AppVersion.vue` | 前后端版本分开显示 |
| `.env` / `.env.example` | 版本号 + LOCAL_SSH + MySQL/Redis 密码 |

### 常用命令

```bash
# 后端单元测试
cd skill/code && python -m pytest -v

# 前端构建
cd frontend && npm run build

# Docker 部署（5.60 服务器）
SSH_PASSWORD='Ubains@123' python deploy/deploy_docker.py

# 验证部署
curl http://192.168.5.60:8088/api/health
curl http://192.168.5.60:8088/api/version

# 验证离线文档
curl http://192.168.5.60:8088/api/troubleshoot/record/1
curl -O http://192.168.5.60:8088/api/troubleshoot/record/1/download

# 验证本级巡检 SSH 模式（容器内）
docker exec troubleshoot python3 -c "
from service_monitor.utils.executor import get_docker_host_ip
print(get_docker_host_ip())
"
```

### 服务器信息

- **SSH**：`ubains@192.168.5.60`，密码：`Ubains@123`
- **部署目录**：`/data/third_party/monitor-platform`
- **服务地址**：`http://192.168.5.60:8088`
- **admin 密码**：`Ubains@1357`

### 环境变量（.env 关键配置）

```bash
# 版本号
BACKEND_VERSION=0.1.0
FRONTEND_VERSION=0.1.0

# 本级巡检 SSH
LOCAL_SSH_USER=ubains
LOCAL_SSH_PASSWORD=Ubains@123

# 中间件默认密码
MYSQL_DEFAULT_PASSWORD=dNrprU&2S
REDIS_DEFAULT_PASSWORD=dNrprU&2S

# 离线模式
OFFLINE_MODE=true
```
