Summary: - Migrate PostgreSQL to pgvector/pgvector:pg15 Docker image - Successfully install and verify pgvector 0.8.1 extension - Create comprehensive Dify-to-pgvector migration plan - Update PKB module documentation with pgvector status - Update system documentation with pgvector integration Key changes: - docker-compose.yml: Switch to pgvector/pgvector:pg15 image - Add EkbDocument and EkbChunk data model design - Design R-C-R-G hybrid retrieval architecture - Add clinical data JSONB fields (pico, studyDesign, regimen, safety, criteria, endpoints) - Create detailed 10-day implementation roadmap Documentation updates: - PKB module status: pgvector RAG infrastructure ready - System status: pgvector 0.8.1 integrated - New: Dify replacement development plan (01-Dify替换为pgvector开发计划.md) - New: Enterprise medical knowledge base solution V2 Tested: PostgreSQL with pgvector verified, frontend and backend functionality confirmed
19 KiB
19 KiB
通用能力层技术债务清单
文档版本: v1.0
创建日期: 2025-12-22
维护者: 平台架构团队
文档目的: 记录通用能力层待优化项,指导未来迭代
📋 概述
本文档基于 DC Tool C 异步架构实践(2025-12-22),总结发现的可以抽象为通用能力的模式。
核心思想:
- ✅ 当前分层已经很好(通用能力层提供完整的基础设施)
- ✅ 业务模块正确使用了这些能力
- ⏭️ 未来可以进一步抽象(锦上添花,非必需)
🎯 技术债务清单
TD-COMMON-001: 前端通用轮询Hook
优先级:⭐⭐⭐ P2(中)
工作量:0.5天
预期收益:代码复用性提升,模块间统一
问题描述
当前状态:
- Tool C 实现:
dc/tool-c/hooks/useSessionStatus.ts - ASL 实现:
asl/hooks/useScreeningTask.ts - 代码重复度:70%
重复的逻辑:
// 每个模块都要写类似的代码
useQuery({
queryKey: ['taskStatus', id],
queryFn: () => api.getStatus(id),
enabled: !!id,
refetchInterval: (query) => {
const status = query.state.data?.status;
if (status === 'ready' || status === 'error') return false;
return 2000;
},
staleTime: 0,
});
解决方案
抽象为通用Hook:
// frontend-v2/src/common/hooks/useAsyncTaskPolling.ts(新建)
import { useQuery } from '@tanstack/react-query';
interface UseAsyncTaskPollingOptions<T> {
/** 任务ID */
taskId: string | null;
/** 状态查询API函数 */
queryFn: (taskId: string) => Promise<T>;
/** 状态提取函数 */
getStatus: (data: T) => 'pending' | 'processing' | 'ready' | 'error' | string;
/** 进度提取函数(可选) */
getProgress?: (data: T) => number;
/** 是否启用 */
enabled?: boolean;
/** 轮询间隔(毫秒),默认2000 */
pollingInterval?: number;
}
export function useAsyncTaskPolling<T>({
taskId,
queryFn,
getStatus,
getProgress,
enabled = true,
pollingInterval = 2000,
}: UseAsyncTaskPollingOptions<T>) {
const { data, isLoading, error } = useQuery({
queryKey: ['asyncTask', taskId],
queryFn: () => queryFn(taskId!),
enabled: enabled && !!taskId,
refetchInterval: (query) => {
if (!query.state.data) return false;
const status = getStatus(query.state.data);
// 完成或失败时停止轮询
if (status === 'ready' || status === 'completed' || status === 'error' || status === 'failed') {
return false;
}
return pollingInterval;
},
staleTime: 0,
retry: 1,
});
const status = data ? getStatus(data) : 'pending';
const progress = getProgress && data ? getProgress(data) : 0;
return {
data,
status,
progress,
isReady: status === 'ready' || status === 'completed',
isError: status === 'error' || status === 'failed',
isProcessing: status === 'processing' || status === 'pending',
isLoading,
error,
};
}
使用示例:
// Tool C 使用
const { status, progress, isReady } = useAsyncTaskPolling({
taskId: sessionId,
queryFn: (sid) => api.getSessionStatus(sid, jobId),
getStatus: (res) => res.data.status,
getProgress: (res) => res.data.progress,
});
// ASL 使用
const { status, isReady } = useAsyncTaskPolling({
taskId: projectId,
queryFn: (pid) => aslApi.getScreeningTask(pid),
getStatus: (res) => res.data.status,
getProgress: (res) => Math.round((res.data.processedItems / res.data.totalItems) * 100),
});
影响范围
- Tool C:
useSessionStatus.ts可简化 - ASL:
useScreeningTask.ts可简化 - Tool B: 未来前端可直接使用
- 其他模块: 直接复用
TD-COMMON-002: Clean Data 缓存服务
优先级:⭐⭐⭐⭐ P1(高)
工作量:1天
预期收益:性能提升99%,所有模块受益
问题描述
当前状态:
- Tool C 实现:保存
${fileKey}_clean.json - ASL、Tool B:未实现,仍然每次重新解析
重复计算问题:
- ASL 文献筛选:每次从 OSS 下载 PDF,重新解析(5-10秒/篇)
- Tool B 数据提取:每次重新读取 Excel
- Tool C 操作:已优化(clean data缓存)
解决方案
抽象为通用服务:
// backend/src/common/services/DataCacheService.ts(新建)
import { storage } from '../storage';
import { prisma } from '../../config/database';
import { logger } from '../logging';
/**
* 数据缓存服务(通用)
*
* 用途:
* - Worker 解析后保存处理结果
* - Service 优先读取缓存,避免重复计算
* - 操作后同步更新缓存
*/
export class DataCacheService {
/**
* 保存清洗/处理后的数据
*
* @param originalKey 原始文件key
* @param cleanData 清洗后的数据
* @param suffix 后缀(默认 '_clean.json')
* @returns clean data 的 OSS key
*/
async saveCleanData(
originalKey: string,
cleanData: any,
suffix: string = '_clean.json'
): Promise<string> {
const cleanDataKey = `${originalKey}${suffix}`;
logger.info('[DataCacheService] Saving clean data', {
originalKey,
cleanDataKey,
rows: Array.isArray(cleanData) ? cleanData.length : 'N/A',
});
// 序列化并上传
const buffer = Buffer.from(JSON.stringify(cleanData), 'utf-8');
await storage.upload(cleanDataKey, buffer);
logger.info('[DataCacheService] Clean data saved', {
size: `${(buffer.length / 1024).toFixed(2)} KB`
});
return cleanDataKey;
}
/**
* 读取清洗后的数据
*
* @param cleanDataKey clean data 的 OSS key
* @returns 清洗后的数据
*/
async getCleanData(cleanDataKey: string): Promise<any> {
logger.info('[DataCacheService] Loading clean data', { cleanDataKey });
const buffer = await storage.download(cleanDataKey);
const data = JSON.parse(buffer.toString('utf-8'));
logger.info('[DataCacheService] Clean data loaded', {
rows: Array.isArray(data) ? data.length : 'N/A',
});
return data;
}
/**
* 更新清洗后的数据
*
* @param cleanDataKey clean data 的 OSS key
* @param newData 新数据
*/
async updateCleanData(cleanDataKey: string, newData: any): Promise<void> {
logger.info('[DataCacheService] Updating clean data', {
cleanDataKey,
rows: Array.isArray(newData) ? newData.length : 'N/A',
});
const buffer = Buffer.from(JSON.stringify(newData), 'utf-8');
await storage.upload(cleanDataKey, buffer);
logger.info('[DataCacheService] Clean data updated');
}
/**
* 删除清洗后的数据
*
* @param cleanDataKey clean data 的 OSS key
*/
async deleteCleanData(cleanDataKey: string): Promise<void> {
try {
await storage.delete(cleanDataKey);
logger.info('[DataCacheService] Clean data deleted', { cleanDataKey });
} catch (error: any) {
logger.warn('[DataCacheService] Clean data deletion failed', {
cleanDataKey,
error: error.message
});
}
}
}
export const dataCacheService = new DataCacheService();
使用示例:
// Worker 中
const cleanDataKey = await dataCacheService.saveCleanData(fileKey, cleanedData);
await prisma.update({ where: { id }, data: { cleanDataKey } });
// Service 中
if (record.cleanDataKey) {
return await dataCacheService.getCleanData(record.cleanDataKey);
}
// 操作后更新
await dataCacheService.updateCleanData(record.cleanDataKey, newData);
影响范围
- Tool C: 简化现有代码
- ASL: 文献解析结果缓存(提升99%)
- Tool B: 数据提取结果缓存
- 所有模块: 统一的缓存机制
TD-COMMON-003: 智能清洗算法通用化
优先级:⭐⭐ P2(中)
工作量:0.5天
预期收益:代码复用,避免重复实现
问题描述
当前状态:
- Tool C 实现:
intelligentCleanData(边界检测+安全阀) - 其他模块:未实现类似功能
可以通用化的算法:
- 幽灵列检测(边界检测)
- 幽灵行过滤
- 安全阀(最大列数、单元格数限制)
解决方案
// backend/src/common/utils/dataCleaningUtils.ts(新建)
export interface CleaningOptions {
maxCols?: number; // 最大列数,默认3000
maxCells?: number; // 最大单元格数,默认500万
removeEmptyRows?: boolean; // 是否删除空行,默认true
removeEmptyCols?: boolean; // 是否删除空列,默认true
}
export function intelligentCleanData(
data: any[],
options: CleaningOptions = {}
): any[] {
// 实现边界检测、幽灵列/行清洗、安全阀
// ...
}
export function isValidValue(value: any): boolean {
// 统一的空值判断
// ...
}
影响范围
- Tool C: 复用通用实现
- 其他上传Excel的功能: 直接使用
TD-COMMON-004: Worker注册辅助工具
优先级:⭐ P3(低)
工作量:0.3天
预期收益:降低Worker注册代码重复
问题描述
当前状态:
- 每个模块都要手写 Worker 注册代码
- 错误处理逻辑重复
解决方案
// backend/src/common/jobs/WorkerHelper.ts(新建)
import { jobQueue } from './index';
import { logger } from '../logging';
import type { JobHandler } from './types';
interface RegisterWorkerOptions {
queueName: string;
handler: JobHandler;
description?: string;
onStart?: (job: any) => void;
onComplete?: (job: any, result: any) => void;
onError?: (job: any, error: any) => void;
}
export function registerWorker(options: RegisterWorkerOptions) {
const { queueName, handler, description } = options;
logger.info(`[WorkerHelper] Registering worker: ${queueName}`, { description });
jobQueue.process(queueName, async (job) => {
try {
options.onStart?.(job);
const result = await handler(job);
options.onComplete?.(job, result);
return result;
} catch (error) {
options.onError?.(job, error);
throw error;
}
});
logger.info(`[WorkerHelper] ✅ Worker registered: ${queueName}`);
}
使用示例:
// 简化的注册代码
registerWorker({
queueName: 'dc_toolc_parse_excel',
description: 'Excel解析Worker',
handler: async (job) => {
// 业务逻辑
return result;
},
onStart: (job) => console.log(`开始处理: ${job.id}`),
onComplete: (job, result) => console.log(`完成: ${result}`),
onError: (job, error) => console.error(`失败: ${error}`),
});
TD-COMMON-005: 数据版本管理系统
优先级:⭐⭐⭐⭐ P1(高)
工作量:3天
预期收益:支持链式操作、undo功能、导出历史版本
问题描述
当前限制:
- 操作不是累积的(每次基于原始数据)
- 无法回退到某个操作前的状态
- 无法导出中间版本的数据
用户期望的工作流:
上传(v0: 100行)
↓
筛选(v1: 50行)← 可以回退到这里
↓
数值映射(v2: 50行,有映射)← 可以回退到这里
↓
Pivot(v3: 不同结构)
↓
导出:可以导出 v0、v1、v2、v3 任意版本
解决方案
Prisma Schema设计:
// 版本管理表(通用)
model DataVersion {
id String @id @default(uuid())
// 关联信息(多态关联)
entityType String // 'dc_toolc_session' | 'asl_project' | ...
entityId String // Session ID | Project ID | ...
// 版本信息
versionNumber Int // 0=原始, 1=第1次操作后, 2=第2次操作后...
dataKey String // OSS中的数据文件key
// 操作记录
operation String? // 'upload' | 'filter' | 'pivot' | 'recode' ...
operationParams Json? // 操作参数
// 元数据
totalRows Int
totalCols Int
columns Json
// 时间戳
createdAt DateTime @default(now())
createdBy String // 用户ID
@@unique([entityType, entityId, versionNumber])
@@index([entityType, entityId])
@@map("data_versions")
@@schema("platform_schema")
}
// 业务表添加字段
model DcToolCSession {
// ...现有字段
currentVersion Int @default(0) // 当前版本号
}
Service实现:
// backend/src/common/services/DataVersionService.ts(新建)
export class DataVersionService {
/**
* 创建新版本
*/
async createVersion(
entityType: string,
entityId: string,
versionNumber: number,
data: any[],
operation?: string,
params?: any
): Promise<string> {
// 保存数据到 OSS
const dataKey = `versions/${entityType}/${entityId}/v${versionNumber}.json`;
await storage.upload(dataKey, JSON.stringify(data));
// 创建版本记录
await prisma.dataVersion.create({
data: {
entityType,
entityId,
versionNumber,
dataKey,
operation,
operationParams: params,
totalRows: data.length,
columns: Object.keys(data[0] || {}),
createdBy: userId,
}
});
return dataKey;
}
/**
* 读取指定版本
*/
async getVersion(
entityType: string,
entityId: string,
versionNumber: number
): Promise<any[]> {
const version = await prisma.dataVersion.findUnique({
where: {
entityType_entityId_versionNumber: {
entityType,
entityId,
versionNumber,
}
}
});
if (!version) throw new Error('版本不存在');
const buffer = await storage.download(version.dataKey);
return JSON.parse(buffer.toString('utf-8'));
}
/**
* 回退到指定版本
*/
async rollbackToVersion(
entityType: string,
entityId: string,
versionNumber: number
): Promise<void> {
// 更新当前版本号
await this.updateCurrentVersion(entityType, entityId, versionNumber);
}
/**
* 获取版本历史
*/
async listVersions(entityType: string, entityId: string) {
return await prisma.dataVersion.findMany({
where: { entityType, entityId },
orderBy: { versionNumber: 'asc' }
});
}
}
QuickAction 集成:
// QuickAction 执行后自动创建新版本
const result = await python.execute(fullData, params);
// 读取当前版本号
const currentVersion = session.currentVersion || 0;
// 创建新版本
await dataVersionService.createVersion(
'dc_toolc_session',
sessionId,
currentVersion + 1,
result.data,
action, // 'filter' | 'pivot' | ...
params
);
// 更新当前版本号
await prisma.update({
where: { id: sessionId },
data: { currentVersion: currentVersion + 1 }
});
影响范围
- Tool C: 支持链式操作、undo、导出历史
- ASL: 文献筛选的多阶段结果管理
- Tool B: 数据提取的版本管理
- 所有模块: 统一的版本管理能力
TD-COMMON-006: 幽灵列/行检测算法库
优先级:⭐⭐ P2(中)
工作量:0.5天
预期收益:Excel处理质量提升
问题描述
当前状态:
- Tool C 实现:边界检测算法
- 其他模块:未实现
Excel格式污染问题:
- 用户刷颜色到16384列 → 解析出16384列(实际只有151列有效)
- 用户删除数据未清理 → 解析出大量空行
解决方案
// backend/src/common/utils/excelCleaning.ts(新建)
export interface CleaningResult {
cleanedData: any[];
originalRows: number;
originalCols: number;
finalRows: number;
finalCols: number;
removedRows: number;
removedCols: number;
warnings: string[];
}
export function cleanExcelData(
rawData: any[],
options?: CleaningOptions
): CleaningResult {
// 实现通用的清洗算法
// ...
}
TD-COMMON-007: 前端进度条组件
优先级:⭐⭐ P2(中)
工作量:0.3天
预期收益:UI统一,用户体验提升
问题描述
当前状态:
- Tool C 实现:内联进度条(蓝色,Header下方)
- 其他模块:未实现或不统一
解决方案
// frontend-v2/src/common/components/AsyncProgressBar.tsx(新建)
interface AsyncProgressBarProps {
visible: boolean;
progress: number; // 0-100
message: string;
status: 'uploading' | 'processing' | 'completed' | 'error';
}
export const AsyncProgressBar: React.FC<AsyncProgressBarProps> = ({
visible,
progress,
message,
status,
}) => {
if (!visible) return null;
return (
<div className="bg-blue-50 border-b border-blue-200 px-6 py-3">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-blue-900">{message}</span>
<span className="text-sm text-blue-700">{progress}%</span>
</div>
<div className="w-full bg-blue-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
</div>
);
};
📅 实施计划
| 技术债务 | 优先级 | 工作量 | 建议时机 | 收益 |
|---|---|---|---|---|
| TD-COMMON-001 前端轮询Hook | P2 | 0.5天 | 下次迭代 | 代码统一 |
| TD-COMMON-002 Clean Data服务 | P1 | 1天 | 下次迭代 | 性能提升99% |
| TD-COMMON-003 智能清洗算法 | P2 | 0.5天 | 需要时 | 质量提升 |
| TD-COMMON-004 Worker注册辅助 | P3 | 0.3天 | 可选 | 代码简化 |
| TD-COMMON-005 数据版本管理 | P1 | 3天 | 下次迭代 | 链式操作 |
| TD-COMMON-006 幽灵列/行检测 | P2 | 0.5天 | 需要时 | 质量提升 |
| TD-COMMON-007 进度条组件 | P2 | 0.3天 | 需要时 | UI统一 |
推荐优先级:
- ⭐⭐⭐⭐⭐ TD-COMMON-002(Clean Data服务)
- ⭐⭐⭐⭐⭐ TD-COMMON-005(数据版本管理)
- ⭐⭐⭐ TD-COMMON-001(前端轮询Hook)
📊 当前架构评价
✅ 已经很好的部分
-
✅ 通用能力层基础设施完整
- jobQueue(pg-boss队列)
- CheckpointService(断点续传)
- 任务拆分工具
- storage、logger、cache
-
✅ 业务模块正确使用通用能力
- Tool C、ASL、Tool B 统一使用 jobQueue
- Platform-Only模式
- 不在业务表中存储任务管理信息
-
✅ 分层清晰,职责明确
- 通用能力层:基础设施
- 业务模块:具体实现
⏭️ 可以改进的部分(非必需)
- ⏭️ 前端轮询模式可以更统一
- ⏭️ clean data缓存可以更通用
- ⏭️ 数据版本管理待建立
结论:当前架构已经符合规范,未来优化是锦上添花!
📚 参考文档
- Postgres-Only异步任务处理指南 - 完整实践
- 云原生开发规范 - 开发规范
- DC Tool C状态 - Day 10实践
维护者: 平台架构团队
最后更新: 2025-12-22
文档状态: ✅ 初始版本