Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop

This commit is contained in:
wong
2026-01-05 11:04:39 +08:00
22 changed files with 10240 additions and 0 deletions

View File

@@ -3,3 +3,7 @@
2025-11-07 18:26:50 pid:842 Workerman[start.php] received signal SIGHUP
2025-11-07 18:26:50 pid:842 Workerman[start.php] stopping
2025-11-07 18:26:50 pid:842 Workerman[start.php] has been stopped
2026-01-05 10:45:32 pid:112703 Workerman[start.php] reloading
2026-01-05 10:46:34 pid:112703 Workerman[start.php] received signal SIGINT
2026-01-05 10:46:34 pid:112703 Workerman[start.php] stopping
2026-01-05 10:46:34 pid:112703 Workerman[start.php] has been stopped

View File

@@ -0,0 +1,164 @@
# 106服务器消费记录采集表分析报告
## 分析时间
2025年12月
## 消费记录Handler字段要求
### 必填字段
- `amount` (消费金额) - 必填
- `actual_amount` (实际支付金额) - 必填
- `consume_time` (消费时间) - 必填
### 用户标识字段三选一Handler会自动解析
- `phone_number` (手机号) - 推荐
- `id_card` (身份证) - 推荐
- `user_id` (用户ID) - 如果源数据已有可直接使用
### 门店标识字段二选一Handler会自动转换
- `store_name` (门店名称) - 推荐
- `store_id` (门店ID) - 如果源数据已有可直接使用
### 可选字段
- `currency` (币种) - 默认CNY
- `status` (记录状态) - 默认0
---
## 支持消费记录采集的表
### ✅ 1. KR_商城.21年贝蒂喜订单整合
**数据库**: `KR_商城`
**集合**: `21年贝蒂喜订单整合`
**记录数**: 10,439条
**数据状态**: ✅ 可用
#### 字段映射关系
| 目标字段 | 源字段 | 字段类型 | 说明 |
|---------|--------|---------|------|
| `phone_number` | `联系手机` | String | 手机号(需去除单引号前缀) |
| `store_name` | `店铺名称` | String | 门店名称(如:贝蒂喜旗舰店) |
| `amount` | `买家应付货款``总金额` | String→Float | 消费金额(需转换为数字) |
| `actual_amount` | `买家实际支付金额` | String→Float | 实际支付金额(需转换为数字,可能为"0" |
| `consume_time` | `订单付款时间``订单创建时间` | String→DateTime | 消费时间格式YYYY-MM-DD HH:mm:ss |
| `store_id` | `店铺Id` | String | 门店ID"0" |
| `status` | `订单状态` | String→Int | 订单状态(需转换:交易成功 →0交易关闭 →1 |
#### 字段说明
**源字段详情**
- `联系手机`: 格式为 `'13759198903`(带单引号前缀),需要处理
- `买家实际支付金额`: 可能是字符串 "0"(表示未支付),需要过滤或处理
- `订单付款时间`: 可能为 null未支付订单优先使用此字段
- `订单创建时间`: 作为备用时间字段
- `订单状态`: 示例值:"卖家已发货,等待买家确认"、"交易关闭" 等
#### 数据示例
```json
{
"_id": "68ad49c51d4abb1611aee2b9",
"联系手机": "'13759198903",
"买家应付货款": "248",
"总金额": "248",
"买家实际支付金额": "248",
"订单状态": "卖家已发货,等待买家确认",
"订单创建时间": "2021-01-31 23:44:53",
"订单付款时间": "2021-01-31 23:45:06",
"店铺名称": "贝蒂喜旗舰店",
"店铺Id": "0"
}
```
#### 推荐配置
**字段映射配置**
```json
{
"phone_number": "联系手机",
"amount": "买家应付货款",
"actual_amount": "买家实际支付金额",
"consume_time": "订单付款时间",
"store_name": "店铺名称"
}
```
**转换函数**
- `联系手机`: 使用 `parse_phone` 去除单引号并验证
- `买家应付货款` / `买家实际支付金额`: 使用 `parse_amount` 转换为数字
- `订单付款时间`: 使用 `parse_datetime` 解析时间
- `订单状态`: 自定义转换逻辑正常→0关闭→2
**过滤条件建议**
- 过滤 `买家实际支付金额` 为 "0" 或 null 的记录(未支付订单)
- 过滤 `订单付款时间` 为 null 的记录(未支付订单)
- 或保留所有记录在Handler中根据状态判断
---
## 其他数据库分析
### ❌ 2. KR_商城.凡客诚品_vancl.com
**状态**: ❌ 不支持
**原因**: 仅包含地址、姓名、电话信息,无金额、时间等消费记录字段
### ❌ 3. KR_商城.嘟嘟牛
**状态**: ❌ 不支持
**原因**: 仅包含邮箱、用户名、密码等账户信息,无消费记录字段
### ❌ 4. KR_商城.购物-北京一电购公司2月整理版30万
**状态**: ❌ 不支持
**原因**: 仅包含号码、名字、省市等基本信息,无消费记录字段
### ❌ 5. KR_淘宝.卖家邮箱去重复后300万
**状态**: ❌ 不支持
**原因**: 仅包含邮箱字段,无消费记录相关信息
### ❌ 6. KR_卡若私域.老坑爹商店 shop.lkdie.com
**状态**: ❌ 不支持
**原因**: 包含用户账户信息(邮箱、手机、密码等),但无订单、支付等消费记录字段
---
## 总结
### 可用的表
1. **KR_商城.21年贝蒂喜订单整合**
### 使用建议
1. **数据预处理**
- 手机号字段需要去除单引号前缀
- 金额字段需要从字符串转换为数字
- 时间字段格式为 `YYYY-MM-DD HH:mm:ss`,需要解析
2. **数据过滤**
- 建议过滤未支付的订单(`买家实际支付金额` 为 "0" 或 `订单付款时间` 为 null
- 或根据 `订单状态` 字段过滤无效订单
3. **字段映射**
- 使用 `订单付款时间` 作为 `consume_time`(优先)
- 如果 `订单付款时间` 为空,可以使用 `订单创建时间` 作为备用
- `店铺名称` 可以映射到 `store_name`Handler会自动转换为 `store_id`
4. **注意事项**
- 该集合包含2021年的订单数据
- 数据量10,439条记录
- 部分订单可能未支付,需要根据业务需求决定是否采集
---
## 下一步操作
1. 在TaskForm中创建采集任务
2. 配置数据源选择106服务器ckb数据库
3. 选择Handler`消费记录处理ConsumptionCollectionHandler`
4. 配置源数据:
- 数据库:`KR_商城`
- 集合:`21年贝蒂喜订单整合`
5. 配置字段映射(按上述映射关系)
6. 配置过滤条件(可选):过滤未支付订单
7. 预览查询结果验证字段映射
8. 保存并启动采集任务

View File

@@ -0,0 +1,319 @@
# QueryBuilder 折叠功能说明
## 更新概览
为了改善用户体验QueryBuilder 组件的过滤条件和联表查询区域现在支持折叠,默认为折叠状态,让界面更加简洁。
---
## 功能特性
### 1. 默认折叠状态
**过滤条件WHERE**
- 默认:折叠
- 图标:`→` (折叠) / `↓` (展开)
- 点击标题栏任意位置即可切换
**联表查询JOIN/LOOKUP**
- 默认:折叠
- 图标:`→` (折叠) / `↓` (展开)
- 点击标题栏任意位置即可切换
### 2. 智能展开
**自动展开时机**
- 点击"添加条件"按钮 → 自动展开过滤条件区域
- 点击"添加关联"按钮 → 自动展开联表查询区域
**好处**
- 用户添加配置时自动展开,无需手动操作
- 提升操作流畅度
### 3. 条件数量提示
**过滤条件**
- 显示绿色标签:`3 个条件`
- 一目了然当前配置数量
**联表查询**
- 显示橙色标签:`2 个关联`
- 直观了解关联表数量
---
## 界面效果
### 折叠状态(默认)
```
┌─ 基础配置 ─────────────────────────────────────────┐
│ 数据源:[MongoDB数据源] │
│ 数据库:[ckb] │
│ 主集合:[consumption_records_202101] │
└────────────────────────────────────────────────────┘
┌─ → 过滤条件WHERE [3个条件] [添加条件] ───┐
│ (折叠状态,内容隐藏) │
└────────────────────────────────────────────────────┘
┌─ → 联表查询JOIN/LOOKUP [2个关联] [添加关联] ─┐
│ (折叠状态,内容隐藏) │
└────────────────────────────────────────────────────┘
┌─ 排序和限制 ───────────────────────────────────────┐
│ 排序字段:[create_time] 排序方式:[降序] │
│ 限制数量:[1000] │
└────────────────────────────────────────────────────┘
```
### 展开状态
```
┌─ ↓ 过滤条件WHERE [3个条件] [添加条件] ───┐
│ ┌──────────────────────────────────────────────────┐│
│ │ 逻辑 | 字段 | 运算符 | 值 | 操作 ││
│ │ - | status | 等于 | success | [删除] ││
│ │ AND | amount | 大于 | 1000 | [删除] ││
│ │ AND | shop_name | 包含 | 淘宝 | [删除] ││
│ └──────────────────────────────────────────────────┘│
└────────────────────────────────────────────────────┘
┌─ ↓ 联表查询JOIN/LOOKUP [2个关联] [添加关联] ─┐
│ ┌──────────────────────────────────────────────────┐│
│ │ 关联集合 | 主字段 | 关联字段 | 结果名 | 操作 ││
│ │ user_profile | user_id | user_id | user | [删除]││
│ │ stores | store_id | _id | store_info | [删除] ││
│ └──────────────────────────────────────────────────┘│
└────────────────────────────────────────────────────┘
```
---
## 交互设计
### 标题栏样式
**视觉提示**
- 鼠标悬停:标题栏背景变为浅灰色 `#f5f7fa`
- 光标变化:`cursor: pointer`
- 过渡动画0.2s 平滑过渡
**元素组成**
```
[图标 →/↓] [标题] [条件数量标签] [操作按钮]
```
### 点击行为
**标题栏点击**
- 点击标题栏任意位置 → 切换折叠/展开状态
- 图标跟随变化
**按钮点击**
- 使用 `@click.stop` 阻止事件冒泡
- 点击"添加条件/关联"按钮不会触发折叠切换
- 但会自动展开对应区域
---
## 技术实现
### 1. 折叠状态管理
```typescript
// 折叠状态
const collapseStates = reactive({
filter: false, // 过滤条件默认折叠
lookup: false // 联表查询默认折叠
})
```
### 2. 模板结构
```vue
<el-card shadow="never">
<template #header>
<!-- 可点击的标题栏 -->
<div
class="card-header"
style="cursor: pointer;"
@click="collapseStates.filter = !collapseStates.filter"
>
<!-- 左侧图标 + 标题 + 数量标签 -->
<div style="display: flex; align-items: center; gap: 8px;">
<el-icon>
<component :is="collapseStates.filter ? 'ArrowDown' : 'ArrowRight'" />
</el-icon>
<span>过滤条件WHERE</span>
<el-tag v-if="queryConfig.filter.length > 0" size="small" type="success">
{{ queryConfig.filter.length }} 个条件
</el-tag>
</div>
<!-- 右侧操作按钮 -->
<el-button
type="primary"
size="small"
@click.stop="handleAddFilter"
>
添加条件
</el-button>
</div>
</template>
<!-- 折叠过渡动画 -->
<el-collapse-transition>
<div v-show="collapseStates.filter">
<!-- 内容区域 -->
</div>
</el-collapse-transition>
</el-card>
```
### 3. 自动展开逻辑
```typescript
// 添加过滤条件
const handleAddFilter = () => {
if (!hasCollection.value) {
ElMessage.warning('请先选择主集合')
return
}
// 添加新条件
queryConfig.filter.push({
logic: queryConfig.filter.length > 0 ? 'and' : undefined,
field: '',
operator: 'eq',
value: ''
})
// 自动展开区域
collapseStates.filter = true
}
```
### 4. 样式设计
```scss
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 500;
font-size: 14px;
// 针对可点击的标题栏
&[style*="cursor: pointer"] {
user-select: none;
transition: background-color 0.2s;
margin: -12px -20px;
padding: 12px 20px;
border-radius: 4px;
&:hover {
background-color: #f5f7fa;
}
}
}
```
---
## 用户操作流程
### 场景1查看已有配置
**用户动作**
1. 打开数据列表配置页面
2. 看到折叠的过滤条件和联表查询
3. 标签显示:"3 个条件"、"2 个关联"
**优势**
- 一眼看出配置数量
- 界面简洁,不需要滚动
### 场景2添加新配置
**用户动作**
1. 点击"添加条件"按钮
2. 区域自动展开
3. 开始配置新条件
**优势**
- 无需手动展开
- 操作流畅,减少点击次数
### 场景3查看详细配置
**用户动作**
1. 点击标题栏
2. 展开查看详细配置
3. 再次点击可折叠
**优势**
- 快速切换查看状态
- 标题栏整行可点击,点击区域大
---
## 优势总结
### 1. 界面简洁
- 默认折叠,减少视觉干扰
- 特别适合复杂配置场景
- 聚焦当前操作
### 2. 信息密度优化
- 数量标签直观显示配置数量
- 不需要展开即可了解配置情况
- 快速定位需要修改的区域
### 3. 操作便捷
- 整行标题栏可点击
- 自动展开机制
- 视觉反馈清晰
### 4. 性能优化
- 折叠状态下不渲染内容
- 大量条件时渲染性能更好
- 使用 `el-collapse-transition` 平滑动画
---
## 兼容性说明
### 向后兼容
- 不影响现有数据结构
- 只是UI层面的改进
- 所有功能保持不变
### 默认行为
- 新建时:默认折叠
- 编辑时:默认折叠
- 添加配置时:自动展开
### 状态保持
- 折叠状态不保存
- 每次进入页面都是默认折叠
- 符合大多数用户习惯
---
## 相关组件
使用了以下 Element Plus 组件:
- `el-collapse-transition`: 折叠动画
- `el-icon`: 图标组件
- `el-tag`: 数量标签
使用了以下图标:
- `ArrowRight`: 折叠状态(→)
- `ArrowDown`: 展开状态(↓)
---
**更新时间**2025-01-XX
**版本**QueryBuilder v2.1
**更新内容**:添加折叠功能,优化界面布局

View File

@@ -0,0 +1,311 @@
# 可视化查询构建器使用说明
## 一、概述
可视化查询构建器是一个用于配置MongoDB查询的UI组件支持通过图形界面配置复杂的查询条件包括过滤条件、联表查询、排序等功能。
---
## 二、功能特性
### 2.1 基础配置
- **数据源选择**:选择已配置的数据源
- **数据库选择**:选择数据源中的数据库
- **主集合选择**:选择查询的主集合(表)
### 2.2 过滤条件WHERE
- **逻辑关系**:支持 AND/OR 逻辑组合
- **字段选择**:从集合字段列表中选择
- **运算符**
- `eq` - 等于
- `ne` - 不等于
- `gt` - 大于
- `gte` - 大于等于
- `lt` - 小于
- `lte` - 小于等于
- `in` - 包含(数组)
- `nin` - 不包含(数组)
- `regex` - 正则匹配
- `exists` - 字段存在
- **值输入**:根据字段类型自动调整输入方式
### 2.3 联表查询JOIN/LOOKUP
- **关联集合**:选择要关联的其他集合
- **主集合字段**:主集合的关联字段
- **关联集合字段**:关联集合的关联字段
- **结果字段名**:关联结果存储的字段名
- **解构**:是否将数组结果展开为对象
- **保留空值**LEFT JOIN效果保留没有关联的记录
### 2.4 排序和限制
- **排序字段**:选择排序的字段
- **排序方式**:升序/降序
- **限制数量**:限制返回的记录数
### 2.5 查询预览
- **SQL预览**实时显示生成的MongoDB聚合管道代码
- **数据预览**:预览查询结果(最多显示预览数据)
---
## 三、使用流程
### 3.1 创建数据列表
1. **进入数据列表管理页面**
- 路径:`/tag-data-lists`
- 点击"创建数据列表"按钮
2. **填写基本信息**
- 列表名称:如"消费记录表"
- 列表编码:如"consumption_records"
- 描述:可选
- 状态:启用/禁用
3. **配置查询**
- 选择数据源
- 选择数据库
- 选择主集合
- 添加过滤条件(可选)
- 添加联表查询(可选)
- 配置排序和限制(可选)
4. **预览查询**
- 点击"预览数据"按钮
- 查看生成的MongoDB查询代码
- 查看预览数据
5. **保存配置**
- 点击"保存"按钮
- 数据列表配置保存成功
### 3.2 在标签定义中使用
1. **创建标签定义**
- 进入标签定义管理页面
- 点击"创建标签定义"
2. **选择数据列表**
- 在"数据列表"下拉框中选择已创建的数据列表
- 系统自动加载该列表的字段
3. **配置规则**
- 选择规则类型(运算规则/正则规则)
- 添加规则条件
- 每个条件选择字段、运算符、值、标签值
4. **保存标签定义**
---
## 四、配置示例
### 4.1 简单查询示例
**场景**查询消费记录表中金额大于1000的记录
**配置**
- 数据源选择MongoDB数据源
- 数据库:`tag_engine`
- 主集合:`consumption_records`
- 过滤条件:
- 字段:`amount`
- 运算符:`gt`
- 值:`1000`
- 排序:按 `create_time` 降序
- 限制1000条
**生成的查询**
```javascript
db.consumption_records.aggregate([
{ $match: { amount: { $gt: 1000 } } },
{ $sort: { create_time: -1 } },
{ $limit: 1000 }
])
```
### 4.2 联表查询示例
**场景**:查询消费记录,并关联用户信息
**配置**
- 数据源选择MongoDB数据源
- 数据库:`tag_engine`
- 主集合:`consumption_records`
- 联表查询:
- 关联集合:`user_profile`
- 主集合字段:`user_id`
- 关联集合字段:`user_id`
- 结果字段名:`user_info`
- 解构:是
- 保留空值:是
- 过滤条件:
- 字段:`status`
- 运算符:`eq`
- 值:`success`
- 限制1000条
**生成的查询**
```javascript
db.consumption_records.aggregate([
{ $match: { status: { $eq: "success" } } },
{ $lookup: {
from: "user_profile",
localField: "user_id",
foreignField: "user_id",
as: "user_info"
} },
{ $unwind: {
path: "$user_info",
preserveNullAndEmptyArrays: true
} },
{ $limit: 1000 }
])
```
### 4.3 复杂条件查询示例
**场景**查询最近30天、金额大于1000、状态为成功的消费记录
**配置**
- 数据源选择MongoDB数据源
- 数据库:`tag_engine`
- 主集合:`consumption_records`
- 过滤条件:
1. 字段:`amount`,运算符:`gt`,值:`1000`,逻辑:-
2. 字段:`status`,运算符:`eq`,值:`success`逻辑AND
3. 字段:`create_time`,运算符:`gte`,值:`2024-12-01`逻辑AND
- 排序:按 `create_time` 降序
- 限制1000条
**生成的查询**
```javascript
db.consumption_records.aggregate([
{ $match: {
amount: { $gt: 1000 },
status: { $eq: "success" },
create_time: { $gte: ISODate("2024-12-01T00:00:00Z") }
} },
{ $sort: { create_time: -1 } },
{ $limit: 1000 }
])
```
---
## 五、技术实现
### 5.1 组件结构
```
QueryBuilder/
├── QueryBuilder.vue # 主组件
└── index.ts # 导出(可选)
```
### 5.2 数据格式
**输入格式**v-model
```typescript
{
data_source_id: string
database: string
collection: string
filter: Array<{
logic?: 'and' | 'or'
field: string
operator: string
value: any
}>
lookups: Array<{
from: string
local_field: string
foreign_field: string
as: string
unwrap?: boolean
preserve_null?: boolean
}>
sort_field: string
sort_order: '1' | '-1'
limit: number
}
```
**输出格式**(保存到数据库):
```json
{
"list_id": "uuid",
"list_name": "消费记录表",
"list_code": "consumption_records",
"data_source_id": "source_123",
"database": "tag_engine",
"collection": "consumption_records",
"query_config": {
"filter": [
{
"field": "amount",
"operator": "gt",
"value": 1000
}
],
"lookups": [
{
"from": "user_profile",
"local_field": "user_id",
"foreign_field": "user_id",
"as": "user_info",
"unwrap": true,
"preserve_null": true
}
],
"sort": {
"create_time": -1
},
"limit": 1000
}
}
```
### 5.3 API接口
**需要实现的接口**
1. `GET /api/data-sources` - 获取数据源列表
2. `GET /api/data-sources/{id}/databases` - 获取数据库列表
3. `GET /api/data-sources/{id}/collections` - 获取集合列表需要database参数
4. `GET /api/data-sources/{id}/fields` - 获取字段列表需要database和collection参数
5. `POST /api/data-sources/preview-query` - 预览查询结果
6. `GET /api/tag-data-lists` - 获取数据列表列表
7. `POST /api/tag-data-lists` - 创建数据列表
8. `GET /api/tag-data-lists/{id}` - 获取数据列表详情
9. `PUT /api/tag-data-lists/{id}` - 更新数据列表
10. `DELETE /api/tag-data-lists/{id}` - 删除数据列表
11. `GET /api/tag-data-lists/{id}/fields` - 获取数据列表字段(用于标签定义)
---
## 六、使用注意事项
1. **数据源配置**:必须先配置数据源,才能使用查询构建器
2. **字段类型**:系统会自动识别字段类型,调整输入方式
3. **联表查询**:支持多个联表查询,按顺序执行
4. **性能考虑**建议设置合理的limit值避免查询过多数据
5. **预览功能**:预览数据最多显示一定数量,用于验证查询配置是否正确
---
## 七、扩展功能
### 7.1 未来可扩展的功能
1. **聚合函数**:支持 $group、$sum、$avg 等聚合操作
2. **子查询**:支持嵌套查询
3. **条件分支**:支持 $cond、$switch 等条件表达式
4. **字段映射**:支持字段重命名、计算字段
5. **查询模板**:保存常用查询为模板
6. **查询历史**:记录查询历史,支持回滚
---
**文档更新时间**2025-01-XX
**组件版本**v1.0.0

View File

@@ -0,0 +1,425 @@
# 多集合模式使用说明
## 问题背景
在实际业务中,**消费记录表**并非单一集合,而是由多个集合构成:
### 场景1按月分片的消费记录
`ckb` 数据库中,消费记录按月分片存储:
- `consumption_records_202101`
- `consumption_records_202102`
- `consumption_records_202103`
- ... (每月一个集合)
### 场景2按商品类型分散的消费记录
`KR_淘宝` 数据库中,消费记录按商品类型分散:
- 女士内衣132.6万条)
- 办公设备文具64.5万条)
-71万条
- zippo1, zippo2, ... (多个集合)
**问题**:如何在数据列表配置中选择这些分散的消费记录表?
---
## 解决方案:多集合模式
QueryBuilder 组件现已支持**多集合模式**,允许同时选择多个集合,查询时自动合并数据。
---
## 使用方法
### 1. 启用多集合模式
在数据列表配置页面QueryBuilder 基础配置区域):
1. 选择数据源
2. 选择数据库
3. **开启"多集合模式"开关**
```
┌─ 基础配置 ─────────────────────────────────┐
│ 数据源:[MongoDB标签引擎数据源] │
│ 数据库:[ckb] │
│ 多集合模式:[●启用] ○禁用 │
│ 说明:启用后可同时选择多个集合 │
└─────────────────────────────────────────────┘
```
### 2. 选择多个集合
启用后,会显示集合复选框列表,并提供强大的筛选和操作功能:
#### 筛选功能
```
[🔍 筛选集合名称...]
```
- 输入关键词实时筛选集合列表
- 例如输入 `2021` 只显示包含 2021 的集合
- 支持模糊匹配,不区分大小写
#### 批量操作按钮
```
[全选] - 选择当前筛选结果的所有集合
[清空] - 清空所有已选集合
[反选] - 反选当前筛选结果的集合
```
#### 快捷筛选(智能识别)
当检测到按日期分片的集合时,自动显示快捷筛选按钮:
```
快捷筛选:
[2021年] [2022年] [2023年] [2024年] [2025年]
[最近3个月] [最近6个月] [最近12个月]
```
**功能说明**
- **按年份筛选**:点击 `2021年` 自动选择所有包含 `2021` 的集合
- **按时间范围**:点击 `最近3个月` 自动选择最近3个月的集合
- 例如当前是 2025-01点击"最近3个月"会选择:
- consumption_records_202501
- consumption_records_202412
- consumption_records_202411
#### 集合列表
```
┌─ 集合列表 ─────────────────────────────────┐
│ ☑ consumption_records_202101 │
│ ☑ consumption_records_202102 │
│ ☑ consumption_records_202103 │
│ ☑ consumption_records_202104 │
│ ☑ consumption_records_202105 │
│ ☑ consumption_records_202106 │
│ ☐ user_profile │
│ ☐ user_tags_shard_0 │
│ ... (最多300px高度超出可滚动) │
└─────────────────────────────────────────────┘
筛选结果28 个集合 | 已选择 6 个集合
查询时将自动合并这些集合的数据
```
### 3. 配置查询条件
多集合模式下,可以正常配置:
- 过滤条件WHERE
- 联表查询JOIN
- 排序和限制
字段列表会从**第一个选中的集合**加载。
### 4. 预览查询
点击"预览数据"SQL预览会显示
```javascript
// 多集合模式:将查询以下 6 个集合并合并结果
// consumption_records_202101, consumption_records_202102, consumption_records_202103, consumption_records_202104, consumption_records_202105, consumption_records_202106
db.consumption_records_202101.aggregate([
{ $match: { status: { $eq: "success" } } },
{ $sort: { create_time: -1 } },
{ $limit: 1000 }
])
```
---
## 完整示例
### 示例1按月分片的消费记录使用快捷筛选
**需求**创建一个包含2021年所有消费记录的数据列表
**步骤**
1. 列表名称:`2021年全年消费记录`
2. 列表编码:`consumption_records_2021`
3. 数据源MongoDB标签引擎
4. 数据库:`ckb`
5. **启用多集合模式**
6. **点击快捷筛选按钮 `2021年`** 👈 自动选择所有2021年的集合
- ✅ 自动选中:
- consumption_records_202101
- consumption_records_202102
- consumption_records_202103
- ... (所有12个月)
- 💡 提示:"已选择 12 个集合"
7. 添加过滤条件(可选):
- 字段:`status`
- 运算符:`等于`
- 值:`success`
8. 保存
**传统方式 vs 快捷筛选**
- ❌ 传统方式需要手动勾选12个复选框
- ✅ 快捷筛选点击1次按钮即可
### 示例1-2最近半年消费记录智能时间范围
**需求**创建一个包含最近6个月消费记录的数据列表
**步骤**
1. 列表名称:`最近半年消费记录`
2. 列表编码:`consumption_records_recent_6m`
3. 数据源MongoDB标签引擎
4. 数据库:`ckb`
5. **启用多集合模式**
6. **点击快捷筛选按钮 `最近6个月`** 👈 自动选择最近6个月的集合
- 系统自动计算时间范围
- 如果当前是 2025-01则选择
- consumption_records_202501
- consumption_records_202412
- consumption_records_202411
- consumption_records_202410
- consumption_records_202409
- consumption_records_202408
7. 保存
### 示例2使用筛选功能精确选择
**需求**只选择2021年第一季度的消费记录
**步骤**
1. 启用多集合模式
2. **在筛选框输入 `202101`**
- 筛选结果:只显示 `consumption_records_202101`
3. **点击 `全选`** 按钮
4. **清空筛选框,输入 `202102`**
5. **点击 `全选`** 按钮(追加选择)
6. **清空筛选框,输入 `202103`**
7. **点击 `全选`** 按钮(追加选择)
8. 最终选中3个集合
**高级技巧**
- 输入 `20210` 可以同时筛选出 202101-202109
- 点击"全选"后,再输入 `202107`,点击"反选"可以排除7月的数据
**保存的数据结构**
```json
{
"list_name": "2021年上半年消费记录",
"list_code": "consumption_records_2021_h1",
"data_source_id": "source_001",
"database": "ckb",
"collection": "consumption_records_202101",
"multi_collection": true,
"collections": [
"consumption_records_202101",
"consumption_records_202102",
"consumption_records_202103",
"consumption_records_202104",
"consumption_records_202105",
"consumption_records_202106"
],
"query_config": {
"filter": [
{ "field": "status", "operator": "eq", "value": "success" }
],
"lookups": [],
"sort": { "create_time": -1 },
"limit": 1000
}
}
```
### 示例3按商品类型的消费记录使用文本筛选
**需求**:创建一个包含女性用品的消费记录数据列表
**步骤**
1. 列表名称:`女性用品消费记录`
2. 列表编码:`female_products_consumption`
3. 数据源MongoDB标签引擎
4. 数据库:`KR_淘宝`
5. **启用多集合模式**
6. **在筛选框输入 `女`**
- 筛选结果显示:女士内衣
7. **点击 `全选`**
8. **清空筛选框,输入 `包`**
9. **点击 `全选`**(追加选择)
10. 最终选中:
- ☑ 女士内衣去重复后132.6万)
- ☑ 包去重复后71万
11. 保存
### 示例4反选功能的妙用
**需求**选择2021年除了1月和12月以外的所有月份
**步骤**
1. **点击快捷筛选 `2021年`**选中全年12个月
2. **在筛选框输入 `202101`**
3. **点击 `反选`**取消选择1月
4. **清空筛选框,输入 `202112`**
5. **点击 `反选`**取消选择12月
6. 最终选中202102-20211110个月
---
## 技术实现
### 前端数据结构
```typescript
const queryConfig = reactive({
data_source_id: string
database: string
collection: string // 单集合模式或多集合的第一个(兼容性)
multi_collection: boolean // 是否启用多集合模式
collections: string[] // 多集合模式下选中的集合列表
filter: Array<...>
lookups: Array<...>
sort_field: string
sort_order: string
limit: number
})
```
### 后端查询逻辑(待实现)
后端在执行查询时,需要处理多集合:
```php
if ($dataList['multi_collection'] && !empty($dataList['collections'])) {
// 多集合模式:对每个集合执行查询,然后合并结果
$allResults = [];
foreach ($dataList['collections'] as $collection) {
$results = $this->executeQuery($dataSource, $database, $collection, $queryConfig);
$allResults = array_merge($allResults, $results);
}
// 如果有排序,需要对合并结果重新排序
if ($queryConfig['sort']) {
$allResults = $this->sortResults($allResults, $queryConfig['sort']);
}
// 如果有限制,需要对合并结果应用限制
if ($queryConfig['limit']) {
$allResults = array_slice($allResults, 0, $queryConfig['limit']);
}
return $allResults;
} else {
// 单集合模式
return $this->executeQuery($dataSource, $database, $collection, $queryConfig);
}
```
---
## API 调整(待实现)
### 预览查询 API
需要支持 `collections` 参数:
**请求**
```json
POST /data-collection-tasks/preview-query
{
"data_source_id": "source_001",
"database": "ckb",
"collection": "consumption_records_202101", // 兼容性保留
"collections": [ // 多集合模式
"consumption_records_202101",
"consumption_records_202102",
"consumption_records_202103"
],
"filter_conditions": [...],
"lookups": [...],
"limit": 10
}
```
**响应**
```json
{
"code": 200,
"data": {
"fields": [...],
"data": [...], // 合并后的数据
"count": 30, // 总条数
"collections_count": { // 各集合的数据条数(可选)
"consumption_records_202101": 10,
"consumption_records_202102": 10,
"consumption_records_202103": 10
}
}
}
```
---
## 使用场景
### 1. 时间分片数据
- 消费记录按月/季度/年分表
- 日志数据按日期分表
- 订单数据按时间分表
### 2. 业务分类数据
- 按商品类型分表的交易数据
- 按地区分表的用户数据
- 按渠道分表的营销数据
### 3. 分库分表数据
- 数据水平切分后的多个分片
- 跨库查询场景
---
## 注意事项
### 1. 字段一致性
多个集合应该有**相同或相似的字段结构**,否则合并查询可能出错。
### 2. 性能考虑
- 选择的集合越多,查询性能越慢
- 建议根据实际需求选择合适的集合范围
- 可以设置合理的 `limit` 限制返回数据量
### 3. 排序和限制
- 多集合模式下,排序和限制会在**合并后的结果**上应用
- 如果每个集合返回1000条3个集合合并后是3000条再应用limit
### 4. 联表查询
- 联表查询在**每个集合上独立执行**
- 关联表应该是同一个集合(不跨集合联表)
---
## 界面效果
### 单集合模式(默认)
```
多集合模式:○启用 ●禁用
主集合:[consumption_records_202101 ▼]
```
### 多集合模式(带筛选和快捷操作)
```
多集合模式:●启用 ○禁用
┌─────────────────────────────────────────────────────┐
│ [🔍 筛选集合名称...] [全选] [清空] [反选] │
│ │
│ 快捷筛选:[2021年] [2022年] [2023年] [2024年] │
│ [最近3个月] [最近6个月] [最近12个月] │
├─────────────────────────────────────────────────────┤
│ 集合列表: │
│ ☑ consumption_records_202101 │
│ ☑ consumption_records_202102 │
│ ☑ consumption_records_202103 │
│ ☐ consumption_records_202104 │
│ ☐ consumption_records_202105 │
│ ... (滚动查看更多) │
└─────────────────────────────────────────────────────┘
已选择 3 个集合,查询时将自动合并这些集合的数据
```
---
**更新时间**2025-01-XX
**状态**:前端已实现,后端待开发

View File

@@ -0,0 +1,491 @@
# 字段定义配置管理方案对比
## 一、问题
标签定义创建时需要选择数据源类型user_profile、user_phone_relations、consumption_records然后显示该数据源的字段列表供用户选择。
**问题**:这些字段列表应该:
1. 存储在数据库中(配置化),通过管理界面动态管理?
2. 还是写在代码中纯代码实现前端直接调用API即可
---
## 二、方案对比
### 方案一:纯代码实现(⭐推荐第一阶段)
**实现方式**
- 字段定义直接写在代码中Service 或 Config 类)
- API 接口直接返回硬编码的字段列表
- 需要修改字段时,修改代码并重新部署
**代码示例**
```php
// app/service/DataSourceFieldService.php
class DataSourceFieldService
{
/**
* 获取数据源的字段列表
*/
public function getFields(string $dataSourceType): array
{
$fieldsMap = [
'user_profile' => [
[
'field' => 'user_id',
'type' => 'string',
'description' => '用户ID',
'source' => 'user_profile',
],
[
'field' => 'total_amount',
'type' => 'number',
'description' => '总消费金额',
'source' => 'user_profile',
'pre_aggregated_from' => 'consumption_records',
],
// ... 更多字段
],
'user_phone_relations' => [
[
'field' => 'phone_number',
'type' => 'string',
'description' => '手机号',
'source' => 'user_phone_relations',
],
// ...
],
'consumption_records' => [
// 消费记录表的预聚合字段
[
'field' => 'total_amount',
'type' => 'number',
'description' => '总消费金额',
'source' => 'pre_aggregated',
'original_source' => 'consumption_records',
'note' => '从消费记录表预聚合到 user_profile',
],
// ...
],
];
return $fieldsMap[$dataSourceType] ?? [];
}
}
```
**优点**
-**实现简单**:代码简洁,无需额外的数据库表和管理界面
-**性能好**:直接返回,无需数据库查询
-**类型安全**:代码中可以定义完整的类型和验证
-**版本控制**:字段定义的变更可以通过 Git 追踪
-**易于测试**:单元测试容易编写
**缺点**
- ⚠️ **灵活性较低**:修改字段需要改代码、重新部署
- ⚠️ **需要开发人员**:非技术人员无法直接修改字段配置
**适用场景**
- 字段定义相对稳定,不频繁变化
- 第一阶段快速实现
- 团队较小,技术栈统一
---
### 方案二:数据库配置(🔶适合长期)
**实现方式**
- 创建 `data_source_fields` 集合MongoDB
- 存储每个数据源类型的字段定义
- 提供管理界面,允许管理员动态添加/修改字段
**数据结构**
```javascript
// data_source_fields 集合
{
field_id: "uuid",
data_source_type: "consumption_records",
field: "total_amount",
type: "number",
description: "总消费金额",
source: "pre_aggregated",
original_source: "consumption_records",
note: "从消费记录表预聚合到 user_profile",
is_active: true,
sort_order: 1,
create_time: ISODate(),
update_time: ISODate(),
}
```
**代码示例**
```php
// app/repository/DataSourceFieldRepository.php
class DataSourceFieldRepository extends Model
{
protected $table = 'data_source_fields';
protected $primaryKey = 'field_id';
// ...
}
// app/service/DataSourceFieldService.php
class DataSourceFieldService
{
public function __construct(
protected DataSourceFieldRepository $fieldRepository
) {}
public function getFields(string $dataSourceType): array
{
return $this->fieldRepository
->where('data_source_type', $dataSourceType)
->where('is_active', true)
->orderBy('sort_order')
->get()
->toArray();
}
}
```
**优点**
-**灵活性高**:可以动态添加/修改字段,无需重新部署
-**非技术人员友好**:管理员可以通过界面管理字段
-**支持多环境**:不同环境可以有不同的字段配置
-**支持字段元数据**:可以存储更多信息(如验证规则、默认值等)
**缺点**
- ⚠️ **实现复杂**:需要创建 Repository、Service、Controller、前端界面
- ⚠️ **性能稍差**:每次请求需要查询数据库(可加缓存优化)
- ⚠️ **需要维护**:需要维护字段配置数据的正确性
- ⚠️ **版本控制困难**:配置变更无法通过 Git 追踪
**适用场景**
- 字段定义频繁变化
- 有非技术人员需要管理字段配置
- 需要支持多环境不同配置
- 长期维护的大型项目
---
### 方案三:混合方案(🔶平衡方案)
**实现方式**
- 基础字段定义写在代码中(作为默认值)
- 支持数据库配置覆盖/扩展
- 合并代码配置和数据库配置
**代码示例**
```php
class DataSourceFieldService
{
/**
* 获取字段列表(代码 + 数据库)
*/
public function getFields(string $dataSourceType): array
{
// 1. 从代码获取默认字段
$defaultFields = $this->getDefaultFields($dataSourceType);
// 2. 从数据库获取自定义字段(如果有)
$customFields = $this->fieldRepository
->where('data_source_type', $dataSourceType)
->where('is_active', true)
->get()
->toArray();
// 3. 合并:数据库字段覆盖代码字段(按 field 名匹配)
$merged = [];
foreach ($defaultFields as $field) {
$merged[$field['field']] = $field;
}
foreach ($customFields as $field) {
$merged[$field['field']] = $field;
}
// 4. 排序
usort($merged, fn($a, $b) => ($a['sort_order'] ?? 999) <=> ($b['sort_order'] ?? 999));
return array_values($merged);
}
/**
* 从代码获取默认字段
*/
private function getDefaultFields(string $dataSourceType): array
{
// 硬编码的默认字段定义
// ...
}
}
```
**优点**
-**灵活性**:支持动态扩展
-**稳定性**:默认字段来自代码,相对稳定
-**向后兼容**:即使数据库没有配置,也能正常工作
**缺点**
- ⚠️ **实现更复杂**:需要处理合并逻辑
- ⚠️ **可能混淆**:代码和数据库配置可能冲突
---
## 三、项目现状分析
### 当前项目的配置管理方式
1. **数据源配置** (`data_sources` 集合)
- ✅ 存储在数据库中
- ✅ 有管理界面DataSourceController
- 说明:业务级配置,需要动态管理
2. **标签定义** (`tag_definitions` 集合)
- ✅ 存储在数据库中
- ✅ 有管理界面TagDefinitionController
- 说明:业务级配置,需要动态管理
3. **系统配置** (`config/` 目录)
- ✅ 配置文件(如 `data_collection_tasks.php`
- 说明:系统级配置,相对稳定
### 字段定义的特点
- **相对稳定**:表结构不会频繁变化
- **与代码耦合**:字段名必须与实际数据库字段一致
- **需要验证**:字段类型、可用性需要与代码逻辑保持一致
- **可能扩展**:未来可能需要添加新的预聚合字段
---
## 四、推荐方案
### 推荐:方案一(纯代码实现)+ 后续扩展为方案三
#### 第一阶段:纯代码实现(立即实施)
**理由**
1.**快速实现**:代码实现简单,可以快速上线
2.**字段相对稳定**:表字段不会频繁变化
3.**与代码耦合**:字段名必须与代码中的字段一致,代码管理更安全
4.**符合当前项目风格**:系统级配置用代码,业务级配置用数据库
**实现步骤**
1. 创建 `DataSourceFieldService` 类,硬编码字段定义
2.`TagDefinitionController` 添加 `getDataSourceFields()` 方法
3. 前端调用 API 获取字段列表
#### 第二阶段:如需扩展,升级为混合方案
**如果后续需要**
- 非技术人员管理字段配置
- 多环境不同配置
- 动态添加字段(如新的预聚合字段)
**则可以升级为方案三**
- 保留代码中的默认字段定义
- 添加数据库配置表和管理界面
- 支持数据库配置覆盖/扩展代码配置
---
## 五、具体实现建议
### 第一阶段实现(纯代码)
```php
// app/service/DataSourceFieldService.php
<?php
namespace app\service;
/**
* 数据源字段服务
*
* 职责:
* - 提供各数据源类型的字段列表
* - 字段定义暂时硬编码,后续可扩展为数据库配置
*/
class DataSourceFieldService
{
/**
* 获取数据源的字段列表
*
* @param string $dataSourceType 数据源类型
* @return array<array<string, mixed>> 字段列表
*/
public function getFields(string $dataSourceType): array
{
$fieldsMap = $this->getFieldsMap();
return $fieldsMap[$dataSourceType] ?? [];
}
/**
* 获取所有数据源的字段映射
*
* @return array<string, array<array<string, mixed>>>
*/
private function getFieldsMap(): array
{
return [
'user_profile' => [
[
'field' => 'user_id',
'type' => 'string',
'description' => '用户ID',
],
[
'field' => 'total_amount',
'type' => 'number',
'description' => '总消费金额',
'pre_aggregated_from' => 'consumption_records',
],
[
'field' => 'total_count',
'type' => 'number',
'description' => '总消费次数',
'pre_aggregated_from' => 'consumption_records',
],
[
'field' => 'last_consume_time',
'type' => 'datetime',
'description' => '最后消费时间',
],
[
'field' => 'gender',
'type' => 'number',
'description' => '性别0=女1=男2=未知)',
],
[
'field' => 'birthday',
'type' => 'datetime',
'description' => '生日',
],
// ... 更多字段
],
'user_phone_relations' => [
[
'field' => 'phone_number',
'type' => 'string',
'description' => '手机号',
],
[
'field' => 'user_id',
'type' => 'string',
'description' => '用户ID',
],
[
'field' => 'effective_time',
'type' => 'datetime',
'description' => '生效时间',
],
[
'field' => 'expire_time',
'type' => 'datetime',
'description' => '失效时间',
],
// ... 更多字段
],
'consumption_records' => [
// 注意:这些字段实际上来自 user_profile 的预聚合字段
[
'field' => 'total_amount',
'type' => 'number',
'description' => '总消费金额',
'source' => 'pre_aggregated',
'original_source' => 'consumption_records',
'note' => '从消费记录表预聚合到 user_profile',
],
[
'field' => 'total_count',
'type' => 'number',
'description' => '总消费次数',
'source' => 'pre_aggregated',
'original_source' => 'consumption_records',
'note' => '从消费记录表预聚合到 user_profile',
],
[
'field' => 'last_consume_time',
'type' => 'datetime',
'description' => '最后消费时间',
'source' => 'pre_aggregated',
'original_source' => 'consumption_records',
],
// 未来可以添加更多预聚合字段:
// - recent_30_days_amount
// - recent_90_days_amount
// - avg_amount
// - max_amount
// ...
],
];
}
}
```
```php
// app/controller/TagDefinitionController.php
// 添加新方法
/**
* 获取数据源的字段列表
*
* GET /api/tag-definitions/data-sources/{dataSourceType}/fields
*/
public function getDataSourceFields(Request $request, string $dataSourceType): Response
{
try {
$fieldService = new \app\service\DataSourceFieldService();
$fields = $fieldService->getFields($dataSourceType);
return ApiResponseHelper::success([
'data_source_type' => $dataSourceType,
'fields' => $fields,
]);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
```
```php
// config/route.php
// 添加路由
Route::get('/api/tag-definitions/data-sources/{dataSourceType}/fields', [TagDefinitionController::class, 'getDataSourceFields']);
```
---
## 六、总结
### 推荐方案
**第一阶段:纯代码实现**
- ✅ 简单、快速、稳定
- ✅ 符合字段定义的特性(与代码耦合、相对稳定)
- ✅ 符合当前项目的配置管理风格
**后续如需要:升级为混合方案**
- 保留代码默认字段
- 添加数据库配置支持动态扩展
- 两全其美
### 关键决策点
- **字段定义是否频繁变化?** → 否,代码实现
- **是否需要非技术人员管理?** → 现阶段不需要,代码实现
- **是否需要多环境不同配置?** → 不需要,代码实现
- **字段与代码是否强耦合?** → 是,代码实现更安全
**结论**:现阶段推荐纯代码实现,简单高效!
---
**文档生成时间**: 2025-01-28

View File

@@ -0,0 +1,330 @@
# 人物主表生成逻辑说明
## 一、设计原则
### 1. 用户ID策略
- **所有用户统一使用 UUID 作为 `user_id`**
- 身份证号只是 `user_profile` 表中的一个字段,不作为主键
- 转为正式用户时,直接更新身份证相关字段(`id_card_hash``id_card_encrypted`),无需变更 `user_id`
### 2. 表结构说明
- **`user_profile`(用户主表)**
- 主键:`user_id` (UUID)
- 身份证字段:`id_card_hash``id_card_encrypted``id_card_type`
- 标识字段:`is_temporary` (true=临时用户, false=正式用户)
- **`user_phone_relations`(手机关联表)**
- 管理手机号与用户的历史关联关系
- 支持时间窗口:`effective_time`(生效时间)、`expire_time`(失效时间)
- 支持手机号回收后二次分配的场景
### 3. 数据来源
- 主表数据来源于消费记录表(`consumption_records`
- 消费记录可能来自不同的数据库,时间线可能不一致
## 二、核心处理流程
### 场景1消费记录只有手机号没有身份证号
**流程:**
```
1. 接收消费记录:{ phone_number: "13800138000", id_card: null, consume_time: "2024-01-01 10:00:00" }
2. 使用 consume_time 作为查询时间点,在 user_phone_relations 表中查找该手机号在该时间点有效的关联
3. 如果找到关联:
- 使用关联的 user_id
- 更新该用户的统计信息total_amount, total_count, last_consume_time
4. 如果找不到关联:
- 创建临时用户is_temporary=true, user_id=UUID
- 在 user_phone_relations 中建立关联effective_time = consume_time
- 更新临时用户的统计信息
```
**关键点:**
- 必须使用 `consume_time` 作为查询时间点,而不是当前时间
- 临时用户创建后,必须建立手机关联,不能跳过
### 场景2消费记录只有身份证号没有手机号
**流程:**
```
1. 接收消费记录:{ phone_number: null, id_card: "110101199001011234", consume_time: "2024-01-01 10:00:00" }
2. 通过 id_card_hash 在 user_profile 中查找
3. 如果找到:
- 使用该 user_id可能是正式用户也可能是临时用户
- 更新统计信息
4. 如果找不到:
- 创建正式用户is_temporary=false, user_id=UUID
- 设置 id_card_hash 和 id_card_encrypted
- 更新统计信息
```
### 场景3消费记录同时有手机号和身份证号核心场景 - 触发合并)
**处理逻辑:**
#### 情况A身份证找到用户A手机号也关联到用户A
```
→ 直接使用用户A更新统计信息
```
#### 情况B身份证找到用户A正式用户手机号关联到用户B可能是临时用户且 A ≠ B
```
→ 触发合并逻辑:
1. 检查用户B是否为临时用户is_temporary=true
2. 如果用户B是临时用户
a. 合并用户B到用户APersonMergeService.mergeUsers(B, A)
b. 合并内容包括:统计数据、标签、消费记录等
c. 将手机号从用户B的关联标记为过期expire_time = consume_time
d. 建立手机号到用户A的新关联effective_time = consume_time
e. 标记用户B为已合并status=1, merged_from_user_id=A
f. 使用用户A
3. 如果用户B也是正式用户酒店预订等代订场景
a. 策略以身份证为准消费记录归属到身份证用户用户A
b. 手机号关联保持不变(不强制转移,因为可能是代订)
c. 记录异常日志,便于后续人工审核
d. 使用用户A身份证用户
```
#### 情况C身份证找到用户A正式用户手机号未关联
```
→ 建立手机关联到用户Aeffective_time = consume_time
→ 使用用户A更新统计信息
```
#### 情况D身份证未找到手机号关联到用户B
```
→ 检查用户B是否为临时用户
1. 如果用户B是临时用户
a. 更新用户B的身份证字段id_card_hash, id_card_encrypted
b. 将用户B标记为正式用户is_temporary=false
c. 使用用户B
2. 如果用户B不是临时用户但身份证不匹配
a. 创建新的正式用户user_id=UUID设置身份证字段
b. 将手机号从用户B的关联标记为过期expire_time = consume_time
c. 建立手机号到新用户的关联effective_time = consume_time
d. 使用新用户
```
#### 情况E身份证未找到手机号也未关联
```
→ 创建正式用户user_id=UUID, is_temporary=false
→ 设置身份证字段id_card_hash, id_card_encrypted
→ 建立手机关联effective_time = consume_time
→ 使用新用户,更新统计信息
```
## 三、时间线冲突处理方案
### 问题描述
**场景示例:**
```
2024-01-01: 用户A身份证I1使用手机M产生消费记录
2024-06-01: 用户A更换手机手机M不再使用
2024-12-01: 用户B身份证I2开始使用手机M产生消费记录
```
### 解决方案:基于消费记录时间点的精确匹配 + 智能过期处理
#### 1. 查询时使用消费记录的实际时间点
```php
// 在处理消费记录时,必须传入 consume_time
$consumeTime = new \DateTimeImmutable($payload['consume_time']);
$userId = $this->userPhoneService->findUserByPhone($phoneNumber, $consumeTime);
```
#### 2. 手机关联的过期时间设置策略
**自动过期检测:**
- 当发现同一手机号需要关联到新用户时,自动将旧关联标记为过期
- 过期时间设置为新关联的 `effective_time`(保证时间连续,避免间隙)
**处理逻辑:**
```php
// 当建立新关联时
if (手机号在 effective_time 已有有效关联 && 关联的用户ID不同) {
旧关联.expire_time = effective_time; // 标记为过期
旧关联.is_active = false;
创建新关联effective_time = consume_time;
}
```
#### 3. 时间线匹配示例
**处理 2024-01-01 的消费记录只有手机号M**
```
1. 查询手机M在 2024-01-01 的有效关联 → 未找到
2. 创建临时用户AUUID-xxx
3. 建立手机M关联effective_time = 2024-01-01, expire_time = null
```
**处理 2024-12-01 的消费记录手机M + 身份证I2**
```
1. 通过身份证I2查找用户 → 未找到
2. 查询手机M在 2024-12-01 的有效关联 → 找到用户A的关联expire_time=null仍然有效
3. 检查冲突用户A的关联有效期包含 2024-12-01
4. 由于提供了新身份证,判断为手机号回收场景:
a. 将用户A的手机关联标记为过期expire_time = 2024-12-01
b. 创建新用户BUUID-yyy设置身份证I2
c. 建立手机M到用户B的新关联effective_time = 2024-12-01
```
## 四、关键实现要点
### 1. 时间点查询机制
- `UserPhoneService::findUserByPhone()` 必须支持 `$atTime` 参数
- 查询时使用 `consume_time`,而不是当前时间
- `UserPhoneRelationRepository::findActiveByPhoneHash()` 需要基于时间窗口查询
### 2. 合并触发时机
- **仅在手机号和身份证号同时出现时触发合并**
- 合并前检查是否为临时用户
- 合并后需要处理手机关联的转移和过期
### 3. 用户ID一致性
- 所有用户统一使用 UUID
- 转为正式用户时,只更新身份证字段,不改变 `user_id`
- 合并时,保持目标用户的 `user_id` 不变
### 4. 数据完整性
- 临时用户必须建立手机关联(不能跳过)
- 正式用户的手机关联需要正确设置时间窗口
- 合并时需要处理统计数据、标签、消费记录等所有关联数据
## 五、合并逻辑详细说明
### 合并内容
1. **统计数据合并**total_amount、total_count、last_consume_time
2. **手机号关联合并**:将所有手机号关联转移到目标用户
3. **标签合并**:根据标签类型智能合并(数值型累加/取最值布尔型取OR等
4. **消费记录合并**:更新所有消费记录的 `user_id`
### 合并后处理
1. 标记源用户为已合并(`status=1`, `merged_from_user_id=目标用户ID`
2. 更新目标用户的标签更新时间
3. 触发标签重新计算(异步)
4. 记录合并历史日志
## 六、特殊场景处理
### 场景1手机号被转手多次历史记录链
**场景描述:**
一个手机号在不同时间段被分配给不同的用户,形成完整的历史记录链。
**时间线示例:**
```
13800138000 的历史关联:
├─ 2024-01-01 → 2024-06-01: 用户A (effective_time: 2024-01-01, expire_time: 2024-06-01)
├─ 2024-06-01 → 2024-12-01: 用户B (effective_time: 2024-06-01, expire_time: 2024-12-01)
└─ 2024-12-01 → 永久: 用户C (effective_time: 2024-12-01, expire_time: null)
```
**处理逻辑:**
1. 每次手机号转手时,系统自动检测冲突
2. 将旧关联的 `expire_time` 设置为新关联的 `effective_time`(保证时间连续)
3. 创建新关联,设置 `effective_time``expire_time`
4. 查询时按 `effective_time` 降序排序,取时间窗口内有效的关联
**查询示例:**
```php
// 查询 2024-03-01 时谁在使用该手机号
$userId = $userPhoneService->findUserByPhone('13800138000', new DateTime('2024-03-01'));
// 返回用户A因为 2024-01-01 <= 2024-03-01 < 2024-06-01
// 查询 2024-08-01 时谁在使用该手机号
$userId = $userPhoneService->findUserByPhone('13800138000', new DateTime('2024-08-01'));
// 返回用户B因为 2024-06-01 <= 2024-08-01 < 2024-12-01
```
**性能考虑:**
- 如果转手非常频繁,历史记录可能很多
- 查询时使用索引优化(`phone_hash` + `effective_time`
- 考虑定期归档过期很久的历史记录
### 场景2酒店预订等代订场景手机号和身份证不匹配
**场景描述:**
用户使用自己的手机号,但提供了其他人的身份证(如代订酒店、代买商品等)。
**示例:**
```
张三的手机号13800138000用户A正式用户
李四的身份证110101199001011234用户B正式用户
消费记录:
{
phone_number: "13800138000",
id_card: "110101199001011234",
consume_time: "2024-01-15 10:00:00"
}
```
**处理策略:**
1. **检测冲突**
- 通过身份证找到用户B
- 通过手机号在消费时间点查询找到用户A
- 发现用户A ≠ 用户B且两者都是正式用户
2. **决策逻辑**
- **以身份证为准**消费记录归属到身份证用户用户B
- **手机号关联保持不变**:不强制转移手机号到身份证用户
- **原因**:这可能是代订场景,手机号仍属于原用户,不应自动转移
3. **日志记录**
```php
LoggerHelper::logBusiness('phone_id_card_mismatch_formal_users', [
'phone_number' => '13800138000',
'phone_user_id' => 'user-a-uuid',
'id_card_user_id' => 'user-b-uuid',
'consume_time' => '2024-01-15 10:00:00',
'decision' => 'use_id_card_user',
'note' => '正式用户冲突,以身份证为准(可能是代订场景)',
]);
```
4. **结果**
- 消费记录归属到用户B身份证用户
- 用户A的手机号关联保持不变
- 记录异常日志,便于后续人工审核
**为什么这样处理?**
- 身份证在业务场景中通常更可信(需要实名验证)
- 代订场景很常见,不应该自动合并不同的正式用户
- 保持手机号关联的准确性,避免误操作
- 通过日志记录异常情况,可以后续人工审核是否需要调整
**对比:临时用户合并场景**
- 如果是临时用户手机号vs 正式用户(身份证),则自动合并
- 因为临时用户通常是系统自动创建的,合并是合理的
## 七、与现有架构的对比
### 现有实现已满足的点:
✅ 手机号关联表支持时间窗口
✅ 临时用户创建和手机关联
✅ 用户合并服务PersonMergeService
✅ 身份证哈希和加密存储
### 需要改进的点:
⚠️ `resolvePersonId()` 需要支持 `$atTime` 参数
⚠️ `ConsumptionService::createRecord()` 需要传入 `consume_time`
⚠️ 合并逻辑需要在手机号+身份证同时出现时自动触发
⚠️ 手机关联建立时需要自动处理过期旧关联
## 八、总结
现有主用户表数据生成逻辑**基本满足要求**,但需要以下改进:
1. **时间线处理**:所有手机号查询必须基于消费记录的实际时间点
2. **自动合并**:当手机号和身份证号同时出现且关联到不同用户时,自动触发合并
3. **冲突解决**:建立新关联时,自动将旧关联标记为过期
4. **用户ID策略**统一使用UUID身份证仅作为字段存储
这些改进确保了:
- 用户身份信息的最大化有效性
- 手机号回收后的正确匹配
- 临时用户到正式用户的平滑转换
- 跨数据源时间线不一致的正确处理
g

View File

@@ -0,0 +1,407 @@
# 前端代码风格和设计思路
## 一、技术栈
### 核心技术
- **Vue 3** (v3.4.21) - 采用 Composition API 和 `<script setup>` 语法
- **TypeScript** (v5.4.2) - 完整的类型系统支持
- **Vite** (v5.1.6) - 快速的前端构建工具
- **Element Plus** (v2.5.6) - UI 组件库
- **Pinia** (v2.1.7) - 状态管理
- **Vue Router** (v4.3.0) - 路由管理
- **Axios** (v1.6.7) - HTTP 请求库
### 开发工具
- **Vue TSC** - TypeScript 类型检查
- **ESLint** - 代码规范检查
- **Sass** - CSS 预处理器
## 二、项目结构
```
TaskShow/
├── src/
│ ├── api/ # API 接口定义(按模块划分)
│ │ ├── dataCollection.ts
│ │ ├── dataSource.ts
│ │ ├── tagTask.ts
│ │ ├── tagDefinition.ts
│ │ ├── tagQuery.ts
│ │ ├── tagCohort.ts
│ │ └── user.ts
│ ├── components/ # 公共组件
│ │ ├── Layout/ # 布局组件
│ │ ├── StatusBadge/ # 状态徽章
│ │ └── ProgressDisplay/ # 进度显示
│ ├── router/ # 路由配置
│ │ └── index.ts
│ ├── store/ # Pinia 状态管理(按模块划分)
│ │ ├── index.ts # 统一导出
│ │ ├── user.ts
│ │ ├── dataCollection.ts
│ │ ├── tagTask.ts
│ │ ├── tagDefinition.ts
│ │ └── dataSource.ts
│ ├── types/ # TypeScript 类型定义
│ │ ├── index.ts # 业务类型
│ │ └── api.ts # API 相关类型
│ ├── utils/ # 工具函数
│ │ ├── request.ts # Axios 请求封装
│ │ ├── format.ts # 日期时间格式化
│ │ ├── mask.ts # 数据脱敏
│ │ ├── validator.ts # 表单验证
│ │ └── index.ts # 统一导出
│ ├── views/ # 页面组件(按功能模块划分)
│ │ ├── Dashboard/ # 首页仪表盘
│ │ ├── DataCollection/ # 数据采集
│ │ ├── DataSource/ # 数据源管理
│ │ ├── TagTask/ # 标签任务
│ │ ├── TagDefinition/ # 标签定义
│ │ ├── TagFilter/ # 标签筛选
│ │ └── TagQuery/ # 标签查询
│ ├── App.vue # 根组件
│ └── main.ts # 入口文件
├── index.html
├── vite.config.ts # Vite 配置
├── tsconfig.json # TypeScript 配置
└── package.json
```
## 三、代码风格规范
### 1. TypeScript 使用规范
- **严格模式**:启用 TypeScript 严格类型检查
- **类型定义**:所有 API 接口、组件 Props、状态数据都有完整的类型定义
- **类型导出**:类型定义统一放在 `types/` 目录,按模块分类
- **接口命名**:使用 PascalCase`DataCollectionTask``TagDefinition`
- **类型推断**:充分利用 TypeScript 类型推断,减少冗余类型声明
```typescript
// ✅ 好的实践
interface DataCollectionTask {
task_id: string
name: string
status: 'pending' | 'running' | 'paused' | 'stopped' | 'error'
}
// ✅ 使用类型推断
const tasks = ref<DataCollectionTask[]>([])
```
### 2. Vue 3 Composition API 规范
- **使用 `<script setup>`**:所有组件使用 `<script setup lang="ts">` 语法
- **响应式数据**:使用 `ref``reactive`,优先使用 `ref`
- **生命周期**:使用组合式 API 的生命周期钩子(`onMounted``onUnmounted` 等)
- **计算属性**:使用 `computed` 定义计算属性
- **方法定义**:使用箭头函数或普通函数,保持一致性
```vue
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const handleClick = () => {
count.value++
}
onMounted(() => {
// 初始化逻辑
})
</script>
```
### 3. 组件设计规范
- **组件命名**:使用 PascalCase`StatusBadge.vue``TaskForm.vue`
- **单文件组件**:每个组件一个文件,文件名与组件名一致
- **Props 定义**:使用 `defineProps` 定义,并指定类型
- **Emits 定义**:使用 `defineEmits` 定义,并指定事件类型
- **组件拆分**:保持组件职责单一,复杂组件拆分为多个子组件
```vue
<script setup lang="ts">
interface Props {
status: 'pending' | 'running' | 'completed'
message?: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
change: [value: string]
}>()
</script>
```
### 4. API 调用规范
- **统一封装**:所有 API 调用通过 `utils/request.ts` 封装的 `request` 实例
- **按模块划分**API 文件按业务模块划分,如 `tagQuery.ts``dataCollection.ts`
- **类型安全**:所有 API 方法都有完整的类型定义
- **错误处理**:统一在请求拦截器中处理错误,组件中只需处理业务逻辑
```typescript
// ✅ API 定义示例
export const getTagStatistics = (params?: {
tag_id?: string
start_date?: string
end_date?: string
}) => {
return request.get<TagStatistics>('/tags/statistics', params)
}
// ✅ 组件中使用
const loadStatistics = async () => {
try {
const response = await getTagStatistics()
statistics.value = response.data
} catch (error) {
console.error('加载统计失败:', error)
}
}
```
### 5. 状态管理规范
- **使用 Pinia**:所有状态管理使用 Pinia
- **按模块划分**Store 按业务模块划分,如 `dataCollection.ts``tagTask.ts`
- **Actions 命名**:使用动词开头,如 `fetchTasks``createTask``updateTask`
- **Getters 使用**:使用 getters 计算派生状态
```typescript
// ✅ Store 定义示例
export const useDataCollectionStore = defineStore('dataCollection', () => {
const tasks = ref<DataCollectionTask[]>([])
const fetchTasks = async (params: any) => {
const response = await getDataCollectionTaskList(params)
tasks.value = response.data.tasks
return response.data
}
return {
tasks,
fetchTasks
}
})
```
### 6. 样式规范
- **使用 Scoped CSS**:组件样式使用 `<style scoped>`
- **使用 SCSS**:使用 SCSS 预处理器,支持嵌套和变量
- **BEM 命名**:类名使用 BEM 命名规范(可选)
- **Element Plus 主题**:使用 Element Plus 默认主题,保持一致性
```vue
<style scoped lang="scss">
.task-list {
padding: 20px;
.task-item {
margin-bottom: 10px;
&__title {
font-size: 16px;
}
}
}
</style>
```
### 7. 文件命名规范
- **组件文件**PascalCase`TaskForm.vue``StatusBadge.vue`
- **工具文件**camelCase`request.ts``format.ts`
- **类型文件**camelCase`api.ts``index.ts`
- **目录名**kebab-case 或 PascalCase保持一致性
## 四、设计思路
### 1. 请求封装设计
**设计目标**:统一处理 HTTP 请求提供类型安全、错误处理、Loading 状态等功能。
**实现特点**
- 自动添加 Token 到请求头
- 统一错误处理和提示
- 自动显示/隐藏 Loading
- 完整的 TypeScript 类型支持
- 支持自定义配置showLoading、showError、timeout 等)
**使用方式**
```typescript
// GET 请求
const response = await request.get<TagStatistics>('/tags/statistics', params)
// POST 请求
const response = await request.post<DataCollectionTask>('/data-collection-tasks', data)
```
### 2. 状态管理设计
**设计思路**
- 按业务模块划分 Store每个模块独立管理自己的状态
- 使用 Composition API 风格的 Store 定义(`setup` 函数)
- Store 中封装 API 调用逻辑,组件直接调用 Store 方法
- 支持响应式更新,组件自动响应状态变化
**优势**
- 代码组织清晰,易于维护
- 状态复用方便
- 类型安全
### 3. 路由设计
**设计思路**
- 使用嵌套路由Layout 组件作为父路由
- 路由按功能模块组织,路径清晰
- 路由元信息meta存储页面标题等信息
- 支持动态路由参数
**路由结构**
```
/ (Layout)
├── /dashboard (首页)
├── /data-collection (数据采集)
│ ├── /tasks (任务列表)
│ ├── /tasks/create (创建任务)
│ └── /tasks/:id (任务详情)
├── /tag-tasks (标签任务)
├── /tag-definitions (标签定义)
├── /tag-filter (标签筛选)
├── /tag-query (标签查询)
└── /data-sources (数据源)
```
### 4. 组件设计
**设计原则**
- **单一职责**:每个组件只负责一个功能
- **可复用性**:公共组件设计为可复用
- **可维护性**:组件结构清晰,易于理解和修改
- **类型安全**Props 和 Emits 都有完整类型定义
**组件分类**
- **布局组件**Layout、Header、Sidebar
- **业务组件**TaskForm、TaskList、StatusBadge
- **工具组件**ProgressDisplay、各种工具函数
### 5. 类型系统设计
**设计思路**
- 所有业务实体都有对应的 TypeScript 接口定义
- API 请求和响应都有完整类型定义
- 使用联合类型定义枚举值(如状态、类型等)
- 类型定义统一管理,便于维护
**类型文件组织**
- `types/index.ts`业务实体类型Task、Tag、User 等)
- `types/api.ts`API 相关类型ApiResponse、RequestConfig 等)
### 6. 工具函数设计
**设计原则**
- 函数职责单一,易于测试
- 完整的类型定义
- 错误处理完善
- 支持多种输入格式
**工具函数分类**
- **格式化工具**日期时间格式化format.ts
- **数据脱敏**手机号、身份证等脱敏mask.ts
- **表单验证**表单字段验证validator.ts
- **HTTP 请求**请求封装request.ts
## 五、最佳实践
### 1. 错误处理
```typescript
// ✅ 好的实践:在组件中处理错误
const loadData = async () => {
try {
const response = await getData()
data.value = response.data
} catch (error: any) {
// 错误已在拦截器中提示,这里只需处理业务逻辑
console.error('加载数据失败:', error)
}
}
```
### 2. Loading 状态
```typescript
// ✅ 使用组件级别的 loading 状态
const loading = ref(false)
const loadData = async () => {
loading.value = true
try {
await fetchData()
} finally {
loading.value = false
}
}
```
### 3. 响应式数据
```typescript
// ✅ 优先使用 ref
const count = ref(0)
const name = ref('')
// ✅ 对象使用 reactive 或 ref
const form = reactive({
name: '',
age: 0
})
// 或
const form = ref({
name: '',
age: 0
})
```
### 4. 计算属性
```typescript
// ✅ 使用 computed 计算派生状态
const filteredTasks = computed(() => {
return tasks.value.filter(task => task.status === 'running')
})
```
### 5. 组件通信
```typescript
// ✅ Props 向下传递
interface Props {
task: DataCollectionTask
}
const props = defineProps<Props>()
// ✅ Emits 向上传递
const emit = defineEmits<{
update: [task: DataCollectionTask]
delete: [id: string]
}>()
```
## 六、开发规范总结
1. **代码风格**:使用 ESLint 自动格式化,保持代码风格一致
2. **类型安全**:充分利用 TypeScript避免使用 `any`
3. **组件化**:保持组件小而专一,提高可复用性
4. **状态管理**:合理使用 Pinia避免过度使用全局状态
5. **API 调用**:统一使用封装的 request 方法,保持一致性
6. **错误处理**:统一错误处理机制,提供良好的用户体验
7. **代码注释**:关键逻辑添加注释,提高代码可读性

View File

@@ -0,0 +1,666 @@
# 前端功能说明
## 一、系统概述
TaskShow 是一个基于 Vue 3 + TypeScript + Element Plus 的前端管理系统,主要用于数据采集任务管理、标签任务管理、标签查询和用户管理等核心功能。
## 二、功能模块
### 1. 首页仪表盘 (Dashboard)
**路由**: `/dashboard`
**组件**: `src/views/Dashboard/index.vue`
**功能说明**
- 显示系统核心统计数据
- 数据采集任务总数
- 标签任务总数
- 运行中任务数量
- 用户总数
- 展示最近任务列表(数据采集任务和标签任务)
- 提供快速操作入口
- 创建数据采集任务
- 创建标签任务
- 标签筛选
- 标签查询
**数据来源**
- 通过 Pinia Store 获取任务列表
- 统计运行中任务数量
- 展示最近更新的任务
---
### 2. 数据采集模块 (Data Collection)
#### 2.1 数据采集任务列表
**路由**: `/data-collection/tasks`
**组件**: `src/views/DataCollection/TaskList.vue`
**功能说明**
- 展示所有数据采集任务列表
- 支持按任务名称、状态筛选
- 支持分页显示
- 提供任务操作功能:
- 查看任务详情
- 编辑任务
- 删除任务
- 启动/暂停/停止任务
- 查看任务进度
**API 接口**
- `getDataCollectionTaskList` - 获取任务列表
- `deleteDataCollectionTask` - 删除任务
- `startDataCollectionTask` - 启动任务
- `pauseDataCollectionTask` - 暂停任务
- `stopDataCollectionTask` - 停止任务
#### 2.2 创建/编辑数据采集任务
**路由**:
- 创建:`/data-collection/tasks/create`
- 编辑:`/data-collection/tasks/:id/edit`
**组件**: `src/views/DataCollection/TaskForm.vue`
**功能说明**
- 创建或编辑数据采集任务
- 配置任务基本信息:
- 任务名称、描述
- 数据源选择(源数据源、目标数据源)
- 数据库和集合选择
- 单集合/多集合模式
- 配置字段映射:
- 源字段到目标字段的映射
- 字段转换规则
- 值映射配置
- 配置 Lookup 关联:
- 多集合关联查询配置
- 配置过滤条件:
- 数据筛选条件
- 配置调度计划:
- 启用/禁用定时任务
- Cron 表达式配置
- 预览查询结果:
- 验证配置是否正确
- 查看查询结果示例
**API 接口**
- `getDataSources` - 获取数据源列表
- `getDatabases` - 获取数据库列表
- `getCollections` - 获取集合列表
- `getFields` - 获取字段列表
- `getHandlerTargetFields` - 获取目标字段列表
- `previewQuery` - 预览查询结果
- `createDataCollectionTask` - 创建任务
- `updateDataCollectionTask` - 更新任务
- `getDataCollectionTaskDetail` - 获取任务详情
#### 2.3 数据采集任务详情
**路由**: `/data-collection/tasks/:id`
**组件**: `src/views/DataCollection/TaskDetail.vue`
**功能说明**
- 展示任务详细信息
- 显示任务配置信息
- 显示任务执行进度:
- 处理数量、成功数量、错误数量
- 进度百分比
- 开始时间、结束时间
- 最后同步时间
- 显示任务统计信息
- 显示任务执行历史
- 提供任务操作按钮(启动、暂停、停止、编辑、删除)
**API 接口**
- `getDataCollectionTaskDetail` - 获取任务详情
- `getDataCollectionTaskProgress` - 获取任务进度
- 任务操作相关接口
---
### 3. 数据源管理模块 (Data Source)
#### 3.1 数据源列表
**路由**: `/data-sources`
**组件**: `src/views/DataSource/List.vue`
**功能说明**
- 展示所有数据源配置列表
- 支持按类型、状态、名称筛选
- 支持分页显示
- 显示数据源基本信息:
- 名称、类型、主机、端口、数据库
- 状态(启用/禁用)
- 提供数据源操作:
- 查看详情
- 编辑配置
- 删除数据源
- 测试连接
**API 接口**
- `getDataSourceList` - 获取数据源列表
- `deleteDataSource` - 删除数据源
- `testDataSourceConnection` - 测试连接
#### 3.2 创建/编辑数据源
**路由**:
- 创建:`/data-sources/create`
- 编辑:`/data-sources/:id/edit`
**组件**: `src/views/DataSource/Form.vue`
**功能说明**
- 创建或编辑数据源配置
- 配置数据源基本信息:
- 名称、类型MongoDB、MySQL、PostgreSQL
- 主机、端口、数据库名
- 用户名、密码(加密存储)
- 认证源MongoDB
- 其他选项配置
- 测试数据源连接
- 标记是否为标签引擎数据库
**API 接口**
- `createDataSource` - 创建数据源
- `updateDataSource` - 更新数据源
- `getDataSourceDetail` - 获取数据源详情
- `testDataSourceConnection` - 测试连接
---
### 4. 标签任务模块 (Tag Task)
#### 4.1 标签任务列表
**路由**: `/tag-tasks`
**组件**: `src/views/TagTask/TaskList.vue`
**功能说明**
- 展示所有标签任务列表
- 支持按任务名称、类型、状态筛选
- 支持分页显示
- 显示任务基本信息:
- 任务名称、类型、状态
- 目标标签、用户范围
- 创建时间、更新时间
- 提供任务操作:
- 查看任务详情
- 编辑任务
- 删除任务
- 启动/暂停/停止任务
**API 接口**
- `getTagTaskList` - 获取任务列表
- `deleteTagTask` - 删除任务
- `startTagTask` - 启动任务
- `pauseTagTask` - 暂停任务
- `stopTagTask` - 停止任务
#### 4.2 创建/编辑标签任务
**路由**:
- 创建:`/tag-tasks/create`
- 编辑:`/tag-tasks/:id/edit`
**组件**: `src/views/TagTask/TaskForm.vue`
**功能说明**
- 创建或编辑标签任务
- 配置任务基本信息:
- 任务名称、描述
- 任务类型(全量、增量、指定)
- 配置目标标签:
- 选择要计算的标签列表
- 配置用户范围:
- 全部用户
- 指定用户列表
- 按条件筛选用户
- 配置调度计划:
- 启用/禁用定时任务
- Cron 表达式配置
- 配置任务参数:
- 并发数、批次大小
- 错误处理策略
**API 接口**
- `getTagDefinitionList` - 获取标签定义列表(用于选择目标标签)
- `createTagTask` - 创建任务
- `updateTagTask` - 更新任务
- `getTagTaskDetail` - 获取任务详情
#### 4.3 标签任务详情
**路由**: `/tag-tasks/:id`
**组件**: `src/views/TagTask/TaskDetail.vue`
**功能说明**
- 展示任务详细信息
- 显示任务配置信息
- 显示任务执行进度:
- 总用户数、已处理用户数
- 成功数量、错误数量
- 进度百分比
- 显示任务统计信息:
- 总执行次数
- 成功/失败次数
- 最后执行时间
- 显示任务执行记录列表
- 提供任务操作按钮(启动、暂停、停止、编辑、删除)
**API 接口**
- `getTagTaskDetail` - 获取任务详情
- `getTagTaskExecutions` - 获取执行记录
---
### 5. 标签定义模块 (Tag Definition)
#### 5.1 标签定义列表
**路由**: `/tag-definitions`
**组件**: `src/views/TagDefinition/List.vue`
**功能说明**
- 展示所有标签定义列表
- 支持按名称、分类、状态筛选
- 支持分页显示
- 显示标签基本信息:
- 标签代码、名称、分类
- 规则类型、更新频率
- 状态(启用/禁用)
- 优先级、版本
- 提供标签操作:
- 查看详情
- 编辑标签
- 删除标签
- 批量初始化
**API 接口**
- `getTagDefinitionList` - 获取标签定义列表
- `deleteTagDefinition` - 删除标签定义
- `batchInitTagDefinitions` - 批量初始化
#### 5.2 创建/编辑标签定义
**路由**:
- 创建:`/tag-definitions/create`
- 编辑:`/tag-definitions/:id/edit`
**组件**: `src/views/TagDefinition/Form.vue`
**功能说明**
- 创建或编辑标签定义
- 配置标签基本信息:
- 标签代码、名称、分类
- 描述
- 配置规则类型:
- 简单规则
- 管道规则
- 自定义规则
- 配置规则条件:
- 字段、操作符、值
- 多个条件的逻辑关系
- 配置标签值:
- 标签值的计算方式
- 配置更新频率:
- 实时、每日、每周、每月
- 配置优先级和版本
**API 接口**
- `createTagDefinition` - 创建标签定义
- `updateTagDefinition` - 更新标签定义
- `getTagDefinitionDetail` - 获取标签定义详情
#### 5.3 标签定义详情
**路由**: `/tag-definitions/:id`
**组件**: `src/views/TagDefinition/Detail.vue`
**功能说明**
- 展示标签定义详细信息
- 显示标签配置信息
- 显示规则配置详情
- 显示标签统计信息(如果有)
- 提供编辑和删除操作
**API 接口**
- `getTagDefinitionDetail` - 获取标签定义详情
---
### 6. 标签筛选模块 (Tag Filter)
**路由**: `/tag-filter`
**组件**: `src/views/TagFilter/index.vue`
**功能说明**
- 根据标签条件筛选用户
- 支持多条件组合:
- 添加多个标签条件
- 设置条件逻辑关系AND/OR
- 支持多种操作符:
- 等于、不等于
- 大于、大于等于、小于、小于等于
- 包含、不包含
- 在列表中、不在列表中
- 显示筛选结果:
- 用户列表
- 用户基本信息(姓名、手机号等)
- 用户标签信息
- 支持分页显示
- 支持导出筛选结果
- 支持保存为人群快照
**API 接口**
- `filterUsersByTags` - 根据标签筛选用户
- `createTagCohort` - 创建人群快照(保存筛选结果)
---
### 7. 标签查询模块 (Tag Query)
#### 7.1 用户标签查询
**路由**: `/tag-query/user`
**组件**: `src/views/TagQuery/User.vue`
**功能说明**
- 通过用户ID或手机号查询用户标签
- 显示用户基本信息:
- 用户ID、姓名、手机号
- 消费总额、消费次数
- 最后消费时间
- 显示用户所有标签:
- 标签名称、代码、分类
- 标签值、值类型
- 置信度
- 生效时间、过期时间
- 更新时间
- 支持重新计算用户标签
- 支持删除用户标签
- 支持查看标签历史记录
**API 接口**
- `getUserTags` - 获取用户标签
- `recalculateUserTags` - 重新计算用户标签
- `deleteUserTag` - 删除用户标签
- `getTagHistory` - 获取标签历史(按用户筛选)
#### 7.2 标签统计
**路由**: `/tag-query/statistics`
**组件**: `src/views/TagQuery/Statistics.vue`
**功能说明**
- 展示标签统计信息
- 标签覆盖度统计:
- 总用户数
- 已打标签用户数
- 覆盖率(百分比)
- 标签值分布:
- 各标签值的出现次数
- 分布图表展示
- 标签趋势数据:
- 按日期统计标签变更次数
- 趋势图表展示
- 支持按标签筛选
- 支持按时间范围筛选
**API 接口**
- `getTagStatistics` - 获取标签统计信息
#### 7.3 标签历史
**路由**: `/tag-query/history`
**组件**: `src/views/TagQuery/History.vue`
**功能说明**
- 展示标签变更历史记录
- 支持多维度筛选:
- 按用户筛选
- 按标签筛选
- 按时间范围筛选
- 显示历史记录详情:
- 用户ID
- 标签ID、标签名称
- 旧值、新值
- 变更原因
- 变更时间
- 操作人
- 支持分页显示
- 支持导出历史记录
**API 接口**
- `getTagHistory` - 获取标签历史记录
---
## 三、公共组件
### 1. Layout 布局组件
**组件**: `src/components/Layout/index.vue`
**功能说明**
- 提供系统整体布局结构
- 侧边栏导航菜单:
- 首页
- 数据采集(任务列表、数据源配置)
- 标签任务(任务列表、标签定义)
- 标签筛选
- 标签查询(用户标签、标签统计、标签历史)
- 顶部导航栏:
- 侧边栏折叠/展开按钮
- 用户信息下拉菜单
- 主内容区域:
- 路由视图容器
### 2. StatusBadge 状态徽章
**组件**: `src/components/StatusBadge/index.vue`
**功能说明**
- 显示任务状态的可视化组件
- 支持多种状态:
- pending待处理- 灰色
- running运行中- 蓝色
- paused已暂停- 黄色
- stopped已停止- 橙色
- completed已完成- 绿色
- error错误- 红色
### 3. ProgressDisplay 进度显示
**组件**: `src/components/ProgressDisplay/index.vue`
**功能说明**
- 显示任务执行进度的可视化组件
- 显示进度条和百分比
- 显示处理数量、成功数量、错误数量
---
## 四、工具函数
### 1. 格式化工具 (`utils/format.ts`)
- `formatDateTime` - 格式化日期时间默认格式YYYY-MM-DD HH:mm:ss
- `formatDate` - 格式化日期格式YYYY-MM-DD
- `formatTime` - 格式化时间格式HH:mm:ss
- `formatRelativeTime` - 相对时间格式化1分钟前、2小时前
### 2. 数据脱敏工具 (`utils/mask.ts`)
- `maskPhone` - 脱敏手机号138****8000
- `maskIdCard` - 脱敏身份证号110101********1234
- `maskBankCard` - 脱敏银行卡号
- `maskName` - 脱敏姓名(如:张*、李**
- `maskEmail` - 脱敏邮箱
### 3. 表单验证工具 (`utils/validator.ts`)
- 提供常用的表单验证规则
- 手机号、邮箱、身份证等格式验证
---
## 五、路由配置
### 路由结构
```
/ (Layout)
├── /dashboard (首页)
├── /data-collection (数据采集)
│ ├── /tasks (任务列表)
│ ├── /tasks/create (创建任务)
│ ├── /tasks/:id (任务详情)
│ └── /tasks/:id/edit (编辑任务)
├── /data-sources (数据源)
│ ├── / (数据源列表)
│ ├── /create (创建数据源)
│ └── /:id/edit (编辑数据源)
├── /tag-tasks (标签任务)
│ ├── / (任务列表)
│ ├── /create (创建任务)
│ ├── /:id (任务详情)
│ └── /:id/edit (编辑任务)
├── /tag-definitions (标签定义)
│ ├── / (标签列表)
│ ├── /create (创建标签)
│ ├── /:id (标签详情)
│ └── /:id/edit (编辑标签)
├── /tag-filter (标签筛选)
└── /tag-query (标签查询)
├── /user (用户标签查询)
├── /statistics (标签统计)
└── /history (标签历史)
```
---
## 六、状态管理 (Pinia Store)
### 1. User Store (`store/user.ts`)
- 管理用户登录状态
- 管理 Token
- 用户信息
### 2. DataCollection Store (`store/dataCollection.ts`)
- 管理数据采集任务列表
- 提供任务操作方法(获取、创建、更新、删除等)
### 3. TagTask Store (`store/tagTask.ts`)
- 管理标签任务列表
- 提供任务操作方法
### 4. TagDefinition Store (`store/tagDefinition.ts`)
- 管理标签定义列表
- 提供标签定义操作方法
### 5. DataSource Store (`store/dataSource.ts`)
- 管理数据源列表
- 提供数据源操作方法
---
## 七、功能特性总结
### 1. 核心功能
- ✅ 数据采集任务管理(创建、编辑、删除、启动、暂停、停止)
- ✅ 数据源管理(配置、测试连接)
- ✅ 标签任务管理(创建、编辑、删除、执行)
- ✅ 标签定义管理(创建、编辑、删除、批量初始化)
- ✅ 标签查询(用户标签、标签统计、标签历史)
- ✅ 标签筛选(多条件组合筛选用户)
- ✅ 人群快照(保存筛选结果、导出)
### 2. 用户体验
- ✅ 统一的 UI 设计Element Plus
- ✅ 响应式布局
- ✅ Loading 状态提示
- ✅ 错误提示和处理
- ✅ 数据脱敏显示
- ✅ 日期时间格式化显示
### 3. 技术特性
- ✅ TypeScript 类型安全
- ✅ 组件化开发
- ✅ 状态管理Pinia
- ✅ 路由管理Vue Router
- ✅ API 统一封装
- ✅ 工具函数复用
---
## 八、使用说明
### 1. 开发环境启动
```bash
cd TaskShow
npm install # 或 yarn install 或 pnpm install
npm run dev # 启动开发服务器
```
### 2. 构建生产版本
```bash
npm run build
```
### 3. 预览构建结果
```bash
npm run preview
```
### 4. 代码检查
```bash
npm run lint
```
---
## 九、注意事项
1. **API 地址配置**
- 开发环境通过 Vite 代理配置
- 生产环境通过环境变量 `VITE_API_BASE_URL` 配置
2. **Token 管理**
- Token 存储在 Pinia Store 中
- 请求时自动添加到请求头
3. **错误处理**
- HTTP 错误和业务错误在请求拦截器中统一处理
- 组件中只需处理业务逻辑
4. **数据脱敏**
- 敏感信息(手机号、身份证等)使用脱敏工具函数处理
- 确保数据安全显示
5. **类型安全**
- 所有 API 接口都有完整的 TypeScript 类型定义
- 建议启用严格模式以获得更好的类型检查

View File

@@ -0,0 +1,693 @@
# 前端已对接API说明
## 一、API 架构设计
### 1. 请求封装
所有 API 调用通过 `src/utils/request.ts` 封装的 `request` 实例,提供以下特性:
- ✅ 自动添加 Token 到请求头(从 Pinia Store 获取)
- ✅ 统一错误处理和提示HTTP 错误、业务错误)
- ✅ 自动显示/隐藏 Loading可配置
- ✅ 完整的 TypeScript 类型支持
- ✅ 支持自定义配置showLoading、showError、timeout 等)
### 2. API 文件组织
API 文件按业务模块划分,位于 `src/api/` 目录:
- `dataCollection.ts` - 数据采集任务相关 API
- `dataSource.ts` - 数据源管理相关 API
- `tagTask.ts` - 标签任务相关 API
- `tagDefinition.ts` - 标签定义相关 API
- `tagQuery.ts` - 标签查询相关 API
- `tagCohort.ts` - 人群快照相关 API
- `user.ts` - 用户相关 API
### 3. 类型定义
所有 API 的请求参数和响应数据都有完整的 TypeScript 类型定义,位于:
- `src/types/index.ts` - 业务实体类型
- `src/types/api.ts` - API 响应类型
## 二、已对接 API 列表
### 1. 数据采集任务 API (`dataCollection.ts`)
#### 1.1 获取数据采集任务列表
```typescript
getDataCollectionTaskList(params: {
name?: string
status?: string
page?: number
page_size?: number
}): Promise<ApiResponse<{
tasks: DataCollectionTask[]
total: number
page: number
page_size: number
}>>
```
- **路径**: `GET /data-collection-tasks`
- **功能**: 获取数据采集任务列表,支持按名称、状态筛选和分页
#### 1.2 获取数据采集任务详情
```typescript
getDataCollectionTaskDetail(taskId: string): Promise<ApiResponse<DataCollectionTask>>
```
- **路径**: `GET /data-collection-tasks/:id`
- **功能**: 获取指定任务的详细信息
#### 1.3 创建数据采集任务
```typescript
createDataCollectionTask(data: Partial<DataCollectionTask>): Promise<ApiResponse<DataCollectionTask>>
```
- **路径**: `POST /data-collection-tasks`
- **功能**: 创建新的数据采集任务
#### 1.4 更新数据采集任务
```typescript
updateDataCollectionTask(taskId: string, data: Partial<DataCollectionTask>): Promise<ApiResponse<DataCollectionTask>>
```
- **路径**: `PUT /data-collection-tasks/:id`
- **功能**: 更新指定任务的信息
#### 1.5 删除数据采集任务
```typescript
deleteDataCollectionTask(taskId: string): Promise<ApiResponse>
```
- **路径**: `DELETE /data-collection-tasks/:id`
- **功能**: 删除指定任务
#### 1.6 启动数据采集任务
```typescript
startDataCollectionTask(taskId: string): Promise<ApiResponse>
```
- **路径**: `POST /data-collection-tasks/:id/start`
- **功能**: 启动指定任务
#### 1.7 暂停数据采集任务
```typescript
pauseDataCollectionTask(taskId: string): Promise<ApiResponse>
```
- **路径**: `POST /data-collection-tasks/:id/pause`
- **功能**: 暂停指定任务
#### 1.8 停止数据采集任务
```typescript
stopDataCollectionTask(taskId: string): Promise<ApiResponse>
```
- **路径**: `POST /data-collection-tasks/:id/stop`
- **功能**: 停止指定任务
#### 1.9 获取任务进度
```typescript
getDataCollectionTaskProgress(taskId: string): Promise<ApiResponse>
```
- **路径**: `GET /data-collection-tasks/:id/progress`
- **功能**: 获取任务执行进度
#### 1.10 获取数据源列表
```typescript
getDataSources(): Promise<ApiResponse<DataSource[]>>
```
- **路径**: `GET /data-collection-tasks/data-sources`
- **功能**: 获取所有可用的数据源列表
#### 1.11 获取数据库列表
```typescript
getDatabases(dataSourceId: string): Promise<ApiResponse<Array<{ name: string; id: string }>>>
```
- **路径**: `GET /data-collection-tasks/data-sources/:id/databases`
- **功能**: 获取指定数据源的数据库列表
#### 1.12 获取集合列表
```typescript
getCollections(dataSourceId: string, database: string | { name: string; id: string }): Promise<ApiResponse<Array<{ name: string; id: string }>>>
```
- **路径**: `GET /data-collection-tasks/data-sources/:id/databases/:db/collections`
- **功能**: 获取指定数据库的集合列表
#### 1.13 获取字段列表
```typescript
getFields(dataSourceId: string, database: string | { name: string; id: string }, collection: string | { name: string; id: string }): Promise<ApiResponse<Array<{ name: string; type: string }>>>
```
- **路径**: `GET /data-collection-tasks/data-sources/:id/databases/:db/collections/:coll/fields`
- **功能**: 获取指定集合的字段列表
#### 1.14 获取Handler目标字段列表
```typescript
getHandlerTargetFields(handlerType: string): Promise<ApiResponse<Array<{
name: string
label: string
type: string
required: boolean
description?: string
}>>>
```
- **路径**: `GET /data-collection-tasks/handlers/:type/target-fields`
- **功能**: 获取指定 Handler 类型的目标字段列表
#### 1.15 预览查询结果
```typescript
previewQuery(data: {
data_source_id: string
database: string
collection: string
lookups?: any[]
filter_conditions?: any[]
limit?: number
}): Promise<ApiResponse<{
fields: Array<{ name: string; type: string }>
data: Array<any>
count: number
}>>
```
- **路径**: `POST /data-collection-tasks/preview-query`
- **功能**: 预览查询结果,用于验证配置
---
### 2. 数据源管理 API (`dataSource.ts`)
#### 2.1 获取数据源列表
```typescript
getDataSourceList(params?: {
type?: string
status?: number
name?: string
page?: number
page_size?: number
}): Promise<ApiResponse<{
data_sources: DataSource[]
total: number
page: number
page_size: number
}>>
```
- **路径**: `GET /data-sources`
- **功能**: 获取数据源列表,支持按类型、状态、名称筛选和分页
#### 2.2 获取数据源详情
```typescript
getDataSourceDetail(dataSourceId: string): Promise<ApiResponse<DataSource>>
```
- **路径**: `GET /data-sources/:id`
- **功能**: 获取指定数据源的详细信息
#### 2.3 创建数据源
```typescript
createDataSource(data: Partial<DataSource>): Promise<ApiResponse<DataSource>>
```
- **路径**: `POST /data-sources`
- **功能**: 创建新的数据源配置
#### 2.4 更新数据源
```typescript
updateDataSource(dataSourceId: string, data: Partial<DataSource>): Promise<ApiResponse<DataSource>>
```
- **路径**: `PUT /data-sources/:id`
- **功能**: 更新指定数据源的配置
#### 2.5 删除数据源
```typescript
deleteDataSource(dataSourceId: string): Promise<ApiResponse>
```
- **路径**: `DELETE /data-sources/:id`
- **功能**: 删除指定数据源
#### 2.6 测试数据源连接
```typescript
testDataSourceConnection(data: {
type: string
host: string
port: number
database: string
username?: string
password?: string
auth_source?: string
options?: Record<string, any>
}): Promise<ApiResponse<{ connected: boolean }>>
```
- **路径**: `POST /data-sources/test-connection`
- **功能**: 测试数据源连接是否正常
---
### 3. 标签任务 API (`tagTask.ts`)
#### 3.1 获取标签任务列表
```typescript
getTagTaskList(params: {
name?: string
task_type?: string
status?: string
page?: number
page_size?: number
}): Promise<ApiResponse<{
tasks: TagTask[]
total: number
page: number
page_size: number
}>>
```
- **路径**: `GET /tag-tasks`
- **功能**: 获取标签任务列表,支持按名称、类型、状态筛选和分页
#### 3.2 获取标签任务详情
```typescript
getTagTaskDetail(taskId: string): Promise<ApiResponse<TagTask>>
```
- **路径**: `GET /tag-tasks/:id`
- **功能**: 获取指定标签任务的详细信息
#### 3.3 创建标签任务
```typescript
createTagTask(data: Partial<TagTask>): Promise<ApiResponse<TagTask>>
```
- **路径**: `POST /tag-tasks`
- **功能**: 创建新的标签任务
#### 3.4 更新标签任务
```typescript
updateTagTask(taskId: string, data: Partial<TagTask>): Promise<ApiResponse<TagTask>>
```
- **路径**: `PUT /tag-tasks/:id`
- **功能**: 更新指定标签任务的信息
#### 3.5 删除标签任务
```typescript
deleteTagTask(taskId: string): Promise<ApiResponse>
```
- **路径**: `DELETE /tag-tasks/:id`
- **功能**: 删除指定标签任务
#### 3.6 启动标签任务
```typescript
startTagTask(taskId: string): Promise<ApiResponse>
```
- **路径**: `POST /tag-tasks/:id/start`
- **功能**: 启动指定标签任务
#### 3.7 暂停标签任务
```typescript
pauseTagTask(taskId: string): Promise<ApiResponse>
```
- **路径**: `POST /tag-tasks/:id/pause`
- **功能**: 暂停指定标签任务
#### 3.8 停止标签任务
```typescript
stopTagTask(taskId: string): Promise<ApiResponse>
```
- **路径**: `POST /tag-tasks/:id/stop`
- **功能**: 停止指定标签任务
#### 3.9 获取任务执行记录
```typescript
getTagTaskExecutions(taskId: string, params?: {
page?: number
page_size?: number
}): Promise<ApiResponse<{
executions: TaskExecution[]
total: number
}>>
```
- **路径**: `GET /tag-tasks/:id/executions`
- **功能**: 获取指定任务的执行记录列表
---
### 4. 标签定义 API (`tagDefinition.ts`)
#### 4.1 获取标签定义列表
```typescript
getTagDefinitionList(params?: {
name?: string
category?: string
status?: number
page?: number
page_size?: number
}): Promise<ApiResponse<{
definitions: TagDefinition[]
total: number
page: number
page_size: number
}>>
```
- **路径**: `GET /tag-definitions`
- **功能**: 获取标签定义列表,支持按名称、分类、状态筛选和分页
#### 4.2 获取标签定义详情
```typescript
getTagDefinitionDetail(tagId: string): Promise<ApiResponse<TagDefinition>>
```
- **路径**: `GET /tag-definitions/:id`
- **功能**: 获取指定标签定义的详细信息
#### 4.3 创建标签定义
```typescript
createTagDefinition(data: Partial<TagDefinition>): Promise<ApiResponse<TagDefinition>>
```
- **路径**: `POST /tag-definitions`
- **功能**: 创建新的标签定义
#### 4.4 更新标签定义
```typescript
updateTagDefinition(tagId: string, data: Partial<TagDefinition>): Promise<ApiResponse<TagDefinition>>
```
- **路径**: `PUT /tag-definitions/:id`
- **功能**: 更新指定标签定义的信息
#### 4.5 删除标签定义
```typescript
deleteTagDefinition(tagId: string): Promise<ApiResponse>
```
- **路径**: `DELETE /tag-definitions/:id`
- **功能**: 删除指定标签定义
#### 4.6 批量初始化标签定义
```typescript
batchInitTagDefinitions(data: {
definitions: Partial<TagDefinition>[]
}): Promise<ApiResponse>
```
- **路径**: `POST /tag-definitions/batch`
- **功能**: 批量创建标签定义
---
### 5. 标签查询 API (`tagQuery.ts`)
#### 5.1 获取用户标签
```typescript
getUserTags(userIdOrPhone: string): Promise<ApiResponse<{
user_id: string
tags: UserTag[]
count: number
}>>
```
- **路径**: `GET /users/:userIdOrPhone/tags`
- **功能**: 获取指定用户通过用户ID或手机号的所有标签
#### 5.2 重新计算用户标签
```typescript
recalculateUserTags(userId: string): Promise<ApiResponse<{
user_id: string
updated_tags: Array<{
tag_id: string
tag_code: string
tag_value: string
}>
count: number
}>>
```
- **路径**: `PUT /users/:userId/tags`
- **功能**: 重新计算指定用户的所有标签
#### 5.3 根据标签筛选用户
```typescript
filterUsersByTags(params: {
tag_conditions: TagCondition[]
logic: 'AND' | 'OR'
page?: number
page_size?: number
include_user_info?: boolean
}): Promise<ApiResponse<{
users: UserInfo[]
total: number
page: number
page_size: number
total_pages?: number
}>>
```
- **路径**: `POST /tags/filter`
- **功能**: 根据标签条件筛选用户列表
#### 5.4 获取标签统计信息
```typescript
getTagStatistics(params?: {
tag_id?: string
start_date?: string
end_date?: string
}): Promise<ApiResponse<TagStatistics>>
```
- **路径**: `GET /tags/statistics`
- **功能**: 获取标签统计信息(值分布、趋势数据、覆盖度统计)
#### 5.5 获取标签历史记录
```typescript
getTagHistory(params?: {
user_id?: string
tag_id?: string
start_date?: string
end_date?: string
page?: number
page_size?: number
}): Promise<ApiResponse<TagHistoryResponse>>
```
- **路径**: `GET /tags/history`
- **功能**: 获取标签变更历史记录,支持按用户、标签、时间范围筛选和分页
---
### 6. 人群快照 API (`tagCohort.ts`)
#### 6.1 获取人群快照列表
```typescript
getTagCohortList(params?: {
page?: number
page_size?: number
}): Promise<ApiResponse<{
cohorts: TagCohortListItem[]
total: number
page: number
page_size: number
}>>
```
- **路径**: `GET /tag-cohorts`
- **功能**: 获取人群快照列表,支持分页
#### 6.2 获取人群快照详情
```typescript
getTagCohortDetail(cohortId: string): Promise<ApiResponse<TagCohort>>
```
- **路径**: `GET /tag-cohorts/:id`
- **功能**: 获取指定人群快照的详细信息
#### 6.3 创建人群快照
```typescript
createTagCohort(data: {
name: string
description?: string
conditions: TagCondition[]
logic?: 'AND' | 'OR'
user_ids?: string[]
created_by?: string
}): Promise<ApiResponse<{
cohort_id: string
name: string
user_count: number
}>>
```
- **路径**: `POST /tag-cohorts`
- **功能**: 创建新的人群快照(支持条件筛选或直接指定用户列表)
#### 6.4 删除人群快照
```typescript
deleteTagCohort(cohortId: string): Promise<ApiResponse>
```
- **路径**: `DELETE /tag-cohorts/:id`
- **功能**: 删除指定人群快照
#### 6.5 导出人群快照
```typescript
exportTagCohort(cohortId: string): Promise<Blob>
```
- **路径**: `POST /tag-cohorts/:id/export`
- **功能**: 导出人群快照为 CSV 文件(返回 Blob 对象)
---
### 7. 用户 API (`user.ts`)
#### 7.1 搜索用户
```typescript
searchUsers(params: {
id_card?: string
phone?: string
name?: string
page?: number
page_size?: number
}): Promise<ApiResponse<{
users: UserInfo[]
total: number
page: number
page_size: number
}>>
```
- **路径**: `POST /users/search`
- **功能**: 根据身份证、手机号、姓名搜索用户
#### 7.2 解密身份证
```typescript
decryptIdCard(userId: string): Promise<ApiResponse<{
user_id: string
id_card: string
}>>
```
- **路径**: `GET /users/:userId/decrypt-id-card`
- **功能**: 解密指定用户的身份证号(需要权限)
#### 7.3 删除用户标签
```typescript
deleteUserTag(userId: string, tagId: string): Promise<ApiResponse>
```
- **路径**: `DELETE /users/:userId/tags/:tagId`
- **功能**: 删除指定用户的指定标签
---
## 三、API 使用示例
### 1. 基本使用
```typescript
import { getTagStatistics } from '@/api/tagQuery'
// 获取所有标签的覆盖度统计
const stats = await getTagStatistics()
// 获取指定标签的值分布和趋势
const tagStats = await getTagStatistics({
tag_id: 'tag1',
start_date: '2025-01-01',
end_date: '2025-01-31'
})
```
### 2. 错误处理
```typescript
try {
const response = await getTagStatistics()
// 处理成功响应
console.log(response.data)
} catch (error: any) {
// 错误已在拦截器中提示,这里只需处理业务逻辑
console.error('加载统计失败:', error)
}
```
### 3. 在组件中使用
```vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getTagStatistics } from '@/api/tagQuery'
import type { TagStatistics } from '@/types'
const statistics = ref<TagStatistics | null>(null)
const loading = ref(false)
const loadStatistics = async () => {
loading.value = true
try {
const response = await getTagStatistics()
statistics.value = response.data
} catch (error) {
console.error('加载统计失败:', error)
} finally {
loading.value = false
}
}
onMounted(() => {
loadStatistics()
})
</script>
```
### 4. 在 Store 中使用
```typescript
import { defineStore } from 'pinia'
import { getTagStatistics } from '@/api/tagQuery'
import type { TagStatistics } from '@/types'
export const useTagQueryStore = defineStore('tagQuery', () => {
const statistics = ref<TagStatistics | null>(null)
const fetchStatistics = async (params?: any) => {
const response = await getTagStatistics(params)
statistics.value = response.data
return response.data
}
return {
statistics,
fetchStatistics
}
})
```
## 四、API 配置说明
### 1. 后端地址配置
- **开发环境**:通过 Vite 代理配置,请求 `/api` 自动转发到 `http://127.0.0.1:8787`
- **生产环境**:通过环境变量 `VITE_API_BASE_URL` 配置实际 API 地址
### 2. 请求拦截器
- 自动从 Pinia Store 获取 Token 并添加到请求头:`Authorization: Bearer ${token}`
- 自动显示 Loading可配置 `showLoading: false` 关闭)
- 统一处理 HTTP 错误和业务错误
### 3. 响应拦截器
- 自动关闭 Loading
- 根据业务状态码code === 200 或 code === 0判断成功
- 统一错误提示(可配置 `showError: false` 关闭)
## 五、API 统计
| 模块 | API 数量 | 状态 |
|------|---------|------|
| 数据采集任务 | 15 | ✅ 已对接 |
| 数据源管理 | 6 | ✅ 已对接 |
| 标签任务 | 9 | ✅ 已对接 |
| 标签定义 | 6 | ✅ 已对接 |
| 标签查询 | 5 | ✅ 已对接 |
| 人群快照 | 5 | ✅ 已对接 |
| 用户 | 3 | ✅ 已对接 |
| **总计** | **49** | **✅ 全部完成** |
## 六、注意事项
1. **分页参数**
- `page` 从 1 开始
- `page_size` 默认 20最大 100
2. **日期格式**
- `start_date``end_date` 使用 `YYYY-MM-DD` 格式
- 例如:`'2025-01-01'`
3. **人群快照创建**
- 如果提供 `user_ids`,直接使用提供的用户列表
- 如果不提供 `user_ids`,会根据 `conditions` 自动筛选用户
- 最多支持 10000 个用户
4. **导出功能**
- `exportTagCohort` 返回的是 Blob 对象
- 需要使用 `window.URL.createObjectURL` 创建下载链接
5. **类型安全**
- 所有接口都有完整的 TypeScript 类型定义
- 建议启用严格模式以获得更好的类型检查
6. **错误处理**
- HTTP 错误401、403、404、500 等)会在拦截器中自动处理
- 业务错误code !== 200 && code !== 0会在拦截器中提示
- 组件中只需处理业务逻辑,无需重复处理错误提示

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,884 @@
# 标签引擎相关功能流程逻辑
## 一、系统概述
标签引擎是系统的核心功能模块,负责根据用户数据自动计算和更新用户标签。系统支持实时计算、批量计算和定时计算三种模式,通过规则引擎实现灵活的标签计算逻辑。
### 1.1 核心组件
- **TagService** - 标签计算服务,核心业务逻辑
- **TagTaskService** - 标签任务管理服务
- **TagTaskExecutor** - 标签任务执行器
- **SimpleRuleEngine** - 简单规则引擎
- **TagCalculationWorker** - 标签计算Worker异步处理
- **TagDefinitionRepository** - 标签定义数据访问
- **UserTagRepository** - 用户标签数据访问
- **TagHistoryRepository** - 标签历史数据访问
### 1.2 数据存储
- **tag_definitions** - 标签定义集合(规则配置)
- **user_tags** - 用户标签集合(标签值)
- **tag_history** - 标签变更历史集合
- **tag_tasks** - 标签任务集合
- **tag_task_executions** - 标签任务执行记录集合
---
## 二、标签定义管理
### 2.1 标签定义结构
标签定义存储在 `tag_definitions` 集合中,包含以下关键字段:
```javascript
{
tag_id: String, // 标签IDUUID
tag_code: String, // 标签代码(唯一标识)
tag_name: String, // 标签名称
category: String, // 标签分类
rule_type: String, // 规则类型simple/pipeline/custom
rule_config: Object, // 规则配置JSON
update_frequency: String, // 更新频率real_time/daily/weekly/monthly
status: Number, // 状态0:启用, 1:禁用)
priority: Number, // 优先级
version: Number, // 版本号
create_time: Date,
update_time: Date
}
```
### 2.2 规则配置格式
#### Simple 规则配置示例
```json
{
"rule_type": "simple",
"conditions": [
{
"field": "total_amount",
"operator": ">=",
"value": 1000
},
{
"field": "total_count",
"operator": ">=",
"value": 10
}
],
"tag_value": "VIP",
"confidence": 0.9
}
```
#### 支持的运算符
- `>` - 大于
- `>=` - 大于等于
- `<` - 小于
- `<=` - 小于等于
- `=` / `==` - 等于
- `!=` - 不等于
- `in` - 在列表中
- `not_in` - 不在列表中
### 2.3 标签定义管理流程
```
创建/编辑标签定义
验证规则配置格式
保存到 tag_definitions 集合
如果状态为启用,立即生效
```
---
## 三、标签计算流程
### 3.1 标签计算触发方式
系统支持三种标签计算触发方式:
#### 3.1.1 实时计算Real-time
**触发场景**
- 消费记录写入时自动触发
- 用户数据更新时触发
**流程**
```
消费记录写入
ConsumptionService->createRecord()
├─→ 写入 consumption_records
├─→ 更新 user_profile 统计信息
└─→ 推送标签计算消息到 RabbitMQ
TagCalculationWorker 消费消息
TagService->calculateTags()
├─→ 获取用户数据
├─→ 获取所有 real_time 标签定义
├─→ 规则引擎计算标签值
├─→ 更新 user_tags
└─→ 记录 tag_history如果值变化
```
**消息格式**
```json
{
"user_id": "用户ID",
"tag_ids": null, // null 表示计算所有 real_time 标签
"trigger_type": "consumption_record",
"record_id": "记录ID",
"timestamp": 1234567890
}
```
#### 3.1.2 批量计算Batch
**触发场景**
- 通过标签任务手动触发
- 定时任务触发Cron
**流程**
```
创建标签任务
├─→ 任务类型full全量/ incremental增量/ specified指定
├─→ 目标标签指定标签ID列表
├─→ 用户范围all全部/ list指定用户/ filter条件筛选
└─→ 调度计划Cron 表达式
用户点击"启动"按钮
TagTaskService->startTask()
├─→ 更新任务状态为 running
└─→ 设置 Redis 标志tag_task:{taskId}:start
TagTaskExecutor->execute()
├─→ 获取用户ID列表根据 user_scope
├─→ 批量处理用户(批次大小可配置)
│ ├─→ 遍历每个用户
│ ├─→ TagService->calculateTags(userId, targetTagIds)
│ └─→ 更新任务进度
└─→ 记录执行结果
```
#### 3.1.3 手动计算Manual
**触发场景**
- 通过 API 手动触发单个用户的标签计算
- 用户标签查询页面点击"重新计算"
**流程**
```
API: PUT /api/users/{user_id}/tags
TagController->calculate()
TagService->calculateTags(userId)
├─→ 获取用户数据
├─→ 获取所有 real_time 标签定义
├─→ 规则引擎计算
└─→ 更新标签
```
### 3.2 标签计算核心流程
#### 3.2.1 TagService->calculateTags() 详细流程
```php
1. 获取用户数据
- user_profile 获取用户统计信息
- 准备用户数据数组:
* total_amount: 总消费金额
* total_count: 总消费次数
* last_consume_time: 最后消费时间(时间戳)
2. 获取标签定义列表
- 如果指定 tagIds只获取指定的标签定义
- 如果 tagIds null,获取所有启用且 update_frequency = 'real_time' 的标签
- 只获取 status = 0(启用)的标签
3. 遍历每个标签定义
For each tagDef:
a. 解析规则配置rule_config
b. 根据规则类型选择计算引擎
- simple: 使用 SimpleRuleEngine
- pipeline: 暂不支持
- custom: 暂不支持
c. 规则引擎计算标签值
- 评估所有条件conditions
- 如果所有条件满足,返回 tag_value confidence
- 如果条件不满足,返回 false confidence = 0.0
d. 获取旧标签值(用于历史记录)
e. 更新或创建 user_tags 记录
- 如果标签已存在,更新 tag_value、confidence、update_time
- 如果标签不存在,创建新记录
f. 记录标签变更历史(仅当值发生变化时)
- 写入 tag_history 集合
- 记录 old_value、new_value、change_reason、change_time
g. 记录计算日志
4. 更新用户的标签更新时间
- user_profile.tags_update_time = now()
5. 返回更新的标签列表
```
#### 3.2.2 SimpleRuleEngine 计算逻辑
```php
1. 验证规则配置
- rule_type 必须是 'simple'
- conditions 必须存在且为数组
2. 评估所有条件
For each condition:
a. userData 获取字段值
b. 根据 operator 进行比较
- >, >=, <, <=: 数值比较
- =, !=: 相等比较
- in, not_in: 数组包含判断
c. 如果字段不存在,默认值为 0
3. 判断结果
- 如果所有条件都满足allMatch = true
* value = ruleConfig['tag_value'] ?? true
* confidence = ruleConfig['confidence'] ?? 1.0
- 如果任一条件不满足:
* value = false
* confidence = 0.0
4. 返回计算结果
{
value: mixed,
confidence: float
}
```
### 3.3 标签值存储格式
标签值统一转换为字符串格式存储:
- **布尔值**`true``"true"`, `false``"false"`
- **数值**:直接转换为字符串
- **数组/对象**JSON 序列化
- **字符串**:直接存储
标签值类型tag_value_type
- `boolean` - 布尔类型
- `number` - 数值类型
- `string` - 字符串类型
- `json` - JSON 类型
---
## 四、标签任务管理
### 4.1 标签任务结构
```javascript
{
task_id: String, // 任务IDUUID
name: String, // 任务名称
description: String, // 任务描述
task_type: String, // 任务类型full/incremental/specified
target_tag_ids: Array, // 目标标签ID列表
user_scope: { // 用户范围
type: String, // all/list/filter
user_ids: Array, // 指定用户列表type=list时
conditions: Array // 筛选条件type=filter时
},
schedule: { // 调度计划
enabled: Boolean, // 是否启用定时任务
cron: String // Cron 表达式
},
config: { // 任务配置
concurrency: Number, // 并发数
batch_size: Number, // 批次大小
error_handling: String // 错误处理策略skip/stop
},
status: String, // 任务状态pending/running/paused/stopped/error
progress: { // 任务进度
total_users: Number, // 总用户数
processed_users: Number, // 已处理用户数
success_count: Number, // 成功数量
error_count: Number, // 错误数量
percentage: Number // 完成百分比
},
statistics: { // 任务统计
total_executions: Number, // 总执行次数
success_executions: Number, // 成功执行次数
failed_executions: Number, // 失败执行次数
last_run_time: Date // 最后执行时间
},
created_by: String,
created_at: Date,
updated_at: Date
}
```
### 4.2 任务类型说明
#### 4.2.1 Full全量计算
- 计算所有用户的所有指定标签
- 适用于首次初始化或全量更新
#### 4.2.2 Incremental增量计算
- 只计算有数据变更的用户
- 适用于定期更新场景
#### 4.2.3 Specified指定计算
- 只计算指定用户列表的标签
- 适用于特定用户群体
### 4.3 用户范围配置
#### 4.3.1 All全部用户
```json
{
"type": "all"
}
```
获取所有 `status = 0` 的用户。
#### 4.3.2 List指定用户列表
```json
{
"type": "list",
"user_ids": ["user1", "user2", "user3"]
}
```
只计算指定用户ID列表的标签。
#### 4.3.3 Filter条件筛选
```json
{
"type": "filter",
"conditions": [
{
"field": "total_amount",
"operator": ">=",
"value": 1000
},
{
"field": "total_count",
"operator": ">=",
"value": 10
}
]
}
```
根据条件筛选用户支持多个条件AND 逻辑)。
### 4.4 任务执行流程
```
1. 创建执行记录
- execution_id: UUID
- task_id: 任务ID
- started_at: 开始时间
- status: running
2. 获取用户ID列表
- 根据 user_scope 配置获取用户列表
- 计算总用户数
3. 更新任务进度
- total_users: 总用户数
- processed_users: 0
- success_count: 0
- error_count: 0
- percentage: 0
4. 批量处理用户
For each batch (batch_size = 100):
a. 检查任务状态(是否被暂停/停止)
b. 遍历批次中的每个用户
- TagService->calculateTags(userId, targetTagIds)
- 成功success_count++
- 失败error_count++
- 根据 error_handling 策略决定是否继续
c. 每处理 10 个用户更新一次进度
- processed_users
- success_count
- error_count
- percentage = (processed_users / total_users) * 100
5. 更新最终进度
- percentage = 100或实际完成百分比
6. 更新执行记录
- status: completed/failed
- finished_at: 结束时间
- processed_users, success_count, error_count
7. 更新任务统计
- total_executions++
- success_executions++ / failed_executions++
- last_run_time = now()
```
### 4.5 任务状态管理
#### 4.5.1 启动任务
```
用户点击"启动"按钮
API: POST /api/tag-tasks/{task_id}/start
TagTaskService->startTask()
├─→ 检查任务状态(不能是 running
├─→ 更新任务状态为 running
└─→ 设置 Redis 标志tag_task:{taskId}:start
TagTaskExecutor 检测到标志
执行任务(见 4.4 任务执行流程)
```
#### 4.5.2 暂停任务
```
用户点击"暂停"按钮
API: POST /api/tag-tasks/{task_id}/pause
TagTaskService->pauseTask()
├─→ 检查任务状态(必须是 running
├─→ 更新任务状态为 paused
└─→ 设置 Redis 标志tag_task:{taskId}:pause
TagTaskExecutor 检测到标志
停止处理新批次,等待当前批次完成
```
#### 4.5.3 停止任务
```
用户点击"停止"按钮
API: POST /api/tag-tasks/{task_id}/stop
TagTaskService->stopTask()
├─→ 更新任务状态为 stopped
└─→ 设置 Redis 标志tag_task:{taskId}:stop
TagTaskExecutor 检测到标志
立即停止处理
```
---
## 五、标签查询和筛选
### 5.1 用户标签查询
#### 5.1.1 查询单个用户的所有标签
```
API: GET /api/users/{user_id}/tags
TagController->getUserTags()
TagService->getUserTags(userId)
├─→ 从 user_tags 查询该用户的所有标签
├─→ 关联 tag_definitions 获取标签定义信息
└─→ 返回标签列表(包含标签值、置信度、生效时间等)
```
**返回格式**
```json
{
"user_id": "用户ID",
"tags": [
{
"tag_id": "标签ID",
"tag_code": "标签代码",
"tag_name": "标签名称",
"category": "标签分类",
"tag_value": "标签值",
"tag_value_type": "值类型",
"confidence": 0.9,
"effective_time": "生效时间",
"expire_time": "过期时间",
"update_time": "更新时间"
}
],
"count": 10
}
```
#### 5.1.2 重新计算用户标签
```
API: PUT /api/users/{user_id}/tags
TagController->calculate()
TagService->calculateTags(userId)
└─→ 执行标签计算流程(见 3.2.1
```
### 5.2 标签筛选用户
#### 5.2.1 根据标签条件筛选用户
```
API: POST /api/tags/filter
TagController->filterUsers()
TagService->filterUsersByTags()
├─→ 根据 tag_code 获取 tag_id 列表
├─→ 根据逻辑类型处理查询
│ ├─→ AND 逻辑:分别查询每个条件,取交集
│ └─→ OR 逻辑:使用 orWhere 查询
├─→ 如果标签未计算,基于规则从 user_profile 筛选
├─→ 分页处理
└─→ 返回用户列表(可选包含用户信息)
```
**请求格式**
```json
{
"tag_conditions": [
{
"tag_code": "VIP",
"operator": "=",
"value": "true"
},
{
"tag_code": "消费金额等级",
"operator": ">=",
"value": "1000"
}
],
"logic": "AND",
"page": 1,
"page_size": 20,
"include_user_info": true
}
```
**筛选逻辑**
1. **AND 逻辑**
- 分别查询每个条件满足的用户ID
- 取所有条件的交集
- 如果标签未计算,基于规则从 `user_profile` 筛选
2. **OR 逻辑**
- 使用 `orWhere` 查询满足任一条件的用户
- 去重后返回
3. **支持的操作符**
- `=`, `!=` - 等于/不等于
- `>`, `>=`, `<`, `<=` - 数值比较
- `in`, `not_in` - 列表包含判断
### 5.3 标签统计
#### 5.3.1 获取标签统计信息
```
API: GET /api/tags/statistics
TagController->getStatistics()
TagService->getTagStatistics()
├─→ 标签覆盖度统计
│ ├─→ 总用户数
│ ├─→ 已打标签用户数
│ └─→ 覆盖率 = (已打标签用户数 / 总用户数) * 100
├─→ 标签值分布
│ └─→ 各标签值的出现次数
└─→ 标签趋势数据
└─→ 按日期统计标签变更次数
```
### 5.4 标签历史查询
#### 5.4.1 获取标签变更历史
```
API: GET /api/tags/history
TagController->getHistory()
TagHistoryRepository->query()
├─→ 支持筛选条件:
│ ├─→ user_id: 按用户筛选
│ ├─→ tag_id: 按标签筛选
│ └─→ start_date, end_date: 按时间范围筛选
├─→ 分页查询
└─→ 返回历史记录列表
```
**历史记录结构**
```javascript
{
history_id: String, // 历史记录ID
user_id: String, // 用户ID
tag_id: String, // 标签ID
old_value: String, // 旧值
new_value: String, // 新值
change_reason: String, // 变更原因auto_calculate/manual/tag_deleted
change_time: Date, // 变更时间
operator: String // 操作人system/user_id
}
```
---
## 六、异步处理机制
### 6.1 RabbitMQ 队列配置
#### 6.1.1 标签计算队列
- **队列名**`tag_calculation`
- **交换机**`tag_calculation_exchange`
- **路由键**`tag.calculation`
- **持久化**:是
- **消息格式**JSON
#### 6.1.2 消息推送
```php
QueueService::pushTagCalculation([
'user_id' => $userId,
'tag_ids' => null, // null 表示计算所有 real_time 标签
'trigger_type' => 'consumption_record',
'record_id' => $recordId,
'timestamp' => time(),
]);
```
### 6.2 TagCalculationWorker 处理流程
```
Worker 启动
初始化 RabbitMQ 连接
├─→ 建立连接
├─→ 声明队列
├─→ 设置 QoSprefetch_count = 1
└─→ 开始消费消息
监听消息(每 0.1 秒检查一次)
收到消息
processMessage()
├─→ 解析消息JSON
├─→ 验证必要字段user_id
├─→ 创建 TagService 实例
├─→ 执行标签计算
│ └─→ TagService->calculateTags(userId, tagIds)
├─→ 记录日志和性能指标
└─→ 确认消息ACK
├─→ 成功message->ack()
└─→ 失败:
├─→ 业务错误InvalidArgumentExceptionack不重试
└─→ 系统错误nack重新入队
```
### 6.3 错误处理和重试
#### 6.3.1 业务错误
- **类型**`InvalidArgumentException`(如用户不存在)
- **处理**直接确认消息ACK不重试
- **原因**:业务逻辑错误,重试不会改变结果
#### 6.3.2 系统错误
- **类型**:数据库连接失败、网络错误等
- **处理**拒绝消息并重新入队NACK with requeue = true
- **原因**:临时性错误,重试可能成功
### 6.4 降级策略
如果 RabbitMQ 不可用或推送失败:
```
推送消息到队列失败
记录错误日志
降级到同步调用
TagService->calculateTags()(同步执行)
返回结果(包含标签信息)
```
---
## 七、数据流图
### 7.1 实时标签计算数据流
```
数据采集任务
消费记录写入
ConsumptionService->createRecord()
├─→ 写入 consumption_records
├─→ 更新 user_profiletotal_amount, total_count
└─→ 推送消息到 RabbitMQ
TagCalculationWorker异步
TagService->calculateTags()
├─→ 获取用户数据user_profile
├─→ 获取标签定义tag_definitions
├─→ 规则引擎计算SimpleRuleEngine
├─→ 更新用户标签user_tags
└─→ 记录变更历史tag_history
```
### 7.2 批量标签计算数据流
```
创建标签任务
用户启动任务
TagTaskService->startTask()
├─→ 更新任务状态
└─→ 设置 Redis 标志
TagTaskExecutor->execute()
├─→ 获取用户列表(根据 user_scope
├─→ 批量处理用户
│ └─→ TagService->calculateTags()
└─→ 更新任务进度和统计
```
### 7.3 标签查询数据流
```
API 请求
TagController
TagService
├─→ 查询 user_tags用户标签
├─→ 关联 tag_definitions标签定义
├─→ 筛选条件处理AND/OR
└─→ 返回结果
```
---
## 八、关键设计要点
### 8.1 性能优化
1. **异步处理**:标签计算通过 RabbitMQ 异步处理,不阻塞主流程
2. **批量处理**:标签任务支持批量处理,提高效率
3. **增量更新**:只计算有数据变更的用户
4. **缓存机制**:标签定义可以缓存,减少数据库查询
### 8.2 数据一致性
1. **事务处理**:标签更新和历史记录在同一事务中
2. **幂等性**:标签计算支持重复执行,结果一致
3. **错误隔离**:单个标签计算失败不影响其他标签
### 8.3 可扩展性
1. **规则引擎扩展**支持添加新的规则类型pipeline/custom
2. **多数据源**:可以从多个数据源获取用户数据
3. **自定义计算**:支持自定义计算逻辑
### 8.4 可观测性
1. **日志记录**:完整的业务日志和性能日志
2. **进度跟踪**:实时跟踪任务进度
3. **历史记录**:记录所有标签变更历史
4. **统计信息**:提供标签统计和趋势分析
---
## 九、API 接口汇总
### 9.1 标签定义接口
- `GET /api/tag-definitions` - 获取标签定义列表
- `POST /api/tag-definitions` - 创建标签定义
- `GET /api/tag-definitions/{tag_id}` - 获取标签定义详情
- `PUT /api/tag-definitions/{tag_id}` - 更新标签定义
- `DELETE /api/tag-definitions/{tag_id}` - 删除标签定义
- `POST /api/tag-definitions/batch` - 批量创建标签定义
### 9.2 标签任务接口
- `GET /api/tag-tasks` - 获取标签任务列表
- `POST /api/tag-tasks` - 创建标签任务
- `GET /api/tag-tasks/{task_id}` - 获取任务详情
- `PUT /api/tag-tasks/{task_id}` - 更新任务
- `DELETE /api/tag-tasks/{task_id}` - 删除任务
- `POST /api/tag-tasks/{task_id}/start` - 启动任务
- `POST /api/tag-tasks/{task_id}/pause` - 暂停任务
- `POST /api/tag-tasks/{task_id}/stop` - 停止任务
- `GET /api/tag-tasks/{task_id}/executions` - 获取执行记录
### 9.3 标签查询接口
- `GET /api/users/{user_id}/tags` - 获取用户标签
- `PUT /api/users/{user_id}/tags` - 重新计算用户标签
- `DELETE /api/users/{user_id}/tags/{tag_id}` - 删除用户标签
- `POST /api/tags/filter` - 根据标签筛选用户
- `GET /api/tags/statistics` - 获取标签统计信息
- `GET /api/tags/history` - 获取标签历史记录
---
## 十、总结
标签引擎系统提供了完整的标签计算、管理和查询功能,支持实时计算、批量计算和定时计算三种模式。通过规则引擎实现灵活的标签计算逻辑,通过异步处理保证系统性能,通过历史记录提供完整的审计追踪。
### 10.1 核心特性
-**实时计算**:消费记录写入时自动触发标签计算
-**批量计算**:支持全量、增量、指定用户的批量计算
-**规则引擎**:支持简单规则,可扩展支持复杂规则
-**异步处理**:通过 RabbitMQ 异步处理,不阻塞主流程
-**任务管理**:完整的任务创建、执行、监控功能
-**标签筛选**:支持多条件组合筛选用户
-**历史追踪**:完整的标签变更历史记录
### 10.2 使用场景
1. **实时标签更新**:用户消费后自动更新标签
2. **批量标签初始化**:首次上线时批量计算所有用户标签
3. **定时标签更新**:按日/周/月定期更新标签
4. **用户分群**:根据标签筛选特定用户群体
5. **标签分析**:统计标签覆盖度、值分布、趋势数据
---
**文档生成时间**2025-01-XX
**项目版本**:基于当前代码库分析

View File

@@ -0,0 +1,278 @@
# 采集进度100%停止机制分析报告
## 分析目标
检查当采集进度达到100%后,代码是否会正确停止采集。
## 代码流程分析
### 1. ConsumptionCollectionHandler::collectFromKrMall() 方法
#### 流程概览
```
初始化 → do-while循环 → 查询批次数据 → 处理批次 → 更新进度 → 检查退出条件
```
#### 详细流程
**步骤1初始化第166-172行**
```php
$offset = 0;
$processedCount = 0;
$successCount = 0;
$errorCount = 0;
$lastUpdateCount = 0;
$isCompleted = false; // 标记是否已完成
```
**步骤2主循环开始第174行**
```php
do {
// 查询批次数据
// 处理批次
// 更新进度
} while (count($batch) === $batchSize && !$isCompleted);
```
**退出条件**:当批次为空(`count($batch) < $batchSize`**或** `$isCompleted = true` 时退出循环。
**步骤3批次处理循环第212-248行**
```php
foreach ($batch as $index => $orderData) {
// 检查是否已达到总数第222-225行
if ($totalCount > 0 && $processedCount >= $totalCount) {
$isCompleted = true;
break; // 跳出当前批次处理循环
}
$processedCount++; // 第227行递增处理计数
// 处理订单数据
$this->processKrMallOrder($orderData, $taskConfig);
$successCount++;
}
```
**⚠️ 问题1检查时机问题**
- 第222行检查 `processedCount >= totalCount` 时,`processedCount` 还没有递增
- 第227行才会递增 `processedCount`
- **这意味着**:如果 `processedCount == totalCount - 1`,检查通过,然后递增后变成 `totalCount`,但此时已经处理了 `totalCount` 条数据
- **实际上**:这个检查应该能正常工作,因为检查的是"是否已经达到或超过总数"
**步骤4批次处理完成后更新进度第250-279行**
```php
if (!empty($taskId) && $totalCount > 0) {
if (($processedCount - $lastUpdateCount) >= $updateInterval || $processedCount >= $totalCount) {
$percentage = round(($processedCount / $totalCount) * 100, 2);
// 检查是否达到100%第257-268行
if ($processedCount >= $totalCount) {
// 进度达到100%,停止采集并更新状态为已完成
$this->updateProgress($taskId, [
'status' => 'completed',
'processed_count' => $processedCount,
'success_count' => $successCount,
'error_count' => $errorCount,
'percentage' => 100,
'end_time' => new \MongoDB\BSON\UTCDateTime(time() * 1000),
]);
\Workerman\Worker::safeEcho("[ConsumptionCollectionHandler] ✅ 采集完成进度已达到100%,已停止采集\n");
$isCompleted = true; // 标记为已完成
}
}
}
```
**步骤5循环退出检查第296行**
```php
} while (count($batch) === $batchSize && !$isCompleted);
```
- 如果 `$isCompleted = true`,循环会退出
- 如果批次为空(`count($batch) < $batchSize`),循环也会退出
#### 停止机制分析
**✅ 停止机制1在批次处理循环中检查第222-225行**
- **触发条件**`processedCount >= totalCount`
- **动作**:设置 `$isCompleted = true``break` 跳出内层循环
- **效果**:停止处理当前批次的剩余数据,但**不会立即退出主循环**
**✅ 停止机制2在批次处理完成后检查第257-268行**
- **触发条件**`processedCount >= totalCount`
- **动作**:设置 `$isCompleted = true`,更新状态为 `completed`
- **效果**:标记任务完成,下次循环检查时会退出
**✅ 停止机制3主循环退出条件第296行**
- **退出条件**`!$isCompleted` 为 false`$isCompleted = true`
- **效果**:退出主循环,停止采集
#### 潜在问题
**问题1第222行检查后第227行仍会执行**
- 如果 `processedCount == totalCount - 1`第222行检查通过不会 break
- 第227行递增后`processedCount` 变成 `totalCount`
- 然后处理数据,`processedCount` 实际上会超过 `totalCount`
- **影响**:可能会多处理一条数据
**问题2第222行 break 后第257行的检查不会执行**
- 如果第222行 break会跳出内层循环
- 第257行的检查不会执行因为已经跳出循环
- 但是 `$isCompleted` 已经设置为 `true`,主循环会在下次迭代时退出
- **影响**:状态可能不会立即更新为 `completed`但会在循环结束后更新第298-316行
**问题3循环退出后再次检查状态第298-316行**
- 循环退出后,会再次检查任务状态
- 如果任务状态不是 `completed``paused``stopped`,会更新为 `completed`
- **这是好的**:确保状态最终被更新
### 2. ConsumptionCollectionHandler::collectFromKrFinance() 方法
#### 流程概览
```
初始化 → foreach循环遍历集合 → 查询数据 → 处理数据 → 更新进度 → 检查退出条件
```
#### 详细流程
**步骤1初始化第570-590行**
```php
$processedCount = 0;
$successCount = 0;
$errorCount = 0;
$lastUpdateCount = 0;
$isCompleted = false;
```
**步骤2外层循环遍历集合第592行**
```php
foreach ($collections as $collectionName) {
if ($isCompleted) {
break; // 如果已完成,退出外层循环
}
// 查询和处理数据
}
```
**步骤3内层循环处理数据第590-655行**
```php
foreach ($cursor as $doc) {
// 检查任务状态第608-612行
if (!empty($taskId) && !$this->checkTaskStatus($taskId)) {
$isCompleted = true;
break 2; // 跳出两层循环
}
// 检查是否已达到总数第615-618行
if ($totalCount > 0 && $processedCount >= $totalCount) {
$isCompleted = true;
break 2; // 跳出两层循环
}
$processedCount++;
// 处理数据
// 更新进度第626-659行
if ($totalCount > 0 && $processedCount >= $totalCount) {
// 更新状态为 completed
$isCompleted = true;
break 2; // 跳出两层循环
}
}
```
#### 停止机制分析
**✅ 停止机制1在每条处理前检查第615-618行**
- **触发条件**`processedCount >= totalCount`
- **动作**:设置 `$isCompleted = true``break 2` 跳出两层循环
- **效果**:立即停止采集
**✅ 停止机制2在进度更新时检查第637-650行**
- **触发条件**`processedCount >= totalCount`
- **动作**:设置 `$isCompleted = true``break 2` 跳出两层循环,更新状态为 `completed`
- **效果**:立即停止采集并更新状态
**✅ 停止机制3外层循环退出条件第594-596行**
- **退出条件**`$isCompleted = true`
- **效果**:退出外层循环,停止采集
### 3. GenericCollectionHandler::collect() 方法
#### 流程概览
```
初始化 → do-while循环 → 查询批次数据 → 处理批次 → 更新进度 → 检查退出条件
```
#### 详细流程
**步骤1主循环第274行**
```php
do {
// 查询和处理数据
} while (count($batch) === $batchSize && $processedCount < $totalCount);
```
**退出条件**:当批次为空**或** `processedCount >= totalCount` 时退出循环。
**步骤2进度更新检查第247-259行**
```php
if ($totalCount > 0 && $processedCount >= $totalCount) {
// 更新状态为 completed
break 2; // 跳出两层循环
}
```
#### 停止机制分析
**✅ 停止机制1循环退出条件第274行**
- **退出条件**`$processedCount >= $totalCount`
- **效果**:退出主循环,停止采集
**✅ 停止机制2进度更新时检查第247-259行**
- **触发条件**`processedCount >= totalCount`
- **动作**:更新状态为 `completed``break 2` 跳出两层循环
- **效果**:立即停止采集并更新状态
## 总结
### ✅ 停止机制正常工作的情况
1. **ConsumptionCollectionHandler::collectFromKrMall()**
- ✅ 在批次处理循环中检查 `processedCount >= totalCount`,设置 `$isCompleted = true`
- ✅ 在批次处理完成后检查,设置 `$isCompleted = true` 并更新状态
- ✅ 主循环退出条件检查 `!$isCompleted`,当 `$isCompleted = true` 时退出
- ✅ 循环退出后再次检查状态,确保更新为 `completed`
2. **ConsumptionCollectionHandler::collectFromKrFinance()**
- ✅ 在每条处理前检查,`break 2` 跳出两层循环
- ✅ 在进度更新时检查,`break 2` 跳出两层循环并更新状态
- ✅ 外层循环检查 `$isCompleted`,当为 `true` 时退出
3. **GenericCollectionHandler::collect()**
- ✅ 循环退出条件直接检查 `$processedCount < $totalCount`
- ✅ 在进度更新时检查,`break 2` 跳出两层循环
### ⚠️ 潜在问题
1. **ConsumptionCollectionHandler::collectFromKrMall() 第222行检查时机**
- 检查在 `$processedCount++` 之前
- 可能导致多处理一条数据(影响较小)
2. **状态更新时机**
- 如果第222行 break状态可能不会立即更新
- 但会在循环结束后更新第298-316行所以最终状态是正确的
### 建议优化
1. **优化检查时机**将第222行的检查移到 `$processedCount++` 之后
2. **确保状态立即更新**如果第222行 break也应该立即更新状态
## 结论
**✅ 采集进度达到100%后,代码会停止采集**
所有三个Handler都有多个停止机制确保当 `processedCount >= totalCount` 时:
1. 设置完成标志
2. 退出循环
3. 更新状态为 `completed`
虽然存在一些小的时机问题但整体逻辑是正确的能够确保采集在达到100%后停止。

View File

@@ -0,0 +1,682 @@
# 数据列表动态打标签实现方案
## 一、核心思路
将SQL/MongoDB查询配置保存到数据表中标签任务执行时从配置表读取查询配置动态执行查询并打标签。
---
## 二、数据表设计
### 2.1 数据列表配置表tag_data_lists
```javascript
{
list_id: String, // 列表IDUUID
list_code: String, // 列表编码(唯一,如: consumption_records
list_name: String, // 列表名称(如: 消费记录表)
data_source_id: String, // 数据源ID
database: String, // 数据库名
collection: String, // 主集合名
query_config: { // 查询配置
filter: [ // 过滤条件WHERE
{
logic: 'and', // 逻辑关系and/or
field: String, // 字段名
operator: String, // 运算符
value: Any // 值
}
],
lookups: [ // 联表查询JOIN
{
from: String, // 关联集合
local_field: String, // 主集合字段
foreign_field: String, // 关联集合字段
as: String, // 结果字段名
unwrap: Boolean, // 是否解构
preserve_null: Boolean // 是否保留空值
}
],
sort: { // 排序
field_name: 1/-1 // 1=升序,-1=降序
},
limit: Number // 限制数量
},
description: String, // 描述
status: Number, // 状态0=禁用1=启用)
create_time: Date,
update_time: Date
}
```
### 2.2 标签定义表tag_definitions
```javascript
{
tag_id: String,
tag_code: String,
tag_name: String,
category: String,
description: String,
rule_type: String, // 规则类型simple/regex
rule_config: {
rule_type: String,
data_list_id: String, // 关联的数据列表ID ⭐
data_list_name: String, // 数据列表名称
conditions: [ // 规则条件
{
field: String, // 字段中文名
field_name: String, // 字段英文名
operator: String, // 运算符
value: Any, // 值
tag_value: String // 标签值 ⭐
}
]
},
update_frequency: String,
status: Number,
create_time: Date,
update_time: Date
}
```
---
## 三、执行流程
### 3.1 标签任务启动流程
```
1. 用户启动标签任务
2. TagTaskExecutor->execute()
├─→ 获取目标标签ID列表target_tag_ids
├─→ 遍历每个标签定义
│ ↓
3. 读取标签定义
├─→ 从 tag_definitions 表读取
├─→ 获取 rule_config.data_list_id
│ ↓
4. 读取数据列表配置
├─→ 根据 data_list_id 从 tag_data_lists 表读取
├─→ 获取 query_config查询配置
│ ↓
5. 执行动态查询
├─→ 连接数据源
├─→ 根据 query_config 构建查询
│ ├─→ 构建过滤条件filter
│ ├─→ 构建联表查询lookups
│ ├─→ 构建排序sort
│ └─→ 构建限制limit
├─→ 执行查询,获取数据
│ ↓
6. 遍历查询结果,动态打标签
For each record in results:
├─→ 提取 user_id从记录中获取
├─→ 遍历 rule_config.conditions
│ ├─→ 根据 field_name 获取字段值
│ ├─→ 根据 operator 进行比较
│ │ ├─→ 运算规则:>, >=, <, <=, =, !=, in, not_in
│ │ └─→ 正则规则:正则表达式匹配
│ └─→ 如果条件满足,使用该条件的 tag_value
├─→ 更新或创建 user_tags 记录
├─→ 记录标签变更历史tag_history
└─→ 更新进度
```
### 3.2 查询构建示例
**数据列表配置**
```json
{
"list_id": "list_001",
"list_name": "消费记录表",
"data_source_id": "source_001",
"database": "tag_engine",
"collection": "consumption_records",
"query_config": {
"filter": [
{
"logic": "and",
"field": "status",
"operator": "eq",
"value": "success"
}
],
"lookups": [
{
"from": "user_profile",
"local_field": "user_id",
"foreign_field": "user_id",
"as": "user_info",
"unwrap": true,
"preserve_null": true
}
],
"sort": {
"create_time": -1
},
"limit": 10000
}
}
```
**执行时构建的MongoDB查询**
```javascript
db.consumption_records.aggregate([
{ $match: { status: { $eq: "success" } } },
{ $lookup: {
from: "user_profile",
localField: "user_id",
foreignField: "user_id",
as: "user_info"
} },
{ $unwind: {
path: "$user_info",
preserveNullAndEmptyArrays: true
} },
{ $sort: { create_time: -1 } },
{ $limit: 10000 }
])
```
### 3.3 标签计算示例
**标签定义**
```json
{
"tag_id": "tag_001",
"tag_code": "consumer_level",
"tag_name": "消费等级",
"rule_type": "simple",
"rule_config": {
"rule_type": "simple",
"data_list_id": "list_001",
"data_list_name": "消费记录表",
"conditions": [
{
"field": "交易金额",
"field_name": "amount",
"operator": "<",
"value": 3000,
"tag_value": "低价值用户"
},
{
"field": "交易金额",
"field_name": "amount",
"operator": ">=",
"value": 3000,
"tag_value": "中等价值用户"
},
{
"field": "交易金额",
"field_name": "amount",
"operator": ">",
"value": 9000,
"tag_value": "高价值用户"
}
]
}
}
```
**执行流程**
```php
// 1. 读取数据列表配置
$dataList = TagDataListRepository::find('list_001');
$queryConfig = $dataList->query_config;
// 2. 执行查询
$results = $this->executeQuery($dataList->data_source_id, $dataList->database, $dataList->collection, $queryConfig);
// 3. 遍历结果,打标签
foreach ($results as $record) {
$userId = $record['user_id'];
$amount = $record['amount'];
// 4. 根据规则条件匹配标签值
$tagValue = null;
foreach ($tagDef->rule_config['conditions'] as $condition) {
$fieldValue = $record[$condition['field_name']]; // 获取字段值
// 5. 判断条件是否满足
if ($this->evaluateCondition($fieldValue, $condition['operator'], $condition['value'])) {
$tagValue = $condition['tag_value'];
break; // 满足第一个条件即可
}
}
// 6. 保存标签
if ($tagValue !== null) {
$this->saveUserTag($userId, $tagDef->tag_id, $tagValue);
}
}
```
---
## 四、后端实现要点
### 4.1 需要创建的文件
**控制器**
- `app/controller/TagDataListController.php` - 数据列表管理控制器
**服务**
- `app/service/TagDataListService.php` - 数据列表服务
**仓储**
- `app/repository/TagDataListRepository.php` - 数据列表数据访问
**修改的文件**
- `app/service/TagTaskExecutor.php` - 标签任务执行器(核心逻辑)
- `app/service/TagService.php` - 标签计算服务
### 4.2 TagDataListController 接口
```php
<?php
namespace app\controller;
use app\repository\TagDataListRepository;
use app\utils\ApiResponseHelper;
use support\Request;
use support\Response;
class TagDataListController
{
// 获取数据列表列表
public function list(Request $request): Response
{
// GET /api/tag-data-lists
}
// 创建数据列表
public function create(Request $request): Response
{
// POST /api/tag-data-lists
}
// 获取数据列表详情
public function detail(Request $request, string $listId): Response
{
// GET /api/tag-data-lists/{list_id}
}
// 更新数据列表
public function update(Request $request, string $listId): Response
{
// PUT /api/tag-data-lists/{list_id}
}
// 删除数据列表
public function delete(Request $request, string $listId): Response
{
// DELETE /api/tag-data-lists/{list_id}
}
// 获取数据列表字段
public function getFields(Request $request, string $listId): Response
{
// GET /api/tag-data-lists/{list_id}/fields
// 1. 读取数据列表配置
// 2. 执行查询limit 10获取样本数据
// 3. 分析字段结构
// 4. 返回字段列表(包含中英文映射)
}
}
```
### 4.3 TagTaskExecutor 核心逻辑修改
```php
<?php
namespace app\service;
class TagTaskExecutor
{
/**
* 执行标签任务(使用数据列表配置)
*/
public function execute(string $taskId): void
{
$task = $this->taskRepository->find($taskId);
$targetTagIds = $task->target_tag_ids;
// 遍历每个标签定义
foreach ($targetTagIds as $tagId) {
$tagDef = $this->tagDefinitionRepository->find($tagId);
$ruleConfig = $tagDef->rule_config;
// ⭐ 获取数据列表配置
$dataListId = $ruleConfig['data_list_id'];
$dataList = $this->tagDataListRepository->find($dataListId);
if (!$dataList) {
// 记录错误并跳过
continue;
}
// ⭐ 执行动态查询
$results = $this->executeDynamicQuery($dataList);
// ⭐ 遍历查询结果,动态打标签
foreach ($results as $record) {
$userId = $this->extractUserId($record);
if (!$userId) {
continue;
}
// 根据规则条件计算标签值
$tagValue = $this->calculateTagValue($record, $ruleConfig);
if ($tagValue !== null) {
// 保存标签
$this->saveUserTag($userId, $tagId, $tagValue);
}
// 更新进度
$this->updateProgress($taskId);
}
}
}
/**
* 执行动态查询
*/
private function executeDynamicQuery(TagDataList $dataList): array
{
$queryConfig = $dataList->query_config;
// 1. 连接数据源
$adapter = $this->getDataSourceAdapter($dataList->data_source_id);
// 2. 构建MongoDB聚合管道
$pipeline = [];
// 2.1 过滤条件($match
if (!empty($queryConfig['filter'])) {
$filter = $this->buildFilter($queryConfig['filter']);
$pipeline[] = ['$match' => $filter];
}
// 2.2 联表查询($lookup
if (!empty($queryConfig['lookups'])) {
foreach ($queryConfig['lookups'] as $lookup) {
$pipeline[] = [
'$lookup' => [
'from' => $lookup['from'],
'localField' => $lookup['local_field'],
'foreignField' => $lookup['foreign_field'],
'as' => $lookup['as']
]
];
// 解构
if ($lookup['unwrap'] ?? false) {
$pipeline[] = [
'$unwind' => [
'path' => '$' . $lookup['as'],
'preserveNullAndEmptyArrays' => $lookup['preserve_null'] ?? true
]
];
}
}
}
// 2.3 排序($sort
if (!empty($queryConfig['sort'])) {
$pipeline[] = ['$sort' => $queryConfig['sort']];
}
// 2.4 限制($limit
if (!empty($queryConfig['limit'])) {
$pipeline[] = ['$limit' => $queryConfig['limit']];
}
// 3. 执行查询
$collection = $adapter->getCollection($dataList->database, $dataList->collection);
$cursor = $collection->aggregate($pipeline);
return iterator_to_array($cursor);
}
/**
* 构建过滤条件
*/
private function buildFilter(array $filterConditions): array
{
$filter = [];
foreach ($filterConditions as $condition) {
$field = $condition['field'];
$operator = $condition['operator'];
$value = $condition['value'];
// 转换为MongoDB运算符
$mongoFilter = match($operator) {
'eq' => ['$eq' => $value],
'ne' => ['$ne' => $value],
'gt' => ['$gt' => $value],
'gte' => ['$gte' => $value],
'lt' => ['$lt' => $value],
'lte' => ['$lte' => $value],
'in' => ['$in' => (array)$value],
'nin' => ['$nin' => (array)$value],
'regex' => ['$regex' => $value, '$options' => 'i'],
'exists' => ['$exists' => $value],
default => $value
};
$filter[$field] = $mongoFilter;
}
return $filter;
}
/**
* 计算标签值
*/
private function calculateTagValue(array $record, array $ruleConfig): ?string
{
$conditions = $ruleConfig['conditions'];
foreach ($conditions as $condition) {
$fieldName = $condition['field_name'];
$operator = $condition['operator'];
$expectedValue = $condition['value'];
$tagValue = $condition['tag_value'];
// 获取字段值支持嵌套字段user_info.mobile
$actualValue = $this->getFieldValue($record, $fieldName);
// 判断条件是否满足
$match = $this->evaluateCondition($actualValue, $operator, $expectedValue);
// 满足第一个条件即返回
if ($match) {
return $tagValue;
}
}
// 所有条件都不满足
return null;
}
/**
* 评估条件
*/
private function evaluateCondition($actualValue, string $operator, $expectedValue): bool
{
// 运算规则
if (in_array($operator, ['>', '>=', '<', '<=', '=', '!=', 'in', 'not_in'])) {
return match($operator) {
'>' => $actualValue > $expectedValue,
'>=' => $actualValue >= $expectedValue,
'<' => $actualValue < $expectedValue,
'<=' => $actualValue <= $expectedValue,
'=' => $actualValue == $expectedValue,
'!=' => $actualValue != $expectedValue,
'in' => in_array($actualValue, (array)$expectedValue),
'not_in' => !in_array($actualValue, (array)$expectedValue),
default => false
};
}
// 正则规则operator是正则表达式字符串
if (str_starts_with($operator, '/')) {
$pattern = $operator;
return preg_match($pattern, (string)$actualValue) ? true : false;
}
return false;
}
/**
* 提取用户ID
*/
private function extractUserId(array $record): ?string
{
// 优先从record中获取user_id
if (isset($record['user_id'])) {
return (string)$record['user_id'];
}
// 如果有联表的user_info从user_info中获取
if (isset($record['user_info']['user_id'])) {
return (string)$record['user_info']['user_id'];
}
return null;
}
/**
* 获取字段值(支持嵌套字段)
*/
private function getFieldValue(array $record, string $fieldName)
{
// 支持嵌套字段user_info.mobile
if (str_contains($fieldName, '.')) {
$parts = explode('.', $fieldName);
$value = $record;
foreach ($parts as $part) {
if (!isset($value[$part])) {
return null;
}
$value = $value[$part];
}
return $value;
}
return $record[$fieldName] ?? null;
}
}
```
---
## 五、API接口实现
### 5.1 数据列表管理接口
```
GET /api/tag-data-lists - 获取数据列表列表
POST /api/tag-data-lists - 创建数据列表
GET /api/tag-data-lists/{list_id} - 获取数据列表详情
PUT /api/tag-data-lists/{list_id} - 更新数据列表
DELETE /api/tag-data-lists/{list_id} - 删除数据列表
GET /api/tag-data-lists/{list_id}/fields - 获取数据列表字段
```
### 5.2 辅助接口
```
GET /api/data-sources/{id}/databases - 获取数据库列表
GET /api/data-sources/{id}/collections - 获取集合列表
GET /api/data-sources/{id}/fields - 获取字段列表
POST /api/data-sources/preview-query - 预览查询结果
```
---
## 六、使用场景示例
### 场景1根据消费金额分级
**步骤**
1. 创建数据列表"消费记录表"
- 主集合:`consumption_records`
- 过滤:`status = success`
- 联表:关联 `user_profile`
2. 创建标签定义"消费等级"
- 选择数据列表:消费记录表
- 添加条件:
- `amount < 3000``"低价值用户"`
- `amount >= 3000``"中等价值用户"`
- `amount > 9000``"高价值用户"`
3. 创建标签任务
- 选择标签:消费等级
- 启动任务
4. 任务执行
- 查询消费记录(状态=成功,关联用户信息)
- 遍历记录,根据金额判断等级
- 保存标签到 user_tags
### 场景2识别淘宝用户
**步骤**
1. 创建数据列表"消费记录表"
- 主集合:`consumption_records`
- 过滤:无
- 联表:无
2. 创建标签定义"平台识别"
- 选择数据列表:消费记录表
- 规则类型:正则规则
- 添加条件:
- 字段:`shop_name`
- 运算符:`/淘宝/`
- 值:`匹配`
- 标签值:`"淘宝平台"`
3. 启动任务,自动识别淘宝用户
---
## 七、优势
1. **配置化**SQL/查询配置保存在数据表,无需修改代码
2. **可复用**:同一个数据列表可被多个标签定义使用
3. **灵活性**:支持复杂查询,包括联表、过滤、排序
4. **可视化**通过UI界面配置无需手写SQL
5. **动态执行**:任务执行时动态读取配置并执行
---
## 八、注意事项
1. **性能优化**
- 设置合理的 limit 值
- 建立合适的索引
- 避免查询过多数据
2. **错误处理**
- 数据列表配置不存在时的处理
- 查询执行失败时的处理
- 字段不存在时的默认值处理
3. **数据一致性**
- user_id 必须存在
- 字段名必须与实际字段匹配
- 联表字段必须正确
---
**文档更新时间**2025-01-XX
**实现方案**:基于数据列表动态打标签

View File

@@ -0,0 +1,417 @@
# 数据列表管理 - 真实API接入说明
## 修改概览
已将 `QueryBuilder` 组件中的基础配置部分从 Mock 数据切换到真实 API复用了数据采集页面的 API。
## 修改的文件
### TaskShow/src/components/QueryBuilder/QueryBuilder.vue
**主要修改**
1. 导入数据采集API模块
2. 替换所有基础配置相关的 Mock 数据为真实 API 调用
3. 添加数据库和集合对象的缓存机制
**修改详情**
#### 1. 导入 API 模块
```typescript
import * as dataCollectionApi from '@/api/dataCollection'
```
#### 2. 添加对象缓存变量
```typescript
// 保存完整的对象用于API调用
const databaseObjects = ref<any[]>([])
const collectionObjects = ref<any[]>([])
```
**用途**:因为 API 可能返回对象格式(如 `{ id, name }`),需要缓存这些对象用于后续 API 调用。
#### 3. 真实 API 调用
##### 加载数据源
**API路径**`GET /data-collection-tasks/data-sources`
```typescript
const loadDataSources = async () => {
try {
dataSourceLoading.value = true
const response = await dataCollectionApi.getDataSources()
// 转换数据格式兼容旧的data_source_id字段
dataSources.value = (response.data || []).map((ds: any) => ({
data_source_id: ds.id || ds.data_source_id,
name: ds.name || ds.id,
type: ds.type,
status: 1
}))
} catch (error: any) {
ElMessage.error(error.message || '加载数据源失败')
} finally {
dataSourceLoading.value = false
}
}
```
##### 加载数据库列表
**API路径**`GET /data-collection-tasks/data-sources/{dataSourceId}/databases`
```typescript
const loadDatabases = async (dataSourceId: string) => {
try {
databaseLoading.value = true
const response = await dataCollectionApi.getDatabases(dataSourceId)
// 转换为字符串数组(只返回数据库名称)
databases.value = (response.data || []).map((db: any) =>
typeof db === 'string' ? db : db.name
)
// 保存完整的数据库对象供后续使用
databaseObjects.value = response.data || []
} catch (error: any) {
ElMessage.error(error.message || '加载数据库列表失败')
} finally {
databaseLoading.value = false
}
}
```
##### 加载集合列表
**API路径**`GET /data-collection-tasks/data-sources/{dataSourceId}/databases/{databaseId}/collections`
```typescript
const loadCollections = async (dataSourceId: string, database: string) => {
try {
collectionLoading.value = true
// 查找数据库对象
const dbObj = databaseObjects.value.find((db: any) =>
(typeof db === 'object' && db.name === database) || db === database
)
const response = await dataCollectionApi.getCollections(
dataSourceId,
dbObj || database
)
// 转换为字符串数组(只返回集合名称)
collections.value = (response.data || []).map((coll: any) =>
typeof coll === 'string' ? coll : coll.name
)
// 保存完整的集合对象供后续使用
collectionObjects.value = response.data || []
} catch (error: any) {
ElMessage.error(error.message || '加载集合列表失败')
} finally {
collectionLoading.value = false
}
}
```
##### 加载字段列表
**API路径**`GET /data-collection-tasks/data-sources/{dataSourceId}/databases/{databaseId}/collections/{collectionId}/fields`
```typescript
const loadFields = async (dataSourceId: string, database: string, collection: string) => {
try {
// 查找数据库和集合对象
const dbObj = databaseObjects.value.find((db: any) =>
(typeof db === 'object' && db.name === database) || db === database
)
const collObj = collectionObjects.value.find((coll: any) =>
(typeof coll === 'object' && coll.name === collection) || coll === collection
)
const response = await dataCollectionApi.getFields(
dataSourceId,
dbObj || database,
collObj || collection
)
// 转换字段格式API返回的是 { name, type },需要转换为 { field, field_name, type }
availableFields.value = (response.data || []).map((field: any) => ({
field: field.name, // 显示名称使用字段名
field_name: field.name, // 实际字段名
type: field.type || 'string'
}))
} catch (error: any) {
ElMessage.error(error.message || '加载字段列表失败')
availableFields.value = []
}
}
```
##### 预览查询数据
**API路径**`POST /data-collection-tasks/preview-query`
```typescript
const handlePreview = async () => {
if (!canPreview.value) {
ElMessage.warning('请先完成基础配置')
return
}
try {
// 构建查询预览请求
const lookups = queryConfig.lookups.map((lookup: any) => ({
from: lookup.foreign_collection,
localField: lookup.local_field,
foreignField: lookup.foreign_field,
as: lookup.as,
unwind: lookup.unwind,
preserveNullAndEmptyArrays: lookup.preserve_null
}))
const filterConditions = queryConfig.filter.map((filter: any) => ({
field: filter.field_name,
operator: filter.operator,
value: filter.value,
logic: filter.logic || 'and'
}))
const response = await dataCollectionApi.previewQuery({
data_source_id: queryConfig.data_source_id,
database: queryConfig.database,
collection: queryConfig.collection,
lookups: lookups.length > 0 ? lookups : undefined,
filter_conditions: filterConditions.length > 0 ? filterConditions : undefined,
limit: queryConfig.limit || 10
})
previewData.value = response.data?.data || []
ElMessage.success(`预览成功,共 ${previewData.value.length} 条数据`)
} catch (error: any) {
ElMessage.error(error.message || '预览失败')
}
}
```
#### 4. 变化处理函数优化
添加了对缓存对象的清理:
```typescript
const handleDataSourceChange = async (dataSourceId: string) => {
if (!dataSourceId) {
databases.value = []
collections.value = []
availableFields.value = []
databaseObjects.value = [] // 新增
collectionObjects.value = [] // 新增
queryConfig.database = ''
queryConfig.collection = ''
return
}
await loadDatabases(dataSourceId)
}
const handleDatabaseChange = async (database: string) => {
if (!database || !queryConfig.data_source_id) {
collections.value = []
availableFields.value = []
collectionObjects.value = [] // 新增
queryConfig.collection = ''
return
}
await loadCollections(queryConfig.data_source_id, database)
}
```
## 复用的 API 模块
### 文件位置
`TaskShow/src/api/dataCollection.ts`
### API 列表
| API 函数 | HTTP 方法 | 路径 | 用途 |
|---------|----------|------|------|
| `getDataSources()` | GET | `/data-collection-tasks/data-sources` | 获取数据源列表 |
| `getDatabases(dataSourceId)` | GET | `/data-collection-tasks/data-sources/{id}/databases` | 获取数据库列表 |
| `getCollections(dataSourceId, database)` | GET | `/data-collection-tasks/data-sources/{id}/databases/{dbId}/collections` | 获取集合列表 |
| `getFields(dataSourceId, database, collection)` | GET | `/data-collection-tasks/data-sources/{id}/databases/{dbId}/collections/{collId}/fields` | 获取字段列表 |
| `previewQuery(data)` | POST | `/data-collection-tasks/preview-query` | 预览查询结果 |
## 数据格式说明
### 数据源Data Source
```typescript
{
id: string
name: string
type: 'mongodb' | 'mysql' | ...
}
```
### 数据库Database
```typescript
{
id: string
name: string
}
// 或简单的字符串数组
string[]
```
### 集合Collection
```typescript
{
id: string
name: string
}
// 或简单的字符串数组
string[]
```
### 字段Field
```typescript
{
name: string
type: 'string' | 'number' | 'datetime' | ...
}
```
### 预览查询请求Preview Query Request
```typescript
{
data_source_id: string
database: string
collection: string
lookups?: Array<{
from: string
localField: string
foreignField: string
as: string
unwind?: boolean
preserveNullAndEmptyArrays?: boolean
}>
filter_conditions?: Array<{
field: string
operator: string
value: any
logic: 'and' | 'or'
}>
limit?: number
}
```
### 预览查询响应Preview Query Response
```typescript
{
fields: Array<{ name: string; type: string }>
data: Array<any>
count: number
}
```
## 兼容性处理
### 1. 数据库/集合对象 vs 字符串
API 可能返回对象(`{ id, name }`)或字符串数组,代码中做了兼容处理:
```typescript
// 显示用字符串数组
databases.value = (response.data || []).map((db: any) =>
typeof db === 'string' ? db : db.name
)
// 缓存完整对象
databaseObjects.value = response.data || []
// 查找时兼容两种格式
const dbObj = databaseObjects.value.find((db: any) =>
(typeof db === 'object' && db.name === database) || db === database
)
```
### 2. 字段名转换
API 返回的字段格式与 QueryBuilder 使用的格式不同:
```typescript
// API 返回: { name, type }
// QueryBuilder 需要: { field, field_name, type }
availableFields.value = (response.data || []).map((field: any) => ({
field: field.name, // 显示名称
field_name: field.name, // 实际字段名
type: field.type || 'string'
}))
```
## 测试建议
1. **数据源加载测试**
- 打开数据列表配置页面
- 检查数据源下拉框是否正确加载
2. **级联加载测试**
- 选择数据源 → 检查数据库列表
- 选择数据库 → 检查集合列表
- 选择集合 → 检查字段列表
3. **预览功能测试**
- 配置完整查询(包含过滤、联表)
- 点击"预览数据"
- 检查返回结果是否正确
4. **错误处理测试**
- 模拟网络错误
- 检查错误提示是否友好
## 注意事项
1. **后端 API 必须已实现**
- 所有使用的 API 端点都必须在后端正确实现
- 数据格式必须与前端期望一致
2. **性能优化**
- 字段列表加载可能较慢(特别是大集合)
- 建议后端添加缓存机制
3. **错误处理**
- 所有 API 调用都有 try-catch 包裹
- 错误信息会通过 ElMessage 显示给用户
4. **数据转换**
- 注意 API 返回的数据格式
- 确保前端正确转换为组件所需格式
## 剩余 Mock 数据
以下部分仍使用 Mock 数据(可后续接入真实 API
1. **TagDataList/List.vue**
- 数据列表的 CRUD 操作
- API 端点:`/api/tag-data-lists`
2. **TagDataList/Form.vue**
- 数据列表配置的保存
- API 端点:`/api/tag-data-lists`
3. **TagDefinition/Form.vue**
- 数据列表下拉选择
- 字段列表加载
- API 端点:`/api/tag-data-lists``/api/tag-data-lists/{id}/fields`
## 下一步工作
1. 开发数据列表管理的后端 API
2. 将 TagDataList 的 CRUD 操作接入真实 API
3. 将 TagDefinition 中的数据列表选择接入真实 API
4. 完整的集成测试
---
**更新时间**2025-01-XX
**状态**QueryBuilder 基础配置已接入真实 API

View File

@@ -0,0 +1,327 @@
# 数据列表管理界面使用说明
## 一、界面概览
数据列表管理界面用于可视化配置MongoDB查询支持过滤条件、联表查询、排序等功能。配置保存后可在标签定义中引用。
### 访问路径
- 列表页面http://localhost:5173/tag-data-lists
- 创建页面http://localhost:5173/tag-data-lists/create
- 编辑页面http://localhost:5173/tag-data-lists/:id/edit
### 菜单位置
左侧菜单 → 标签任务 → 数据列表管理
---
## 二、界面功能
### 2.1 数据列表管理List.vue
**功能列表**
- ✅ 展示所有数据列表配置
- ✅ 按名称搜索
- ✅ 按状态筛选
- ✅ 创建新数据列表
- ✅ 编辑数据列表
- ✅ 删除数据列表
- ✅ 分页展示
**表格列**
- 列表名称
- 列表编码
- 数据源ID
- 数据库
- 主集合
- 状态(启用/禁用)
- 描述
- 创建时间
- 操作(编辑、删除)
### 2.2 数据列表配置Form.vue
**基本信息配置**
- 列表名称(必填)- 如:"消费记录表"
- 列表编码(必填)- 如:"consumption_records"
- 描述(可选)
- 状态(启用/禁用)
**查询配置QueryBuilder组件**
#### 1. 基础配置
- 数据源选择
- 数据库选择
- 主集合选择
#### 2. 过滤条件WHERE
- 支持多个条件
- 逻辑关系AND/OR
- 字段选择(自动加载)
- 运算符选择:
- 等于、不等于
- 大于、大于等于、小于、小于等于
- 包含、不包含
- 模糊匹配、存在
- 值输入(根据字段类型自动调整)
#### 3. 联表查询JOIN/LOOKUP
- 支持多个联表
- 关联集合选择
- 主集合字段选择
- 关联集合字段输入
- 结果字段名配置
- 解构开关(是否展开数组)
- 保留空值开关LEFT JOIN效果
#### 4. 排序和限制
- 排序字段选择
- 排序方式(升序/降序)
- 限制数量默认1000
#### 5. 查询预览
- 实时显示生成的MongoDB聚合管道代码
- 预览查询结果数据最多显示配置的limit条
---
## 三、使用示例
### 示例1创建简单的消费记录查询
**步骤**
1. 访问 `/tag-data-lists`,点击"创建数据列表"
2. 填写基本信息:
- 列表名称:`消费记录表`
- 列表编码:`consumption_records`
- 描述:`用于标签定义的消费记录数据`
- 状态:`启用`
3. 配置查询:
- 数据源:选择"MongoDB标签引擎数据源"
- 数据库:选择"tag_engine"
- 主集合:选择"consumption_records"
4. 添加过滤条件:
- 字段:`交易状态`
- 运算符:`等于`
- 值:`success`
5. 配置排序:
- 排序字段:`创建时间`
- 排序方式:`降序`
- 限制数量:`1000`
6. 点击"预览数据"查看效果
7. 点击"保存"
**生成的查询**
```javascript
db.consumption_records.aggregate([
{ $match: { status: { $eq: "success" } } },
{ $sort: { create_time: -1 } },
{ $limit: 1000 }
])
```
### 示例2创建带联表的查询
**步骤**
1. 基本配置同上
2. 添加过滤条件:
- 字段:`交易金额`
- 运算符:`大于`
- 值:`1000`
3. 添加联表查询:
- 关联集合:`user_profile`
- 主集合字段:`user_id`
- 关联集合字段:`user_id`
- 结果字段名:`user_info`
- 解构:`是`
- 保留空值:`是`
4. 保存
**生成的查询**
```javascript
db.consumption_records.aggregate([
{ $match: { amount: { $gt: 1000 } } },
{ $lookup: {
from: "user_profile",
localField: "user_id",
foreignField: "user_id",
as: "user_info"
} },
{ $unwind: {
path: "$user_info",
preserveNullAndEmptyArrays: true
} },
{ $limit: 1000 }
])
```
### 示例3在标签定义中使用
**步骤**
1. 访问 `/tag-definitions/create`
2. 填写标签编码和名称
3. 选择数据列表:`消费记录表`
4. 系统自动加载字段:`交易金额``店铺名称``交易状态`
5. 添加规则条件:
- 字段:`交易金额`
- 运算符:`<`
- 值:`3000`
- 标签值:`低价值用户`
6. 保存
---
## 四、Mock数据位置
所有Mock数据都标记为 `TODO: 替换为真实API`,方便后续替换。
### 4.1 List.vue
**位置**`loadData()` 函数
```typescript
// 第106-142行
// Mock数据
const mockData = [
{
list_id: 'list_001',
list_code: 'consumption_records',
list_name: '消费记录表',
// ...
},
// ...
]
```
### 4.2 QueryBuilder.vue
**位置1**`loadDataSources()` - 第368-383行
```typescript
dataSources.value = [
{ data_source_id: 'source_001', name: 'MongoDB标签引擎数据源' }
]
```
**位置2**`loadDatabases()` - 第398-410行
```typescript
databases.value = ['tag_engine', 'business_db', 'analytics_db']
```
**位置3**`loadCollections()` - 第425-443行
```typescript
if (database === 'tag_engine') {
collections.value = ['consumption_records', 'user_profile', ...]
}
```
**位置4**`loadFields()` - 第458-492行
```typescript
if (collection === 'consumption_records') {
availableFields.value = [
{ field: '交易金额', field_name: 'amount', type: 'number' },
// ...
]
}
```
**位置5**`handlePreview()` - 第530-569行
```typescript
previewData.value = [
{ user_id: 'user_001', amount: 5000, ... },
// ...
]
```
### 4.3 TagDefinition/Form.vue
**位置1**`loadDataLists()` - 第285-303行
```typescript
dataLists.value = [
{ list_id: 'list_001', list_name: '消费记录表' },
{ list_id: 'list_002', list_name: '用户档案表' }
]
```
**位置2**`loadFields()` - 第318-347行
```typescript
if (listId === 'list_001') {
fields.value = [
{ field: '交易金额', field_name: 'amount', ... },
// ...
]
}
```
---
## 五、后续开发计划
### 阶段1完善界面✅ 已完成)
- ✅ QueryBuilder可视化查询构建器
- ✅ 数据列表管理(列表、创建、编辑)
- ✅ 标签定义表单(集成数据列表选择)
- ✅ 所有Mock数据界面可独立运行
### 阶段2后端API开发待开发
- ⏳ TagDataListController - 数据列表管理API
- ⏳ TagDataListRepository - 数据访问层
- ⏳ TagDataListService - 业务逻辑层
- ⏳ 数据源相关APIdatabases、collections、fields
- ⏳ 查询预览API
### 阶段3集成联调待开发
- ⏳ 删除Mock代码
- ⏳ 集成真实API
- ⏳ 测试完整流程
- ⏳ 错误处理优化
### 阶段4标签任务执行待开发
- ⏳ 修改TagTaskExecutor
- ⏳ 从tag_data_lists读取配置
- ⏳ 执行动态查询
- ⏳ 遍历结果打标签
---
## 六、界面预览
### 数据列表管理列表
![列表页面]()
- 显示所有配置的数据列表
- 支持搜索和筛选
- 操作按钮:编辑、删除
### 数据列表配置表单
![配置表单]()
- 基本信息区域
- 可视化查询构建器
- 实时查询预览
- 数据预览
### 标签定义表单
![标签定义]()
- 选择数据列表
- 自动加载字段
- 配置规则条件
---
## 七、技术要点
### 7.1 组件通信
- QueryBuilder使用 `v-model` 双向绑定
- 父组件监听配置变化
### 7.2 字段映射
- 前端显示中文字段名field
- 后端存储英文字段名field_name
- 保存时同时存储两者
### 7.3 查询构建
- 实时生成MongoDB聚合管道
- 支持复杂的嵌套查询
- 可视化预览查询结果
---
**文档更新时间**2025-01-XX
**状态**界面已完成使用Mock数据

View File

@@ -0,0 +1,454 @@
# Moncter 系统最新架构逻辑提示词
> **用途说明**:本文档作为系统架构的核心记忆点,用于快速理解和开发维护 Moncter 系统。
---
## 一、系统定位
**Moncter** 是一个基于 **Webman (Workerman)** 框架的**用户标签引擎和数据采集中心**,核心功能包括:
1. **多数据源数据采集**:支持 MongoDB、MySQL 等多种数据源,支持批量采集和实时监听两种模式
2. **用户标签计算引擎**:基于用户消费数据实时计算和更新标签,支持规则引擎配置
3. **用户身份管理**:支持身份证、手机号等标识的统一管理,处理临时用户、正式用户转换和身份合并
4. **数据库实时同步**:使用 MongoDB Change Streams 实现数据库间实时同步
---
## 二、技术栈
### 后端
- **框架**Webman (Workerman) - 高性能 PHP 框架,多进程架构
- **PHP版本**>= 8.1
- **数据库**MongoDB (主数据库)
- **消息队列**RabbitMQ - 异步任务处理
- **缓存/锁**Redis - 分布式锁、任务状态存储
### 前端
- **框架**Vue 3 + TypeScript
- **UI组件**Element Plus
- **状态管理**Pinia
- **构建工具**Vite
---
## 三、系统架构
### 3.1 分层架构
```
┌─────────────────────────────────────────┐
│ 应用层 (HTTP API) │
│ Controller 层处理HTTP请求 │
└──────────────────┬──────────────────────┘
┌──────────────────▼──────────────────────┐
│ 业务服务层 (Service) │
│ - ConsumptionService: 消费记录处理 │
│ - IdentifierService: 身份解析 │
│ - TagService: 标签计算 │
│ - DataCollectionTaskService: 任务管理 │
└──────────────────┬──────────────────────┘
┌──────────────────▼──────────────────────┐
│ 数据访问层 (Repository) │
│ - UserProfileRepository │
│ - UserPhoneRelationRepository │
│ - ConsumptionRecordRepository │
└──────────────────┬──────────────────────┘
┌──────────────────▼──────────────────────┐
│ 数据存储层 │
│ MongoDB / Redis / RabbitMQ │
└─────────────────────────────────────────┘
```
### 3.2 进程架构Workerman
系统包含以下进程(配置在 `config/process.php`
1. **webman (HTTP Server)**
- 处理 HTTP API 请求
- 进程数:`CPU核心数 × 4`
- 监听端口:`8787`
2. **monitor (文件监控)**
- 监控文件变化,自动重载
- 单进程
3. **data_sync_scheduler (数据采集任务调度器)**
- 读取任务配置(配置文件 + 数据库)
- 启动和管理所有数据采集任务
- 进程数:`10`
4. **data_sync_worker (数据同步Worker)**
- 消费 RabbitMQ `data_sync` 队列
- 写入目标数据库,更新用户统计
- 进程数:`20`
5. **tag_calculation_worker (标签计算Worker)**
- 消费 RabbitMQ `tag_calculation` 队列
- 根据用户数据计算标签值
- 进程数:`2`
---
## 四、核心业务逻辑
### 4.1 消费记录到用户创建流程
**核心流程:消费记录 → 身份解析 → 用户创建/关联 → 手机关联建立 → 写入记录**
#### 关键步骤
1. **身份解析** (`IdentifierService::resolvePersonId`)
- 优先级:身份证 > 手机号
- 如果只有手机号,通过 `UserPhoneService::findUserByPhone` 查询
- **关键**:使用 `consume_time` 作为查询时间点(不是当前时间)
2. **临时用户创建** (`IdentifierService::createTemporaryPerson`)
- 生成 UUID 作为 `user_id`
- 设置 `is_temporary = true`
- **必须**建立手机关联(使用消费时间作为 `effective_time`
3. **手机关联建立** (`UserPhoneService::addPhoneToUser`)
- 冲突检测:检查手机号在 `effective_time` 是否已有有效关联
- 自动过期旧关联:如果冲突,将旧关联的 `expire_time` 设置为新关联的 `effective_time`
- 创建新关联:`effective_time = consume_time``expire_time = null`
4. **用户合并** (`ConsumptionService::handleMergeIfNeeded`)
- 当手机号和身份证同时出现,且关联到不同用户时触发
- 临时用户 → 自动合并到正式用户
- 正式用户冲突 → 以身份证为准(代订场景),记录日志
5. **写入消费记录** (`ConsumptionService::createRecord`)
- 写入 `consumption_records` 表(按时间分表:`consumption_records_YYYYMM`
- 更新 `user_profile` 统计信息(`total_amount``total_count``last_consume_time`
- 触发标签计算(推送到 RabbitMQ 队列)
#### 关键设计要点
- **时间点精确匹配**:所有手机号查询必须基于 `consume_time`,支持历史数据导入
- **时间窗口管理**:手机关联支持 `effective_time``expire_time`,支持手机号回收
- **临时用户机制**:处理只有手机号的情况,后续可转换为正式用户
- **UUID策略**:所有用户使用 UUID 作为 `user_id`,身份证只作为字段存储
### 4.2 数据采集系统
#### 任务配置方式
1. **配置文件方式** (`config/data_collection_tasks.php`)
- 适用于系统级任务(如数据库实时同步)
- 需要版本控制
2. **数据库方式** (`data_collection_tasks` 集合)
- 通过前端界面动态创建和管理
- 支持批量采集batch和实时监听realtime两种模式
#### Handler 类型
- **ConsumptionCollectionHandler**:消费记录采集,自动处理用户身份解析
- **GenericCollectionHandler**:通用数据采集,支持字段映射、过滤条件、连表查询
- **DatabaseSyncHandler**数据库同步支持全量同步和增量同步Change Streams
#### 采集模式
- **批量采集Batch**分页查询数据支持定时调度Cron
- **实时监听Realtime**:使用 MongoDB Change Streams 持续监听变更
### 4.3 标签计算系统
#### 标签定义
存储在 `tag_definitions` 集合,包含:
- `tag_id``tag_code``tag_name`
- `rule_config`规则配置JSON格式
- `update_frequency`real_time/daily/weekly
#### 标签计算流程
```
消费记录写入
更新用户统计信息
推送标签计算消息到 RabbitMQ
TagCalculationWorker 消费消息
TagService::calculateTags()
├─→ 获取用户数据
├─→ 获取标签定义列表
├─→ SimpleRuleEngine 计算标签值
├─→ 更新或创建 user_tags 记录
└─→ 记录 tag_history 变更历史
```
---
## 五、数据存储设计
### 5.1 MongoDB 核心集合
#### 用户相关
**user_profile**(用户画像)
- `user_id`UUID主键
- `id_card_hash`:身份证哈希值
- `id_card_encrypted`:加密的身份证号
- `is_temporary`是否为临时用户true/false
- `total_amount`:总消费金额
- `total_count`:总消费次数
- `last_consume_time`:最后消费时间
**user_phone_relations**(手机关联表)
- `phone_number`:手机号
- `phone_hash`:手机号哈希值
- `user_id`关联的用户ID
- `effective_time`:生效时间
- `expire_time`过期时间null 表示当前有效)
- `is_active`:是否有效
**consumption_records**(消费记录)
- 按时间分表:`consumption_records_YYYYMM`
- `record_id`记录IDUUID
- `user_id`用户ID
- `consume_time`:消费时间
- `amount`:消费金额
- `actual_amount`:实际金额
#### 标签相关
**tag_definitions**(标签定义)
- `tag_id``tag_code``tag_name`
- `rule_config`规则配置JSON
- `update_frequency`:更新频率
**user_tags**(用户标签)
- `user_id``tag_id`
- `tag_value`:标签值
- `confidence`:置信度
**tag_history**(标签变更历史)
- `user_id``tag_id`
- `old_value``new_value`
- `change_time`:变更时间
#### 任务相关
**data_collection_tasks**(数据采集任务)
- `task_id`任务IDUUID
- `mode`采集模式batch/realtime
- `field_mappings`:字段映射配置
- `filter_conditions`:过滤条件
- `progress`:任务进度
- `status`任务状态pending/running/paused/stopped
**data_sources**(数据源)
- `data_source_id`数据源ID
- `type`数据源类型mongodb/mysql
- `host``port``database`
### 5.2 Redis 存储
- **分布式锁**`lock:data_collection:{task_id}`
- **任务状态标志**`data_collection_task:{task_id}:start/pause/stop`
- **同步状态**`data_collection:{task_id}:last_sync_time`
### 5.3 RabbitMQ 队列
- **data_sync**:数据同步队列
- **tag_calculation**:标签计算队列
---
## 六、设计模式
### 6.1 Handler 模式
- `ConsumptionCollectionHandler`:消费记录采集
- `GenericCollectionHandler`:通用数据采集
- `DatabaseSyncHandler`:数据库同步
### 6.2 适配器模式
- `DataSourceAdapterInterface`:数据源适配器接口
- `MongoDBAdapter`MongoDB适配器
- `MySQLAdapter`MySQL适配器
### 6.3 仓库模式Repository
- 所有 Repository 类封装数据访问逻辑
- 提供统一的查询接口
### 6.4 工厂模式
- `DataSourceAdapterFactory`:创建数据源适配器
- `PollingStrategyFactory`:创建轮询策略
---
## 七、关键代码位置
### 核心服务类
**ConsumptionService** (`app/service/ConsumptionService.php`)
- `createRecord()`:创建消费记录的主入口
- `handleMergeIfNeeded()`:处理用户合并逻辑
**IdentifierService** (`app/service/IdentifierService.php`)
- `resolvePersonId()`解析用户ID统一入口
- `resolvePersonIdByPhone()`:通过手机号解析
- `resolvePersonIdByIdCard()`:通过身份证解析
- `createTemporaryPerson()`:创建临时用户
- `bindIdCardToPerson()`:绑定身份证
**UserPhoneService** (`app/service/UserPhoneService.php`)
- `addPhoneToUser()`:添加手机号关联(核心方法)
- `findUserByPhone()`:根据手机号查找用户(支持时间点查询)
- `removePhoneFromUser()`:移除手机号关联
**TagService** (`app/service/TagService.php`)
- `calculateTags()`:计算用户标签
**DataCollectionTaskService** (`app/service/DataCollectionTaskService.php`)
- 数据采集任务的创建、启动、暂停、停止等操作
### 进程类
- **DataSyncScheduler** (`app/process/DataSyncScheduler.php`):数据采集任务调度器
- **DataSyncWorker** (`app/process/DataSyncWorker.php`)数据同步Worker
- **TagCalculationWorker** (`app/process/TagCalculationWorker.php`)标签计算Worker
---
## 八、API 接口体系
### 用户相关
- `POST /api/users`:创建用户
- `GET /api/users/{user_id}`:查询用户
- `POST /api/users/search`:搜索用户
### 标签相关
- `GET /api/users/{user_id}/tags`:查询用户标签
- `PUT /api/users/{user_id}/tags`:计算/更新用户标签
- `POST /api/tags/filter`:根据标签筛选用户
### 数据采集任务
- `POST /api/data-collection-tasks`:创建任务
- `POST /api/data-collection-tasks/{task_id}/start`:启动任务
- `POST /api/data-collection-tasks/{task_id}/pause`:暂停任务
- `POST /api/data-collection-tasks/{task_id}/stop`:停止任务
- `GET /api/data-collection-tasks/{task_id}/progress`:获取任务进度
### 数据源
- `GET /api/data-sources`:获取数据源列表
- `POST /api/data-sources`:创建数据源
- `POST /api/data-sources/test-connection`:测试连接
---
## 九、关键设计原则
### 9.1 时间点精确匹配
- 所有手机号查询必须基于消费记录的实际时间点(`consume_time`
- 支持历史数据导入和批量处理
- 正确处理手机号回收后的重新分配
### 9.2 临时用户机制
- 只有手机号时创建临时用户(`is_temporary=true`
- 临时用户必须建立手机关联
- 绑定身份证后自动转为正式用户
### 9.3 时间窗口管理
- 手机关联支持 `effective_time``expire_time`
- 时间窗口必须连续,不能有间隙
- 支持手机号在不同时间段属于不同用户
### 9.4 UUID 策略
- 所有用户统一使用 UUID 作为 `user_id`
- 身份证只作为字段存储,不作为主键
- 转为正式用户时,只更新身份证字段,不改变 `user_id`
### 9.5 配置化设计
- 任务配置支持配置文件和数据库两种方式
- 数据源配置统一管理
- 业务逻辑与配置分离
---
## 十、特殊场景处理
### 场景1手机号回收
- 一个手机号在不同时间段被不同用户使用
- 通过时间窗口精确管理历史关联
- 新关联建立时,自动将旧关联标记为过期
### 场景2临时用户转正式用户
- 第一次消费:只有手机号 → 创建临时用户
- 第二次消费:手机号 + 身份证 → 临时用户转为正式用户
### 场景3代订场景
- 手机号和身份证关联到不同的正式用户
- 策略:以身份证为准,消费记录归属到身份证用户
- 手机号关联保持不变(可能是代订),记录日志
### 场景4用户合并
- 临时用户自动合并到正式用户
- 合并内容:统计数据、标签、消费记录、手机关联
---
## 十一、数据流图
```
数据源 (MongoDB/MySQL)
数据采集任务 (DataSyncScheduler)
├─→ ConsumptionCollectionHandler
│ ├─→ 批量采集:分页查询
│ └─→ 实时监听Change Streams
└─→ DatabaseSyncHandler
├─→ 全量同步:批量读取写入
└─→ 增量同步Change Streams
消费记录写入 (ConsumptionService)
├─→ 身份解析 (IdentifierService)
│ ├─→ 手机号 → user_id
│ └─→ 如果不存在,创建临时用户
├─→ 写入 consumption_records
├─→ 更新 user_profile 统计
└─→ 触发标签计算RabbitMQ
标签计算 (TagCalculationWorker)
├─→ TagService::calculateTags()
├─→ SimpleRuleEngine 计算
├─→ 更新 user_tags
└─→ 记录 tag_history
```
---
## 十二、配置文件位置
- **应用配置**`config/app.php`
- **进程配置**`config/process.php`
- **路由配置**`config/route.php`
- **数据采集任务配置**`config/data_collection_tasks.php`
- **数据源配置**`config/data_sources.php`
- **数据库配置**`config/database.php`
---
## 十三、关键注意事项
1. **时间点查询**:所有手机号查询必须传入 `consume_time`,不能使用当前时间
2. **临时用户关联**:临时用户创建后必须建立手机关联,不能跳过
3. **时间窗口连续性**:手机关联的时间窗口必须连续,不能有间隙
4. **UUID策略**用户ID统一使用UUID身份证只作为字段存储
5. **合并时机**:仅在手机号和身份证号同时出现且关联到不同用户时触发合并
6. **代订场景**:正式用户冲突时,以身份证为准,手机号关联保持不变
---
**文档版本**v1.0
**最后更新**2025-01-28
**维护说明**:本文档作为系统架构的核心记忆点,如有架构变更,请及时更新本文档

View File

@@ -0,0 +1,294 @@
# 标签定义逻辑文档对比分析
## 一、文档概述
- **`标签定逻辑.md`**原始需求文档66行- 简洁版本
- **`标签定逻辑真实.md`**完善后的技术文档579行- 详细版本
---
## 二、核心含义一致性分析
### ✅ 2.1 一致的部分
#### 1. **后台预先工作**
- **原始版本**提到需要预先处理数据列表API通过SQL查询配置化数据集合
- **详细版本**详细说明了数据源类型和字段API的实现
- **结论**:✅ **含义一致**,详细版本是对原始需求的扩展说明
#### 2. **规则配置概念**
- **原始版本**:提到运算规则和正则规则两种类型
- **详细版本**详细说明了simple规则和regex规则扩展方案
- **结论**:✅ **含义一致**,详细版本补充了实现细节
#### 3. **标签任务处理流程**
- **原始版本**4个简单步骤
```
1、选择已定义的标签
2、选择后点击保存状态为待启动
3、在列表启动的时候后台先从后台读取定义标签的集合并查询出来
4、根据定义标签的规则条件直接根据用户表遍历过去进行打标签然后将标签存储。
```
- **详细版本**:详细的流程图和代码逻辑
- **结论**:✅ **含义一致**,详细版本是对原始流程的细化
#### 4. **标签基本信息**
- **原始版本**:提到"标签编码、标签名称"
- **详细版本**详细列出了所有字段tag_code、tag_name、category、description等
- **结论**:✅ **含义一致**,详细版本补充了完整字段列表
---
## 三、存在差异的部分
### ⚠️ 3.1 JSON配置格式差异
#### 原始版本的格式:
```json
[
{
field:"交易金额",
operator:"<",
value:3000,
tag_value"低价值用户" // ❌ tag_value在条件中
},
{
field:"交易金额",
operator:">=",
value:3000,
tag_value"中等价值用户" // ❌ tag_value在条件中
}
]
```
#### 详细版本的格式(符合实际代码):
```json
{
"rule_type": "simple",
"conditions": [
{
"field": "total_amount",
"operator": "<",
"value": 3000
// ✅ tag_value不在条件中
}
],
"tag_value": "低价值用户", // ✅ tag_value在rule_config中
"confidence": 1.0
}
```
**差异说明**
- ❌ **原始版本**:每个条件都有独立的 `tag_value`,这不符合实际的数据结构
- ✅ **详细版本**`tag_value` 在 `rule_config` 中,所有条件满足时使用同一个 `tag_value`
**实际代码验证**
根据 `SimpleRuleEngine.php` 和 `TagService.php` 的代码,正确的格式应该是:
- `tag_value` 在 `rule_config` 中,不在 `conditions` 中
- 多个条件之间是 AND 关系,所有条件满足时使用同一个 `tag_value`
**建议**
- 原始版本可能是业务需求的简化描述
- 实际实现应该按照详细版本的格式
---
### ⚠️ 3.2 字段命名差异
#### 原始版本:
- 使用中文字段名:`"交易金额"`、`"店铺名称"`、`"交易状态"`、`"手机号"`
#### 详细版本:
- 使用英文字段名:`"total_amount"`、`"shop_name"`、`"status"`、`"phone"`
**差异说明**
- 原始版本可能是面向业务人员的描述
- 详细版本是面向开发人员的技术实现
- 实际数据库中应该使用英文字段名
**建议**
- 前端界面可以显示中文名称
- 后端存储和计算使用英文字段名
- 需要建立字段映射关系
---
### ⚠️ 3.3 "数据列表"概念差异
#### 原始版本:
```
-先用sql语句查询出符合条件的结果这段数据库的查询操作可以配置化来增减数据集合这个结果可以命名例如:消费记录表。
```
#### 详细版本:
```
系统支持以下数据源类型:
1. user_profile - 用户档案表
2. consumption_records - 消费记录表
3. user_phone_relations - 用户手机号关系表
```
**差异说明**
- 原始版本提到"SQL语句"但MongoDB使用查询语句不是SQL
- 原始版本提到"配置化查询",可能是指数据源配置
- 详细版本明确了数据源类型和字段定义
**建议**
- 原始版本中的"数据列表"应该理解为"数据源"
- "SQL语句"应该理解为"MongoDB查询"或"数据源配置"
- 需要明确数据源配置的管理方式
---
### ⚠️ 3.4 正则规则实现差异
#### 原始版本:
```json
{
field:"店铺名称",
operator:"/淘宝/", // ❌ 直接使用正则表达式作为operator
value:true,
tag_value"淘宝平台"
}
```
#### 详细版本:
```json
{
"rule_type": "regex", // ✅ 使用rule_type区分
"conditions": [
{
"field": "shop_name",
"operator": "match",
"pattern": "/淘宝/", // ✅ pattern字段
"value": true
}
],
"tag_value": "淘宝平台"
}
```
**差异说明**
- 原始版本:正则表达式直接作为 `operator`
- 详细版本:需要 `rule_type="regex"` 和 `pattern` 字段
- **当前代码**:实际只支持 `simple` 规则,正则规则需要扩展实现
**建议**
- 如果使用 `simple` 规则,可以用 `in` 运算符替代:
```json
{
"field": "shop_name",
"operator": "in",
"value": ["淘宝", "天猫"]
}
```
---
## 四、关键差异总结
| 对比项 | 原始版本 | 详细版本 | 实际代码 | 建议 |
|--------|---------|---------|---------|------|
| **JSON格式** | tag_value在条件中 | tag_value在rule_config中 | ✅ 详细版本正确 | 使用详细版本格式 |
| **字段命名** | 中文名称 | 英文名称 | ✅ 详细版本正确 | 前端显示中文,后端使用英文 |
| **数据列表** | SQL查询配置 | 数据源类型定义 | ✅ 详细版本更准确 | 明确为数据源配置 |
| **正则规则** | operator直接使用正则 | 需要rule_type和pattern | ⚠️ 当前未实现 | 使用simple规则的in运算符替代 |
---
## 五、一致性结论
### ✅ 核心含义:**基本一致**
1. **业务逻辑一致**
- 都描述了标签定义、规则配置、任务处理的流程
- 都支持运算规则和正则规则的概念
2. **实现细节差异**
- 原始版本是业务需求的简化描述
- 详细版本是技术实现的完整说明
- 差异主要体现在数据格式和实现细节上
3. **建议**
- ✅ **使用详细版本的JSON格式**(符合实际代码)
- ✅ **使用英文字段名**(符合数据库设计)
- ✅ **明确数据源配置方式**不是SQL是MongoDB查询或配置
- ⚠️ **正则规则当前未实现**建议使用simple规则的in运算符替代
---
## 六、修正建议
### 6.1 修正原始版本的JSON格式
**原始版本(需要修正)**
```json
[
{
field:"交易金额",
operator:"<",
value:3000,
tag_value"低价值用户" // ❌ 错误位置
}
]
```
**修正后的格式**
```json
{
"rule_type": "simple",
"conditions": [
{
"field": "total_amount", // ✅ 使用英文字段名
"operator": "<",
"value": 3000
}
],
"tag_value": "低价值用户", // ✅ 在rule_config中
"confidence": 1.0
}
```
### 6.2 明确数据源配置
**原始版本描述**
> "先用sql语句查询出符合条件的结果这段数据库的查询操作可以配置化"
**修正为**
> "配置数据源类型consumption_records系统提供该数据源的字段列表API前端选择字段进行规则配置"
### 6.3 正则规则说明
**原始版本**
> operator:"/淘宝/"
**修正为**
> 当前系统支持 `simple` 规则,正则匹配可以通过 `in` 运算符实现:
> ```json
> {
> "field": "shop_name",
> "operator": "in",
> "value": ["淘宝", "天猫"]
> }
> ```
---
## 七、最终结论
**两个文档的核心含义基本一致**,但存在以下差异:
1.**业务逻辑**:完全一致
2. ⚠️ **数据格式**原始版本需要修正tag_value位置、字段命名
3. ⚠️ **技术细节**:详细版本更准确(符合实际代码实现)
4.**流程描述**:详细版本是对原始版本的细化
**建议**
- 保留详细版本作为技术实现文档
- 修正原始版本中的格式问题
- 明确数据源配置方式不是SQL是MongoDB数据源配置
---
**分析时间**2025-01-XX
**基于代码版本**:当前代码库分析

View File

@@ -0,0 +1,68 @@
1、标签定义
## 后台预先工作:
// 标签定义数据列表API,由于mongodb表具有特殊性因此需要预先处理
-用代码先写好数据查询如果可以可以做个手动配置sql的方式例如
标签表:[
消费数据=> 消费记录表的sql
]
// 数据列表字段API
-通过上方的列表id返回展示下字段
## 前台界面
-标签规则配置这块在UI界面是可以选择的
1、运算规则
选择"数据列表字段API"展示出来的API例如消费记录展示了字段交易金额、店铺名称、交易状态、手机号
配置示例JSON
[
{
field:"交易金额",
operator:"<" ,
value:3000,
tag_value"低价值用户"
},
{
field:"交易金额",
operator:">=" ,
value:3000,
tag_value"中等价值用户"
},
{
field:"交易金额",
operator:">" ,
value:9000,
tag_value"高价值用户"
},
]
注意一个item代表一个条件
2、正则规则
#判断字符串是否含有淘宝二字,如果有就是淘宝平台的
[
{
field:"店铺名称",
operator:"/淘宝/" ,
value:true,
tag_value"淘宝平台"
},
]
注意一个item代表一个条件。
标签保存的时候,要输入标签编码、标签名称
##标签任务处理逻辑顺序
1、选择已定义的标签
2、选择后点击保存状态为待启动
3、在列表启动的时候后台先从后台读取定义标签的集合并查询出来
4、根据定义标签的规则条件直接根据用户表遍历过去进行打标签然后将标签存储。

View File

@@ -0,0 +1,737 @@
# 标签定义逻辑说明
## 一、概述
标签定义是标签引擎的核心功能,用于定义如何根据用户数据自动计算和打标签。系统支持两种规则类型:运算规则和正则规则。
---
## 二、后台预先工作
### 2.1 标签定义数据列表API
由于 MongoDB 表的特殊性,需要预先配置数据查询操作。
#### 2.1.1 数据列表配置
**功能说明**
- 通过配置化的查询操作,从 MongoDB 集合中查询出符合条件的结果
- 这个查询操作可以配置化,支持动态增减数据集合
- 每个查询结果可以命名,例如:`消费记录表``用户档案表``订单明细表`
**配置示例**
```json
{
"list_id": "consumption_records_list",
"list_name": "消费记录表",
"data_source_id": "source_123",
"database": "tag_engine",
"collection": "consumption_records",
"query_config": {
"filter": {},
"sort": { "create_time": -1 },
"limit": 1000
},
"description": "消费记录数据列表",
"status": 1,
"create_time": "2025-01-01T00:00:00Z"
}
```
**数据结构**
```javascript
{
list_id: String, // 列表ID唯一标识
list_name: String, // 列表名称(如:消费记录表)
data_source_id: String, // 数据源ID
database: String, // 数据库名
collection: String, // 集合名
query_config: Object, // 查询配置MongoDB查询条件
description: String, // 描述
status: Number, // 状态0:禁用, 1:启用)
create_time: Date,
update_time: Date
}
```
**API接口**
- `GET /api/tag-data-lists` - 获取数据列表列表
- `POST /api/tag-data-lists` - 创建数据列表配置
- `GET /api/tag-data-lists/{list_id}` - 获取数据列表详情
- `PUT /api/tag-data-lists/{list_id}` - 更新数据列表配置
- `DELETE /api/tag-data-lists/{list_id}` - 删除数据列表配置
#### 2.1.2 数据列表字段API
**功能说明**
- 通过数据列表ID返回该列表的字段信息
- 用于前端界面选择字段进行规则配置
**API接口**`GET /api/tag-data-lists/{list_id}/fields`
**返回格式**
```json
{
"code": 200,
"message": "查询成功",
"data": {
"list_id": "consumption_records_list",
"list_name": "消费记录表",
"fields": [
{
"field": "交易金额",
"field_name": "amount",
"type": "number",
"description": "交易金额"
},
{
"field": "店铺名称",
"field_name": "shop_name",
"type": "string",
"description": "店铺名称"
},
{
"field": "交易状态",
"field_name": "status",
"type": "string",
"description": "交易状态"
},
{
"field": "手机号",
"field_name": "phone",
"type": "string",
"description": "手机号"
}
]
}
}
```
**字段说明**
- `field`:前端显示的字段名称(中文)
- `field_name`:数据库中的实际字段名(英文)
- `type`字段类型number、string、datetime、boolean等
- `description`:字段描述
**实现方式**
1. 从数据列表配置中获取 `collection` 信息
2. 查询该集合的样本数据前10条
3. 分析样本数据的字段结构
4. 返回字段列表(包含中英文映射)
---
## 三、前台界面配置
### 3.1 标签基本信息
创建标签定义时需要填写以下基本信息:
- **标签编码** (`tag_code`):唯一标识,如 `high_consumer``vip_user`
- **标签名称** (`tag_name`):显示名称,如 `高消费用户``VIP用户`
**可选字段**
- **分类** (`category`):标签分类,如 `消费能力``活跃度``风险等级``生命周期`
- **描述** (`description`):标签的详细说明
- **更新频率** (`update_frequency`)
- `real_time` - 实时更新
- `daily` - 每日更新
- `weekly` - 每周更新
- `monthly` - 每月更新
- **状态** (`status`)
- `0` - 启用
- `1` - 禁用
### 3.2 规则配置
标签规则配置在UI界面可以选择两种类型**运算规则** 和 **正则规则**
#### 3.2.1 运算规则
**配置流程**
1. 选择数据列表(如:消费记录表)
2. 调用数据列表字段API获取字段列表
3. 选择字段(如:交易金额、店铺名称、交易状态、手机号)
4. 配置条件:字段、运算符、值、标签值
**配置示例JSON**
```json
[
{
"field": "交易金额",
"field_name": "amount",
"operator": "<",
"value": 3000,
"tag_value": "低价值用户"
},
{
"field": "交易金额",
"field_name": "amount",
"operator": ">=",
"value": 3000,
"tag_value": "中等价值用户"
},
{
"field": "交易金额",
"field_name": "amount",
"operator": ">",
"value": 9000,
"tag_value": "高价值用户"
}
]
```
**重要说明**
- **一个item代表一个条件**
- 每个条件都有独立的 `tag_value`
- 当条件满足时,用户将被标记为该条件的 `tag_value`
- 多个条件之间是**互斥**的OR关系满足任一条件即可
**支持的运算符**
- `>` - 大于
- `>=` - 大于等于
- `<` - 小于
- `<=` - 小于等于
- `=` / `==` - 等于
- `!=` - 不等于
- `in` - 在列表中(值为数组)
- `not_in` - 不在列表中(值为数组)
**存储格式**(转换为标准格式):
```json
{
"rule_type": "simple",
"data_list_id": "consumption_records_list",
"conditions": [
{
"field": "交易金额",
"field_name": "amount",
"operator": "<",
"value": 3000,
"tag_value": "低价值用户"
},
{
"field": "交易金额",
"field_name": "amount",
"operator": ">=",
"value": 3000,
"tag_value": "中等价值用户"
},
{
"field": "交易金额",
"field_name": "amount",
"operator": ">",
"value": 9000,
"tag_value": "高价值用户"
}
]
}
```
#### 3.2.2 正则规则
**配置说明**
- 使用正则表达式匹配字符串字段
- 判断字符串是否含有指定的模式
**配置示例**
```json
[
{
"field": "店铺名称",
"field_name": "shop_name",
"operator": "/淘宝/",
"value": true,
"tag_value": "淘宝平台"
}
]
```
**重要说明**
- **一个item代表一个条件**
- `operator` 字段直接使用正则表达式(如:`/淘宝/`
- `value` 字段表示是否匹配(`true` 表示匹配,`false` 表示不匹配)
- 当正则匹配成功时,用户将被标记为 `tag_value`
**存储格式**(转换为标准格式):
```json
{
"rule_type": "regex",
"data_list_id": "consumption_records_list",
"conditions": [
{
"field": "店铺名称",
"field_name": "shop_name",
"operator": "/淘宝/",
"pattern": "淘宝",
"value": true,
"tag_value": "淘宝平台"
}
]
}
```
**正则表达式说明**
- 支持标准的正则表达式语法
- 常用模式:
- `/淘宝/` - 包含"淘宝"
- `/^淘宝/` - 以"淘宝"开头
- `/淘宝$/` - 以"淘宝"结尾
- `/淘宝|天猫/` - 包含"淘宝"或"天猫"
### 3.3 完整的标签定义数据结构
```json
{
"tag_id": "uuid",
"tag_code": "consumer_level",
"tag_name": "消费等级",
"category": "消费能力",
"description": "根据消费金额划分用户等级",
"rule_type": "simple",
"rule_config": {
"rule_type": "simple",
"data_list_id": "consumption_records_list",
"data_list_name": "消费记录表",
"conditions": [
{
"field": "交易金额",
"field_name": "amount",
"operator": "<",
"value": 3000,
"tag_value": "低价值用户"
},
{
"field": "交易金额",
"field_name": "amount",
"operator": ">=",
"value": 3000,
"tag_value": "中等价值用户"
},
{
"field": "交易金额",
"field_name": "amount",
"operator": ">",
"value": 9000,
"tag_value": "高价值用户"
}
]
},
"update_frequency": "real_time",
"status": 0,
"priority": 1,
"version": 1,
"create_time": "2025-01-01T00:00:00Z",
"update_time": "2025-01-01T00:00:00Z"
}
```
---
## 四、标签任务处理逻辑
### 4.1 标签任务创建流程
```
1. 用户在前台创建标签任务
├─→ 选择已定义的标签(可多选)
├─→ 配置任务类型full/incremental/specified
├─→ 配置用户范围all/list/filter
├─→ 配置调度计划(可选)
└─→ 点击保存
2. 任务保存后
├─→ 状态设置为 "pending"(待启动)
├─→ 任务信息保存到 tag_tasks 集合
└─→ 返回任务ID
```
### 4.2 标签任务启动流程
```
1. 用户在任务列表点击"启动"按钮
2. 调用API: POST /api/tag-tasks/{task_id}/start
3. TagTaskService->startTask()
├─→ 检查任务状态(不能是 running
├─→ 更新任务状态为 "running"
└─→ 设置 Redis 标志tag_task:{taskId}:start
4. TagTaskExecutor->execute()
├─→ 创建执行记录tag_task_executions
├─→ 获取目标标签ID列表target_tag_ids
├─→ 遍历每个标签定义
│ ├─→ 从 tag_definitions 读取标签定义
│ ├─→ 获取 rule_config 中的 data_list_id
│ ├─→ 根据 data_list_id 查询数据列表配置
│ ├─→ 根据数据列表配置查询数据集合
│ └─→ 获取用户ID列表从数据集合中提取
└─→ 批量处理用户批次大小可配置默认100
5. 遍历每个用户批次
For each user in batch:
├─→ 检查任务状态(是否被暂停/停止)
├─→ 根据标签定义的规则条件计算标签值
│ ├─→ 获取用户在该数据列表中的数据
│ ├─→ 遍历 rule_config.conditions
│ │ ├─→ 根据 field_name 获取字段值
│ │ ├─→ 根据 operator 进行比较
│ │ │ ├─→ 运算规则:数值/字符串比较
│ │ │ └─→ 正则规则:正则表达式匹配
│ │ └─→ 如果条件满足,使用该条件的 tag_value
│ ├─→ 如果多个条件满足,使用第一个满足条件的 tag_value
│ └─→ 如果所有条件都不满足,标签值为 null不打标签
├─→ 更新或创建 user_tags 记录
├─→ 记录标签变更历史tag_history
├─→ 成功success_count++
├─→ 失败error_count++
└─→ 每处理10个用户更新一次进度
├─→ processed_users
├─→ success_count
├─→ error_count
└─→ percentage = (processed_users / total_users) * 100
6. 更新最终进度和统计
├─→ percentage = 100
├─→ 更新执行记录状态为 "completed"
└─→ 更新任务统计信息
```
### 4.3 标签计算核心逻辑
#### 4.3.1 数据获取
**步骤1读取标签定义**
```php
// 从 tag_definitions 集合读取标签定义
$tagDefinition = TagDefinitionRepository::find($tagId);
$ruleConfig = $tagDefinition->rule_config;
$dataListId = $ruleConfig['data_list_id'];
```
**步骤2获取数据列表配置**
```php
// 根据 data_list_id 查询数据列表配置
$dataList = TagDataListRepository::find($dataListId);
$collection = $dataList->collection;
$database = $dataList->database;
$queryConfig = $dataList->query_config;
```
**步骤3查询数据集合**
```php
// 根据数据列表配置查询数据
$collectionObj = MongoDB::connection($database)->collection($collection);
$data = $collectionObj->find($queryConfig['filter'])
->sort($queryConfig['sort'] ?? [])
->limit($queryConfig['limit'] ?? 1000)
->toArray();
```
#### 4.3.2 规则计算
**运算规则计算**
```php
foreach ($ruleConfig['conditions'] as $condition) {
$fieldName = $condition['field_name'];
$operator = $condition['operator'];
$expectedValue = $condition['value'];
$tagValue = $condition['tag_value'];
// 从数据中获取字段值
$actualValue = $data[$fieldName] ?? null;
// 根据运算符进行比较
$match = false;
switch ($operator) {
case '>':
$match = $actualValue > $expectedValue;
break;
case '>=':
$match = $actualValue >= $expectedValue;
break;
case '<':
$match = $actualValue < $expectedValue;
break;
case '<=':
$match = $actualValue <= $expectedValue;
break;
case '=':
case '==':
$match = $actualValue == $expectedValue;
break;
case '!=':
$match = $actualValue != $expectedValue;
break;
case 'in':
$match = in_array($actualValue, (array)$expectedValue);
break;
case 'not_in':
$match = !in_array($actualValue, (array)$expectedValue);
break;
}
// 如果条件满足,返回该条件的 tag_value
if ($match) {
return $tagValue;
}
}
// 所有条件都不满足,返回 null
return null;
```
**正则规则计算**
```php
foreach ($ruleConfig['conditions'] as $condition) {
$fieldName = $condition['field_name'];
$pattern = $condition['pattern'] ?? $condition['operator']; // 从operator或pattern获取正则
$expectedMatch = $condition['value']; // true表示匹配false表示不匹配
$tagValue = $condition['tag_value'];
// 从数据中获取字段值
$actualValue = $data[$fieldName] ?? '';
// 执行正则匹配
$isMatch = preg_match($pattern, $actualValue);
// 判断是否满足条件
if (($expectedMatch && $isMatch) || (!$expectedMatch && !$isMatch)) {
return $tagValue;
}
}
// 所有条件都不满足,返回 null
return null;
```
#### 4.3.3 标签值存储
**存储到 user_tags 集合**
```javascript
{
user_id: "用户ID",
tag_id: "标签ID",
tag_value: "标签值(字符串)", // 从条件的tag_value获取
tag_value_type: "string",
confidence: 1.0,
effective_time: "生效时间",
create_time: "创建时间",
update_time: "更新时间"
}
```
**重要说明**
- 如果计算出的标签值为 `null`,则不创建或删除该用户的该标签记录
- 如果标签值发生变化,记录到 `tag_history` 集合
---
## 五、数据存储结构
### 5.1 标签定义集合tag_definitions
```javascript
{
tag_id: String, // 标签IDUUID
tag_code: String, // 标签代码(唯一标识)
tag_name: String, // 标签名称
category: String, // 标签分类(可选)
description: String, // 描述(可选)
rule_type: String, // 规则类型simple/regex
rule_config: Object, // 规则配置JSON
update_frequency: String, // 更新频率(可选)
status: Number, // 状态0:启用, 1:禁用)
priority: Number, // 优先级(可选)
version: Number, // 版本号(可选)
create_time: Date,
update_time: Date
}
```
### 5.2 数据列表配置集合tag_data_lists
```javascript
{
list_id: String, // 列表IDUUID
list_name: String, // 列表名称(如:消费记录表)
data_source_id: String, // 数据源ID
database: String, // 数据库名
collection: String, // 集合名
query_config: Object, // 查询配置MongoDB查询条件
description: String, // 描述
status: Number, // 状态0:禁用, 1:启用)
create_time: Date,
update_time: Date
}
```
### 5.3 用户标签集合user_tags
```javascript
{
user_id: String, // 用户ID
tag_id: String, // 标签ID
tag_value: String, // 标签值(字符串格式)
tag_value_type: String, // 值类型string
confidence: Number, // 置信度0.0-1.0
effective_time: Date, // 生效时间
expire_time: Date, // 过期时间(可选)
create_time: Date,
update_time: Date
}
```
### 5.4 标签历史集合tag_history
```javascript
{
history_id: String, // 历史记录ID
user_id: String, // 用户ID
tag_id: String, // 标签ID
old_value: String, // 旧值
new_value: String, // 新值
change_reason: String, // 变更原因
change_time: Date, // 变更时间
operator: String // 操作人
}
```
### 5.5 标签任务集合tag_tasks
```javascript
{
task_id: String, // 任务ID
name: String, // 任务名称
description: String, // 任务描述
task_type: String, // 任务类型full/incremental/specified
target_tag_ids: Array, // 目标标签ID列表
user_scope: Object, // 用户范围配置
schedule: Object, // 调度计划
config: Object, // 任务配置
status: String, // 任务状态pending/running/paused/stopped/error
progress: Object, // 任务进度
statistics: Object, // 任务统计
created_by: String,
created_at: Date,
updated_at: Date
}
```
---
## 六、API接口说明
### 6.1 数据列表接口
- `GET /api/tag-data-lists` - 获取数据列表列表
- `POST /api/tag-data-lists` - 创建数据列表配置
- `GET /api/tag-data-lists/{list_id}` - 获取数据列表详情
- `PUT /api/tag-data-lists/{list_id}` - 更新数据列表配置
- `DELETE /api/tag-data-lists/{list_id}` - 删除数据列表配置
- `GET /api/tag-data-lists/{list_id}/fields` - 获取数据列表字段
### 6.2 标签定义接口
- `GET /api/tag-definitions` - 获取标签定义列表
- `POST /api/tag-definitions` - 创建标签定义
- `GET /api/tag-definitions/{tag_id}` - 获取标签定义详情
- `PUT /api/tag-definitions/{tag_id}` - 更新标签定义
- `DELETE /api/tag-definitions/{tag_id}` - 删除标签定义
### 6.3 标签任务接口
- `GET /api/tag-tasks` - 获取标签任务列表
- `POST /api/tag-tasks` - 创建标签任务
- `GET /api/tag-tasks/{task_id}` - 获取任务详情
- `POST /api/tag-tasks/{task_id}/start` - 启动任务
- `POST /api/tag-tasks/{task_id}/pause` - 暂停任务
- `POST /api/tag-tasks/{task_id}/stop` - 停止任务
---
## 七、关键代码文件
### 7.1 后端代码(需要实现)
- `app/controller/TagDataListController.php` - 数据列表控制器
- `app/repository/TagDataListRepository.php` - 数据列表数据访问
- `app/service/TagDataListService.php` - 数据列表服务
- `app/controller/TagDefinitionController.php` - 标签定义控制器
- `app/service/TagService.php` - 标签计算服务(需要修改)
- `app/service/TagRuleEngine/SimpleRuleEngine.php` - 简单规则引擎(需要修改)
- `app/service/TagRuleEngine/RegexRuleEngine.php` - 正则规则引擎(需要新增)
- `app/service/TagTaskService.php` - 标签任务服务
- `app/service/TagTaskExecutor.php` - 标签任务执行器(需要修改)
### 7.2 前端代码(需要实现)
- `TaskShow/src/views/TagDataList/List.vue` - 数据列表管理
- `TaskShow/src/views/TagDataList/Form.vue` - 数据列表配置表单
- `TaskShow/src/views/TagDefinition/Form.vue` - 标签定义表单(需要修改)
- `TaskShow/src/views/TagDefinition/List.vue` - 标签定义列表
- `TaskShow/src/views/TagTask/TaskForm.vue` - 标签任务表单
- `TaskShow/src/store/tagDataList.ts` - 数据列表状态管理(需要新增)
---
## 八、注意事项
1. **数据列表配置**:必须先配置数据列表,才能创建标签定义
2. **字段映射**前端使用中文字段名field后端使用英文字段名field_name
3. **条件逻辑**多个条件之间是互斥的OR关系满足第一个条件即使用该条件的tag_value
4. **标签值**如果所有条件都不满足标签值为null不创建标签记录
5. **数据查询**根据数据列表配置的query_config查询数据支持MongoDB查询语法
6. **正则规则**operator字段直接存储正则表达式字符串需要解析后使用preg_match匹配
---
## 九、实现要点
### 9.1 数据列表配置
1. **查询配置格式**
```json
{
"filter": { "status": "active" },
"sort": { "create_time": -1 },
"limit": 1000
}
```
2. **字段自动识别**
- 查询样本数据前10条
- 分析字段结构
- 返回字段列表(包含中英文映射)
### 9.2 规则计算逻辑
1. **运算规则**
- 从数据中获取字段值
- 根据运算符进行比较
- 满足条件即返回该条件的tag_value
2. **正则规则**
- 从数据中获取字段值
- 使用preg_match进行正则匹配
- 匹配成功即返回该条件的tag_value
### 9.3 标签任务执行
1. **数据获取**
- 根据标签定义的data_list_id获取数据列表配置
- 根据数据列表配置查询数据集合
- 提取用户ID列表
2. **标签计算**
- 遍历每个用户
- 获取该用户在数据列表中的数据
- 根据规则条件计算标签值
- 存储标签结果
---
**文档更新时间**2025-01-XX
**基于需求**:按照原始思路完善

View File

@@ -0,0 +1,342 @@
# 集合筛选功能使用技巧
## 功能概览
在多集合模式下,提供了强大的筛选和批量操作功能,让选择大量集合变得简单快捷。
---
## 核心功能
### 1. 文本筛选
**输入框筛选**
```
[🔍 筛选集合名称...]
```
**功能**
- 实时筛选集合列表
- 支持模糊匹配
- 不区分大小写
- 支持中文和英文
**示例**
```
输入 "2021" → 显示所有包含"2021"的集合
输入 "女" → 显示所有包含"女"的集合
输入 "cons" → 显示所有包含"cons"的集合
```
### 2. 批量操作按钮
#### 全选
- **功能**:选择当前筛选结果的**所有**集合
- **行为**:追加到已选列表(不清除其他已选项)
- **场景**:快速选择某一类集合
**示例**
```
1. 输入 "2021"
2. 点击 [全选]
3. 清空筛选
4. 输入 "2022"
5. 点击 [全选]
→ 结果同时选中2021和2022的所有集合
```
#### 清空
- **功能**:清空**所有**已选集合
- **行为**:清除整个选择列表
- **场景**:重新开始选择
#### 反选
- **功能**:反选当前筛选结果的集合
- **行为**
- 已选中的 → 取消选择
- 未选中的 → 选中
- 不在筛选结果中的已选项 → 保持选中
- **场景**:排除某些集合
**示例**
```
1. 点击快捷筛选 [2021年](选中全年)
2. 输入 "202101"只显示1月
3. 点击 [反选]取消选择1月
→ 结果选中2021年的2-12月
```
### 3. 快捷筛选(智能按钮)
当检测到按日期格式的集合(如 `collection_202101`)时,自动显示快捷筛选按钮。
#### 按年份筛选
```
[2021年] [2022年] [2023年] [2024年] [2025年]
```
**功能**:一键选择某一年的所有集合
**匹配规则**:包含年份字符串(如 `2021`)的集合
**示例**
```
点击 [2021年]
→ 自动选中:
consumption_records_202101
consumption_records_202102
...
consumption_records_202112
```
#### 按时间范围筛选
```
[最近3个月] [最近6个月] [最近12个月]
```
**功能**智能计算并选择最近N个月的集合
**匹配规则**
1. 获取当前年月(如 2025-01
2. 向前推算N个月
3. 查找包含这些年月YYYYMM格式的集合
**示例**(假设当前是 2025-01
```
点击 [最近3个月]
→ 自动选中:
consumption_records_202501 (2025-01)
consumption_records_202412 (2024-12)
consumption_records_202411 (2024-11)
```
```
点击 [最近6个月]
→ 自动选中:
consumption_records_202501
consumption_records_202412
consumption_records_202411
consumption_records_202410
consumption_records_202409
consumption_records_202408
```
---
## 使用技巧
### 技巧1组合使用快捷筛选和文本筛选
**需求**选择2021年第一季度
**方法**
```
1. 输入筛选 "20210"
→ 显示 202101, 202102, 202103, ..., 202109
2. 点击 [全选]
3. 输入 "202104"
→ 只显示 202104
4. 点击 [反选]取消4月
5. 重复步骤3-4排除5-9月
```
**更简单的方法**
```
1. 输入 "202101",点击 [全选]
2. 输入 "202102",点击 [全选]
3. 输入 "202103",点击 [全选]
```
### 技巧2使用反选排除特定月份
**需求**选择2021年除了春节月份1、2月的所有数据
**方法**
```
1. 点击快捷筛选 [2021年]
→ 选中全年12个月
2. 输入 "202101"
3. 点击 [反选]
→ 取消1月
4. 输入 "202102"
5. 点击 [反选]
→ 取消2月
6. 清空筛选框查看结果
→ 已选中202103-20211210个月
```
### 技巧3跨年选择
**需求**选择2020年下半年和2021年上半年
**方法**
```
1. 输入 "202007",点击 [全选]
2. 输入 "202008",点击 [全选]
3. 输入 "202009",点击 [全选]
4. 输入 "202010",点击 [全选]
5. 输入 "202011",点击 [全选]
6. 输入 "202012",点击 [全选]
7. 输入 "202101",点击 [全选]
8. 输入 "202102",点击 [全选]
9. 输入 "202103",点击 [全选]
10. 输入 "202104",点击 [全选]
11. 输入 "202105",点击 [全选]
12. 输入 "202106",点击 [全选]
```
**更快的方法**(如果命名规则一致):
```
1. 输入 "2020"
2. 点击 [全选]选中2020全年
3. 输入 "20200"202001-202009前9个月
4. 点击 [反选]排除前6个月只留7-12月
5. 输入 "2021"
6. 点击 [全选]
7. 输入 "202107"7月及以后
8. 向后筛选并反选只留1-6月
```
### 技巧4按商品类型批量选择
**需求**:在 `KR_淘宝` 数据库中选择所有zippo相关的集合
**方法**
```
1. 启用多集合模式
2. 输入 "zippo"
→ 显示zippo1, zippo2, zippo3, zippo4, zippo5
3. 点击 [全选]
→ 一次性选中所有5个zippo集合
```
### 技巧5逐步累加选择
**需求**:精确选择特定几个月份(不连续)
**方法**
```
需要1月、3月、6月、9月、12月
1. 输入 "202101",点击 [全选]
2. 输入 "202103",点击 [全选]
3. 输入 "202106",点击 [全选]
4. 输入 "202109",点击 [全选]
5. 输入 "202112",点击 [全选]
6. 清空筛选框查看
→ 已选中5个月份
```
---
## 最佳实践
### 1. 大量集合的选择策略
**场景**有100+个集合,需要选择大部分
**推荐流程**
```
1. 使用快捷筛选或全选按钮一次性选中
2. 使用文本筛选+反选排除不需要的
3. 清空筛选查看最终结果
```
**示例**
```
选择2021-2023的所有数据但排除测试月份
1. 点击 [2021年]
2. 点击 [2022年]
3. 点击 [2023年]
→ 已选36个月
4. 输入 "test"
5. 点击 [反选]
→ 排除测试集合
```
### 2. 少量集合的选择策略
**场景**:只需要选择几个集合
**推荐流程**
```
直接使用文本筛选+全选
```
**示例**
```
只选择3个月
1. 输入 "202101",点击 [全选]
2. 输入 "202102",点击 [全选]
3. 输入 "202103",点击 [全选]
```
### 3. 动态时间范围
**场景**:需要最近的数据(随时间变化)
**推荐**
```
使用 [最近N个月] 快捷按钮
优点:
- 自动计算当前时间
- 配置一次,永久有效
- 不需要手动更新月份
```
### 4. 固定时间范围
**场景**:需要特定历史时期的数据
**推荐**
```
使用年份按钮或文本筛选
例如统计2021年的历史数据
→ 点击 [2021年]
```
---
## 常见场景快速指南
| 需求 | 最佳方法 | 步骤 |
|------|----------|------|
| 选择某一年全年数据 | 年份按钮 | 点击 `[2021年]` |
| 选择最近半年数据 | 时间范围按钮 | 点击 `[最近6个月]` |
| 选择第一季度 | 文本筛选+全选 | 输入 `Q1` 或逐月选择 |
| 选择所有zippo集合 | 文本筛选+全选 | 输入 `zippo`,点击 `[全选]` |
| 排除某几个月 | 全选+反选 | 先全选年份,再筛选排除项并反选 |
| 选择不连续月份 | 逐个筛选+全选 | 每个月份单独筛选后全选 |
| 跨年选择 | 多次年份按钮 | 点击多个年份按钮 |
---
## 注意事项
1. **筛选不影响已选项**
- 筛选只影响显示,不会取消已选中的集合
- 即使筛选后看不到某个已选集合,它仍然被选中
2. **全选是追加操作**
- 点击"全选"会追加到已选列表
- 不会清除之前的选择
- 如需重新开始,先点击"清空"
3. **反选的作用域**
- 反选只对当前筛选结果生效
- 不在筛选结果中的已选项不受影响
4. **快捷筛选的智能识别**
- 快捷按钮仅在检测到日期格式集合时显示
- 如果集合命名不包含日期,不会显示这些按钮
5. **性能考虑**
- 选择大量集合50+)可能影响查询性能
- 建议根据实际需求选择合适的时间范围
---
**更新时间**2025-01-XX
**适用版本**QueryBuilder v2.0+

View File

@@ -0,0 +1,858 @@
# 项目完整代码逻辑分析报告
## 一、项目概述
### 1.1 项目定位
本项目是一个**基于Webman框架的用户标签引擎和数据采集中心**,主要功能包括:
- **多数据源数据采集**支持MongoDB、MySQL等多种数据源
- **数据库实时同步**使用MongoDB Change Streams实现数据库间实时同步
- **用户标签计算引擎**:基于用户消费数据实时计算和更新标签
- **任务配置化管理**:通过配置文件统一管理所有数据采集任务
- **用户身份管理**:支持身份证、手机号等标识的统一管理
### 1.2 技术栈
- **框架**Webman (Workerman) - 高性能PHP框架
- **数据库**MongoDB (主数据库)
- **消息队列**RabbitMQ - 异步任务处理
- **缓存/锁**Redis - 分布式锁、状态存储
- **前端**Vue 3 + TypeScript + Element Plus
- **PHP版本**>= 8.1
---
## 二、系统架构
### 2.1 分层架构
```
┌─────────────────────────────────────────┐
│ 应用层 (HTTP API) │
│ User/Tag/Task/DataSource API │
└──────────────────┬──────────────────────┘
┌──────────────────▼──────────────────────┐
│ 业务服务层 (Service) │
│ UserService / TagService / │
│ DataCollectionTaskService / │
│ ConsumptionService │
└──────────────────┬──────────────────────┘
┌──────────────────▼──────────────────────┐
│ 数据访问层 (Repository) │
│ UserProfile / UserTag / │
│ ConsumptionRecord / DataCollectionTask │
└──────────────────┬──────────────────────┘
┌──────────────────▼──────────────────────┐
│ 数据存储层 │
│ MongoDB / Redis / RabbitMQ │
└─────────────────────────────────────────┘
```
### 2.2 进程架构
系统使用Workerman多进程架构包含以下进程
1. **webman (HTTP Server)**
- 处理HTTP请求
- 进程数CPU核心数 × 4
2. **monitor (文件监控)**
- 监控文件变化,自动重载
3. **data_sync_scheduler (数据采集任务调度器)**
- 读取任务配置(配置文件 + 数据库)
- 启动和管理所有数据采集任务
- 进程数10
4. **data_sync_worker (数据同步Worker)**
- 消费RabbitMQ消息队列
- 写入目标数据库,更新用户统计
- 进程数20
5. **tag_calculation_worker (标签计算Worker)**
- 消费RabbitMQ标签计算队列
- 根据用户数据计算标签值
- 进程数2
---
## 三、核心业务逻辑
### 3.1 数据采集系统
#### 3.1.1 任务配置体系
系统支持两种任务配置方式:
**1. 配置文件方式** (`config/data_collection_tasks.php`)
```php
'tasks' => [
'database_sync' => [
'name' => '数据库实时同步',
'source_data_source' => 'kr_mongodb',
'target_data_source' => 'sync_mongodb',
'handler_class' => DatabaseSyncHandler::class,
'schedule' => ['enabled' => false], // 持续运行
],
]
```
**2. 数据库方式** (`data_collection_tasks`集合)
- 通过前端界面动态创建和管理任务
- 支持批量采集batch和实时监听realtime两种模式
- 支持字段映射、过滤条件、连表查询等配置
#### 3.1.2 任务执行流程
```
用户创建任务
保存到MongoDB (data_collection_tasks集合)
用户点击"启动"按钮
API: POST /api/data-collection-tasks/{taskId}/start
DataCollectionTaskService->startTask()
- 更新任务状态为 'running'
- Redis设置标志: data_collection_task:{taskId}:start
DataSyncScheduler进程检测到Redis标志
从数据库加载任务配置
根据任务模式执行:
- batch模式根据schedule配置定时执行或立即执行
- realtime模式立即启动持续运行Change Stream监听
```
#### 3.1.3 Handler处理机制
系统通过Handler模式实现不同类型的数据采集
**ConsumptionCollectionHandler** - 消费记录采集
- 专门处理消费记录采集
- 支持KR_商城和KR_金融两种数据源
- 自动提取手机号、解析用户ID、写入消费记录
**GenericCollectionHandler** - 通用数据采集
- 支持动态字段映射
- 支持批量采集和实时监听两种模式
- 支持连表查询lookup
**DatabaseSyncHandler** - 数据库同步
- 全量同步:首次启动时同步所有数据
- 增量同步使用MongoDB Change Streams实时监听
### 3.2 用户身份管理系统
#### 3.2.1 身份标识体系
系统采用**以身份证为主键,手机号为弱标识**的设计:
1. **user_profile (用户画像表)**
- `user_id`: 用户唯一标识UUID
- `id_card_hash`: 身份证哈希值(用于匹配)
- `id_card_encrypted`: 加密的身份证号
- `is_temporary`: 是否为临时人true/false
- `total_amount`: 总消费金额
- `total_count`: 总消费次数
2. **user_phone_relations (手机号关联表)**
- `phone_number`: 手机号
- `user_id`: 关联的用户ID
- `is_primary`: 是否为主手机号
#### 3.2.2 身份解析流程
```
收到数据(包含手机号)
IdentifierService->resolvePersonIdByPhone()
查询user_phone_relations表
├─→ 找到 → 返回user_id
└─→ 未找到 → 创建临时人
├─→ 创建user_profile记录is_temporary=true
├─→ 创建user_phone_relations关联
└─→ 返回user_id
```
#### 3.2.3 身份合并机制
当发现手机号对应的身份证后:
```
发现身份证
IdentifierService->bindIdCardToPerson()
检查身份证是否已被其他用户使用
├─→ 是 → 抛出异常
└─→ 否 → 更新用户信息
├─→ 设置id_card_hash和id_card_encrypted
├─→ is_temporary = false
└─→ 标记为正式人
PersonMergeService->mergePhoneToIdCard()
重新计算所有标签
```
### 3.3 标签计算系统
#### 3.3.1 标签定义
标签定义存储在`tag_definitions`集合中:
```javascript
{
tag_id: "标签ID",
tag_code: "标签代码",
tag_name: "标签名称",
rule_config: {
rule_type: "simple",
conditions: [...],
// 规则配置
},
update_frequency: "real_time" | "daily" | "weekly",
status: 0 // 0:启用, 1:禁用
}
```
#### 3.3.2 标签计算流程
```
消费记录写入
ConsumptionService->createRecord()
├─→ 写入consumption_records表
├─→ 更新user_profile统计信息total_amount, total_count
└─→ 触发标签计算(异步)
推送到RabbitMQ队列
TagCalculationWorker消费消息
TagService->calculateTags()
├─→ 获取用户数据
├─→ 获取标签定义列表
├─→ 遍历每个标签
│ ├─→ 解析规则配置
│ ├─→ SimpleRuleEngine计算标签值
│ ├─→ 更新或创建user_tags记录
│ └─→ 记录标签变更历史(如果值发生变化)
└─→ 返回更新的标签列表
```
#### 3.3.3 规则引擎
系统使用`SimpleRuleEngine`计算标签值:
```php
// 示例规则配置
{
"rule_type": "simple",
"conditions": [
{
"field": "total_amount",
"operator": ">=",
"value": 1000
}
],
"result": {
"value": "VIP",
"confidence": 0.9
}
}
```
### 3.4 消费记录处理
#### 3.4.1 消费记录数据结构
```javascript
{
record_id: "记录ID",
user_id: "用户ID",
consume_time: "消费时间",
amount: 100.00, // 消费金额
actual_amount: 95.00, // 实际金额
currency: "CNY",
store_id: "店铺ID",
status: 0,
create_time: "创建时间"
}
```
#### 3.4.2 处理流程
```
数据采集任务采集到订单数据
ConsumptionCollectionHandler处理
├─→ 提取手机号
├─→ 字段映射和转换
└─→ 调用ConsumptionService->createRecord()
IdentifierService->resolvePersonId()
├─→ 通过手机号解析user_id
└─→ 如果不存在,创建临时人
写入consumption_records表
UserProfileRepository->increaseStats()
├─→ total_amount += actual_amount
├─→ total_count += 1
└─→ last_consume_time = consume_time
触发标签计算(异步)
```
---
## 四、数据存储设计
### 4.1 MongoDB集合
#### 4.1.1 用户相关集合
**user_profile (用户画像)**
```javascript
{
user_id: String, // 用户唯一标识
id_card_hash: String, // 身份证哈希
id_card_encrypted: String, // 加密身份证
id_card_type: String, // 身份证类型
is_temporary: Boolean, // 是否临时人
name: String,
phone: String,
total_amount: Number, // 总消费金额
total_count: Number, // 总消费次数
last_consume_time: Date, // 最后消费时间
tags_update_time: Date, // 标签更新时间
status: Number,
create_time: Date,
update_time: Date
}
```
**user_phone_relations (手机号关联)**
```javascript
{
phone_number: String, // 手机号
user_id: String, // 用户ID
is_primary: Boolean, // 是否为主手机号
effective_time: Date,
expire_time: Date,
create_time: Date
}
```
**consumption_records (消费记录)**
```javascript
{
record_id: String,
user_id: String,
consume_time: Date,
amount: Number,
actual_amount: Number,
currency: String,
store_id: String,
status: Number,
create_time: Date
}
```
#### 4.1.2 标签相关集合
**tag_definitions (标签定义)**
```javascript
{
tag_id: String,
tag_code: String,
tag_name: String,
rule_config: Object, // 规则配置JSON
update_frequency: String, // real_time/daily/weekly
status: Number,
create_time: Date,
update_time: Date
}
```
**user_tags (用户标签)**
```javascript
{
user_id: String,
tag_id: String,
tag_value: String, // 标签值
tag_value_type: String, // 值类型
confidence: Number, // 置信度
effective_time: Date,
create_time: Date,
update_time: Date
}
```
**tag_history (标签变更历史)**
```javascript
{
user_id: String,
tag_id: String,
old_value: String,
new_value: String,
change_time: Date
}
```
#### 4.1.3 任务相关集合
**data_collection_tasks (数据采集任务)**
```javascript
{
task_id: String,
name: String,
data_source_id: String,
database: String,
collection: String,
target_type: String, // consumption_record/generic
handler_type: String,
mode: String, // batch/realtime
schedule: {
enabled: Boolean,
cron: String
},
field_mappings: Array,
filter_conditions: Object,
progress: {
status: String,
processed_count: Number,
success_count: Number,
error_count: Number,
total_count: Number,
percentage: Number
},
status: String, // pending/running/paused/stopped
create_time: Date,
update_time: Date
}
```
**data_sources (数据源)**
```javascript
{
data_source_id: String,
name: String,
type: String, // mongodb/mysql
host: String,
port: Number,
database: String,
username: String,
password: String,
is_tag_engine: Boolean, // 是否为标签引擎数据库
status: Number,
create_time: Date,
update_time: Date
}
```
### 4.2 Redis存储
#### 4.2.1 分布式锁
- `lock:data_collection:{task_id}` - 数据采集任务锁
- `lock:database_sync` - 数据库同步锁
#### 4.2.2 任务状态标志
- `data_collection_task:{task_id}:start` - 启动标志
- `data_collection_task:{task_id}:pause` - 暂停标志
- `data_collection_task:{task_id}:stop` - 停止标志
#### 4.2.3 同步状态
- `data_collection:{task_id}:last_sync_time` - 上次同步时间
- `data_collection:{task_id}:last_sync_id` - 上次同步ID
### 4.3 RabbitMQ队列
#### 4.3.1 数据同步队列
- 队列名:`data_sync`
- 消息格式:
```json
{
"user_id": "用户ID",
"data": {...},
"action": "insert|update|delete"
}
```
#### 4.3.2 标签计算队列
- 队列名:`tag_calculation`
- 消息格式:
```json
{
"user_id": "用户ID",
"tag_ids": null, // null表示计算所有real_time标签
"trigger_type": "consumption_record",
"record_id": "记录ID",
"timestamp": 1234567890
}
```
---
## 五、API接口体系
### 5.1 用户相关接口
- `POST /api/users` - 创建用户
- `GET /api/users/{user_id}` - 查询用户
- `PUT /api/users/{user_id}` - 更新用户
- `DELETE /api/users/{user_id}` - 删除用户
- `POST /api/users/search` - 搜索用户(复杂查询)
- `GET /api/users/{user_id}/decrypt-id-card` - 解密身份证
### 5.2 标签相关接口
- `GET /api/users/{user_id}/tags` - 查询用户标签
- `PUT /api/users/{user_id}/tags` - 计算/更新用户标签
- `DELETE /api/users/{user_id}/tags/{tag_id}` - 删除用户标签
- `POST /api/tags/filter` - 根据标签筛选用户
- `GET /api/tags/statistics` - 获取标签统计信息
- `GET /api/tags/history` - 获取标签历史记录
### 5.3 标签定义接口
- `GET /api/tag-definitions` - 获取标签定义列表
- `POST /api/tag-definitions` - 创建标签定义
- `GET /api/tag-definitions/{tag_id}` - 获取标签定义详情
- `PUT /api/tag-definitions/{tag_id}` - 更新标签定义
- `DELETE /api/tag-definitions/{tag_id}` - 删除标签定义
### 5.4 数据采集任务接口
- `POST /api/data-collection-tasks` - 创建任务
- `PUT /api/data-collection-tasks/{task_id}` - 更新任务
- `DELETE /api/data-collection-tasks/{task_id}` - 删除任务
- `GET /api/data-collection-tasks` - 任务列表
- `GET /api/data-collection-tasks/{task_id}` - 任务详情
- `GET /api/data-collection-tasks/{task_id}/progress` - 任务进度
- `POST /api/data-collection-tasks/{task_id}/start` - 启动任务
- `POST /api/data-collection-tasks/{task_id}/pause` - 暂停任务
- `POST /api/data-collection-tasks/{task_id}/stop` - 停止任务
### 5.5 数据源接口
- `GET /api/data-sources` - 获取数据源列表
- `GET /api/data-sources/{data_source_id}` - 获取数据源详情
- `POST /api/data-sources` - 创建数据源
- `PUT /api/data-sources/{data_source_id}` - 更新数据源
- `DELETE /api/data-sources/{data_source_id}` - 删除数据源
- `POST /api/data-sources/test-connection` - 测试数据源连接
### 5.6 身份合并接口
- `POST /api/person-merge/phone-to-id-card` - 合并手机号到身份证
- `POST /api/person-merge/temporary-to-formal` - 合并临时人到正式人
### 5.7 数据库同步接口
- `GET /api/database-sync/progress` - 查询同步进度
- `GET /api/database-sync/stats` - 查询同步统计
- `POST /api/database-sync/reset` - 重置同步进度
- `POST /api/database-sync/skip-error` - 跳过错误数据库
---
## 六、关键设计模式
### 6.1 工厂模式
- `DataSourceAdapterFactory` - 创建数据源适配器
- `PollingStrategyFactory` - 创建轮询策略
### 6.2 策略模式
- `PollingStrategyInterface` - 轮询策略接口
- `MongoDBConsumptionStrategy` - MongoDB消费策略
- `DefaultConsumptionStrategy` - 默认消费策略
### 6.3 适配器模式
- `DataSourceAdapterInterface` - 数据源适配器接口
- `MongoDBAdapter` - MongoDB适配器
- `MySQLAdapter` - MySQL适配器
### 6.4 仓库模式
- 所有Repository类继承MongoDB Model
- 封装数据访问逻辑
- 提供统一的查询接口
### 6.5 Handler模式
- `ConsumptionCollectionHandler` - 消费记录采集
- `GenericCollectionHandler` - 通用数据采集
- `DatabaseSyncHandler` - 数据库同步
---
## 七、数据流图
### 7.1 完整数据流
```
数据源 (KR MongoDB)
数据采集任务 (DataSyncScheduler)
├─→ ConsumptionCollectionHandler
│ ├─→ 批量采集模式:分页查询数据
│ └─→ 实时监听模式Change Stream监听
└─→ DatabaseSyncHandler
├─→ 全量同步:批量读取写入
└─→ 增量同步Change Stream监听
消费记录写入 (ConsumptionService)
├─→ 身份解析 (IdentifierService)
│ ├─→ 手机号 → user_id
│ └─→ 如果不存在,创建临时人
├─→ 写入consumption_records表
├─→ 更新user_profile统计信息
└─→ 触发标签计算推送到RabbitMQ
标签计算 (TagCalculationWorker)
├─→ TagService->calculateTags()
├─→ SimpleRuleEngine计算标签值
├─→ 更新user_tags表
└─→ 记录tag_history变更历史
```
### 7.2 任务执行时序图
```
用户操作
├─→ 创建任务 → 保存到MongoDB
└─→ 启动任务 → API → DataCollectionTaskService
├─→ 更新任务状态为running
└─→ Redis设置启动标志
DataSyncScheduler进程
├─→ 检测Redis标志
├─→ 从数据库加载任务配置
├─→ 创建数据源适配器
├─→ 实例化Handler
└─→ 调用Handler->collect()
├─→ 批量模式:定时执行
└─→ 实时模式:持续运行
```
---
## 八、关键技术实现
### 8.1 MongoDB Change Streams实时监听
```php
// 实时监听集合变更
$changeStream = $collection->watch(
[
['$match' => ['operationType' => ['$in' => ['insert', 'update']]]],
],
['fullDocument' => 'updateLookup']
);
foreach ($changeStream as $change) {
// 处理变更事件
$document = $change['fullDocument'];
// 处理数据...
}
```
### 8.2 分布式锁实现
```php
// 使用Redis实现分布式锁
$lockKey = "lock:data_collection:{$taskId}";
$locked = RedisHelper::setnx($lockKey, time(), 300); // TTL 300秒
if ($locked) {
try {
// 执行任务
} finally {
RedisHelper::del($lockKey);
}
}
```
### 8.3 异步消息队列
```php
// 推送标签计算任务到队列
QueueService::pushTagCalculation([
'user_id' => $userId,
'tag_ids' => null,
'trigger_type' => 'consumption_record',
'record_id' => $recordId,
]);
// Worker消费队列
$message = $channel->basic_get('tag_calculation');
if ($message) {
$data = json_decode($message->body, true);
$tagService->calculateTags($data['user_id']);
$channel->basic_ack($message->delivery_info['delivery_tag']);
}
```
### 8.4 身份证加密存储
```php
// 加密存储
$idCardEncrypted = EncryptionHelper::encrypt($idCard);
$idCardHash = EncryptionHelper::hash($idCard); // 用于匹配
// 解密读取(需要权限)
$idCard = EncryptionHelper::decrypt($idCardEncrypted);
```
---
## 九、系统特性
### 9.1 配置化设计
- 数据源配置统一管理
- 任务配置支持配置文件和数据库两种方式
- 业务逻辑与配置分离
### 9.2 高可用性
- 多进程架构,提高并发能力
- 分布式锁防止任务重复执行
- 错误重试机制
- 断点续传支持
### 9.3 可扩展性
- Handler模式易于添加新的采集任务
- 适配器模式易于支持新的数据源类型
- 策略模式易于扩展业务逻辑
### 9.4 可观测性
- 完善的日志系统(业务日志、错误日志、性能日志)
- 实时进度跟踪
- API接口提供进度和统计查询
### 9.5 数据安全性
- 身份证加密存储
- 支持数据脱敏
- 日志脱敏处理
---
## 十、前端架构
### 10.1 技术栈
- Vue 3 + TypeScript
- Element Plus (UI组件库)
- Pinia (状态管理)
- Vue Router (路由)
- Axios (HTTP请求)
- Vite (构建工具)
### 10.2 主要功能模块
1. **数据采集任务管理**
- 任务列表、创建、编辑、删除
- 任务启动、暂停、停止
- 任务进度查看
- 数据源选择、数据库/集合选择
- 字段映射配置
2. **标签管理**
- 标签定义管理
- 用户标签查看
- 标签筛选功能
3. **数据源管理**
- 数据源列表、创建、编辑、删除
- 连接测试
---
## 十一、总结
### 11.1 系统核心价值
1. **统一的数据采集中心**
- 支持多种数据源
- 支持批量采集和实时监听
- 配置化管理,易于扩展
2. **智能的用户标签引擎**
- 基于规则引擎自动计算标签
- 支持实时更新和定时更新
- 标签变更历史追踪
3. **灵活的身份管理体系**
- 支持身份证、手机号等多种标识
- 临时人机制
- 身份合并功能
### 11.2 系统优势
-**高性能**基于Workerman多进程架构
-**高可用**:分布式锁、错误重试、断点续传
-**易扩展**:配置化、组件化设计
-**可观测**:完善的日志和监控
-**安全**:数据加密、权限控制
### 11.3 系统流程总结
**核心流程:数据源 → 采集任务 → 消息队列 → 数据同步 → 标签计算 → 存储**
1. 数据采集:从多个数据源采集数据
2. 身份解析:根据手机号/身份证解析用户ID
3. 数据存储:写入消费记录,更新用户统计
4. 标签计算:基于用户数据计算标签值
5. 标签应用:支持标签筛选、人群分析等应用场景
---
## 十二、扩展建议
### 12.1 功能扩展
1. **定时批量标签更新**
- 支持daily/weekly频率的标签批量更新
- 添加定时任务配置
2. **标签血缘关系**
- 追踪标签来源和数据血缘
- 标签影响分析
3. **人群分析功能**
- 基于标签的人群分群
- 人群画像分析
4. **数据质量监控**
- 数据采集质量监控
- 异常数据告警
### 12.2 性能优化
1. **批量处理优化**
- 增加批量大小
- 优化数据库查询
2. **缓存优化**
- 标签定义缓存
- 用户数据缓存
3. **分片策略优化**
- 按时间分片
- 按数据库分片
---
**报告生成时间**: 2025-12-26
**项目版本**: 基于当前代码库分析