From c40b66b7034f9b695d678dd72d5569b92c79676a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Fri, 5 Jun 2026 23:40:36 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=94=9F=E6=88=90=E5=99=A8=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要更新: - 添加字段必填/选填设置和空值率配置 - 移除标签管理系统(20条规则无需标签) - 添加组合策略保证唯一性生成 - 添加价格生成器(真实分布/心理定价) - 添加场景化生成器(年龄/数量/评分/折扣/库存) - 更新所有文档移除emoji,使用纯文字 - 更新类型定义和生成逻辑 --- docs/test-data-generator/core-features.md | 130 +++ docs/test-data-generator/generators.md | 250 +++++- docs/test-data-generator/rule-management.md | 158 ++-- .../technical-implementation.md | 744 +++++++++++++++++- docs/test-data-generator/ui-design.md | 121 +-- 5 files changed, 1225 insertions(+), 178 deletions(-) diff --git a/docs/test-data-generator/core-features.md b/docs/test-data-generator/core-features.md index 50f7979..620ba5e 100644 --- a/docs/test-data-generator/core-features.md +++ b/docs/test-data-generator/core-features.md @@ -8,11 +8,14 @@ | | 删除字段 | 删除不需要的字段 | P0 | | | 字段排序 | 拖拽调整字段顺序 | P1 | | | 字段命名 | 自定义字段名称 | P0 | +| | 必填/选填设置 | 控制字段是否必填 | P0 | +| | 空值率配置 | 选填字段空值概率 | P1 | | **生成器系统** | 生成器选择 | 从生成器库中选择 | P0 | | | 参数配置 | 配置生成器参数 | P0 | | | 实时预览 | 配置时查看生成结果 | P1 | | **数据生成** | 批量生成 | 支持 10000+ 条数据 | P0 | | | 唯一性约束 | 保证字段值唯一 | P1 | +| | 组合策略 | 自动选择生成策略 | P1 | | | 数据预览 | 生成前预览数据 | P1 | | **数据导出** | JSON 导出 | 导出为 JSON 格式 | P0 | | | CSV 导出 | 导出为 CSV 格式 | P0 | @@ -48,6 +51,20 @@ - 支持中英文命名 - 同一规则内字段名不可重复 +#### 必填/选填设置 + +- 每个字段可设置为"必填"或"选填" +- **必填字段**: 100% 生成数据 +- **选填字段**: 可配置空值概率(0-100%) +- 默认所有字段为必填 + +#### 空值率配置 + +- 选填字段可单独设置空值率 +- 提供预设值:低(20%)、中(50%)、高(80%) +- 支持滑块精细调节(0-100%) +- 未单独设置的选填字段使用全局默认空值率 + --- ### 2. 生成器系统 @@ -58,6 +75,98 @@ - 支持搜索生成器 - 点击选择后添加到字段配置 +#### 价格生成器(业务数据) + +生成真实的价格数据,支持多种策略: + +| 策略 | 说明 | 适用场景 | +| -------- | ------------------------------ | ------------ | +| 真实分布 | 对数正态分布,模拟真实商品价格 | 通用电商场景 | +| 均匀随机 | 所有价格等概率 | 测试数据 | +| 心理定价 | .99/.98/.95 结尾 | 营销场景 | + +**参数配置**: + +- 最低价/最高价:控制价格区间 +- 小数位数:0(整数)、1、2 位 +- 生成策略:选择分布类型 + +#### 年龄生成器(个人信息) + +生成真实分布的年龄数据: + +| 策略 | 说明 | 适用场景 | +| -------- | ---------------------------- | ------------ | +| 真实分布 | 正态分布,均值 35,标准差 10 | 通用用户数据 | +| 人口比例 | 按中国人口年龄比例分布 | 市场调研数据 | +| 均匀随机 | 所有年龄等概率 | 测试数据 | + +**参数配置**: + +- 最小年龄/最大年龄:控制年龄区间 +- 生成策略:选择分布类型 + +#### 数量生成器(业务数据) + +生成真实分布的数量数据: + +| 策略 | 说明 | 适用场景 | +| -------- | --------------------------- | -------- | +| 真实分布 | 泊松分布,大多数购买 1-3 件 | 零售订单 | +| 批发模式 | 10-100 之间均匀分布 | 批发订单 | +| 均匀随机 | 所有数量等概率 | 测试数据 | + +**参数配置**: + +- 最小值/最大值:控制数量区间 +- 生成策略:选择分布类型 + +#### 评分生成器(业务数据) + +生成真实分布的评分数据: + +| 策略 | 说明 | 适用场景 | +| -------- | ------------------ | -------- | +| 真实分布 | 偏态分布,偏向高分 | 电商平台 | +| 严格评价 | 偏态分布,偏向低分 | 严格评审 | +| 均匀随机 | 所有评分等概率 | 测试数据 | + +**参数配置**: + +- 最低分/最高分:控制评分区间 +- 小数位数:0、1 位 +- 生成策略:选择分布类型 + +#### 折扣生成器(业务数据) + +生成真实分布的折扣数据: + +| 策略 | 说明 | 适用场景 | +| -------- | ------------------------ | -------- | +| 心理定价 | 常见折扣点(8折、9折等) | 电商促销 | +| 清仓模式 | 大折扣区间(3-7折) | 清仓处理 | +| 均匀随机 | 所有折扣等概率 | 测试数据 | + +**参数配置**: + +- 最低折扣/最高折扣:控制折扣区间(0.1-0.9) +- 生成策略:选择分布类型 + +#### 库存生成器(业务数据) + +生成真实分布的库存数据: + +| 策略 | 说明 | 适用场景 | +| -------- | ------------------------ | -------- | +| 真实分布 | 指数分布,大多数库存较少 | 通用商品 | +| 热销商品 | 低库存区间(0-50) | 热销商品 | +| 均匀随机 | 所有库存等概率 | 测试数据 | + +**参数配置**: + +- 最小库存/最大库存:控制库存区间 +- 生成策略:选择分布类型 + #### 参数配置 - 每个生成器有对应的参数配置界面 @@ -87,6 +196,27 @@ - 启用后,该字段生成的值不会重复 - 如果无法生成唯一值,使用重试机制 +#### 组合策略(唯一性生成) + +系统根据数据量自动选择最优生成策略: + +| 数据量 | 策略 | 说明 | +| --------- | --------------- | ---------------------- | +| ≤ 1000 条 | 随机生成 + 重试 | 保证随机性,性能可接受 | +| > 1000 条 | 索引生成 | 高性能,保证唯一性 | + +**实现方式**: + +- 生成器提供两个方法:`generate()` 和 `generateAtIndex()` +- `generate()`: 随机生成,用于小数据量或非唯一性字段 +- `generateAtIndex()`: 索引生成,用于大数据量唯一性字段 +- 系统根据配置自动选择合适的方法 + +**示例**: + +- 手机号字段:`generateAtIndex(params, index)` 使用 index 生成唯一手机号 +- 姓名字段:`generateAtIndex(params, index)` 使用 index 组合姓和名 + #### 数据预览 - 生成前可预览数据 diff --git a/docs/test-data-generator/generators.md b/docs/test-data-generator/generators.md index ad4abf7..9b2175f 100644 --- a/docs/test-data-generator/generators.md +++ b/docs/test-data-generator/generators.md @@ -2,13 +2,13 @@ ## 分类概览 -| 分类 | 生成器数量 | 说明 | -| ----------- | ---------- | ------------------------ | -| 👤 个人信息 | 5 个 | 中文姓名、邮箱、手机号等 | -| 💼 业务数据 | 4 个 | 订单号、价格、日期等 | -| 🔧 技术数据 | 3 个 | UUID、IP地址、URL | -| 📝 基础类型 | 4 个 | 整数、浮点数、字符串等 | -| **总计** | **16 个** | | +| 分类 | 生成器数量 | 说明 | +| ----------- | ---------- | ------------------------------------------------ | +| 👤 个人信息 | 6 个 | 中文姓名、邮箱、手机号、年龄等 | +| 💼 业务数据 | 8 个 | 订单号、价格、日期、状态、数量、评分、折扣、库存 | +| 技术数据 | 3 个 | UUID、IP地址、URL | +| 基础类型 | 4 个 | 整数、浮点数、字符串等 | +| **总计** | **21 个** | | --- @@ -148,6 +148,44 @@ --- +### 6. 年龄 (age) + +**说明**: 生成真实分布的年龄数据 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| -------- | ----------------------------------------- | ---- | ----------- | -------- | +| min | number | 否 | 1 | 最小年龄 | +| max | number | 否 | 100 | 最大年龄 | +| strategy | 'realistic' \| 'demographic' \| 'uniform' | 否 | 'realistic' | 生成策略 | + +**策略说明**: + +| 策略 | 说明 | 适用场景 | +| ----------- | ---------------------------- | ------------ | +| realistic | 正态分布,均值 35,标准差 10 | 通用用户数据 | +| demographic | 按中国人口年龄比例分布 | 市场调研数据 | +| uniform | 均匀随机 | 测试数据 | + +**示例**: + +```json +{ + "name": "age", + "generator": "age", + "params": { + "min": 18, + "max": 65, + "strategy": "realistic" + } +} +``` + +**生成结果**: 28, 42, 35, 51, 23 + +--- + ## 💼 业务数据生成器 ### 1. 订单号 (orderId) @@ -180,31 +218,45 @@ ### 2. 价格 (price) -**说明**: 生成价格,保留 2 位小数 +**说明**: 生成真实的价格数据,支持多种分布策略 **参数**: -| 参数名 | 类型 | 必填 | 默认值 | 说明 | -| -------- | ----------------------- | ---- | ------- | -------- | -| min | number | 否 | 0.01 | 最小值 | -| max | number | 否 | 9999.99 | 最大值 | -| currency | 'CNY' \| 'USD' \| 'EUR' | 否 | 'CNY' | 货币类型 | +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| -------- | ------------------------------------------- | ---- | ----------- | -------- | +| min | number | 是 | 1 | 最低价 | +| max | number | 是 | 9999 | 最高价 | +| decimals | 0 \| 1 \| 2 | 否 | 2 | 小数位数 | +| strategy | 'realistic' \| 'uniform' \| 'psychological' | 否 | 'realistic' | 生成策略 | + +**生成策略说明**: + +| 策略 | 说明 | 适用场景 | +| ------------- | ------------------------------ | ------------ | +| realistic | 对数正态分布,模拟真实商品价格 | 通用电商场景 | +| uniform | 均匀随机,所有价格等概率 | 测试数据 | +| psychological | 心理定价,.99/.98/.95 结尾 | 营销场景 | **示例**: ```json { - "name": "amount", + "name": "price", "generator": "price", "params": { "min": 10, "max": 5000, - "currency": "CNY" + "decimals": 2, + "strategy": "realistic" } } ``` -**生成结果**: 299.00, 1599.50, 0.01 +**生成结果**: + +- 真实分布: 299.00, 1599.50, 49.99, 1299.00 +- 心理定价: 299.99, 1599.98, 49.95, 1299.99 +- 均匀随机: 1234.56, 5678.90, 890.12 --- @@ -265,7 +317,161 @@ --- -## 🔧 技术数据生成器 +### 5. 数量 (quantity) + +**说明**: 生成真实分布的数量数据 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| -------- | ---------------------------------- | ---- | ----------- | -------- | +| min | number | 否 | 1 | 最小值 | +| max | number | 否 | 100 | 最大值 | +| strategy | 'realistic' \| 'bulk' \| 'uniform' | 否 | 'realistic' | 生成策略 | + +**策略说明**: + +| 策略 | 说明 | 适用场景 | +| --------- | --------------------------- | -------- | +| realistic | 泊松分布,大多数购买 1-3 件 | 零售订单 | +| bulk | 10-100 之间均匀分布 | 批发订单 | +| uniform | 均匀随机 | 测试数据 | + +**示例**: + +```json +{ + "name": "quantity", + "generator": "quantity", + "params": { + "min": 1, + "max": 50, + "strategy": "realistic" + } +} +``` + +**生成结果**: 2, 1, 5, 3, 1 + +--- + +### 6. 评分 (rating) + +**说明**: 生成真实分布的评分数据 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| -------- | ------------------------------------ | ---- | ----------- | -------- | +| min | number | 否 | 1 | 最低分 | +| max | number | 否 | 5 | 最高分 | +| decimals | number | 否 | 1 | 小数位数 | +| strategy | 'realistic' \| 'strict' \| 'uniform' | 否 | 'realistic' | 生成策略 | + +**策略说明**: + +| 策略 | 说明 | 适用场景 | +| --------- | ------------------ | -------- | +| realistic | 偏态分布,偏向高分 | 电商平台 | +| strict | 偏态分布,偏向低分 | 严格评审 | +| uniform | 均匀随机 | 测试数据 | + +**示例**: + +```json +{ + "name": "rating", + "generator": "rating", + "params": { + "min": 1, + "max": 5, + "decimals": 1, + "strategy": "realistic" + } +} +``` + +**生成结果**: 4.5, 4.8, 3.9, 5.0, 4.2 + +--- + +### 7. 折扣 (discount) + +**说明**: 生成真实分布的折扣数据 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| -------- | ------------------------------------------- | ---- | --------------- | -------- | +| min | number | 否 | 0.1 | 最低折扣 | +| max | number | 否 | 0.9 | 最高折扣 | +| strategy | 'psychological' \| 'clearance' \| 'uniform' | 否 | 'psychological' | 生成策略 | + +**策略说明**: + +| 策略 | 说明 | 适用场景 | +| ------------- | ------------------------ | -------- | +| psychological | 常见折扣点(8折、9折等) | 电商促销 | +| clearance | 大折扣区间(3-7折) | 清仓处理 | +| uniform | 均匀随机 | 测试数据 | + +**示例**: + +```json +{ + "name": "discount", + "generator": "discount", + "params": { + "min": 0.1, + "max": 0.9, + "strategy": "psychological" + } +} +``` + +**生成结果**: 0.80, 0.90, 0.85, 0.95, 0.70 + +--- + +### 8. 库存 (stock) + +**说明**: 生成真实分布的库存数据 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| -------- | --------------------------------- | ---- | ----------- | -------- | +| min | number | 否 | 0 | 最小库存 | +| max | number | 否 | 1000 | 最大库存 | +| strategy | 'realistic' \| 'hot' \| 'uniform' | 否 | 'realistic' | 生成策略 | + +**策略说明**: + +| 策略 | 说明 | 适用场景 | +| --------- | ------------------------ | -------- | +| realistic | 指数分布,大多数库存较少 | 通用商品 | +| hot | 低库存区间(0-50) | 热销商品 | +| uniform | 均匀随机 | 测试数据 | + +**示例**: + +```json +{ + "name": "stock", + "generator": "stock", + "params": { + "min": 0, + "max": 500, + "strategy": "realistic" + } +} +``` + +**生成结果**: 23, 8, 156, 45, 12 + +--- + +## 技术数据生成器 ### 1. UUID (uuid) @@ -347,7 +553,7 @@ --- -## 📝 基础类型生成器 +## 基础类型生成器 ### 1. 随机整数 (randomInt) @@ -469,17 +675,17 @@ ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 🔧 选择生成器 │ +│ 选择生成器 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ 🔍 搜索: [搜索生成器... ] │ +│ 搜索: [搜索生成器... ] │ │ │ -│ 📁 分类筛选 │ +│ 分类筛选 │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 全部 │ │ 个人信息 │ │ 业务数据 │ │ 技术数据 │ │ 基础类型 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ -│ 📋 生成器列表 │ +│ 生成器列表 │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 👤 个人信息 │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ diff --git a/docs/test-data-generator/rule-management.md b/docs/test-data-generator/rule-management.md index e2f13f6..dcf8ab2 100644 --- a/docs/test-data-generator/rule-management.md +++ b/docs/test-data-generator/rule-management.md @@ -7,7 +7,6 @@ | 保存规则 | 将当前配置保存为模板 | P0 | | 规则列表 | 查看所有已保存的规则 | P0 | | 规则搜索 | 按关键词搜索规则 | P1 | -| 标签筛选 | 按标签分类筛选规则 | P1 | | 规则编辑 | 修改已保存的规则 | P0 | | 规则删除 | 删除不需要的规则 | P0 | | 规则加载 | 一键加载规则配置 | P0 | @@ -15,6 +14,12 @@ | 规则导出 | 导出为 JSON 文件 | P1 | | 规则导入 | 从 JSON 文件导入 | P1 | +## 规则数量限制 + +- **最大规则数量**: 20 条 +- **超出限制**: 当规则数量达到 20 条时,保存按钮置灰,提示"已达到最大规则数量" +- **解决方式**: 用户需要删除已有规则后才能保存新规则 + --- ## 数据结构 @@ -26,11 +31,11 @@ interface RuleTemplate { id: string; // 唯一标识 name: string; // 规则名称 description?: string; // 规则描述 - tags: string[]; // 标签列表 fields: FieldConfig[]; // 字段配置 options: { total: number; // 生成数量 format: 'json' | 'csv' | 'sql' | 'typescript'; + defaultEmptyRate: number; // 默认空值率(0-100) }; metadata: { createdAt: number; // 创建时间 @@ -50,6 +55,8 @@ interface FieldConfig { generator: string; // 生成器名称 params: Record; // 生成器参数 unique: boolean; // 唯一性约束 + required: boolean; // 是否必填 + emptyRate?: number; // 选填字段的空值概率(0-100) } ``` @@ -65,10 +72,10 @@ interface FieldConfig { ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 💾 保存规则 │ +│ 保存规则 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ 📝 规则信息 │ +│ 规则信息 │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 规则名称: │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ @@ -80,14 +87,9 @@ interface FieldConfig { │ │ │ [用于测试用户注册功能,包含姓名、邮箱、手机号 ]│ │ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ │ -│ │ 标签 (可选): │ │ -│ │ ┌─────────────────────────────────────────────────────────┐ │ │ -│ │ │ [电商] [用户] [测试数据] [+ 添加标签] │ │ │ -│ │ └─────────────────────────────────────────────────────────┘ │ │ -│ │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ 📊 规则预览 │ +│ 规则预览 │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 字段数量: 4 个 │ │ │ │ • name (中文姓名) │ │ @@ -99,7 +101,7 @@ interface FieldConfig { │ │ 数据格式: JSON │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ [💾 保存] [❌ 取消] │ +│ [保存] [取消] │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -107,10 +109,12 @@ interface FieldConfig { **保存逻辑**: 1. 验证规则名称不为空 -2. 生成唯一 ID -3. 设置创建时间和更新时间 -4. 初始化使用次数为 0 -5. 保存到 localStorage +2. 检查规则数量是否达到上限(20 条) +3. 如果达到上限,显示提示"已达到最大规则数量" +4. 生成唯一 ID +5. 设置创建时间和更新时间 +6. 初始化使用次数为 0 +7. 保存到 localStorage --- @@ -120,33 +124,27 @@ interface FieldConfig { ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 📚 规则管理 │ +│ 规则管理 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ 🔍 搜索: [搜索规则名称或标签... ] │ +│ 搜索: [搜索规则名称... ] │ │ │ -│ 📁 标签筛选 │ -│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ -│ │ 全部 │ │ 电商 │ │ 用户 │ │ 测试数据 │ │ 最近使用 │ │ -│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ -│ │ -│ 📋 规则列表 │ +│ 规则列表 (已保存 15/20 条) │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ -│ │ │ 📦 电商用户数据 - 测试用 │ │ │ +│ │ │ 电商用户数据 - 测试用 │ │ │ │ │ │ ─────────────────────────────────────────────────────── │ │ │ -│ │ │ 📝 用于测试用户注册功能,包含姓名、邮箱、手机号 │ │ │ -│ │ │ 🏷️ 电商 用户 测试数据 │ │ │ -│ │ │ 📊 4 个字段 | 100 条 | JSON │ │ │ -│ │ │ 📅 2024-01-15 14:30 创建 | 2024-01-20 10:15 最后使用 │ │ │ +│ │ │ 用于测试用户注册功能,包含姓名、邮箱、手机号 │ │ │ +│ │ │ 4 个字段 | 100 条 | JSON │ │ │ +│ │ │ 2024-01-15 14:30 创建 | 2024-01-20 10:15 最后使用 │ │ │ │ │ │ │ │ │ -│ │ │ [📥 加载] [📋 复制] [📤 导出] [✏️ 编辑] [🗑️ 删除] │ │ │ +│ │ │ [加载] [复制] [导出] [编辑] [删除] │ │ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ [📥 导入规则] [📤 导出全部] [🗑️ 清空全部] │ +│ [导入规则] [导出全部] [清空全部] │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -155,7 +153,6 @@ interface FieldConfig { - 规则名称 - 规则描述 -- 标签 - 字段数量、生成数量、数据格式 - 创建时间、最后使用时间 - 操作按钮 @@ -168,7 +165,6 @@ interface FieldConfig { - 支持按规则名称搜索 - 支持按规则描述搜索 -- 支持按标签搜索 - 搜索为模糊匹配,不区分大小写 **实现方式**: @@ -180,64 +176,46 @@ search(keyword: string): RuleTemplate[] { return rules.filter(r => r.name.toLowerCase().includes(lowerKeyword) || - r.description?.toLowerCase().includes(lowerKeyword) || - r.tags.some(t => t.toLowerCase().includes(lowerKeyword)) + r.description?.toLowerCase().includes(lowerKeyword) ); } ``` --- -### 4. 标签筛选 - -**标签来源**: - -- 从所有已保存的规则中提取 -- 自动去重 -- 按使用频率排序 - -**筛选逻辑**: - -- 点击标签,筛选包含该标签的规则 -- 支持多标签筛选(交集) -- "全部"标签显示所有规则 - ---- - -### 5. 规则编辑 +### 4. 规则编辑 **界面设计**: ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ ✏️ 编辑规则 - 电商用户数据 │ +│ 编辑规则 - 电商用户数据 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ 📝 基本信息 │ +│ 基本信息 │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 规则名称: [电商用户数据 - 测试用 ]│ │ │ │ 规则描述: [用于测试用户注册功能,包含姓名、邮箱、手机号 ]│ │ -│ │ 标签: [电商] [用户] [测试数据] [+ 添加标签] │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ 📋 字段配置 (可直接编辑) │ +│ 字段配置 (可直接编辑) │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ │ -│ │ 1. name [中文姓名 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ -│ │ 2. email [邮箱地址 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ -│ │ 3. phone [手机号 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ -│ │ 4. age [年龄 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ +│ │ 1. name [中文姓名 ▼] [配置] [删除] [上移][下移] │ │ +│ │ 2. email [邮箱地址 ▼] [配置] [删除] [上移][下移] │ │ +│ │ 3. phone [手机号 ▼] [配置] [删除] [上移][下移] │ │ +│ │ 4. age [年龄 ▼] [配置] [删除] [上移][下移] │ │ │ │ │ │ │ │ [+ 添加字段] │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ ⚙️ 生成选项 │ +│ 生成选项 │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 数量: [100] 格式: [JSON ▼] │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ [💾 保存修改] [❌ 取消] │ +│ [保存修改] [取消] │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -252,7 +230,7 @@ search(keyword: string): RuleTemplate[] { --- -### 6. 规则删除 +### 5. 规则删除 **删除流程**: @@ -265,21 +243,21 @@ search(keyword: string): RuleTemplate[] { ``` ┌─────────────────────────────────────────┐ -│ ⚠️ 确认删除 │ +│ 确认删除 │ ├─────────────────────────────────────────┤ │ │ │ 确定要删除规则 "电商用户数据" 吗? │ │ │ │ 此操作不可撤销。 │ │ │ -│ [❌ 取消] [🗑️ 删除] │ +│ [取消] [删除] │ │ │ └─────────────────────────────────────────┘ ``` --- -### 7. 规则加载 +### 6. 规则加载 **加载流程**: @@ -308,7 +286,7 @@ loadRule(ruleId: string): void { --- -### 8. 规则复制 +### 7. 规则复制 **复制逻辑**: @@ -320,31 +298,31 @@ loadRule(ruleId: string): void { --- -### 9. 规则导出 +### 8. 规则导出 **导出界面**: ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 📤 导出规则 │ +│ 导出规则 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ 📋 选择要导出的规则: │ +│ 选择要导出的规则: │ │ ┌───────────────────────────────────────────────────────────────┐ │ -│ │ ☑️ 电商用户数据 - 测试用 (4个字段, 100条) │ │ -│ │ ☑️ 电商订单数据 - 测试用 (6个字段, 500条) │ │ +│ │ ☑ 电商用户数据 - 测试用 (4个字段, 100条) │ │ +│ │ ☑ 电商订单数据 - 测试用 (6个字段, 500条) │ │ │ │ ☐ 用户登录数据 - 测试用 (3个字段, 50条) │ │ │ │ │ │ │ │ [全选] [全不选] │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ 📤 导出格式: │ +│ 导出格式: │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ ○ 导出为单个 JSON 文件 (所有规则合并) │ │ │ │ ● 导出为多个 JSON 文件 (每个规则一个文件) │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ [📤 导出] [❌ 取消] │ +│ [导出] [取消] │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -360,7 +338,6 @@ loadRule(ruleId: string): void { "id": "rule_123456", "name": "电商用户数据 - 测试用", "description": "用于测试用户注册功能", - "tags": ["电商", "用户", "测试数据"], "fields": [...], "options": {...}, "metadata": {...} @@ -371,21 +348,21 @@ loadRule(ruleId: string): void { --- -### 10. 规则导入 +### 9. 规则导入 **导入界面**: ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 📥 导入规则 │ +│ 导入规则 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ 📤 选择导入方式: │ +│ 选择导入方式: │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ ○ 从文件导入 │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ -│ │ │ 📁 拖拽文件到这里,或 [点击选择文件] │ │ │ +│ │ │ 拖拽文件到这里,或 [点击选择文件] │ │ │ │ │ │ 支持格式: .json │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ │ @@ -396,15 +373,15 @@ loadRule(ruleId: string): void { │ │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ 📊 预览导入内容: │ +│ 预览导入内容: │ │ ┌───────────────────────────────────────────────────────────────┐ │ -│ │ ✅ 格式验证通过 │ │ +│ │ 格式验证通过 │ │ │ │ 规则数量: 2 个 │ │ │ │ • 电商用户数据 (4个字段) │ │ │ │ • 电商订单数据 (6个字段) │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ [📥 导入] [❌ 取消] │ +│ [导入] [取消] │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -437,10 +414,21 @@ class RuleStorage { } // 保存规则 - save(rule: RuleTemplate): void { + save(rule: RuleTemplate): { success: boolean; message?: string } { const rules = this.getAll(); + + // 规则数量限制:最多 20 条 + const MAX_RULES = 20; + if (rules.length >= MAX_RULES) { + return { + success: false, + message: `已达到最大规则数量(${MAX_RULES}条),请删除一些规则后再保存`, + }; + } + rules.push(rule); localStorage.setItem(this.STORAGE_KEY, JSON.stringify(rules)); + return { success: true }; } // 更新规则 @@ -475,9 +463,10 @@ class RuleStorage { ### 存储限制 +- **规则数量限制**: 最多 20 条 - localStorage 容量: 5-10MB(浏览器限制) - 单个规则约 1-5KB -- 预计可存储 1000-5000 个规则 +- 预计可存储 1000-5000 个规则(但受数量限制,最多 20 条) --- @@ -487,7 +476,6 @@ class RuleStorage { | -------- | ------------------------------ | | 本地存储 | 数据保存在浏览器本地,无需后端 | | 快速复用 | 一键加载已保存的规则 | -| 标签管理 | 按标签分类管理规则 | | 导入导出 | 支持 JSON 文件导入导出 | | 使用统计 | 记录使用次数和时间 | -| 搜索筛选 | 支持关键词搜索和标签筛选 | +| 搜索筛选 | 支持关键词搜索 | diff --git a/docs/test-data-generator/technical-implementation.md b/docs/test-data-generator/technical-implementation.md index 68e4455..3689294 100644 --- a/docs/test-data-generator/technical-implementation.md +++ b/docs/test-data-generator/technical-implementation.md @@ -61,6 +61,8 @@ export interface FieldConfig { generator: string; params: Record; unique: boolean; + required: boolean; // 是否必填 + emptyRate?: number; // 选填字段的空值概率(0-100),仅当 required=false 时生效 } // 规则配置 @@ -68,11 +70,11 @@ export interface DataRule { id: string; name: string; description?: string; - tags: string[]; fields: FieldConfig[]; options: { total: number; format: 'json' | 'csv' | 'sql' | 'typescript'; + defaultEmptyRate: number; // 默认空值率(0-100),用于未设置 emptyRate 的选填字段 }; metadata: { createdAt: number; @@ -90,6 +92,10 @@ export interface GeneratorDefinition { category: 'personal' | 'business' | 'technical' | 'basic'; icon: string; params: GeneratorParam[]; + // 普通生成方法 + generate: (params: Record) => any; + // 索引生成方法(可选,用于大数据量唯一性生成) + generateAtIndex?: (params: Record, index: number) => any; } // 生成器参数 @@ -201,6 +207,18 @@ export const personalGenerators = { givenName += givenNames[Math.floor(Math.random() * givenNames.length)]; } + return surname + givenName; + }, + // 索引生成(唯一性保证) + generateAtIndex: (params: any, index: number) => { + const surnames = + '王李张刘陈杨赵黄周吴徐孙胡朱高林何郭马罗梁宋郑谢韩唐冯于董萧程曹袁邓许傅沈曾彭吕苏卢蒋蔡贾丁魏薛叶阎余潘杜戴夏钟汪田任姜范方石姚谭廖邹熊金陆郝孔白崔康毛邱秦江史顾侯邵孟龙万段雷钱汤尹黎易常武乔贺赖龚文'; + const givenNames = + '伟芳娜秀英敏静丽强磊洋勇艳杰娟涛超明华雪飞平刚慧建华玲桂英旭峰辉志强建平婷欣怡梦琪雅琴晓红'; + + const surname = surnames[index % surnames.length]; + const givenName = givenNames[index % givenNames.length]; + return surname + givenName; }, }, @@ -231,6 +249,16 @@ export const personalGenerators = { localPart += chars[Math.floor(Math.random() * chars.length)]; } + return `${localPart}@${domain}`; + }, + // 索引生成(唯一性保证) + generateAtIndex: (params: any, index: number) => { + const domains = params.domains || ['qq.com', '163.com']; + const domain = domains[index % domains.length]; + + // 使用索引生成唯一用户名 + const localPart = `user${String(index).padStart(6, '0')}`; + return `${localPart}@${domain}`; }, }, @@ -279,6 +307,7 @@ export const personalGenerators = { ], }, ], + // 普通生成(随机) generate: (params: any) => { const prefixes = params.prefix || ['138', '139', '150', '151']; const prefix = prefixes[Math.floor(Math.random() * prefixes.length)]; @@ -290,10 +319,669 @@ export const personalGenerators = { return prefix + suffix; }, + // 索引生成(唯一性保证) + generateAtIndex: (params: any, index: number) => { + const prefixes = params.prefix || ['138', '139', '150', '151']; + const prefix = prefixes[index % prefixes.length]; + + // 使用索引生成唯一后缀 + const suffix = String(10000000 + index).slice(-8); + + return prefix + suffix; + }, + }, + + age: { + name: 'age', + label: '年龄', + description: '生成真实分布的年龄', + icon: '🎂', + params: [ + { + name: 'min', + label: '最小年龄', + type: 'number', + required: false, + default: 1, + }, + { + name: 'max', + label: '最大年龄', + type: 'number', + required: false, + default: 100, + }, + { + name: 'strategy', + label: '生成策略', + type: 'select', + required: false, + default: 'realistic', + options: [ + { label: '真实分布(推荐)', value: 'realistic' }, + { label: '均匀随机', value: 'uniform' }, + { label: '人口比例', value: 'demographic' }, + ], + }, + ], + generate: (params: any) => { + const { min = 1, max = 100, strategy = 'realistic' } = params; + let value: number; + + switch (strategy) { + case 'realistic': + // 正态分布:均值 35,标准差 10 + value = normalRandom(35, 10); + break; + case 'demographic': + // 人口比例分布:年轻人多,老年人少 + value = demographicRandom(min, max); + break; + default: + // 均匀分布 + value = Math.random() * (max - min) + min; + } + + return Math.round(Math.max(min, Math.min(max, value))); + }, + generateAtIndex: (params: any, index: number) => { + const { min = 1, max = 100 } = params; + const step = (max - min) / 10000; + return Math.round(min + step * index); + }, }, }; ``` +```typescript +// generators/business.ts +export const businessGenerators = { + price: { + name: 'price', + label: '价格', + description: '生成真实的价格数据', + icon: '💰', + params: [ + { + name: 'min', + label: '最低价', + type: 'number', + required: true, + default: 1, + }, + { + name: 'max', + label: '最高价', + type: 'number', + required: true, + default: 9999, + }, + { + name: 'decimals', + label: '小数位数', + type: 'select', + required: false, + default: 2, + options: [ + { label: '0 位(整数)', value: 0 }, + { label: '1 位', value: 1 }, + { label: '2 位(常见)', value: 2 }, + ], + }, + { + name: 'strategy', + label: '生成策略', + type: 'select', + required: false, + default: 'realistic', + options: [ + { label: '真实分布(推荐)', value: 'realistic' }, + { label: '均匀随机', value: 'uniform' }, + { label: '心理定价', value: 'psychological' }, + ], + }, + ], + // 普通生成(随机) + generate: (params: any) => { + const { min = 1, max = 9999, decimals = 2, strategy = 'realistic' } = params; + let value: number; + + switch (strategy) { + case 'psychological': + // 心理定价:.99, .98, .95 结尾 + value = generatePsychologicalPrice(min, max); + break; + case 'realistic': + // 真实分布:对数正态分布 + value = generateRealisticPrice(min, max); + break; + default: + // 均匀分布 + value = Math.random() * (max - min) + min; + } + + return parseFloat(value.toFixed(decimals)); + }, + // 索引生成(唯一性保证) + generateAtIndex: (params: any, index: number) => { + const { min = 1, max = 9999, decimals = 2, strategy = 'realistic' } = params; + let value: number; + + // 索引生成时使用均匀分布,保证唯一性 + const step = (max - min) / 10000; + value = min + step * index; + + // 确保在区间内 + value = Math.max(min, Math.min(max, value)); + + return parseFloat(value.toFixed(decimals)); + }, + }, + + orderId: { + name: 'orderId', + label: '订单号', + description: '生成订单号', + icon: '📋', + params: [ + { + name: 'prefix', + label: '前缀', + type: 'string', + required: false, + default: 'ORD', + }, + { + name: 'length', + label: '编号长度', + type: 'number', + required: false, + default: 12, + }, + ], + generate: (params: any) => { + const prefix = params.prefix || 'ORD'; + const length = params.length || 12; + let suffix = ''; + for (let i = 0; i < length; i++) { + suffix += Math.floor(Math.random() * 10); + } + return `${prefix}${suffix}`; + }, + generateAtIndex: (params: any, index: number) => { + const prefix = params.prefix || 'ORD'; + const length = params.length || 12; + const suffix = String(index).padStart(length, '0'); + return `${prefix}${suffix}`; + }, + }, + + date: { + name: 'date', + label: '日期', + description: '生成随机日期', + icon: '📅', + params: [ + { + name: 'start', + label: '开始日期', + type: 'string', + required: false, + default: '2020-01-01', + }, + { + name: 'end', + label: '结束日期', + type: 'string', + required: false, + default: '2024-12-31', + }, + { + name: 'format', + label: '日期格式', + type: 'select', + required: false, + default: 'YYYY-MM-DD', + options: [ + { label: 'YYYY-MM-DD', value: 'YYYY-MM-DD' }, + { label: 'YYYY/MM/DD', value: 'YYYY/MM/DD' }, + { label: 'DD/MM/YYYY', value: 'DD/MM/YYYY' }, + { label: '时间戳', value: 'timestamp' }, + ], + }, + ], + generate: (params: any) => { + const start = new Date(params.start || '2020-01-01').getTime(); + const end = new Date(params.end || '2024-12-31').getTime(); + const timestamp = start + Math.random() * (end - start); + return formatDate(new Date(timestamp), params.format || 'YYYY-MM-DD'); + }, + generateAtIndex: (params: any, index: number) => { + const start = new Date(params.start || '2020-01-01').getTime(); + const end = new Date(params.end || '2024-12-31').getTime(); + const step = (end - start) / 10000; + const timestamp = start + step * index; + return formatDate(new Date(timestamp), params.format || 'YYYY-MM-DD'); + }, + }, + + status: { + name: 'status', + label: '状态', + description: '生成随机状态', + icon: '🔄', + params: [ + { + name: 'options', + label: '状态选项', + type: 'array', + required: false, + default: ['待处理', '处理中', '已完成', '已取消'], + }, + ], + generate: (params: any) => { + const options = params.options || ['待处理', '处理中', '已完成', '已取消']; + return options[Math.floor(Math.random() * options.length)]; + }, + generateAtIndex: (params: any, index: number) => { + const options = params.options || ['待处理', '处理中', '已完成', '已取消']; + return options[index % options.length]; + }, + }, + + quantity: { + name: 'quantity', + label: '数量', + description: '生成真实分布的数量', + icon: '📦', + params: [ + { + name: 'min', + label: '最小值', + type: 'number', + required: false, + default: 1, + }, + { + name: 'max', + label: '最大值', + type: 'number', + required: false, + default: 100, + }, + { + name: 'strategy', + label: '生成策略', + type: 'select', + required: false, + default: 'realistic', + options: [ + { label: '真实分布(推荐)', value: 'realistic' }, + { label: '批发模式', value: 'bulk' }, + { label: '均匀随机', value: 'uniform' }, + ], + }, + ], + generate: (params: any) => { + const { min = 1, max = 100, strategy = 'realistic' } = params; + let value: number; + + switch (strategy) { + case 'bulk': + // 批发模式:10-100 之间均匀分布 + value = Math.random() * 90 + 10; + break; + case 'realistic': + // 泊松分布:大多数购买 1-3 件 + value = poissonRandom(2); + break; + default: + // 均匀分布 + value = Math.random() * (max - min) + min; + } + + return Math.round(Math.max(min, Math.min(max, value))); + }, + generateAtIndex: (params: any, index: number) => { + const { min = 1, max = 100 } = params; + const step = (max - min) / 10000; + return Math.round(min + step * index); + }, + }, + + rating: { + name: 'rating', + label: '评分', + description: '生成真实分布的评分', + icon: '⭐', + params: [ + { + name: 'min', + label: '最低分', + type: 'number', + required: false, + default: 1, + }, + { + name: 'max', + label: '最高分', + type: 'number', + required: false, + default: 5, + }, + { + name: 'decimals', + label: '小数位数', + type: 'number', + required: false, + default: 1, + }, + { + name: 'strategy', + label: '生成策略', + type: 'select', + required: false, + default: 'realistic', + options: [ + { label: '真实分布(推荐)', value: 'realistic' }, + { label: '严格评价', value: 'strict' }, + { label: '均匀随机', value: 'uniform' }, + ], + }, + ], + generate: (params: any) => { + const { min = 1, max = 5, decimals = 1, strategy = 'realistic' } = params; + let value: number; + + switch (strategy) { + case 'strict': + // 严格评价:偏向低分 + value = skewedRandom(min, max, 0.3); + break; + case 'realistic': + // 真实分布:偏向高分(电商平台常见) + value = skewedRandom(min, max, 0.7); + break; + default: + // 均匀分布 + value = Math.random() * (max - min) + min; + } + + return parseFloat(Math.max(min, Math.min(max, value)).toFixed(decimals)); + }, + generateAtIndex: (params: any, index: number) => { + const { min = 1, max = 5, decimals = 1 } = params; + const step = (max - min) / 10000; + return parseFloat((min + step * index).toFixed(decimals)); + }, + }, + + discount: { + name: 'discount', + label: '折扣', + description: '生成真实分布的折扣', + icon: '🏷️', + params: [ + { + name: 'min', + label: '最低折扣', + type: 'number', + required: false, + default: 0.1, + }, + { + name: 'max', + label: '最高折扣', + type: 'number', + required: false, + default: 0.9, + }, + { + name: 'strategy', + label: '生成策略', + type: 'select', + required: false, + default: 'psychological', + options: [ + { label: '心理定价(推荐)', value: 'psychological' }, + { label: '清仓模式', value: 'clearance' }, + { label: '均匀随机', value: 'uniform' }, + ], + }, + ], + generate: (params: any) => { + const { min = 0.1, max = 0.9, strategy = 'psychological' } = params; + let value: number; + + switch (strategy) { + case 'clearance': + // 清仓模式:大折扣区间(3-7折) + value = Math.random() * 0.4 + 0.3; + break; + case 'psychological': + // 心理定价:常见折扣点 + value = generatePsychologicalDiscount(min, max); + break; + default: + // 均匀分布 + value = Math.random() * (max - min) + min; + } + + return parseFloat(Math.max(min, Math.min(max, value)).toFixed(2)); + }, + generateAtIndex: (params: any, index: number) => { + const { min = 0.1, max = 0.9 } = params; + const step = (max - min) / 10000; + return parseFloat((min + step * index).toFixed(2)); + }, + }, + + stock: { + name: 'stock', + label: '库存', + description: '生成真实分布的库存', + icon: '🏭', + params: [ + { + name: 'min', + label: '最小库存', + type: 'number', + required: false, + default: 0, + }, + { + name: 'max', + label: '最大库存', + type: 'number', + required: false, + default: 1000, + }, + { + name: 'strategy', + label: '生成策略', + type: 'select', + required: false, + default: 'realistic', + options: [ + { label: '真实分布(推荐)', value: 'realistic' }, + { label: '热销商品', value: 'hot' }, + { label: '均匀随机', value: 'uniform' }, + ], + }, + ], + generate: (params: any) => { + const { min = 0, max = 1000, strategy = 'realistic' } = params; + let value: number; + + switch (strategy) { + case 'hot': + // 热销商品:低库存(0-50) + value = Math.random() * 50; + break; + case 'realistic': + // 真实分布:指数分布,大多数库存较少 + value = exponentialRandom(min, max); + break; + default: + // 均匀分布 + value = Math.random() * (max - min) + min; + } + + return Math.round(Math.max(min, Math.min(max, value))); + }, + generateAtIndex: (params: any, index: number) => { + const { min = 0, max = 1000 } = params; + const step = (max - min) / 10000; + return Math.round(min + step * index); + }, + }, +}; + +// 辅助函数:生成心理定价 +function generatePsychologicalPrice(min: number, max: number): number { + let price = Math.random() * (max - min) + min; + + const strategies = [ + (p: number) => Math.floor(p) + 0.99, + (p: number) => Math.floor(p) + 0.98, + (p: number) => Math.floor(p) + 0.95, + (p: number) => Math.floor(p / 10) * 10 + 9.9, + ]; + + const strategy = strategies[Math.floor(Math.random() * strategies.length)]; + price = strategy(price); + + return Math.max(min, Math.min(max, price)); +} + +// 辅助函数:生成真实分布价格(对数正态分布) +function generateRealisticPrice(min: number, max: number): number { + const mu = Math.log((min + max) / 2); + const sigma = 0.5; + + const u1 = Math.random(); + const u2 = Math.random(); + const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); + + let value = Math.exp(mu + sigma * z); + value = Math.max(min, Math.min(max, value)); + + return value; +} + +// 辅助函数:格式化日期 +function formatDate(date: Date, format: string): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + + if (format === 'timestamp') { + return date.getTime().toString(); + } + + return format.replace('YYYY', year.toString()).replace('MM', month).replace('DD', day); +} + +// 辅助函数:正态分布随机数 +function normalRandom(mean: number, stddev: number): number { + const u1 = Math.random(); + const u2 = Math.random(); + const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); + return mean + stddev * z; +} + +// 辅助函数:人口比例分布(模拟真实年龄分布) +function demographicRandom(min: number, max: number): number { + // 中国人口年龄分布大致比例(简化版) + const weights = [ + { age: 5, weight: 0.05 }, // 0-5岁 + { age: 15, weight: 0.12 }, // 6-15岁 + { age: 25, weight: 0.18 }, // 16-25岁 + { age: 35, weight: 0.2 }, // 26-35岁 + { age: 45, weight: 0.18 }, // 36-45岁 + { age: 55, weight: 0.15 }, // 46-55岁 + { age: 65, weight: 0.08 }, // 56-65岁 + { age: 80, weight: 0.04 }, // 66-80岁 + ]; + + // 按权重随机选择年龄段 + const totalWeight = weights.reduce((sum, w) => sum + w.weight, 0); + let random = Math.random() * totalWeight; + + for (const w of weights) { + if (random <= w.weight) { + // 在该年龄段内均匀分布 + const ageMin = Math.max(min, w.age - 10); + const ageMax = Math.min(max, w.age); + return Math.random() * (ageMax - ageMin) + ageMin; + } + random -= w.weight; + } + + return Math.random() * (max - min) + min; +} + +// 辅助函数:泊松分布随机数(模拟购买数量) +function poissonRandom(lambda: number): number { + let L = Math.exp(-lambda); + let k = 0; + let p = 1; + + do { + k++; + p *= Math.random(); + } while (p > L); + + return k - 1; +} + +// 辅助函数:偏态分布随机数(模拟评分) +function skewedRandom(min: number, max: number, skewness: number): number { + // 使用 beta 分布的简化实现 + const u = Math.random(); + const v = Math.random(); + + // 根据偏度调整 + let value; + if (skewness > 0.5) { + // 右偏(偏向高分) + value = Math.pow(u, 1 - skewness) * (max - min) + min; + } else { + // 左偏(偏向低分) + value = (1 - Math.pow(v, skewness)) * (max - min) + min; + } + + return Math.max(min, Math.min(max, value)); +} + +// 辅助函数:指数分布随机数(模拟库存) +function exponentialRandom(min: number, max: number): number { + const lambda = 0.5; // 指数分布参数 + const u = Math.random(); + const value = -Math.log(1 - u) / lambda; + + // 缩放到指定区间 + const normalized = value / 10; // 归一化 + return normalized * (max - min) + min; +} + +// 辅助函数:生成心理定价折扣 +function generatePsychologicalDiscount(min: number, max: number): number { + // 常见折扣点:8折、85折、9折、95折 + const commonDiscounts = [0.8, 0.85, 0.9, 0.95, 0.7, 0.75]; + + // 随机选择一个常见折扣,或生成随机折扣 + if (Math.random() < 0.7) { + // 70% 概率使用常见折扣 + const discount = commonDiscounts[Math.floor(Math.random() * commonDiscounts.length)]; + return Math.max(min, Math.min(max, discount)); + } else { + // 30% 概率生成随机折扣 + return Math.random() * (max - min) + min; + } +} +``` + ### 3. 数据生成引擎 ```typescript @@ -320,25 +1008,48 @@ self.onmessage = function (e) { // 生成数据 const data = []; + const usedValues: Record> = {}; + + // 根据总数量决定策略 + const isLargeBatch = rule.options.total > 1000; + for (let i = 0; i < rule.options.total; i++) { const record: Record = {}; - const usedValues: Record> = {}; for (const field of rule.fields) { const generator = generators[field.generator]; + // 判断是否需要生成数据 + const shouldGenerate = + field.required || Math.random() * 100 >= (field.emptyRate ?? rule.options.defaultEmptyRate); + + if (!shouldGenerate) { + record[field.name] = null; + continue; + } + let value; - let retryCount = 0; - const maxRetry = 100; - do { + if (field.unique && isLargeBatch) { + // 策略1:大数据量 + 唯一性 → 索引生成(高性能) + value = generator.generateAtIndex?.(field.params, i) ?? generator.generate(field.params); + } else if (field.unique) { + // 策略2:小数据量 + 唯一性 → 随机生成 + 重试 + let retryCount = 0; + const maxRetry = 100; + + do { + value = generator.generate(field.params); + + if (usedValues[field.name]?.has(value)) { + retryCount++; + value = undefined; + } + } while (value === undefined && retryCount < maxRetry); + } else { + // 策略3:非唯一性 → 普通生成 value = generator.generate(field.params); - - if (field.unique && usedValues[field.name]?.has(value)) { - retryCount++; - value = undefined; - } - } while (value === undefined && retryCount < maxRetry); + } if (value !== undefined) { record[field.name] = value; @@ -349,6 +1060,8 @@ self.onmessage = function (e) { } usedValues[field.name].add(value); } + } else { + record[field.name] = null; } } @@ -463,17 +1176,10 @@ export class RuleStorage { return rules.filter( (r) => r.name.toLowerCase().includes(lowerKeyword) || - r.description?.toLowerCase().includes(lowerKeyword) || - r.tags.some((t) => t.toLowerCase().includes(lowerKeyword)), + r.description?.toLowerCase().includes(lowerKeyword), ); } - // 按标签筛选 - filterByTag(tag: string): DataRule[] { - const rules = this.getAll(); - return rules.filter((r) => r.tags.includes(tag)); - } - // 获取最近使用 getRecent(limit: number = 10): DataRule[] { const rules = this.getAll(); diff --git a/docs/test-data-generator/ui-design.md b/docs/test-data-generator/ui-design.md index 6747cb1..8493864 100644 --- a/docs/test-data-generator/ui-design.md +++ b/docs/test-data-generator/ui-design.md @@ -19,7 +19,7 @@ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────┐ ┌───────────────────────┐ │ -│ │ 📋 字段配置 │ │ 📊 数据预览 │ │ +│ │ 字段配置 │ │ 数据预览 │ │ │ │ │ │ │ │ │ │ [+ 添加字段] │ │ [实时预览数据] │ │ │ │ │ │ │ │ @@ -28,13 +28,13 @@ │ └─────────────────────────────────────┘ └───────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ -│ │ ⚙️ 生成选项 │ │ +│ │ 生成选项 │ │ │ │ 数量、格式、约束... │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ -│ │ 📤 操作栏 │ │ -│ │ [🚀 生成] [💾 保存规则] [📚 规则管理] │ │ +│ │ 操作栏 │ │ +│ │ [生成] [保存规则] [规则管理] │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ @@ -52,16 +52,16 @@ ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 📋 字段配置 │ +│ 字段配置 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ [+ 添加字段] │ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ -│ │ 1. name [中文姓名 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ -│ │ 2. email [邮箱地址 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ -│ │ 3. phone [手机号 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ -│ │ 4. age [年龄 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ +│ │ 1. name [中文姓名 ▼] [配置] [删除] [上移][下移] │ │ +│ │ 2. email [邮箱地址 ▼] [配置] [删除] [上移][下移] │ │ +│ │ 3. phone [手机号 ▼] [配置] [删除] [上移][下移] │ │ +│ │ 4. age [年龄 ▼] [配置] [删除] [上移][下移] │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ │ [+ 添加字段] │ @@ -73,9 +73,9 @@ - 点击字段名可编辑 - 点击生成器下拉框可切换生成器 -- 点击 ⚙️ 展开参数配置 -- 点击 ❌ 删除字段 -- 点击 ⬆️⬇️ 调整顺序 +- 点击 配置 展开参数配置 +- 点击 删除 删除字段 +- 点击 上移下移 调整顺序 --- @@ -90,36 +90,50 @@ │ 字段配置 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ 📝 基础配置 │ +│ 基础配置 │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 字段名称: [name ] │ │ │ │ 字段描述: [用户姓名 ] │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ 🔧 生成器选择 │ +│ 生成器选择 │ │ ┌───────────────────────────────────────────────────────────────┐ │ -│ │ 分类: [👤 个人信息 ▼] │ │ +│ │ 分类: [个人信息 ▼] │ │ │ │ 生成器: [中文姓名 ▼] │ │ -│ │ 📖 说明: 生成中文姓名,如张三、李四 │ │ +│ │ 说明: 生成中文姓名,如张三、李四 │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ ⚙️ 参数配置 │ +│ 参数配置 │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 姓氏池: [百家姓前100 ▼] │ │ │ │ 名字长度: [1] ~ [2] │ │ │ │ ☑ 唯一性约束 │ │ +│ │ ☑ 必填 │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ 🎲 预览示例 │ +│ 空值率设置 (仅选填字段显示) │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 空值概率: [====|----] 50% │ │ +│ │ 预设: [低 20%] [中 50%] [高 80%] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ 预览示例 │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 张伟, 李娜, 王强, 赵敏, 刘洋 │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ [✅ 保存] [❌ 取消] │ +│ [保存] [取消] │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` +**交互说明**: + +- **必填复选框**: 勾选后该字段必填,100% 生成数据 +- **空值率设置**: 仅当选填时显示,控制该字段生成空值的概率 +- **预设按钮**: 快速设置常用空值率(低/中/高) +- **空值率滑块**: 精细调节空值概率(0-100%) + --- ### 3. 数据预览组件 @@ -130,10 +144,10 @@ ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 📊 数据预览 │ +│ 数据预览 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ 📋 预览: 前 10 条 │ +│ 预览: 前 10 条 │ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ { │ │ @@ -151,7 +165,7 @@ │ │ ... │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ 📊 格式切换: │ +│ 格式切换: │ │ [JSON] [CSV] [SQL] [TypeScript] │ │ │ └─────────────────────────────────────────────────────────────────────┘ @@ -174,11 +188,11 @@ ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ ⚙️ 生成选项 │ +│ 生成选项 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌───────────────────────┐ │ -│ │ 📊 生成数量 │ │ 📄 数据格式 │ │ 🔒 约束条件 │ │ +│ │ 生成数量 │ │ 数据格式 │ │ 约束条件 │ │ │ │ │ │ │ │ │ │ │ │ [100 ] │ │ ○ JSON │ │ ☑ name 唯一 │ │ │ │ [10 ▼] │ │ ○ CSV │ │ ☑ email 唯一 │ │ @@ -187,6 +201,13 @@ │ │ │ │ │ │ │ │ │ └─────────────────┘ └─────────────────┘ └───────────────────────┘ │ │ │ +│ 默认空值率 (选填字段) │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 空值概率: [====|----] 50% │ │ +│ │ 预设: [低 20%] [中 50%] [高 80%] │ │ +│ │ 说明: 未单独设置空值率的选填字段将使用此默认值 │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -200,10 +221,10 @@ ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 📤 操作栏 │ +│ 操作栏 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ [🚀 生成数据] [💾 保存规则] [📚 规则管理] [📥 导入规则] [📤 导出]│ +│ [生成数据] [保存规则] [规则管理] [导入规则] [导出]│ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -218,29 +239,29 @@ ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 📊 生成结果 │ +│ 生成结果 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ ✅ 成功生成 100 条数据 │ +│ 成功生成 100 条数据 │ │ │ -│ 📊 数据统计: │ +│ 数据统计: │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ • 总条数: 100 │ │ │ │ • 文件大小: 12.5 KB │ │ -│ │ • 唯一性检查: ✅ 全部通过 │ │ +│ │ • 唯一性检查: 全部通过 │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ 📋 数据预览: │ +│ 数据预览: │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ [JSON 格式预览...] │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ 📤 导出选项: │ +│ 导出选项: │ │ ┌───────────────────────────────────────────────────────────────┐ │ -│ │ [📋 复制全部] [📥 下载 JSON] [📥 下载 CSV] [📥 下载 SQL] │ │ +│ │ [复制全部] [下载 JSON] [下载 CSV] [下载 SQL] │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ [❌ 关闭] │ +│ [关闭] │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -255,31 +276,27 @@ ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 📚 规则管理 │ +│ 规则管理 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ -│ 🔍 搜索: [搜索规则名称或标签... ] │ +│ 搜索: [搜索规则名称... ] │ │ │ -│ 📁 标签筛选: [全部] [电商] [用户] [测试数据] [最近使用] │ -│ │ -│ 📋 规则列表: │ +│ 规则列表: │ │ ┌───────────────────────────────────────────────────────────────┐ │ -│ │ 📦 电商用户数据 - 测试用 │ │ -│ │ 📝 用于测试用户注册功能 │ │ -│ │ 🏷️ 电商 用户 测试数据 │ │ -│ │ 📊 4 个字段 | 100 条 | JSON │ │ -│ │ 📅 2024-01-15 创建 | 使用 12 次 │ │ -│ │ [📥 加载] [✏️ 编辑] [📤 导出] [🗑️ 删除] │ │ +│ │ 电商用户数据 - 测试用 │ │ +│ │ 用于测试用户注册功能 │ │ +│ │ 4 个字段 | 100 条 | JSON │ │ +│ │ 2024-01-15 创建 | 使用 12 次 │ │ +│ │ [加载] [编辑] [导出] [删除] │ │ │ │ │ │ -│ │ 📦 电商订单数据 - 测试用 │ │ -│ │ 📝 用于测试订单功能 │ │ -│ │ 🏷️ 电商 订单 测试数据 │ │ -│ │ 📊 6 个字段 | 500 条 | JSON │ │ -│ │ 📅 2024-01-16 创建 | 使用 8 次 │ │ -│ │ [📥 加载] [✏️ 编辑] [📤 导出] [🗑️ 删除] │ │ +│ │ 电商订单数据 - 测试用 │ │ +│ │ 用于测试订单功能 │ │ +│ │ 6 个字段 | 500 条 | JSON │ │ +│ │ 2024-01-16 创建 | 使用 8 次 │ │ +│ │ [加载] [编辑] [导出] [删除] │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ -│ [📥 导入规则] [📤 导出全部] │ +│ [导入规则] [导出全部] │ │ │ └─────────────────────────────────────────────────────────────────────┘ ```