feat(admin): Implement System Knowledge Base management module

Features:

- Backend: SystemKbService with full CRUD (knowledge bases + documents)

- Backend: 8 RESTful API endpoints (list/detail/create/update/delete/upload/download)

- Backend: OSS storage integration (system/knowledge-bases/{kbId}/{docId})

- Backend: RAG engine integration (document parsing, chunking, vectorization)

- Frontend: SystemKbListPage with card-based layout

- Frontend: SystemKbDetailPage with document management table

- Frontend: Master-Detail UX pattern for better user experience

- Document upload (single/batch), download (preserving original filename), delete

Technical:

- Database migration for system_knowledge_bases and system_kb_documents tables

- OSSAdapter.getSignedUrl with Content-Disposition for original filename

- Reuse RAG engine from common/rag for document processing

Tested: Local environment verified, all features working
This commit is contained in:
2026-01-28 21:57:44 +08:00
parent 3a4aa9123c
commit 0d9e6b9922
28 changed files with 2827 additions and 247 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -1,77 +0,0 @@
# 测试文档
> **文档定位:** 测试策略、测试用例、测试报告
> **适用范围:** 开发团队、QA团队
---
## 📋 测试策略
### 1. 单元测试
- 核心业务逻辑测试
- 工具函数测试
- 覆盖率目标60%+
### 2. 集成测试
- API端点测试
- 数据库集成测试
- 外部服务集成测试
### 3. 端到端测试
- 关键业务流程测试
- UI自动化测试
### 4. 性能测试
- API响应时间
- 并发测试
- 压力测试
---
## 📚 测试文档清单
| 文档 | 说明 | 状态 |
|------|------|------|
| **01-测试策略.md** | 整体测试策略和方法 | ⏳ 待创建 |
| **02-自动化测试.md** | 自动化测试框架和实践 | ⏳ 待创建 |
| **03-性能测试.md** | 性能测试标准和工具 | ⏳ 待创建 |
---
## 🎯 各模块测试文档
每个业务模块的测试文档在各自的目录下:
- `03-业务模块/ASL-AI智能文献/04-测试文档/`
- `03-业务模块/AIA-AI智能问答/04-测试文档/`
- ...
---
**最后更新:** 2025-11-06
**维护人:** 技术架构师

View File

@@ -1,148 +0,0 @@
# **PG Boss 任务重复故障分析与修复方案**
## **1\. 故障核心分析 (Root Cause Analysis) \- 修正版**
针对 "同一 TaskID 被创建 7 次" 且 "创建时间在同一毫秒" 的现象,在确认 **仅有 1 个 SAE 实例** 运行的情况下,我们排除了多实例并发的可能性。
结合已清理的 "rvw\_review\_task 有 7 个重复条目" 这一关键证据,我们得出了确切的结论:
### **核心根因:持久化配置重复 (Persisted Configuration Duplication)**
**问题不在于有多少个实例在跑,而在于数据库里存了多少份重复的指令。**
#### **💡 深度解析:为什么会有 7 个?(7 个闹钟的比喻)**
你的疑问是:*"每次处理应该是生成不同的任务ID不可能是重复的对吗"*
**答案是的pg-boss 生成了 7 个完全不同的 Job ID但它们都在做同一件事。**
这就好比你为了早上 7 点起床,设置了 **7 个闹钟**
1. **指令 (Schedules/Definitions)**:数据库里那些被清理的 "7 个重复条目",就像是 7 个闹钟配置。它们都设定在同一个触发条件下(比如 Cron 表达式,或系统启动时)。
2. **触发 (Trigger)**:当时间到了,或者系统启动扫描时,这 **7 个闹钟同时响了**
3. **执行 (Jobs)**:系统听到第 1 个闹钟,创建了 Job A听到第 2 个闹钟,创建了 Job B... 直到 Job G。
* **结果**:你在一毫秒内,被叫醒了 7 次。
* **数据**:这 7 个 Job 都有**不同的 UUID**(符合数据库约束),但它们的\*\*内容Payload\*\*全是 "处理 Task bd19c3d3"。
这就是为什么你在数据库里看到 created\_on 完全一致,但 Job ID 不同。因为那 1 个 SAE 实例在极短的时间内,忠实地执行了数据库里残留的 7 条指令。
* **机制解析**pg-boss 是一个基于数据库的任务队列。它的调度Schedules和某些队列配置是**持久化**在 PostgreSQL 数据库中的(通常在 pgboss.schedule 表中)。
* **故障复盘**
1. **积累阶段**:在过去的历史部署或重启中,代码可能在启动时调用了 boss.schedule('queue', 'cron')。由于没有加去重逻辑,每次部署都在数据库里**新增**了一条调度记录,而不是更新旧的。日积月累,数据库里就有了 7 条完全一样的调度记录。
2. **爆发阶段**:当你当前的 **1 个 SAE 实例** 运行时pg-boss 内部的轮询器扫描数据库,读取到了这 7 条重复的记录。
3. **瞬间执行**:当触发条件满足,这单个实例在极短的 CPU 周期内,为这 7 条记录分别生成了一个 Job。
* **证据链闭环**
* **7 次重复** 对应 **7 个重复的 Schedule/配置记录**
* **同一毫秒创建** 对应 **单实例在一次事件循环中连续处理了这 7 条指令**
**结论**:你执行的 "清理了 32 个重复的队列定义" 操作,实际上就是**关掉了多余的 6 个闹钟**,这已经移除了问题的根源。
### **为什么 SingletonKey 之前没生效?**
虽然这是单实例产生的重复,但如果代码使用的是 insert 或者是没有严格 unique constraint 保护的 send在极快的循环中Event Loop数据库可能仍未完成第一条的提交第二条就来了。
但最可能的原因是:**生成 Key 的逻辑有问题**,或者根本没有在产生任务的那段特定逻辑中加上 singletonKey。
## **2\. 解决方案:三层防御体系**
虽然根因(重复配置)已被你清理,但为了防止未来代码逻辑再次意外引入重复配置,或者防止前端意外的连击,我们依然强烈建议保留以下防御措施。
### **第一层:入队时防御 (生产者层面 \- 强制去重)**
这是最关键的一步。无论是因为配置重复导致被调用 7 次,还是前端点了 7 次,这里都能拦住。
**修改代码建议 (Producer/Service):**
// reviewTaskProducer.ts
import { PgBoss } from 'pg-boss';
// 假设这是你的入队逻辑
export async function createReviewTask(boss: PgBoss, taskId: string, payload: any) {
const queueName \= 'rvw\_review\_task';
// ✅ 核心修复:构造确定性的 singletonKey
// 不要包含时间戳等变量,只包含业务唯一标识 (如 taskId)
const singletonKey \= \`review\_task\_${taskId}\`;
// 发送任务
const jobId \= await boss.send(queueName, payload, {
// ✅ 启用单例模式
singletonKey: singletonKey,
// ✅ 节流/防抖如果任务已存在且活跃300秒内不再创建
singletonSeconds: 300,
// ✅ 即使旧任务完成了保留Key一段时间以防重复触发
singletonNextSlot: false
});
if (\!jobId) {
console.warn(\`\[Duplicate Prevented\] Task ${taskId} already exists in queue.\`);
return null;
}
return jobId;
}
### **第二层:处理时防御 (Worker 层面 \- 幂等性检查)**
你已经添加了状态检查,这很好。为了处理潜在的竞争(虽然单实例下竞争少,但为了健壮性),建议保持乐观锁逻辑。
**修改代码建议 (Worker):**
// reviewWorker.ts
export async function processReviewTask(job: Job) {
const { taskId } \= job.data;
// 1\. 业务状态检查 (你已经做了)
const task \= await db.task.findUnique({ where: { id: taskId } });
// ✅ 状态检查:如果已经是处理中或完成,直接跳过
if (task.status \=== 'COMPLETED' || task.status \=== 'PROCESSING') {
console.log(\`\[Skipped\] Task ${taskId} is already ${task.status}\`);
return;
}
// 2\. 乐观锁更新 (Database Atomic Update)
const updateResult \= await db.task.updateMany({
where: {
id: taskId,
status: 'PENDING' // 👈 关键:只有当前状态是 PENDING 时才更新
},
data: {
status: 'PROCESSING',
startedAt: new Date()
}
});
if (updateResult.count \=== 0\) {
console.log(\`\[Concurrency Control\] Task ${taskId} claimed by another worker or logic.\`);
return;
}
// 3\. 执行逻辑
try {
await performReviewLogic(taskId);
await db.task.update({ where: { id: taskId }, data: { status: 'COMPLETED' }});
} catch (error) {
await db.task.update({ where: { id: taskId }, data: { status: 'FAILED' }});
throw error;
}
}
### **第三层:初始化代码审查 (防止复发)**
针对 **"32 个重复队列定义"** 的来源,需要检查你的启动代码。
1. **检查 schedule 调用**
如果你在代码里使用了 boss.schedule('queue', cron, ...),请确保不要在每次应用启动时都无脑调用。
* **错误做法**:在 main.ts 直接调用 boss.schedule(...)。每次部署都会尝试再加一个(取决于 pg-boss 版本行为)。
* **正确做法**:通常 pg-boss 会处理去重,但如果参数稍有不同(比如 cron 表达式或数据),它可能会视为新 Schedules。建议检查 pg-boss 的 schedules 表,确保没有垃圾数据。
2. **清理脚本**
保留一个数据库迁移脚本或运维 SQL定期检查 pg-boss 的 job 表中是否有异常激增的 created 状态的任务。
## **3\. 总结**
* **问题原因**数据库中残留的历史重复配置7个重复条目导致单实例在循环中瞬间创建了 7 个任务。
* **当前状态**:你清理了重复条目,这已经解决了根源。
* **未来保障**:部署带有 singletonKey 的代码,这将是永远的防线,即使数据库里有 100 个重复配置pg-boss 也会拒绝创建第 2 到 第 100 个任务。