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
This commit is contained in:
2025-12-22 21:39:35 +08:00
parent 4c6eaaecbf
commit 5fa7b0bbe1

View File

@@ -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<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,
};
}
```
**使用示例**
```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<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();
```
**使用示例**
```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行有映射← 可以回退到这里
Pivotv3: 不同结构)
导出:可以导出 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<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 集成**
```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<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统一 |
**推荐优先级**
1. ⭐⭐⭐⭐⭐ TD-COMMON-002Clean Data服务
2. ⭐⭐⭐⭐⭐ TD-COMMON-005数据版本管理
3. ⭐⭐⭐ TD-COMMON-001前端轮询Hook
---
## 📊 当前架构评价
### ✅ 已经很好的部分
1.**通用能力层基础设施完整**
- jobQueuepg-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
**文档状态**: ✅ 初始版本