# 通用能力层技术债务清单 > **文档版本:** 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% **重复的逻辑**: ```typescript // 每个模块都要写类似的代码 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**: ```typescript // frontend-v2/src/common/hooks/useAsyncTaskPolling.ts(新建) import { useQuery } from '@tanstack/react-query'; interface UseAsyncTaskPollingOptions { /** 任务ID */ taskId: string | null; /** 状态查询API函数 */ queryFn: (taskId: string) => Promise; /** 状态提取函数 */ getStatus: (data: T) => 'pending' | 'processing' | 'ready' | 'error' | string; /** 进度提取函数(可选) */ getProgress?: (data: T) => number; /** 是否启用 */ enabled?: boolean; /** 轮询间隔(毫秒),默认2000 */ pollingInterval?: number; } export function useAsyncTaskPolling({ taskId, queryFn, getStatus, getProgress, enabled = true, pollingInterval = 2000, }: UseAsyncTaskPollingOptions) { 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, }; } ``` **使用示例**: ```typescript // 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缓存) #### 解决方案 **抽象为通用服务**: ```typescript // 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 { 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 { 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 { 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 { 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(); ``` **使用示例**: ```typescript // 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`(边界检测+安全阀) - 其他模块:未实现类似功能 **可以通用化的算法**: 1. 幽灵列检测(边界检测) 2. 幽灵行过滤 3. 安全阀(最大列数、单元格数限制) #### 解决方案 ```typescript // 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 注册代码 - 错误处理逻辑重复 #### 解决方案 ```typescript // 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}`); } ``` **使用示例**: ```typescript // 简化的注册代码 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设计**: ```prisma // 版本管理表(通用) 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实现**: ```typescript // backend/src/common/services/DataVersionService.ts(新建) export class DataVersionService { /** * 创建新版本 */ async createVersion( entityType: string, entityId: string, versionNumber: number, data: any[], operation?: string, params?: any ): Promise { // 保存数据到 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 { 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 { // 更新当前版本号 await this.updateCurrentVersion(entityType, entityId, versionNumber); } /** * 获取版本历史 */ async listVersions(entityType: string, entityId: string) { return await prisma.dataVersion.findMany({ where: { entityType, entityId }, orderBy: { versionNumber: 'asc' } }); } } ``` **QuickAction 集成**: ```typescript // 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列有效) - 用户删除数据未清理 → 解析出大量空行 #### 解决方案 ```typescript // 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下方) - 其他模块:未实现或不统一 #### 解决方案 ```typescript // 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 = ({ visible, progress, message, status, }) => { if (!visible) return null; return (
{message} {progress}%
); }; ``` --- ## 📅 实施计划 | 技术债务 | 优先级 | 工作量 | 建议时机 | 收益 | |---------|--------|--------|---------|------| | 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统一 | **推荐优先级**: 1. ⭐⭐⭐⭐⭐ TD-COMMON-002(Clean Data服务) 2. ⭐⭐⭐⭐⭐ TD-COMMON-005(数据版本管理) 3. ⭐⭐⭐ TD-COMMON-001(前端轮询Hook) --- ## 📊 当前架构评价 ### ✅ 已经很好的部分 1. ✅ **通用能力层基础设施完整** - jobQueue(pg-boss队列) - CheckpointService(断点续传) - 任务拆分工具 - storage、logger、cache 2. ✅ **业务模块正确使用通用能力** - Tool C、ASL、Tool B 统一使用 jobQueue - Platform-Only模式 - 不在业务表中存储任务管理信息 3. ✅ **分层清晰,职责明确** - 通用能力层:基础设施 - 业务模块:具体实现 ### ⏭️ 可以改进的部分(非必需) 1. ⏭️ 前端轮询模式可以更统一 2. ⏭️ clean data缓存可以更通用 3. ⏭️ 数据版本管理待建立 **结论**:当前架构已经符合规范,未来优化是锦上添花! --- ## 📚 参考文档 - [Postgres-Only异步任务处理指南](./Postgres-Only异步任务处理指南.md) - 完整实践 - [云原生开发规范](../04-开发规范/08-云原生开发规范.md) - 开发规范 - [DC Tool C状态](../03-业务模块/DC-数据清洗整理/00-工具C当前状态与开发指南.md) - Day 10实践 --- **维护者**: 平台架构团队 **最后更新**: 2025-12-22 **文档状态**: ✅ 初始版本