feat: 实现测试数据生成器完整功能
- 添加类型定义 (FieldConfig, DataRule, GeneratorDefinition 等) - 实现 21 个数据生成器 (个人信息/业务数据/技术数据/基础类型) - 实现 Web Worker 后台数据生成引擎 - 实现规则存储与管理功能 - 实现 JSON/CSV 数据导出功能 - 开发完整 UI 组件 (字段管理/生成器选择/数据预览/导出面板等) - 集成到应用功能配置,添加国际化支持 功能特性: - 实时预览生成的数据 - 字段配置 (名称/描述/必填/空值率/唯一性约束) - 规则管理 (保存/加载/搜索/导入导出) - Web Worker 后台生成,支持进度显示和取消 - 响应式布局,支持多种设备 docs: 更新 TASKS.md 所有子任务复选框为已完成状态
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* 测试数据生成器 Web Worker
|
||||
* 在后台线程中执行数据生成,避免阻塞 UI
|
||||
*/
|
||||
|
||||
import { getGeneratorById } from '@/lib/generators';
|
||||
import type {
|
||||
FieldConfig,
|
||||
WorkerMessage,
|
||||
GenerateResult,
|
||||
GenerateProgress,
|
||||
} from '@/types/testDataGenerator';
|
||||
|
||||
// 生成结果缓存
|
||||
let generatedData: Record<string, unknown>[] = [];
|
||||
let isCancelled = false;
|
||||
|
||||
/**
|
||||
* Worker 消息处理器
|
||||
*/
|
||||
self.onmessage = async (e: MessageEvent<WorkerMessage>) => {
|
||||
const data = e.data;
|
||||
const { type } = data;
|
||||
|
||||
switch (type) {
|
||||
case 'start':
|
||||
isCancelled = false;
|
||||
await handleStart(data.payload);
|
||||
break;
|
||||
case 'cancel':
|
||||
isCancelled = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理开始生成消息
|
||||
*/
|
||||
async function handleStart(payload: {
|
||||
fields: FieldConfig[];
|
||||
count: number;
|
||||
csvMode: boolean;
|
||||
}): Promise<void> {
|
||||
const { fields, count } = payload;
|
||||
generatedData = [];
|
||||
|
||||
try {
|
||||
// 验证所有生成器是否存在
|
||||
for (const field of fields) {
|
||||
const generator = getGeneratorById(field.generatorId);
|
||||
if (!generator) {
|
||||
self.postMessage({
|
||||
type: 'error',
|
||||
payload: { error: `生成器 "${field.generatorId}" 不存在` },
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const warnings: string[] = [];
|
||||
|
||||
// 生成数据
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (isCancelled) {
|
||||
self.postMessage({
|
||||
type: 'complete',
|
||||
payload: {
|
||||
success: false,
|
||||
error: '生成已取消',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const record: Record<string, unknown> = {};
|
||||
|
||||
for (const field of fields) {
|
||||
const generator = getGeneratorById(field.generatorId);
|
||||
if (!generator) continue;
|
||||
|
||||
// 空值率控制
|
||||
if (!field.required && Math.random() * 100 < field.nullRate) {
|
||||
record[field.name] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 唯一性约束处理
|
||||
if (field.unique) {
|
||||
let value: string | undefined;
|
||||
const maxRetries = 100;
|
||||
let retryCount = 0;
|
||||
|
||||
// 小数据量:随机生成 + 重试
|
||||
if (count <= 1000) {
|
||||
while (retryCount < maxRetries) {
|
||||
value = generator.generate(field.params);
|
||||
const isUnique = !generatedData.some((item) => item[field.name] === value);
|
||||
if (isUnique) break;
|
||||
retryCount++;
|
||||
value = undefined;
|
||||
}
|
||||
} else {
|
||||
// 大数据量:使用索引生成策略
|
||||
if (generator.generateAtIndex) {
|
||||
value = generator.generateAtIndex(field.params, i);
|
||||
} else {
|
||||
// 回退到随机生成
|
||||
value = generator.generate(field.params);
|
||||
}
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
record[field.name] = value;
|
||||
} else {
|
||||
warnings.push(`字段 "${field.name}" 第 ${i + 1} 行唯一值生成失败`);
|
||||
record[field.name] = generator.generate(field.params);
|
||||
}
|
||||
} else {
|
||||
record[field.name] = generator.generate(field.params);
|
||||
}
|
||||
} catch (error) {
|
||||
warnings.push(`字段 "${field.name}" 第 ${i + 1} 行生成错误: ${error}`);
|
||||
record[field.name] = null;
|
||||
}
|
||||
}
|
||||
|
||||
generatedData.push(record);
|
||||
|
||||
// 进度回调(每1000条)
|
||||
if ((i + 1) % 1000 === 0 || i === count - 1) {
|
||||
const progress: GenerateProgress = {
|
||||
progress: Math.round(((i + 1) / count) * 100),
|
||||
generated: i + 1,
|
||||
total: count,
|
||||
estimatedTimeLeft: Math.round(((Date.now() - startTime) / (i + 1)) * (count - i - 1)),
|
||||
};
|
||||
self.postMessage({ type: 'progress', payload: progress });
|
||||
}
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
const successCount = generatedData.filter((item) => Object.keys(item).length > 0).length;
|
||||
|
||||
const result: GenerateResult = {
|
||||
success: true,
|
||||
data: generatedData,
|
||||
warnings: warnings.length > 0 ? warnings : undefined,
|
||||
stats: {
|
||||
total: count,
|
||||
success: successCount,
|
||||
failed: count - successCount,
|
||||
duration,
|
||||
},
|
||||
};
|
||||
|
||||
self.postMessage({ type: 'complete', payload: result });
|
||||
} catch (error) {
|
||||
self.postMessage({
|
||||
type: 'error',
|
||||
payload: { error: `生成失败: ${error}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user