# 工作日志 — 2026-05-20

---

## 上午：状态检查

### 8核32G 蜂驰云 (120.53.227.155) 检查结果

| 项目 | 状态 | 说明 |
|------|------|------|
| Docker (mysql/redis/qdrant) | ✅ | 正常运行 |
| NestJS API (zhixi-api) | ✅ | systemd active |
| gitea-runner | ✅ | systemd active |
| Nginx + HTTPS | ✅ | api.longde.cloud |
| rag-worker 代码部署 | ✅ | `/opt/zhixi/backend/rag-worker/` |
| rag-worker .env | ✅ | 所有密钥/endpoint 已配置 |
| Python 依赖 | ❌→✅ | 缺→28+ packages 全装 |
| Python 版本 | ✅ | 3.11.15（systemd 显式指定 /usr/bin/python3.11） |
| zhixi-worker systemd | ❌→✅ | enabled active，polling 正常 |

### 4核4G 轻量云 (81.70.187.179) 检查结果

全部正常，无变化。

---

## 今日完成

### 1. Python Worker 补全部署 ✅
- [x] 修正 requirements.txt（补 qdrant-client + python-dotenv）
- [x] config.py 添加 python-dotenv 加载兜底
- [x] pip3.11 安装全部依赖（28+ packages）
- [x] 创建 systemd zhixi-worker.service（PYTHONUNBUFFERED=1 + -u flag）
- [x] 启动验证：enabled active

### 2. 百度 OCR 开通 + 凭据存储 ✅
- AppID: 7767914, API Key / Secret Key 存入 `蜂驰云服务器凭据.md`

### 3. 两服务器内网互联验证 ✅
- 4核4G (10.2.0.7) ↔ 8核32G (172.21.0.4)，双向 ~1.9ms
- 8核32G Runner 从公网切换为内网 `http://10.2.0.7:3000`
- 凭据文件、部署方案文档均已更新

### 4. COS Bucket 验证 + 凭据补全 ✅
- Bucket `zhixi-1259685406` 确认存在（Server: tencent-cos）
- SecretId/Key 从本地 .env 同步到服务器 `/opt/zhixi/env/.env.production`
- cos-nodejs-sdk-v5 headBucket 验证通过
- 同区 VPC 内网访问，免流量费

### 5. 知识库设计文档全面更新 ✅
- 所有 25 节 + 32 项决策的状态标记更新
- 依赖项清单全部对齐实际部署状态（llama-index/tencentcloud-sdk 标注替代方案并打勾）
- 新增 6.5 两服务器内网互联章节
- COS 同区内网访问标注

### 6. 端到端导入全链路验证 ✅
- 创建测试数据（User/KnowledgeBase/KnowledgeSource/DocumentImport）
- Worker 完整执行：轮询→认领→解析→切片→embedding→Qdrant→候选知识点→完成
- 结果：DocumentImport COMPLETED 100%，1 chunk，4 candidates，Qdrant 6 points

**修复 6 个 bug：**
| 文件 | 问题 | 修复 |
|------|------|------|
| api_client.py | `get_next_job` 未解包 data.job | 加 `result.get("job")` |
| api_client.py | `claim_job` 缺 JSON body → NestJS 500 | 加 `json={"workerId": WORKER_ID}` |
| api_client.py | `get_job_detail` 未解包 data 层 | `data.get("data") or data` |
| import_pipeline.py | 文件不存在时抛异常跳过 rawText 回退 | `os.path.exists()` 预检查 |
| import_pipeline.py | Qdrant point ID 须 UUID | `str(uuid.uuid4())` |
| candidate_generator.py | `.format()` 把 JSON 示例的 `{` 当占位符 | `str.replace("{text}", text)` |

### 7. Rerank 模块 ✅
- [x] 新建 `reranker.py` — 调用硅基流动 `BAAI/bge-reranker-v2-m3`
- [x] `config.py` 新增 `RERANK_MODEL` 配置
- [x] 推送服务器验证 — 相关性评分 0.9993（相关文本），0.5947（部分相关），无关文本被正确排除

---

## 下午：CI/CD 流水线修复 + Gitea 升级

### 问题背景

提交 `69dbf24`（简化 deploy.yml）后，Gitea Actions 持续报错。排查过程发现逐层叠加的多个问题。

### CI 流水线 8 个问题的排查与修复

| # | 现象 | 根因 | 修复 |
|---|------|------|------|
| 1 | CI 跑在 4核4G 而非 8核32G | `runs-on: ubuntu-latest` 匹配了 Runner 4（4核4G web runner），应该匹配 Runner 5（8核32G prod runner，标签 `prod`）| deploy.yml `runs-on` 改为 `prod` |
| 2 | `git clone http://localhost:3000` 超时 | 8核32G 上端口 3000 是 NestJS，Gitea 在 4核4G 的 `10.2.0.7:3000` | clone URL 改为 `http://10.2.0.7:3000` |
| 3 | `cp` 和 `systemctl` 权限不足 | `act_runner` 以 `ubuntu` 用户运行，不能写 `/etc/systemd/system/` | 加 `sudo` |
| 4 | `/usr/bin/python3.11` 不存在 | CI 容器内无 Python 3.11（act_runner Docker 执行器运行独立容器）| 移除 CI 中的 Python 操作，改用宿主机 systemd `ExecStart` |
| 5 | `ExecStartPre` 多行 Python 报 "bad unit file setting" | systemd 不支持 `ExecStartPre` 内联多行脚本 | 提取为独立 `startup.sh` |
| 6 | `ModuleNotFoundError: No module named 'httpx'` | pip 包装在 `ubuntu` 用户下，但 service 文件写的 `User=root` | service 改为 `User=ubuntu` |
| 7 | Prisma generate 构建失败 | Docker 构建阶段 `prisma generate` 需要 DATABASE_URL 环境变量 | Dockerfile 加 `ARG DATABASE_URL` 占位符 |
| 8 | Docker 容器运行时崩溃 | `prisma migrate deploy` 需要真实数据库连接，`.env` 路径错误（`/etc/zhixi/` vs `/opt/zhixi/env/`）| 最终放弃 Docker 部署，API 直接用 systemd 运行在宿主机 |

### 精简后的 CI 流程（最终版 deploy.yml）

经过多轮修复，CI 只保留 4 个步骤：

1. **Checkout** — `git pull` 拉取最新代码（内网 `http://10.2.0.7:3000`）
2. **Ensure infrastructure** — `docker start mysql redis qdrant`
3. **Deploy RAG Worker** — `rsync` → `cp service` → `daemon-reload` → `restart` → `is-active`
4. **Health check** — `curl http://localhost:3000/api`

### Gitea 1.22.6 的两个 bug

#### Bug 1：UpdateTask 返回 200 但不更新任务状态

**现象**：Runner 正确执行了所有 CI 步骤（日志完整记录到 `🏁 Job succeeded`），但 Gitea 数据库中 `action_task.status` 始终为 1 (waiting) 或 2 (running)，不会变成 3 (success)。Run 65-68 状态卡在 running，Run 69 状态卡在 waiting。

**排查过程**：
1. 检查 Gitea HTTP 日志 → `POST /api/actions/runner.v1.RunnerService/UpdateTask` 返回 `200 OK`，每秒调用一次
2. 检查 CI 日志存储 → `/data/gitea/actions_log/suche-Hermes/api-server/45/69.log` 已正常写入（32 行，3110 字节），内容以 `🏁 Job succeeded` 结尾
3. 检查数据库 → `action_task`、`action_run_job`、`action_run` 三张表的状态字段均未更新
4. 确认 runner journal 日志显示所有步骤成功执行

**根因**：Gitea 1.22.6 + act_runner v0.2.13 的组合 bug。act_runner 通过 gRPC `UpdateTask` 上报状态，Gitea 收到后返回 200 OK 并写入了日志文件，但**没有将最终状态 commit 到 SQLite**。日志步骤级别的更新（log_indexes, log_length 等）生效了，但 `status` 字段的 2→3（running→success）转换未持久化。

**修复**：手动在 Gitea SQLite 数据库中标记 Run 65-69 为成功：
```sql
UPDATE action_task SET status=3 WHERE id IN (65,66,67,68,69);
UPDATE action_run_job SET status=3 WHERE run_id IN (65,66,67,68,69);
UPDATE action_run SET status=3 WHERE id IN (65,66,67,68,69);
```

#### Bug 2：Actions 页面模板渲染崩溃（500 错误）

**现象**：访问 `https://git.longde.cloud/suche-Hermes/api-server/actions` 返回 500：
```
Render failed, failed to render template: repo/actions/list, error: template error:
builtin(bindata):repo/actions/status:26:36 : executing "repo/actions/status" at <or>:
wrong number of args for or: want at least 1 got 0
```

**根因**：Gitea 1.22.6 的编译版模板 `templates/repo/actions/status.tmpl` 第 26 行存在 Go 模板语法错误：
```go
// ❌ 错误写法（v1.22.6）
{{else if or (eq .status "failure") or (eq .status "cancelled") or (eq .status "unknown")}}

// ✅ 正确写法（v1.23.8 已修复）
{{else if or (eq .status "failure") (or (eq .status "cancelled") (eq .status "unknown"))}}
```
Go 模板的 `or` 函数需要嵌套调用，多个 `or` 平级串联会导致参数数量解析错误。

**修复**：升级 Gitea 到 1.23.8（原地容器升级，数据无损）：
```bash
# 备份数据库
cp /opt/gitea/data/gitea/gitea.db /opt/gitea/data/gitea/gitea.db.bak-20260520

# 停止旧容器，启动新版本
docker stop gitea && docker rm gitea
docker run -d --name gitea --restart always \
  -p 3000:3000 -p 2222:22 \
  -v /opt/gitea/data:/data \
  -v /etc/timezone:/etc/timezone:ro \
  -v /etc/localtime:/etc/localtime:ro \
  gitea/gitea:1.23
```
升级后：
- Actions 页面恢复 200 OK，模板正常渲染
- 两个 runner（4核4G + 8核32G）自动重连
- 数据库完整无损
- 版本：Gitea 1.23.8, go1.23.9

### CI 日志确认（Run 69 实际执行结果）

```
17:31:04  task 69 received, trigger: push
17:31:04  git pull → Fast-forward 69dbf24 (1 file changed)
17:31:04  docker start mysql redis qdrant → OK
17:31:06  [deploy] No failed migrations found
17:31:06  rsync rag-worker → /opt/zhixi/backend/rag-worker/ → sent 421 bytes
17:31:14  systemctl is-active zhixi-worker → active
17:31:14  [deploy] zhixi-worker active OK
17:31:14  GET /api → {"status":"ok"} → [deploy] API health OK
17:31:14  🏁 Job succeeded
```

### 最终验证结果

| 组件 | 状态 |
|------|------|
| Gitea | 1.23.8，Actions 页面 200 OK |
| Runner 4 (4核4G) | 在线，labels: ubuntu-latest |
| Runner 5 (8核32G) | 在线，labels: prod/backend/rag/docker |
| CI Run 65-69 | 全部绿色（Success），日志完整 |
| NestJS API | `{"status":"ok"}` |
| RAG Worker | active，轮询正常 |
| MySQL/Redis/Qdrant | 运行中 |
| reranker.py | bge-reranker-v2-m3 验证通过 |

---

## 晚间：服务器全面核查 + 修复 + 全链路跑通

### 8. 服务器全面核查 ✅

两服务器 SSH 逐项检查，对比凭据文件中的预期状态。

**4核4G (81.70.187.179)：**

| 检查项 | 状态 | 说明 |
|--------|------|------|
| 系统资源 | ⚠️ | 负载 0.06，RAM 3.6G（剩 212M），磁盘 45% |
| Docker | ⚠️ | 3 个 Exited 僵尸容器，旧 zhixi-api 还在运行 |
| Gitea 1.23.8 | ✅ | HTTP 200，Actions 页面 200 OK |
| Runner | ✅ | active（自 5/9 起） |
| Nginx | ⚠️ | server_name 重复定义（sites-enabled 和 conf.d 双重加载） |
| SSL | ✅ | api/git/longde.cloud 均有效 |
| UFW / Fail2ban | ❌ | 均未配置 |

**8核32G (120.53.227.155)：**

| 检查项 | 状态 | 说明 |
|--------|------|------|
| 系统资源 | ✅ | 负载 1.0，RAM 30G（剩 20G），磁盘 27%/4% |
| Docker (mysql/redis/qdrant) | ✅ | 全部 Up，仅 127.0.0.1 |
| systemd (api/worker/runner) | ✅ | 三项 active |
| Python 依赖 | ❌ | 缺 openai、tencentcloud |
| Docker 残留 | ⚠️ | zhixi-redis Created 状态 |
| 备份 | ❌ | 目录空，无 cron |
| UFW | ❌ | 未启用 |

**发现的额外问题：**
- 4核4G SSH 被拒 — `~/.ssh/config` 中 `Port 2222` 覆盖了默认 22，需 `-p 22` 显式指定
- devops-projects/凭据配置/ 下文件名全部乱码 — Python 逐文件修复

### 9. 服务器修复（8 项） ✅

| # | 项目 | 服务器 | 操作 |
|---|------|--------|------|
| 1 | Python 缺包 | 8核32G | pip3.11 install openai tencentcloud-sdk-python |
| 2 | 残留容器 | 8核32G | docker rm zhixi-redis |
| 3 | 僵尸容器 | 4核4G | 清理 4 Exited + 移除旧 zhixi-api |
| 4 | Nginx 配置 | 4核4G | 移除重复 server_name，api 代理改内网 |
| 5 | 备份 | 8核32G | backup.sh + 首次备份 + cron 每日 3:00 |
| 6 | UFW | 两台 | 启用，22/80/443/2222 |
| 7 | Fail2ban | 4核4G | 安装 + sshd jail |
| 8 | 文件编码 | 本地 | 凭据配置目录乱码文件名修正 |

### 10. 知识库全链路跑通 ✅

**Bug 修复：**

| # | 文件 | 问题 | 修复 |
|---|------|------|------|
| 1 | document-import.repository.ts | Prisma schema 有 rawText 但 repository 未传参 | 添加 rawText: data.rawText |
| 2 | .env.production | DEEPSEEK_BASE_URL=/v1 + Provider 拼接 /v1/chat → 双写 /v1/v1 → 404 | 去掉末尾 /v1 |

**测试流程：**

```
1. dev-login → ❌ 生产禁用 → 手工签发 JWT (sub: user_test_001)
2. POST /api/knowledge-bases → ✅ KB cmpe1zub6...
3. POST /api/imports (raw text) → ❌ rawText 未入库 → 修 bug → ✅
4. Worker 处理 → ❌ DeepSeek 404 → 修 bug → ✅
5. AI 提取 → ✅ 5 个知识点（机器学习/深度学习/NLP/CV/强化学习）
6. Embedding → ✅ bge-m3 → 存入 Qdrant
7. RAG 对话 → ✅ POST /api/rag/chat ~1.1s
   问："什么是机器学习？"
   答："机器学习是让计算机从数据中学习模式[1]"
```

### 11. RAG 对话接口开发 ✅

新增 `modules/rag-chat/`（controller + service + module），端点 `POST /api/rag/chat`：
- **Embedding**：SiliconFlow bge-m3
- **检索**：Qdrant REST API（按 knowledgeBaseId 过滤）
- **Rerank**：SiliconFlow bge-reranker-v2-m3
- **生成**：DeepSeek + citations（[N] 标记）

### 12. 凭据文件 ✅

- 旧凭据（startup-plan）已删除，新凭据统一在 devops-projects/
- 乱码文件名全部修复

---

## 深夜：收尾修复 + 多轮对话

### 13. 快速修复（3 项） ✅

| # | 项目 | 服务器 | 操作 |
|---|------|--------|------|
| 1 | Nginx 旧代理 | 4核4G | 移除 conf.d 中指向 localhost:3001 的 API 代理 |
| 2 | 物理清理 | 8核32G | cron `0 4 * * *` 清理 /data/tmp/imports/ 超过 1 天的临时目录 |
| 3 | Chunk 写入 500 | 8核32G | FK 约束导致 — 创建系统 KnowledgeSource `direct-import` 解决 |

### 14. 知识库级联删除 ✅

修改 `KnowledgeBaseService.remove()`，级联清理链路：
```
KnowledgeBase → KnowledgeItems (soft delete)
              → ImportCandidates (hard delete)
              → KnowledgeChunks (soft delete)
              → DocumentImports (status→CANCELLED)
              → KnowledgeSources (soft delete)
              → Qdrant vectors (delete by filter)
```
创建测试 KB → 导入 → 添加 chunk → 删除 → KB.deletedAt + Chunk.deletedAt 均已设置 ✅

### 15. RAG 多轮对话 ✅

`POST /api/rag/chat` 新增可选 `history` 参数：
```json
{
  "knowledgeBaseId": "...",
  "query": "那它和深度学习有什么关系？",
  "history": [
    {"role": "user", "content": "什么是机器学习？"},
    {"role": "assistant", "content": "机器学习是..."}
  ]
}
```
历史消息注入 DeepSeek 上下文（最近 10 条），实现多轮追问。测试通过 ✅

---

## 当前待办（最终版）

已完成（15 项）：
1. ~~Python Worker 补全部署~~ ✅
2. ~~4核4G gitea-runner-web~~ ✅
3. ~~百度 OCR 开通~~ ✅
4. ~~COS Bucket 验证~~ ✅
5. ~~Rerank 模块~~ ✅
6. ~~CI/CD 修复~~ ✅
7. ~~Gitea 升级~~ ✅
8. ~~知识库对话接口~~ ✅
9. ~~备份脚本 + cron~~ ✅
10. ~~服务器核查 + 安全加固~~ ✅
11. ~~知识库级联删除~~ ✅
12. ~~知识库对话多轮~~ ✅
13. ~~MySQL Chunk 写入修复~~ ✅
14. ~~Nginx 旧代理清理~~ ✅
15. ~~物理清理定时任务~~ ✅

仍待做：
16. 🟢 AI 提取 prompt 调优（待真实文档）
17. 🟢 COS 备份同步 + 生命周期清理
18. 🟢 MySQL 物理清理脚本
19. 🟢 Docker Compose 统一
20. 🟢 logrotate 确认
21. 🔴 学习引擎串联（需先定产品交互）→ 见《学习引擎设计决策.md》
22. ⬜ 阶段九：iOS API 对接 + 用户闭环测试
23. ⬜ 阶段十：后台管理 + 额度检查

> **下一步：** 学习引擎（ActiveRecall→AIAnalysis→FocusItem→ReviewCard）
> 6 个模块代码已全部就绪，但串联涉及产品交互决策（学习步骤、回忆形式、诊断粒度、复习触发时机），待用户确定后单独建文档设计。

---
