数据中心变化
This commit is contained in:
24
Moncter/.env.example
Normal file
24
Moncter/.env.example
Normal file
@@ -0,0 +1,24 @@
|
||||
# 存客宝标签系统 - 环境变量配置示例
|
||||
# 复制此文件为 .env 并修改相应的配置值,不要提交到版本控制系统
|
||||
|
||||
# ============================================
|
||||
# 加密配置
|
||||
# ============================================
|
||||
|
||||
# AES 加密密钥(至少32字符,建议使用随机生成的强密钥)
|
||||
# 生产环境请务必修改此密钥,并妥善保管
|
||||
ENCRYPTION_AES_KEY=your-32-byte-secret-key-here-12345678
|
||||
|
||||
# 哈希盐值(用于身份证哈希,增强安全性)
|
||||
# 生产环境请务必修改此盐值
|
||||
ENCRYPTION_HASH_SALT=your-hash-salt-here-change-in-production
|
||||
|
||||
# ============================================
|
||||
# 应用配置
|
||||
# ============================================
|
||||
|
||||
# 应用环境(development/production)
|
||||
APP_ENV=development
|
||||
|
||||
# 应用调试模式(true/false)
|
||||
APP_DEBUG=true
|
||||
@@ -1,11 +1,26 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"MongoDB": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mongodb-mcp-server@latest", "--readOnly"],
|
||||
"env": {
|
||||
"MDB_MCP_CONNECTION_STRING": "mongodb://ckb:123456@192.168.1.106:27017/ckb"
|
||||
}
|
||||
"mcpServers": {
|
||||
"MongoDB_ckb": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mongodb-mcp-server@1.2.0", "--readOnly"],
|
||||
"env": {
|
||||
"MDB_MCP_CONNECTION_STRING": "mongodb://ckb:123456@192.168.1.106:27017/ckb"
|
||||
}
|
||||
},
|
||||
"MongoDB_KR": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mongodb-mcp-server@1.2.0", "--readOnly"],
|
||||
"env": {
|
||||
"MDB_MCP_CONNECTION_STRING": "mongodb://admin:key123456@192.168.2.16:27017/admin"
|
||||
}
|
||||
},
|
||||
"Moncter": {
|
||||
"command": "node",
|
||||
"args": ["./MCP/moncter-mcp-server/dist/index.js"],
|
||||
"cwd": "E:/Cunkebao/Cunkebao02/Moncter",
|
||||
"env": {
|
||||
"MONCTER_API_URL": "http://127.0.0.1:8787"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Moncter/TaskShow/v0.39.1.tar.gz
Normal file
BIN
Moncter/TaskShow/v0.39.1.tar.gz
Normal file
Binary file not shown.
@@ -21,4 +21,134 @@ class IndexController
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 MongoDB 数据库连接
|
||||
* GET /api/test/db
|
||||
*/
|
||||
public function testDb(Request $request)
|
||||
{
|
||||
$result = [
|
||||
'code' => 0,
|
||||
'msg' => 'ok',
|
||||
'data' => [
|
||||
'config' => [],
|
||||
'connection' => [],
|
||||
'test_query' => [],
|
||||
],
|
||||
];
|
||||
|
||||
try {
|
||||
// 读取数据库配置
|
||||
$dbConfig = config('database', []);
|
||||
$mongoConfig = $dbConfig['connections']['mongodb'] ?? null;
|
||||
|
||||
if (!$mongoConfig) {
|
||||
throw new \Exception('MongoDB 配置不存在');
|
||||
}
|
||||
|
||||
$result['data']['config'] = [
|
||||
'driver' => $mongoConfig['driver'] ?? 'unknown',
|
||||
'database' => $mongoConfig['database'] ?? 'unknown',
|
||||
'dsn' => $mongoConfig['dsn'] ?? 'unknown',
|
||||
'has_username' => !empty($mongoConfig['username']),
|
||||
'has_password' => !empty($mongoConfig['password']),
|
||||
];
|
||||
|
||||
// 尝试使用 MongoDB 客户端直接连接
|
||||
try {
|
||||
// 构建包含认证信息的 DSN(如果配置了用户名和密码)
|
||||
$dsn = $mongoConfig['dsn'];
|
||||
if (!empty($mongoConfig['username']) && !empty($mongoConfig['password'])) {
|
||||
// 如果 DSN 中不包含认证信息,则添加
|
||||
if (strpos($dsn, '@') === false) {
|
||||
// 从 mongodb://host:port 格式转换为 mongodb://username:password@host:port/database
|
||||
$dsn = str_replace(
|
||||
'mongodb://',
|
||||
'mongodb://' . urlencode($mongoConfig['username']) . ':' . urlencode($mongoConfig['password']) . '@',
|
||||
$dsn
|
||||
);
|
||||
// 添加数据库名和认证源
|
||||
$dsn .= '/' . $mongoConfig['database'];
|
||||
if (!empty($mongoConfig['options']['authSource'])) {
|
||||
$dsn .= '?authSource=' . urlencode($mongoConfig['options']['authSource']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤掉空字符串的选项(MongoDB 客户端不允许空字符串)
|
||||
$options = array_filter($mongoConfig['options'] ?? [], function ($value) {
|
||||
return $value !== '';
|
||||
});
|
||||
|
||||
$client = new \MongoDB\Client(
|
||||
$dsn,
|
||||
$options
|
||||
);
|
||||
|
||||
// 尝试执行 ping 命令
|
||||
$adminDb = $client->selectDatabase('admin');
|
||||
$pingResult = $adminDb->command(['ping' => 1])->toArray();
|
||||
|
||||
$result['data']['connection'] = [
|
||||
'status' => 'connected',
|
||||
'ping' => 'ok',
|
||||
'server_info' => $client->getManager()->getServers(),
|
||||
];
|
||||
|
||||
// 尝试选择目标数据库并列出集合
|
||||
$targetDb = $client->selectDatabase($mongoConfig['database']);
|
||||
$collections = $targetDb->listCollections();
|
||||
$collectionNames = [];
|
||||
foreach ($collections as $collection) {
|
||||
$collectionNames[] = $collection->getName();
|
||||
}
|
||||
|
||||
$result['data']['test_query'] = [
|
||||
'database' => $mongoConfig['database'],
|
||||
'collections_count' => count($collectionNames),
|
||||
'collections' => $collectionNames,
|
||||
];
|
||||
|
||||
} catch (\MongoDB\Driver\Exception\Exception $e) {
|
||||
$result['data']['connection'] = [
|
||||
'status' => 'failed',
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
$result['code'] = 500;
|
||||
$result['msg'] = 'MongoDB 连接失败';
|
||||
}
|
||||
|
||||
// 尝试使用 Repository 查询(如果连接成功)
|
||||
if ($result['data']['connection']['status'] === 'connected') {
|
||||
try {
|
||||
$userRepo = new \app\repository\UserProfileRepository();
|
||||
$count = $userRepo->newQuery()->count();
|
||||
$result['data']['repository_test'] = [
|
||||
'status' => 'ok',
|
||||
'user_profile_count' => $count,
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
$result['data']['repository_test'] = [
|
||||
'status' => 'failed',
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$result = [
|
||||
'code' => 500,
|
||||
'msg' => '测试失败: ' . $e->getMessage(),
|
||||
'data' => [
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return json($result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,557 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use app\repository\UserProfileRepository;
|
||||
use app\service\UserService;
|
||||
use app\utils\ApiResponseHelper;
|
||||
use app\utils\DataMaskingHelper;
|
||||
use app\utils\LoggerHelper;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
|
||||
class UserController
|
||||
{
|
||||
public function hello(Request $request)
|
||||
/**
|
||||
* 创建用户
|
||||
*
|
||||
* POST /api/users
|
||||
*
|
||||
* 请求体示例:
|
||||
* {
|
||||
* "id_card": "110101199001011234",
|
||||
* "id_card_type": "身份证",
|
||||
* "name": "张三",
|
||||
* "phone": "13800138000",
|
||||
* "email": "zhangsan@example.com",
|
||||
* "gender": 1,
|
||||
* "birthday": "1990-01-01",
|
||||
* "address": "北京市朝阳区"
|
||||
* }
|
||||
*/
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$default_name = 'webman';
|
||||
// 从get请求里获得name参数,如果没有传递name参数则返回$default_name
|
||||
$name = $request->get('name', $default_name);
|
||||
// 向浏览器返回字符串
|
||||
return response('hello ' . $name);
|
||||
try {
|
||||
LoggerHelper::logRequest('POST', '/api/users');
|
||||
|
||||
$rawBody = $request->rawBody();
|
||||
|
||||
// 调试:记录原始请求体
|
||||
if (empty($rawBody)) {
|
||||
return ApiResponseHelper::error('请求体为空,请确保 Content-Type 为 application/json 并发送有效的 JSON 数据', 400);
|
||||
}
|
||||
|
||||
$body = json_decode($rawBody, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$errorMsg = '请求体必须是有效的 JSON 格式';
|
||||
$jsonError = json_last_error_msg();
|
||||
if ($jsonError) {
|
||||
$errorMsg .= ': ' . $jsonError;
|
||||
}
|
||||
// 开发环境输出更多调试信息
|
||||
if (getenv('APP_DEBUG') === 'true') {
|
||||
$errorMsg .= ' (原始请求体: ' . substr($rawBody, 0, 200) . ')';
|
||||
}
|
||||
return ApiResponseHelper::error($errorMsg, 400);
|
||||
}
|
||||
|
||||
$userService = new UserService(new UserProfileRepository());
|
||||
$result = $userService->createUser($body);
|
||||
|
||||
return ApiResponseHelper::success($result, '用户创建成功');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return ApiResponseHelper::error($e->getMessage(), 400);
|
||||
} catch (\Throwable $e) {
|
||||
return ApiResponseHelper::exception($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户信息
|
||||
*
|
||||
* GET /api/users/{user_id}?decrypt_id_card=1
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function show(Request $request): Response
|
||||
{
|
||||
try {
|
||||
// 从请求路径中解析 user_id
|
||||
$path = $request->path();
|
||||
if (preg_match('#/api/users/([^/]+)#', $path, $matches)) {
|
||||
$userId = $matches[1];
|
||||
} else {
|
||||
$userId = $request->get('user_id');
|
||||
if (!$userId) {
|
||||
throw new \InvalidArgumentException('缺少 user_id 参数');
|
||||
}
|
||||
}
|
||||
|
||||
LoggerHelper::logRequest('GET', $path, ['user_id' => $userId]);
|
||||
|
||||
// 检查是否需要解密身份证(需要权限控制,这里简单用参数控制)
|
||||
$decryptIdCard = (bool)$request->get('decrypt_id_card', false);
|
||||
|
||||
$userService = new UserService(new UserProfileRepository());
|
||||
$user = $userService->getUserById($userId, $decryptIdCard);
|
||||
|
||||
if (!$user) {
|
||||
return ApiResponseHelper::error('用户不存在', 404, 404);
|
||||
}
|
||||
|
||||
// 如果不需要解密身份证,对敏感字段进行脱敏
|
||||
if (!$decryptIdCard) {
|
||||
$user = DataMaskingHelper::maskArray($user, ['phone', 'email']);
|
||||
}
|
||||
|
||||
LoggerHelper::logBusiness('get_user_info', [
|
||||
'user_id' => $userId,
|
||||
'decrypt_id_card' => $decryptIdCard,
|
||||
]);
|
||||
|
||||
return ApiResponseHelper::success($user);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return ApiResponseHelper::error($e->getMessage(), 400);
|
||||
} catch (\Throwable $e) {
|
||||
return ApiResponseHelper::exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*
|
||||
* PUT /api/users/{user_id}
|
||||
*
|
||||
* 请求体示例:
|
||||
* {
|
||||
* "name": "张三",
|
||||
* "phone": "13800138000",
|
||||
* "email": "zhangsan@example.com",
|
||||
* "gender": 1,
|
||||
* "birthday": "1990-01-01",
|
||||
* "address": "北京市朝阳区",
|
||||
* "status": 0
|
||||
* }
|
||||
*/
|
||||
public function update(Request $request): Response
|
||||
{
|
||||
try {
|
||||
// 从请求路径中解析 user_id
|
||||
$path = $request->path();
|
||||
if (preg_match('#/api/users/([^/]+)#', $path, $matches)) {
|
||||
$userId = $matches[1];
|
||||
} else {
|
||||
$userId = $request->get('user_id');
|
||||
if (!$userId) {
|
||||
throw new \InvalidArgumentException('缺少 user_id 参数');
|
||||
}
|
||||
}
|
||||
|
||||
LoggerHelper::logRequest('PUT', $path, ['user_id' => $userId]);
|
||||
|
||||
$rawBody = $request->rawBody();
|
||||
|
||||
// 调试:记录原始请求体
|
||||
if (empty($rawBody)) {
|
||||
return ApiResponseHelper::error('请求体为空,请确保 Content-Type 为 application/json 并发送有效的 JSON 数据', 400);
|
||||
}
|
||||
|
||||
$body = json_decode($rawBody, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$errorMsg = '请求体必须是有效的 JSON 格式';
|
||||
$jsonError = json_last_error_msg();
|
||||
if ($jsonError) {
|
||||
$errorMsg .= ': ' . $jsonError;
|
||||
}
|
||||
// 开发环境输出更多调试信息
|
||||
if (getenv('APP_DEBUG') === 'true') {
|
||||
$errorMsg .= ' (原始请求体: ' . substr($rawBody, 0, 200) . ')';
|
||||
}
|
||||
return ApiResponseHelper::error($errorMsg, 400);
|
||||
}
|
||||
|
||||
if (empty($body)) {
|
||||
return ApiResponseHelper::error('请求体不能为空', 400);
|
||||
}
|
||||
|
||||
$userService = new UserService(new UserProfileRepository());
|
||||
$result = $userService->updateUser($userId, $body);
|
||||
|
||||
// 脱敏处理
|
||||
$result = DataMaskingHelper::maskArray($result, ['phone', 'email']);
|
||||
|
||||
return ApiResponseHelper::success($result, '用户更新成功');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return ApiResponseHelper::error($e->getMessage(), 400);
|
||||
} catch (\Throwable $e) {
|
||||
return ApiResponseHelper::exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密身份证号
|
||||
*
|
||||
* GET /api/users/{user_id}/decrypt-id-card
|
||||
*/
|
||||
public function decryptIdCard(Request $request): Response
|
||||
{
|
||||
try {
|
||||
// 从请求路径中解析 user_id
|
||||
$path = $request->path();
|
||||
if (preg_match('#/api/users/([^/]+)/decrypt-id-card#', $path, $matches)) {
|
||||
$userId = $matches[1];
|
||||
} else {
|
||||
$userId = $request->get('user_id');
|
||||
if (!$userId) {
|
||||
throw new \InvalidArgumentException('缺少 user_id 参数');
|
||||
}
|
||||
}
|
||||
|
||||
LoggerHelper::logRequest('GET', $path, ['user_id' => $userId]);
|
||||
|
||||
$userService = new UserService(new UserProfileRepository());
|
||||
$user = $userService->getUserById($userId, true); // 强制解密
|
||||
|
||||
if (!$user) {
|
||||
return ApiResponseHelper::error('用户不存在', 404, 404);
|
||||
}
|
||||
|
||||
LoggerHelper::logBusiness('decrypt_id_card', [
|
||||
'user_id' => $userId,
|
||||
]);
|
||||
|
||||
return ApiResponseHelper::success([
|
||||
'user_id' => $user['user_id'],
|
||||
'id_card' => $user['id_card'] ?? ''
|
||||
]);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return ApiResponseHelper::error($e->getMessage(), 400);
|
||||
} catch (\Throwable $e) {
|
||||
return ApiResponseHelper::exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户(软删除)
|
||||
*
|
||||
* DELETE /api/users/{user_id}
|
||||
*/
|
||||
public function destroy(Request $request): Response
|
||||
{
|
||||
try {
|
||||
// 从请求路径中解析 user_id
|
||||
$path = $request->path();
|
||||
if (preg_match('#/api/users/([^/]+)#', $path, $matches)) {
|
||||
$userId = $matches[1];
|
||||
} else {
|
||||
$userId = $request->get('user_id');
|
||||
if (!$userId) {
|
||||
throw new \InvalidArgumentException('缺少 user_id 参数');
|
||||
}
|
||||
}
|
||||
|
||||
LoggerHelper::logRequest('DELETE', $path, ['user_id' => $userId]);
|
||||
|
||||
$userService = new UserService(new UserProfileRepository());
|
||||
$userService->deleteUser($userId);
|
||||
|
||||
return ApiResponseHelper::success(null, '用户删除成功');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return ApiResponseHelper::error($e->getMessage(), 400);
|
||||
} catch (\Throwable $e) {
|
||||
return ApiResponseHelper::exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索用户(支持多种搜索条件组合)
|
||||
*
|
||||
* POST /api/users/search
|
||||
*
|
||||
* 支持以下搜索方式:
|
||||
* 1. 基础字段搜索:姓名、手机号、邮箱、身份证号等
|
||||
* 2. 标签筛选:根据用户标签筛选
|
||||
* 3. 组合搜索:基础字段 + 标签筛选
|
||||
*
|
||||
* 请求体示例1(姓名模糊搜索):
|
||||
* {
|
||||
* "name": "张三",
|
||||
* "page": 1,
|
||||
* "page_size": 20
|
||||
* }
|
||||
*
|
||||
* 请求体示例2(组合搜索:姓名 + 手机号):
|
||||
* {
|
||||
* "name": "张",
|
||||
* "phone": "138",
|
||||
* "page": 1,
|
||||
* "page_size": 20
|
||||
* }
|
||||
*
|
||||
* 请求体示例3(根据标签筛选):
|
||||
* {
|
||||
* "tag_conditions": [
|
||||
* {
|
||||
* "tag_code": "high_consumer",
|
||||
* "operator": "=",
|
||||
* "value": "high"
|
||||
* }
|
||||
* ],
|
||||
* "logic": "AND",
|
||||
* "page": 1,
|
||||
* "page_size": 20
|
||||
* }
|
||||
*
|
||||
* 请求体示例4(组合搜索:基础字段 + 标签):
|
||||
* {
|
||||
* "name": "张",
|
||||
* "min_total_amount": 1000,
|
||||
* "tag_conditions": [
|
||||
* {
|
||||
* "tag_code": "active_user",
|
||||
* "operator": "=",
|
||||
* "value": "active"
|
||||
* }
|
||||
* ],
|
||||
* "page": 1,
|
||||
* "page_size": 20
|
||||
* }
|
||||
*/
|
||||
public function search(Request $request): Response
|
||||
{
|
||||
try {
|
||||
LoggerHelper::logRequest('POST', '/api/users/search');
|
||||
|
||||
$rawBody = $request->rawBody();
|
||||
|
||||
// 调试:记录原始请求体
|
||||
if (empty($rawBody)) {
|
||||
return ApiResponseHelper::error('请求体为空,请确保 Content-Type 为 application/json 并发送有效的 JSON 数据', 400);
|
||||
}
|
||||
|
||||
$body = json_decode($rawBody, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$errorMsg = '请求体必须是有效的 JSON 格式';
|
||||
$jsonError = json_last_error_msg();
|
||||
if ($jsonError) {
|
||||
$errorMsg .= ': ' . $jsonError;
|
||||
}
|
||||
// 开发环境输出更多调试信息
|
||||
if (getenv('APP_DEBUG') === 'true') {
|
||||
$errorMsg .= ' (原始请求体: ' . substr($rawBody, 0, 200) . ')';
|
||||
}
|
||||
return ApiResponseHelper::error($errorMsg, 400);
|
||||
}
|
||||
|
||||
$page = (int)($body['page'] ?? 1);
|
||||
$pageSize = (int)($body['page_size'] ?? 20);
|
||||
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
}
|
||||
if ($pageSize < 1 || $pageSize > 100) {
|
||||
$pageSize = 20;
|
||||
}
|
||||
|
||||
$userService = new UserService(new UserProfileRepository());
|
||||
|
||||
// 情况1:仅根据身份证号查找(返回单个用户,不分页)
|
||||
if (!empty($body['id_card']) && empty($body['tag_conditions']) && empty($body['name']) && empty($body['phone']) && empty($body['email'])) {
|
||||
$user = $userService->findUserByIdCard($body['id_card']);
|
||||
|
||||
if (!$user) {
|
||||
return ApiResponseHelper::error('未找到该身份证号对应的用户', 404, 404);
|
||||
}
|
||||
|
||||
// 脱敏处理
|
||||
$user = DataMaskingHelper::maskArray($user, ['phone', 'email']);
|
||||
|
||||
LoggerHelper::logBusiness('search_user_by_id_card', [
|
||||
'found' => true,
|
||||
]);
|
||||
|
||||
return ApiResponseHelper::success($user);
|
||||
}
|
||||
|
||||
// 情况2:根据标签筛选用户(可能结合基础字段搜索)
|
||||
if (!empty($body['tag_conditions'])) {
|
||||
$tagService = new \app\service\TagService(
|
||||
new \app\repository\TagDefinitionRepository(),
|
||||
new UserProfileRepository(),
|
||||
new \app\repository\UserTagRepository(),
|
||||
new \app\repository\TagHistoryRepository(),
|
||||
new \app\service\TagRuleEngine\SimpleRuleEngine()
|
||||
);
|
||||
|
||||
$conditions = $body['tag_conditions'];
|
||||
$logic = $body['logic'] ?? 'AND';
|
||||
$includeUserInfo = true; // 标签筛选需要用户信息
|
||||
|
||||
// 验证条件格式
|
||||
foreach ($conditions as $condition) {
|
||||
if (!isset($condition['tag_code']) || !isset($condition['operator']) || !isset($condition['value'])) {
|
||||
throw new \InvalidArgumentException('每个条件必须包含 tag_code、operator 和 value 字段');
|
||||
}
|
||||
}
|
||||
|
||||
// 先根据标签筛选用户
|
||||
$tagResult = $tagService->filterUsersByTags(
|
||||
$conditions,
|
||||
$logic,
|
||||
1, // 先获取所有符合条件的用户ID
|
||||
10000, // 临时设置大值,获取所有用户ID
|
||||
true
|
||||
);
|
||||
|
||||
$userIds = array_column($tagResult['users'], 'user_id');
|
||||
|
||||
if (empty($userIds)) {
|
||||
return ApiResponseHelper::success([
|
||||
'users' => [],
|
||||
'total' => 0,
|
||||
'page' => $page,
|
||||
'page_size' => $pageSize,
|
||||
'total_pages' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
// 如果有基础字段搜索条件,进一步筛选
|
||||
$baseConditions = [];
|
||||
if (!empty($body['name'])) {
|
||||
$baseConditions['name'] = $body['name'];
|
||||
}
|
||||
if (!empty($body['phone'])) {
|
||||
$baseConditions['phone'] = $body['phone'];
|
||||
$baseConditions['phone_exact'] = $body['phone_exact'] ?? false;
|
||||
}
|
||||
if (!empty($body['email'])) {
|
||||
$baseConditions['email'] = $body['email'];
|
||||
$baseConditions['email_exact'] = $body['email_exact'] ?? false;
|
||||
}
|
||||
if (isset($body['gender']) && $body['gender'] !== '') {
|
||||
$baseConditions['gender'] = $body['gender'];
|
||||
}
|
||||
if (isset($body['status']) && $body['status'] !== '') {
|
||||
$baseConditions['status'] = $body['status'];
|
||||
}
|
||||
if (isset($body['min_total_amount'])) {
|
||||
$baseConditions['min_total_amount'] = $body['min_total_amount'];
|
||||
}
|
||||
if (isset($body['max_total_amount'])) {
|
||||
$baseConditions['max_total_amount'] = $body['max_total_amount'];
|
||||
}
|
||||
if (isset($body['min_total_count'])) {
|
||||
$baseConditions['min_total_count'] = $body['min_total_count'];
|
||||
}
|
||||
if (isset($body['max_total_count'])) {
|
||||
$baseConditions['max_total_count'] = $body['max_total_count'];
|
||||
}
|
||||
|
||||
// 如果有基础字段条件,需要进一步筛选
|
||||
if (!empty($baseConditions)) {
|
||||
$baseConditions['user_ids'] = $userIds; // 限制在标签筛选的用户范围内
|
||||
$result = $userService->searchUsers($baseConditions, $page, $pageSize);
|
||||
} else {
|
||||
// 没有基础字段条件,直接使用标签筛选结果并分页
|
||||
$total = count($userIds);
|
||||
$offset = ($page - 1) * $pageSize;
|
||||
$pagedUserIds = array_slice($userIds, $offset, $pageSize);
|
||||
|
||||
// 获取用户详细信息
|
||||
$users = [];
|
||||
foreach ($pagedUserIds as $userId) {
|
||||
$user = $userService->getUserById($userId, false);
|
||||
if ($user) {
|
||||
$users[] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [
|
||||
'users' => $users,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'page_size' => $pageSize,
|
||||
'total_pages' => (int)ceil($total / $pageSize),
|
||||
];
|
||||
}
|
||||
|
||||
// 对返回的用户信息进行脱敏处理
|
||||
if (isset($result['users']) && is_array($result['users'])) {
|
||||
foreach ($result['users'] as &$user) {
|
||||
$user = DataMaskingHelper::maskArray($user, ['phone', 'email']);
|
||||
}
|
||||
unset($user);
|
||||
}
|
||||
|
||||
LoggerHelper::logBusiness('search_users_by_tags', [
|
||||
'conditions_count' => count($conditions),
|
||||
'base_conditions' => !empty($baseConditions),
|
||||
'result_count' => $result['total'] ?? 0,
|
||||
]);
|
||||
|
||||
return ApiResponseHelper::success($result);
|
||||
}
|
||||
|
||||
// 情况3:仅基础字段搜索(无标签条件)
|
||||
$baseConditions = [];
|
||||
if (!empty($body['name'])) {
|
||||
$baseConditions['name'] = $body['name'];
|
||||
}
|
||||
if (!empty($body['phone'])) {
|
||||
$baseConditions['phone'] = $body['phone'];
|
||||
$baseConditions['phone_exact'] = $body['phone_exact'] ?? false;
|
||||
}
|
||||
if (!empty($body['email'])) {
|
||||
$baseConditions['email'] = $body['email'];
|
||||
$baseConditions['email_exact'] = $body['email_exact'] ?? false;
|
||||
}
|
||||
if (!empty($body['id_card'])) {
|
||||
$baseConditions['id_card'] = $body['id_card'];
|
||||
}
|
||||
if (isset($body['gender']) && $body['gender'] !== '') {
|
||||
$baseConditions['gender'] = $body['gender'];
|
||||
}
|
||||
if (isset($body['status']) && $body['status'] !== '') {
|
||||
$baseConditions['status'] = $body['status'];
|
||||
}
|
||||
if (isset($body['min_total_amount'])) {
|
||||
$baseConditions['min_total_amount'] = $body['min_total_amount'];
|
||||
}
|
||||
if (isset($body['max_total_amount'])) {
|
||||
$baseConditions['max_total_amount'] = $body['max_total_amount'];
|
||||
}
|
||||
if (isset($body['min_total_count'])) {
|
||||
$baseConditions['min_total_count'] = $body['min_total_count'];
|
||||
}
|
||||
if (isset($body['max_total_count'])) {
|
||||
$baseConditions['max_total_count'] = $body['max_total_count'];
|
||||
}
|
||||
|
||||
if (empty($baseConditions)) {
|
||||
return ApiResponseHelper::error('请提供至少一个搜索条件', 400);
|
||||
}
|
||||
|
||||
$result = $userService->searchUsers($baseConditions, $page, $pageSize);
|
||||
|
||||
// 对返回的用户信息进行脱敏处理
|
||||
if (isset($result['users']) && is_array($result['users'])) {
|
||||
foreach ($result['users'] as &$user) {
|
||||
$user = DataMaskingHelper::maskArray($user, ['phone', 'email']);
|
||||
}
|
||||
unset($user);
|
||||
}
|
||||
|
||||
LoggerHelper::logBusiness('search_users_by_base_fields', [
|
||||
'conditions' => array_keys($baseConditions),
|
||||
'result_count' => $result['total'] ?? 0,
|
||||
]);
|
||||
|
||||
return ApiResponseHelper::success($result);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return ApiResponseHelper::error($e->getMessage(), 400);
|
||||
} catch (\Throwable $e) {
|
||||
return ApiResponseHelper::exception($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,12 @@
|
||||
"php": ">=8.1",
|
||||
"workerman/webman-framework": "^2.1",
|
||||
"monolog/monolog": "^2.0",
|
||||
"mongodb/laravel-mongodb": "^4.0"
|
||||
"mongodb/laravel-mongodb": "^4.0",
|
||||
"vlucas/phpdotenv": "^5.6",
|
||||
"predis/predis": "^2.0",
|
||||
"dragonmantank/cron-expression": "^3.6",
|
||||
"php-amqplib/php-amqplib": "^3.7",
|
||||
"ramsey/uuid": "^4.7"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "For better performance. "
|
||||
|
||||
896
Moncter/composer.lock
generated
896
Moncter/composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b36fd3581fc1bf43e25a6294dd7efc58",
|
||||
"content-hash": "6cafa2c36c31a9f9ddfcf8df3e7da924",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -225,6 +225,132 @@
|
||||
],
|
||||
"time": "2025-08-10T19:31:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dragonmantank/cron-expression",
|
||||
"version": "v3.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dragonmantank/cron-expression.git",
|
||||
"reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013",
|
||||
"reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.2|^8.3|^8.4|^8.5"
|
||||
},
|
||||
"replace": {
|
||||
"mtdowling/cron-expression": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan": "^1.12.32|^2.1.31",
|
||||
"phpunit/phpunit": "^8.5.48|^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Cron\\": "src/Cron/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Tankersley",
|
||||
"email": "chris@ctankersley.com",
|
||||
"homepage": "https://github.com/dragonmantank"
|
||||
}
|
||||
],
|
||||
"description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
|
||||
"keywords": [
|
||||
"cron",
|
||||
"schedule"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dragonmantank/cron-expression/issues",
|
||||
"source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/dragonmantank",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-31T18:51:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
|
||||
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GrahamCampbell\\ResultType\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "An Implementation Of The Result Type",
|
||||
"keywords": [
|
||||
"Graham Campbell",
|
||||
"GrahamCampbell",
|
||||
"Result Type",
|
||||
"Result-Type",
|
||||
"result"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-20T21:45:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/bus",
|
||||
"version": "v11.46.1",
|
||||
@@ -1335,6 +1461,453 @@
|
||||
},
|
||||
"time": "2018-02-13T20:26:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/constant_time_encoding",
|
||||
"version": "v3.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/constant_time_encoding.git",
|
||||
"reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
|
||||
"reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8"
|
||||
},
|
||||
"require-dev": {
|
||||
"infection/infection": "^0",
|
||||
"nikic/php-fuzzer": "^0",
|
||||
"phpunit/phpunit": "^9|^10|^11",
|
||||
"vimeo/psalm": "^4|^5|^6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ParagonIE\\ConstantTime\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Steve 'Sc00bz' Thomas",
|
||||
"email": "steve@tobtu.com",
|
||||
"homepage": "https://www.tobtu.com",
|
||||
"role": "Original Developer"
|
||||
}
|
||||
],
|
||||
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
|
||||
"keywords": [
|
||||
"base16",
|
||||
"base32",
|
||||
"base32_decode",
|
||||
"base32_encode",
|
||||
"base64",
|
||||
"base64_decode",
|
||||
"base64_encode",
|
||||
"bin2hex",
|
||||
"encoding",
|
||||
"hex",
|
||||
"hex2bin",
|
||||
"rfc4648"
|
||||
],
|
||||
"support": {
|
||||
"email": "info@paragonie.com",
|
||||
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
|
||||
"source": "https://github.com/paragonie/constant_time_encoding"
|
||||
},
|
||||
"time": "2025-09-24T15:06:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v9.99.100",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">= 7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*",
|
||||
"vimeo/psalm": "^1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"type": "library",
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"polyfill",
|
||||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"support": {
|
||||
"email": "info@paragonie.com",
|
||||
"issues": "https://github.com/paragonie/random_compat/issues",
|
||||
"source": "https://github.com/paragonie/random_compat"
|
||||
},
|
||||
"time": "2020-10-15T08:29:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-amqplib/php-amqplib",
|
||||
"version": "v3.7.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-amqplib/php-amqplib.git",
|
||||
"reference": "381b6f7c600e0e0c7463cdd7f7a1a3bc6268e5fd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/381b6f7c600e0e0c7463cdd7f7a1a3bc6268e5fd",
|
||||
"reference": "381b6f7c600e0e0c7463cdd7f7a1a3bc6268e5fd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"ext-sockets": "*",
|
||||
"php": "^7.2||^8.0",
|
||||
"phpseclib/phpseclib": "^2.0|^3.0"
|
||||
},
|
||||
"conflict": {
|
||||
"php": "7.4.0 - 7.4.1"
|
||||
},
|
||||
"replace": {
|
||||
"videlalvaro/php-amqplib": "self.version"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"nategood/httpful": "^0.2.20",
|
||||
"phpunit/phpunit": "^7.5|^9.5",
|
||||
"squizlabs/php_codesniffer": "^3.6"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpAmqpLib\\": "PhpAmqpLib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alvaro Videla",
|
||||
"role": "Original Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Raúl Araya",
|
||||
"email": "nubeiro@gmail.com",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Luke Bakken",
|
||||
"email": "luke@bakken.io",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Ramūnas Dronga",
|
||||
"email": "github@ramuno.lt",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "Formerly videlalvaro/php-amqplib. This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.",
|
||||
"homepage": "https://github.com/php-amqplib/php-amqplib/",
|
||||
"keywords": [
|
||||
"message",
|
||||
"queue",
|
||||
"rabbitmq"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-amqplib/php-amqplib/issues",
|
||||
"source": "https://github.com/php-amqplib/php-amqplib/tree/v3.7.4"
|
||||
},
|
||||
"time": "2025-11-23T17:00:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/schmittjoh/php-option.git",
|
||||
"reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d",
|
||||
"reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpOption\\": "src/PhpOption/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Johannes M. Schmitt",
|
||||
"email": "schmittjoh@gmail.com",
|
||||
"homepage": "https://github.com/schmittjoh"
|
||||
},
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "Option Type for PHP",
|
||||
"keywords": [
|
||||
"language",
|
||||
"option",
|
||||
"php",
|
||||
"type"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/schmittjoh/php-option/issues",
|
||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-21T11:53:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.48",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/64065a5679c50acb886e82c07aa139b0f757bb89",
|
||||
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"paragonie/constant_time_encoding": "^1|^2|^3",
|
||||
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
|
||||
"php": ">=5.6.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
|
||||
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
|
||||
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
|
||||
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
|
||||
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"phpseclib/bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"phpseclib3\\": "phpseclib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jim Wigginton",
|
||||
"email": "terrafrost@php.net",
|
||||
"role": "Lead Developer"
|
||||
},
|
||||
{
|
||||
"name": "Patrick Monnerat",
|
||||
"email": "pm@datasphere.ch",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Andreas Fischer",
|
||||
"email": "bantu@phpbb.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Hans-Jürgen Petrich",
|
||||
"email": "petrich@tronic-media.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "graham@alt-three.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
|
||||
"homepage": "http://phpseclib.sourceforge.net",
|
||||
"keywords": [
|
||||
"BigInteger",
|
||||
"aes",
|
||||
"asn.1",
|
||||
"asn1",
|
||||
"blowfish",
|
||||
"crypto",
|
||||
"cryptography",
|
||||
"encryption",
|
||||
"rsa",
|
||||
"security",
|
||||
"sftp",
|
||||
"signature",
|
||||
"signing",
|
||||
"ssh",
|
||||
"twofish",
|
||||
"x.509",
|
||||
"x509"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.48"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/terrafrost",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpseclib",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-15T11:51:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"version": "v2.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/predis/predis.git",
|
||||
"reference": "07105e050622ed80bd60808367ced9e379f31530"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/predis/predis/zipball/07105e050622ed80bd60808367ced9e379f31530",
|
||||
"reference": "07105e050622ed80bd60808367ced9e379f31530",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.3",
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpcov": "^6.0 || ^8.0",
|
||||
"phpunit/phpunit": "^8.0 || ^9.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Predis\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Till Krüss",
|
||||
"homepage": "https://till.im",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "A flexible and feature-complete Redis/Valkey client for PHP.",
|
||||
"homepage": "http://github.com/predis/predis",
|
||||
"keywords": [
|
||||
"nosql",
|
||||
"predis",
|
||||
"redis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/predis/predis/issues",
|
||||
"source": "https://github.com/predis/predis/tree/v2.4.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/tillkruss",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-11-12T18:00:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/clock",
|
||||
"version": "1.0.0",
|
||||
@@ -1537,6 +2110,160 @@
|
||||
},
|
||||
"time": "2021-10-29T13:26:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ramsey/collection",
|
||||
"version": "2.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ramsey/collection.git",
|
||||
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
|
||||
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"captainhook/plugin-composer": "^5.3",
|
||||
"ergebnis/composer-normalize": "^2.45",
|
||||
"fakerphp/faker": "^1.24",
|
||||
"hamcrest/hamcrest-php": "^2.0",
|
||||
"jangregor/phpstan-prophecy": "^2.1",
|
||||
"mockery/mockery": "^1.6",
|
||||
"php-parallel-lint/php-console-highlighter": "^1.0",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.4",
|
||||
"phpspec/prophecy-phpunit": "^2.3",
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpstan/phpstan-mockery": "^2.0",
|
||||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"ramsey/coding-standard": "^2.3",
|
||||
"ramsey/conventional-commits": "^1.6",
|
||||
"roave/security-advisories": "dev-latest"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"captainhook": {
|
||||
"force-install": true
|
||||
},
|
||||
"ramsey/conventional-commits": {
|
||||
"configFile": "conventional-commits.json"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Ramsey\\Collection\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Ramsey",
|
||||
"email": "ben@benramsey.com",
|
||||
"homepage": "https://benramsey.com"
|
||||
}
|
||||
],
|
||||
"description": "A PHP library for representing and manipulating collections.",
|
||||
"keywords": [
|
||||
"array",
|
||||
"collection",
|
||||
"hash",
|
||||
"map",
|
||||
"queue",
|
||||
"set"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/ramsey/collection/issues",
|
||||
"source": "https://github.com/ramsey/collection/tree/2.1.1"
|
||||
},
|
||||
"time": "2025-03-22T05:38:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ramsey/uuid",
|
||||
"version": "4.9.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ramsey/uuid.git",
|
||||
"reference": "8429c78ca35a09f27565311b98101e2826affde0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0",
|
||||
"reference": "8429c78ca35a09f27565311b98101e2826affde0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
|
||||
"php": "^8.0",
|
||||
"ramsey/collection": "^1.2 || ^2.0"
|
||||
},
|
||||
"replace": {
|
||||
"rhumsaa/uuid": "self.version"
|
||||
},
|
||||
"require-dev": {
|
||||
"captainhook/captainhook": "^5.25",
|
||||
"captainhook/plugin-composer": "^5.3",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
|
||||
"ergebnis/composer-normalize": "^2.47",
|
||||
"mockery/mockery": "^1.6",
|
||||
"paragonie/random-lib": "^2",
|
||||
"php-mock/php-mock": "^2.6",
|
||||
"php-mock/php-mock-mockery": "^1.5",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.4.0",
|
||||
"phpbench/phpbench": "^1.2.14",
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpstan/phpstan-mockery": "^2.0",
|
||||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"slevomat/coding-standard": "^8.18",
|
||||
"squizlabs/php_codesniffer": "^3.13"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
|
||||
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
|
||||
"ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
|
||||
"paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
|
||||
"ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"captainhook": {
|
||||
"force-install": true
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Ramsey\\Uuid\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
|
||||
"keywords": [
|
||||
"guid",
|
||||
"identifier",
|
||||
"uuid"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/ramsey/uuid/issues",
|
||||
"source": "https://github.com/ramsey/uuid/tree/4.9.2"
|
||||
},
|
||||
"time": "2025-12-14T04:43:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/clock",
|
||||
"version": "v7.3.0",
|
||||
@@ -1678,6 +2405,89 @@
|
||||
],
|
||||
"time": "2024-09-25T14:21:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.33.0",
|
||||
@@ -2185,6 +2995,90 @@
|
||||
],
|
||||
"time": "2024-09-27T08:32:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
"version": "v5.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
|
||||
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pcre": "*",
|
||||
"graham-campbell/result-type": "^1.1.3",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.3",
|
||||
"symfony/polyfill-ctype": "^1.24",
|
||||
"symfony/polyfill-mbstring": "^1.24",
|
||||
"symfony/polyfill-php80": "^1.24"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"ext-filter": "*",
|
||||
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-filter": "Required to use the boolean validator."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "5.6-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dotenv\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Vance Lucas",
|
||||
"email": "vance@vancelucas.com",
|
||||
"homepage": "https://github.com/vlucas"
|
||||
}
|
||||
],
|
||||
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
|
||||
"keywords": [
|
||||
"dotenv",
|
||||
"env",
|
||||
"environment"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-30T23:37:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "voku/portable-ascii",
|
||||
"version": "2.0.3",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
return [
|
||||
'files' => [
|
||||
base_path() . '/app/functions.php',
|
||||
base_path() . '/support/Request.php',
|
||||
base_path() . '/support/Response.php',
|
||||
]
|
||||
|
||||
@@ -14,4 +14,5 @@
|
||||
|
||||
return [
|
||||
support\bootstrap\Session::class,
|
||||
support\bootstrap\MongoDB::class,
|
||||
];
|
||||
|
||||
@@ -9,17 +9,17 @@ return [
|
||||
// MongoDB 官方连接配置
|
||||
'mongodb' => [
|
||||
'driver' => 'mongodb',
|
||||
'dsn' => 'mongodb://127.0.0.1:27017', // 集群可写:mongodb://node1:27017,node2:27017
|
||||
'database' => 'Moncter', // 目标数据库名
|
||||
'username' => 'Moncter', // 无认证则省略
|
||||
'dsn' => 'mongodb://192.168.1.106:27017', // 集群可写:mongodb://node1:27017,node2:27017
|
||||
'database' => 'ckb', // 目标数据库名
|
||||
'username' => 'ckb', // 无认证则省略
|
||||
'password' => '123456', // 无认证则省略
|
||||
'options' => [
|
||||
'replicaSet' => '', // 副本集名称(无则留空)
|
||||
// 'replicaSet' => '', // 副本集名称(如果使用副本集,取消注释并填写名称)
|
||||
'ssl' => false, // 是否启用 SSL
|
||||
'connectTimeoutMS' => 3000, // 连接超时
|
||||
'socketTimeoutMS' => 5000, // 读写超时
|
||||
// 认证相关(若 MongoDB 启用认证)
|
||||
'authSource' => 'admin', // 认证数据库(默认 admin)
|
||||
'authSource' => 'ckb', // 认证数据库(默认 admin)
|
||||
'authMechanism' => 'SCRAM-SHA-256', // 认证机制(默认推荐)
|
||||
],
|
||||
],
|
||||
|
||||
@@ -28,5 +28,10 @@ return [
|
||||
],
|
||||
]
|
||||
],
|
||||
'processors' => [
|
||||
[
|
||||
'class' => app\utils\LogMaskingProcessor::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -58,5 +58,26 @@ return [
|
||||
'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/',
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
// 数据采集任务调度器(从 config/data_collection_tasks.php 读取所有采集任务配置)
|
||||
'data_sync_scheduler' => [
|
||||
'handler' => app\process\DataSyncScheduler::class,
|
||||
'count' => 10, // Worker 进程数量(可根据任务数量调整)
|
||||
'reloadable' => false,
|
||||
],
|
||||
// 数据同步 Worker(消费 RabbitMQ 消息队列)
|
||||
// 处理从采集任务推送过来的数据,写入目标数据库
|
||||
'data_sync_worker' => [
|
||||
'handler' => app\process\DataSyncWorker::class,
|
||||
'count' => 20, // Worker 进程数量(可根据消息量调整)
|
||||
'reloadable' => false,
|
||||
],
|
||||
// 标签计算 Worker(消费 RabbitMQ 消息队列)
|
||||
// 根据用户数据计算标签值
|
||||
'tag_calculation_worker' => [
|
||||
'handler' => app\process\TagCalculationWorker::class,
|
||||
'count' => 2, // Worker 进程数量(可根据消息量调整)
|
||||
'reloadable' => false,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -15,7 +15,106 @@
|
||||
use Webman\Route;
|
||||
|
||||
|
||||
// 数据库连接测试接口
|
||||
Route::get('/api/test/db', [app\controller\IndexController::class, 'testDb']);
|
||||
|
||||
// ============================================
|
||||
// 用户相关接口(RESTful)
|
||||
// ============================================
|
||||
Route::post('/api/users', [app\controller\UserController::class, 'store']); // 创建用户
|
||||
Route::get('/api/users/{user_id}', [app\controller\UserController::class, 'show']); // 查询用户
|
||||
Route::put('/api/users/{user_id}', [app\controller\UserController::class, 'update']); // 更新用户
|
||||
Route::delete('/api/users/{user_id}', [app\controller\UserController::class, 'destroy']); // 删除用户
|
||||
Route::get('/api/users/{user_id}/decrypt-id-card', [app\controller\UserController::class, 'decryptIdCard']); // 解密身份证
|
||||
Route::post('/api/users/search', [app\controller\UserController::class, 'search']); // 搜索用户(复杂查询)
|
||||
|
||||
// ============================================
|
||||
// 用户标签相关接口(RESTful)
|
||||
// ============================================
|
||||
Route::get('/api/users/{user_id}/tags', [app\controller\TagController::class, 'listByUser']); // 查询用户标签
|
||||
Route::put('/api/users/{user_id}/tags', [app\controller\TagController::class, 'calculate']); // 更新/计算用户标签
|
||||
Route::delete('/api/users/{user_id}/tags/{tag_id}', [app\controller\TagController::class, 'destroy']); // 删除用户标签
|
||||
|
||||
// ============================================
|
||||
// 消费记录相关接口
|
||||
// ============================================
|
||||
Route::post('/api/consumption/record', [app\controller\ConsumptionController::class, 'store']); // 创建消费记录
|
||||
|
||||
// ============================================
|
||||
// 标签定义相关接口(管理接口)
|
||||
// ============================================
|
||||
Route::post('/api/tags/filter', [app\controller\TagController::class, 'filter']); // 根据标签筛选用户
|
||||
Route::get('/api/tags/statistics', [app\controller\TagController::class, 'statistics']); // 获取标签统计信息
|
||||
Route::get('/api/tags/history', [app\controller\TagController::class, 'history']); // 获取标签历史记录
|
||||
Route::post('/api/tag-definitions/batch', [app\controller\TagController::class, 'init']); // 批量初始化标签定义
|
||||
Route::get('/api/tag-definitions', [app\controller\TagDefinitionController::class, 'list']); // 获取标签定义列表
|
||||
Route::post('/api/tag-definitions', [app\controller\TagDefinitionController::class, 'create']); // 创建标签定义
|
||||
Route::get('/api/tag-definitions/{tag_id}', [app\controller\TagDefinitionController::class, 'detail']); // 获取标签定义详情
|
||||
Route::put('/api/tag-definitions/{tag_id}', [app\controller\TagDefinitionController::class, 'update']); // 更新标签定义
|
||||
Route::delete('/api/tag-definitions/{tag_id}', [app\controller\TagDefinitionController::class, 'delete']); // 删除标签定义
|
||||
|
||||
// ============================================
|
||||
// 标签任务管理接口
|
||||
// ============================================
|
||||
Route::post('/api/tag-tasks', [app\controller\TagTaskController::class, 'create']); // 创建标签任务
|
||||
Route::put('/api/tag-tasks/{task_id}', [app\controller\TagTaskController::class, 'update']); // 更新标签任务
|
||||
Route::delete('/api/tag-tasks/{task_id}', [app\controller\TagTaskController::class, 'delete']); // 删除标签任务
|
||||
Route::get('/api/tag-tasks', [app\controller\TagTaskController::class, 'list']); // 标签任务列表
|
||||
Route::get('/api/tag-tasks/{task_id}', [app\controller\TagTaskController::class, 'detail']); // 标签任务详情
|
||||
Route::get('/api/tag-tasks/{task_id}/executions', [app\controller\TagTaskController::class, 'executions']); // 获取任务执行记录
|
||||
Route::post('/api/tag-tasks/{task_id}/start', [app\controller\TagTaskController::class, 'start']); // 启动标签任务
|
||||
Route::post('/api/tag-tasks/{task_id}/pause', [app\controller\TagTaskController::class, 'pause']); // 暂停标签任务
|
||||
Route::post('/api/tag-tasks/{task_id}/stop', [app\controller\TagTaskController::class, 'stop']); // 停止标签任务
|
||||
|
||||
// ============================================
|
||||
// 身份合并相关接口(场景4:手机号发现身份证后合并)
|
||||
// ============================================
|
||||
Route::post('/api/person-merge/phone-to-id-card', [app\controller\PersonMergeController::class, 'mergePhoneToIdCard']); // 合并手机号到身份证
|
||||
Route::post('/api/person-merge/temporary-to-formal', [app\controller\PersonMergeController::class, 'mergeTemporaryToFormal']); // 合并临时人到正式人
|
||||
|
||||
// ============================================
|
||||
// 数据库同步相关接口
|
||||
// ============================================
|
||||
Route::get('/database-sync/dashboard', [app\controller\DatabaseSyncController::class, 'dashboard']); // 同步进度看板页面
|
||||
Route::get('/api/database-sync/progress', [app\controller\DatabaseSyncController::class, 'progress']); // 查询同步进度
|
||||
Route::get('/api/database-sync/stats', [app\controller\DatabaseSyncController::class, 'stats']); // 查询同步统计
|
||||
Route::post('/api/database-sync/reset', [app\controller\DatabaseSyncController::class, 'reset']); // 重置同步进度
|
||||
Route::post('/api/database-sync/skip-error', [app\controller\DatabaseSyncController::class, 'skipError']); // 跳过错误数据库
|
||||
|
||||
// ============================================
|
||||
// 数据采集任务管理接口
|
||||
// ============================================
|
||||
Route::post('/api/data-collection-tasks', [app\controller\DataCollectionTaskController::class, 'create']); // 创建任务
|
||||
Route::put('/api/data-collection-tasks/{task_id}', [app\controller\DataCollectionTaskController::class, 'update']); // 更新任务
|
||||
Route::delete('/api/data-collection-tasks/{task_id}', [app\controller\DataCollectionTaskController::class, 'delete']); // 删除任务
|
||||
Route::get('/api/data-collection-tasks', [app\controller\DataCollectionTaskController::class, 'list']); // 任务列表
|
||||
Route::get('/api/data-collection-tasks/data-sources', [app\controller\DataCollectionTaskController::class, 'getDataSources']); // 获取数据源列表
|
||||
Route::get('/api/data-collection-tasks/{task_id}', [app\controller\DataCollectionTaskController::class, 'detail']); // 任务详情
|
||||
Route::get('/api/data-collection-tasks/{task_id}/progress', [app\controller\DataCollectionTaskController::class, 'progress']); // 任务进度
|
||||
Route::post('/api/data-collection-tasks/{task_id}/start', [app\controller\DataCollectionTaskController::class, 'start']); // 启动任务
|
||||
Route::post('/api/data-collection-tasks/{task_id}/pause', [app\controller\DataCollectionTaskController::class, 'pause']); // 暂停任务
|
||||
Route::post('/api/data-collection-tasks/{task_id}/stop', [app\controller\DataCollectionTaskController::class, 'stop']); // 停止任务
|
||||
Route::get('/api/data-collection-tasks/data-sources/{data_source_id}/databases', [app\controller\DataCollectionTaskController::class, 'getDatabases']); // 获取数据库列表
|
||||
Route::get('/api/data-collection-tasks/data-sources/{data_source_id}/databases/{database}/collections', [app\controller\DataCollectionTaskController::class, 'getCollections']); // 获取集合列表
|
||||
Route::get('/api/data-collection-tasks/data-sources/{data_source_id}/databases/{database}/collections/{collection}/fields', [app\controller\DataCollectionTaskController::class, 'getFields']); // 获取字段列表
|
||||
Route::get('/api/data-collection-tasks/handlers/{handler_type}/target-fields', [app\controller\DataCollectionTaskController::class, 'getHandlerTargetFields']); // 获取Handler的目标字段列表
|
||||
Route::post('/api/data-collection-tasks/preview-query', [app\controller\DataCollectionTaskController::class, 'previewQuery']); // 预览查询结果
|
||||
|
||||
// ============================================
|
||||
// 数据源管理接口
|
||||
// ============================================
|
||||
Route::get('/api/data-sources', [app\controller\DataSourceController::class, 'list']); // 获取数据源列表
|
||||
Route::get('/api/data-sources/{data_source_id}', [app\controller\DataSourceController::class, 'detail']); // 获取数据源详情
|
||||
Route::post('/api/data-sources', [app\controller\DataSourceController::class, 'create']); // 创建数据源
|
||||
Route::put('/api/data-sources/{data_source_id}', [app\controller\DataSourceController::class, 'update']); // 更新数据源
|
||||
Route::delete('/api/data-sources/{data_source_id}', [app\controller\DataSourceController::class, 'delete']); // 删除数据源
|
||||
Route::post('/api/data-sources/test-connection', [app\controller\DataSourceController::class, 'testConnection']); // 测试数据源连接
|
||||
|
||||
// ============================================
|
||||
// 人群快照相关接口
|
||||
// ============================================
|
||||
Route::get('/api/tag-cohorts', [app\controller\TagCohortController::class, 'list']); // 获取人群快照列表
|
||||
Route::get('/api/tag-cohorts/{cohort_id}', [app\controller\TagCohortController::class, 'detail']); // 获取人群快照详情
|
||||
Route::post('/api/tag-cohorts', [app\controller\TagCohortController::class, 'create']); // 创建人群快照
|
||||
Route::delete('/api/tag-cohorts/{cohort_id}', [app\controller\TagCohortController::class, 'delete']); // 删除人群快照
|
||||
Route::post('/api/tag-cohorts/{cohort_id}/export', [app\controller\TagCohortController::class, 'export']); // 导出人群快照
|
||||
54
Moncter/env.txt
Normal file
54
Moncter/env.txt
Normal file
@@ -0,0 +1,54 @@
|
||||
# ============================================
|
||||
# 加密配置
|
||||
# ============================================
|
||||
|
||||
# AES 加密密钥(至少32字符,建议使用随机生成的强密钥)
|
||||
# 生产环境请务必修改此密钥,并妥善保管
|
||||
ENCRYPTION_AES_KEY=your-32-byte-secret-key-here-12345678
|
||||
|
||||
# 哈希盐值(用于身份证哈希,增强安全性)
|
||||
# 生产环境请务必修改此盐值
|
||||
ENCRYPTION_HASH_SALT=your-hash-salt-here-change-in-production
|
||||
|
||||
# ============================================
|
||||
# 应用配置
|
||||
# ============================================
|
||||
|
||||
# 应用环境(development/production)
|
||||
APP_ENV=development
|
||||
|
||||
# 应用调试模式(true/false)
|
||||
APP_DEBUG=true
|
||||
|
||||
|
||||
# ============================================
|
||||
# 以下为:超级主机资源数据库
|
||||
# ============================================
|
||||
#主机标签数据库
|
||||
|
||||
TAG_MONGODB_DRIVER = "mongodb"
|
||||
TAG_MONGODB_DNS = mongodb://192.168.1.106:27017
|
||||
TAG_MONGODB_DATABASE = ckb
|
||||
TAG_MONGODB_USER = ckb
|
||||
TAG_MONGODB_AUTH = ckb
|
||||
TAG_MONGODB_PASSWORD = 123456
|
||||
|
||||
#主机同步KR数据库
|
||||
SYNC_MONGODB_HOST = 192.168.1.106
|
||||
SYNC_MONGODB_PORT = 27017
|
||||
SYNC_MONGODB_AUTH = KR
|
||||
SYNC_MONGODB_USER = KR
|
||||
SYNC_MONGODB_PASS = 123456
|
||||
|
||||
# ============================================
|
||||
# 以下为:爬虫抓取的业务数据库
|
||||
# ============================================
|
||||
|
||||
#卡若的数据库
|
||||
KR_MONGODB_HOST = 192.168.2.8
|
||||
KR_MONGODB_PORT = 27017
|
||||
KR_MONGODB_DATABASE = admin
|
||||
KR_MONGODB_USER = admin
|
||||
KR_MONGODB_PASSWORD = key123456
|
||||
KR_MONGODB_AUTH_SOURCE=admin
|
||||
|
||||
@@ -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
|
||||
|
||||
38
Moncter/start_debug.sh
Normal file
38
Moncter/start_debug.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 数据采集任务调试启动脚本(显示实时日志)
|
||||
# 使用方法: chmod +x start_debug.sh && ./start_debug.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "=================================================="
|
||||
echo " 数据采集任务 - 调试模式启动"
|
||||
echo " 实时显示所有日志输出"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
# 检查 PHP
|
||||
if ! command -v php &> /dev/null; then
|
||||
echo "❌ 错误: 未找到 PHP,请先安装 PHP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ PHP 版本: $(php -v | head -n 1)"
|
||||
echo ""
|
||||
|
||||
# 停止已有进程
|
||||
if [ -f "runtime/webman.pid" ]; then
|
||||
echo "🛑 停止已运行的进程..."
|
||||
php start.php stop
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
# 以调试模式启动(不使用 daemon 模式,输出到终端)
|
||||
echo "🚀 启动 Workerman(调试模式)..."
|
||||
echo " 提示: 按 Ctrl+C 停止"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
# 使用 start 而不是 start -d(daemon),这样输出会显示在终端
|
||||
php start.php start
|
||||
|
||||
@@ -29,9 +29,17 @@ if (empty(Worker::$eventLoopClass)) {
|
||||
}
|
||||
|
||||
set_error_handler(function ($level, $message, $file = '', $line = 0) {
|
||||
// 忽略 MongoDB Laravel 的废弃警告(E_USER_DEPRECATED = 16384)
|
||||
// 这些警告不影响功能,只是提示使用新的API
|
||||
if ($level === E_USER_DEPRECATED && strpos($message, 'Using "$collection" property is deprecated') !== false) {
|
||||
return true; // 忽略此警告
|
||||
}
|
||||
|
||||
if (error_reporting() & $level) {
|
||||
throw new ErrorException($message, 0, $level, $file, $line);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if ($worker) {
|
||||
|
||||
2492
Moncter/yarn.lock
Normal file
2492
Moncter/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
164
Moncter/提示词/106服务器消费记录采集表分析.md
Normal file
164
Moncter/提示词/106服务器消费记录采集表分析.md
Normal 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. 保存并启动采集任务
|
||||
|
||||
319
Moncter/提示词/QueryBuilder折叠功能说明.md
Normal file
319
Moncter/提示词/QueryBuilder折叠功能说明.md
Normal 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
|
||||
**更新内容**:添加折叠功能,优化界面布局
|
||||
311
Moncter/提示词/可视化查询构建器使用说明.md
Normal file
311
Moncter/提示词/可视化查询构建器使用说明.md
Normal 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
|
||||
425
Moncter/提示词/多集合模式使用说明.md
Normal file
425
Moncter/提示词/多集合模式使用说明.md
Normal 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-202111(10个月)
|
||||
|
||||
---
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 前端数据结构
|
||||
|
||||
```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
|
||||
**状态**:前端已实现,后端待开发
|
||||
491
Moncter/提示词/字段定义配置管理方案对比.md
Normal file
491
Moncter/提示词/字段定义配置管理方案对比.md
Normal 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
|
||||
330
Moncter/提示词/当前架构设计/人物主表生成逻辑说明.md
Normal file
330
Moncter/提示词/当前架构设计/人物主表生成逻辑说明.md
Normal 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到用户A(PersonMergeService.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(正式用户),手机号未关联
|
||||
```
|
||||
→ 建立手机关联到用户A(effective_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. 创建临时用户A(UUID-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. 创建新用户B(UUID-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
|
||||
407
Moncter/提示词/当前架构设计/前端代码风格和设计思路.md
Normal file
407
Moncter/提示词/当前架构设计/前端代码风格和设计思路.md
Normal 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. **代码注释**:关键逻辑添加注释,提高代码可读性
|
||||
|
||||
666
Moncter/提示词/当前架构设计/前端功能说明.md
Normal file
666
Moncter/提示词/当前架构设计/前端功能说明.md
Normal 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 类型定义
|
||||
- 建议启用严格模式以获得更好的类型检查
|
||||
|
||||
693
Moncter/提示词/当前架构设计/前端已对接API说明.md
Normal file
693
Moncter/提示词/当前架构设计/前端已对接API说明.md
Normal 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)会在拦截器中提示
|
||||
- 组件中只需处理业务逻辑,无需重复处理错误提示
|
||||
|
||||
1089
Moncter/提示词/当前架构设计/数据采集业务逻辑.md
Normal file
1089
Moncter/提示词/当前架构设计/数据采集业务逻辑.md
Normal file
File diff suppressed because it is too large
Load Diff
884
Moncter/提示词/当前架构设计/标签引擎相关功能流程逻辑.md
Normal file
884
Moncter/提示词/当前架构设计/标签引擎相关功能流程逻辑.md
Normal 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, // 标签ID(UUID)
|
||||
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, // 任务ID(UUID)
|
||||
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 连接
|
||||
├─→ 建立连接
|
||||
├─→ 声明队列
|
||||
├─→ 设置 QoS(prefetch_count = 1)
|
||||
└─→ 开始消费消息
|
||||
↓
|
||||
监听消息(每 0.1 秒检查一次)
|
||||
↓
|
||||
收到消息
|
||||
↓
|
||||
processMessage()
|
||||
├─→ 解析消息(JSON)
|
||||
├─→ 验证必要字段(user_id)
|
||||
├─→ 创建 TagService 实例
|
||||
├─→ 执行标签计算
|
||||
│ └─→ TagService->calculateTags(userId, tagIds)
|
||||
├─→ 记录日志和性能指标
|
||||
└─→ 确认消息(ACK)
|
||||
├─→ 成功:message->ack()
|
||||
└─→ 失败:
|
||||
├─→ 业务错误(InvalidArgumentException):ack(不重试)
|
||||
└─→ 系统错误: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_profile(total_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
|
||||
**项目版本**:基于当前代码库分析
|
||||
|
||||
278
Moncter/提示词/当前架构设计/采集进度100%停止机制分析报告.md
Normal file
278
Moncter/提示词/当前架构设计/采集进度100%停止机制分析报告.md
Normal 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%后停止。
|
||||
|
||||
682
Moncter/提示词/数据列表动态打标签实现方案.md
Normal file
682
Moncter/提示词/数据列表动态打标签实现方案.md
Normal file
@@ -0,0 +1,682 @@
|
||||
# 数据列表动态打标签实现方案
|
||||
|
||||
## 一、核心思路
|
||||
|
||||
将SQL/MongoDB查询配置保存到数据表中,标签任务执行时从配置表读取查询配置,动态执行查询并打标签。
|
||||
|
||||
---
|
||||
|
||||
## 二、数据表设计
|
||||
|
||||
### 2.1 数据列表配置表(tag_data_lists)
|
||||
|
||||
```javascript
|
||||
{
|
||||
list_id: String, // 列表ID(UUID)
|
||||
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
|
||||
**实现方案**:基于数据列表动态打标签
|
||||
417
Moncter/提示词/数据列表管理_真实API接入说明.md
Normal file
417
Moncter/提示词/数据列表管理_真实API接入说明.md
Normal 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
|
||||
327
Moncter/提示词/数据列表管理界面使用说明.md
Normal file
327
Moncter/提示词/数据列表管理界面使用说明.md
Normal 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 - 业务逻辑层
|
||||
- ⏳ 数据源相关API(databases、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数据
|
||||
454
Moncter/提示词/最新架构逻辑.md
Normal file
454
Moncter/提示词/最新架构逻辑.md
Normal 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`:记录ID(UUID)
|
||||
- `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`:任务ID(UUID)
|
||||
- `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
|
||||
**维护说明**:本文档作为系统架构的核心记忆点,如有架构变更,请及时更新本文档
|
||||
294
Moncter/提示词/标签定义逻辑对比分析.md
Normal file
294
Moncter/提示词/标签定义逻辑对比分析.md
Normal 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
|
||||
**基于代码版本**:当前代码库分析
|
||||
68
Moncter/提示词/标签定逻辑.md
Normal file
68
Moncter/提示词/标签定逻辑.md
Normal 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、根据定义标签的规则条件,直接根据用户表遍历过去进行打标签,然后将标签存储。
|
||||
|
||||
737
Moncter/提示词/标签定逻辑真实.md
Normal file
737
Moncter/提示词/标签定逻辑真实.md
Normal 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, // 标签ID(UUID)
|
||||
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, // 列表ID(UUID)
|
||||
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
|
||||
**基于需求**:按照原始思路完善
|
||||
342
Moncter/提示词/集合筛选功能使用技巧.md
Normal file
342
Moncter/提示词/集合筛选功能使用技巧.md
Normal 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-202112(10个月)
|
||||
```
|
||||
|
||||
### 技巧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+
|
||||
858
Moncter/提示词/项目完整代码逻辑分析报告.md
Normal file
858
Moncter/提示词/项目完整代码逻辑分析报告.md
Normal 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
|
||||
**项目版本**: 基于当前代码库分析
|
||||
|
||||
Reference in New Issue
Block a user