From 5fa7b0bbe1c53b1d1ad3cdd6b774c0410bd5944b Mon Sep 17 00:00:00 2001 From: HaHafeng Date: Mon, 22 Dec 2025 21:39:35 +0800 Subject: [PATCH] docs(common): Add platform infrastructure technical debt list Summary: - Document future optimization opportunities based on Tool C practice - 7 technical debts identified with priority and effort estimation - Focus on: Clean data service, Data version management, Generic polling Hook Content: - TD-COMMON-001: Generic polling Hook for frontend (P2, 0.5d) - TD-COMMON-002: Clean data caching service (P1, 1d, High priority) - TD-COMMON-003: Generic data cleaning algorithms (P2, 0.5d) - TD-COMMON-004: Worker registration helper (P3, 0.3d) - TD-COMMON-005: Data version management system (P1, 3d, High priority) - TD-COMMON-006: Ghost column/row detection library (P2, 0.5d) - TD-COMMON-007: Generic progress bar component (P2, 0.3d) Recommendation: - Priority 1: Clean data service + Data version management - Priority 2: Generic polling Hook - Current architecture is already good, these are enhancements Status: Documentation complete, ready for future implementation --- docs/02-通用能力层/通用能力层技术债务清单.md | 774 +++++++++++++++++++ 1 file changed, 774 insertions(+) create mode 100644 docs/02-通用能力层/通用能力层技术债务清单.md diff --git a/docs/02-通用能力层/通用能力层技术债务清单.md b/docs/02-通用能力层/通用能力层技术债务清单.md new file mode 100644 index 00000000..b2597aea --- /dev/null +++ b/docs/02-通用能力层/通用能力层技术债务清单.md @@ -0,0 +1,774 @@ +# 通用能力层技术债务清单 + +> **文档版本:** 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 +**文档状态**: ✅ 初始版本 +