diff --git a/Moncter/.env.example b/Moncter/.env.example
new file mode 100644
index 00000000..7ea7304e
--- /dev/null
+++ b/Moncter/.env.example
@@ -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
\ No newline at end of file
diff --git a/Moncter/MCP/mcp.json b/Moncter/MCP/mcp.json
index 2decb6d2..f44cf4b9 100644
--- a/Moncter/MCP/mcp.json
+++ b/Moncter/MCP/mcp.json
@@ -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"
}
}
- }
\ No newline at end of file
+ }
+}
\ No newline at end of file
diff --git a/Moncter/TaskShow/v0.39.1.tar.gz b/Moncter/TaskShow/v0.39.1.tar.gz
new file mode 100644
index 00000000..d5513603
Binary files /dev/null and b/Moncter/TaskShow/v0.39.1.tar.gz differ
diff --git a/Moncter/app/controller/IndexController.php b/Moncter/app/controller/IndexController.php
index 7d904faa..688d17ba 100644
--- a/Moncter/app/controller/IndexController.php
+++ b/Moncter/app/controller/IndexController.php
@@ -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);
+ }
+
}
diff --git a/Moncter/app/controller/UserController.php b/Moncter/app/controller/UserController.php
index b23f213c..9835c82a 100644
--- a/Moncter/app/controller/UserController.php
+++ b/Moncter/app/controller/UserController.php
@@ -1,16 +1,557 @@
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);
+ }
}
-}
\ No newline at end of file
+
+ /**
+ * 查询用户信息
+ *
+ * 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);
+ }
+ }
+}
diff --git a/Moncter/composer.json b/Moncter/composer.json
index 82eaf4f0..fcc1e8a5 100644
--- a/Moncter/composer.json
+++ b/Moncter/composer.json
@@ -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. "
diff --git a/Moncter/composer.lock b/Moncter/composer.lock
index 3ece18e4..bc535e33 100644
--- a/Moncter/composer.lock
+++ b/Moncter/composer.lock
@@ -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",
diff --git a/Moncter/config/autoload.php b/Moncter/config/autoload.php
index 69a8135e..8d207b5c 100644
--- a/Moncter/config/autoload.php
+++ b/Moncter/config/autoload.php
@@ -14,7 +14,6 @@
return [
'files' => [
- base_path() . '/app/functions.php',
base_path() . '/support/Request.php',
base_path() . '/support/Response.php',
]
diff --git a/Moncter/config/bootstrap.php b/Moncter/config/bootstrap.php
index 95d2e87e..63e07bd0 100644
--- a/Moncter/config/bootstrap.php
+++ b/Moncter/config/bootstrap.php
@@ -14,4 +14,5 @@
return [
support\bootstrap\Session::class,
+ support\bootstrap\MongoDB::class,
];
diff --git a/Moncter/config/database.php b/Moncter/config/database.php
index fd7cd4b5..630702e9 100644
--- a/Moncter/config/database.php
+++ b/Moncter/config/database.php
@@ -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', // 认证机制(默认推荐)
],
],
diff --git a/Moncter/config/log.php b/Moncter/config/log.php
index 7f05de57..817e13cf 100644
--- a/Moncter/config/log.php
+++ b/Moncter/config/log.php
@@ -28,5 +28,10 @@ return [
],
]
],
+ 'processors' => [
+ [
+ 'class' => app\utils\LogMaskingProcessor::class,
+ ],
+ ],
],
];
diff --git a/Moncter/config/process.php b/Moncter/config/process.php
index 892dc826..f987b577 100644
--- a/Moncter/config/process.php
+++ b/Moncter/config/process.php
@@ -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,
+ ],
+
];
diff --git a/Moncter/config/route.php b/Moncter/config/route.php
index a5064fca..3a66a3ec 100644
--- a/Moncter/config/route.php
+++ b/Moncter/config/route.php
@@ -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']); // 导出人群快照
\ No newline at end of file
diff --git a/Moncter/env.txt b/Moncter/env.txt
new file mode 100644
index 00000000..af53e480
--- /dev/null
+++ b/Moncter/env.txt
@@ -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
+
diff --git a/Moncter/runtime/logs/workerman.log b/Moncter/runtime/logs/workerman.log
index 9f4c1a7e..36fd4faa 100644
--- a/Moncter/runtime/logs/workerman.log
+++ b/Moncter/runtime/logs/workerman.log
@@ -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
diff --git a/Moncter/start_debug.sh b/Moncter/start_debug.sh
new file mode 100644
index 00000000..971f0566
--- /dev/null
+++ b/Moncter/start_debug.sh
@@ -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
+
diff --git a/Moncter/support/bootstrap.php b/Moncter/support/bootstrap.php
index d913defd..92ce480c 100644
--- a/Moncter/support/bootstrap.php
+++ b/Moncter/support/bootstrap.php
@@ -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) {
diff --git a/Moncter/yarn.lock b/Moncter/yarn.lock
new file mode 100644
index 00000000..157d8630
--- /dev/null
+++ b/Moncter/yarn.lock
@@ -0,0 +1,2492 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ai-sdk/gateway@2.0.18":
+ version "2.0.18"
+ resolved "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.18.tgz"
+ integrity sha512-sDQcW+6ck2m0pTIHW6BPHD7S125WD3qNkx/B8sEzJp/hurocmJ5Cni0ybExg6sQMGo+fr/GWOwpHF1cmCdg5rQ==
+ dependencies:
+ "@ai-sdk/provider" "2.0.0"
+ "@ai-sdk/provider-utils" "3.0.18"
+ "@vercel/oidc" "3.0.5"
+
+"@ai-sdk/provider-utils@^3.0.0", "@ai-sdk/provider-utils@3.0.18":
+ version "3.0.18"
+ resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.18.tgz"
+ integrity sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==
+ dependencies:
+ "@ai-sdk/provider" "2.0.0"
+ "@standard-schema/spec" "^1.0.0"
+ eventsource-parser "^3.0.6"
+
+"@ai-sdk/provider@^2.0.0", "@ai-sdk/provider@2.0.0":
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz"
+ integrity sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==
+ dependencies:
+ json-schema "^0.4.0"
+
+"@modelcontextprotocol/sdk@^1.24.2":
+ version "1.24.3"
+ resolved "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.24.3.tgz"
+ integrity sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw==
+ dependencies:
+ ajv "^8.17.1"
+ ajv-formats "^3.0.1"
+ content-type "^1.0.5"
+ cors "^2.8.5"
+ cross-spawn "^7.0.5"
+ eventsource "^3.0.2"
+ eventsource-parser "^3.0.0"
+ express "^5.0.1"
+ express-rate-limit "^7.5.0"
+ jose "^6.1.1"
+ pkce-challenge "^5.0.0"
+ raw-body "^3.0.0"
+ zod "^3.25 || ^4.0"
+ zod-to-json-schema "^3.25.0"
+
+"@mongodb-js/atlas-local-win32-x64-msvc@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@mongodb-js/atlas-local-win32-x64-msvc/-/atlas-local-win32-x64-msvc-1.1.0.tgz"
+ integrity sha512-K+kGoGfIPqv6JstlzmnbkOOJ6/0cTH4bjRbEApmvscnTDBk1c9eO8nXORPQWFfKnMMxo+/9GQESvaxC05MhwBQ==
+
+"@mongodb-js/atlas-local@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@mongodb-js/atlas-local/-/atlas-local-1.1.0.tgz"
+ integrity sha512-ZGvBy8PdqNgcfZPaElSrmc3ItafSbodowipgQkJN65jKuAM8owETpMc8/ASxHTlI91y4sOll1V5WQR0FkqDHjA==
+ optionalDependencies:
+ "@mongodb-js/atlas-local-darwin-arm64" "1.1.0"
+ "@mongodb-js/atlas-local-darwin-x64" "1.1.0"
+ "@mongodb-js/atlas-local-linux-arm64-gnu" "1.1.0"
+ "@mongodb-js/atlas-local-linux-x64-gnu" "1.1.0"
+ "@mongodb-js/atlas-local-win32-x64-msvc" "1.1.0"
+
+"@mongodb-js/device-id@^0.3.1":
+ version "0.3.3"
+ resolved "https://registry.npmjs.org/@mongodb-js/device-id/-/device-id-0.3.3.tgz"
+ integrity sha512-y2SIriQec/HvIzFI1QCJAmjedShwbgmgUWkh3+qPgT2OyRKWE0yB7Lii/jANCtUNqqwBcUsMHY9/mPnuu5wfPQ==
+
+"@mongodb-js/devtools-connect@^3.9.4":
+ version "3.12.0"
+ resolved "https://registry.npmjs.org/@mongodb-js/devtools-connect/-/devtools-connect-3.12.0.tgz"
+ integrity sha512-/aiGAKE5k6y1noI6hFo3pkLarNCNjEn+J3iqWTAMBuX4SpKUWsDdpMAyyxkqou7qH97gvon4A7wQafWFgWTXvA==
+ dependencies:
+ "@mongodb-js/devtools-proxy-support" "^0.5.5"
+ "@mongodb-js/oidc-http-server-pages" "1.1.8"
+ lodash.merge "^4.6.2"
+ mongodb-connection-string-url "^3.0.0"
+ socks "^2.7.3"
+ optionalDependencies:
+ kerberos "^2.1.0"
+ mongodb-client-encryption "^6.1.0"
+ os-dns-native "^1.2.0"
+ resolve-mongodb-srv "^1.1.1"
+
+"@mongodb-js/devtools-proxy-support@^0.5.3", "@mongodb-js/devtools-proxy-support@^0.5.5":
+ version "0.5.5"
+ resolved "https://registry.npmjs.org/@mongodb-js/devtools-proxy-support/-/devtools-proxy-support-0.5.5.tgz"
+ integrity sha512-yrz6rCMgq+52SL34O16mvZsGeQs+Gq1rRmJiHxj/Ca6LNvJo+MhDHvISSwoiObl1hWdcnDz1qz5HyaxMzpkETg==
+ dependencies:
+ "@mongodb-js/socksv5" "^0.0.10"
+ agent-base "^7.1.1"
+ debug "^4.4.0"
+ http-proxy-agent "^7.0.2"
+ https-proxy-agent "^7.0.5"
+ lru-cache "^11.0.0"
+ node-fetch "^3.3.2"
+ pac-proxy-agent "^7.0.2"
+ socks-proxy-agent "^8.0.4"
+ ssh2 "^1.15.0"
+ system-ca "^2.0.1"
+
+"@mongodb-js/oidc-http-server-pages@1.1.8":
+ version "1.1.8"
+ resolved "https://registry.npmjs.org/@mongodb-js/oidc-http-server-pages/-/oidc-http-server-pages-1.1.8.tgz"
+ integrity sha512-qQjV6TTozJa3jMfsmajhCCzESnsTGa3rQwxGbC97GIplaF2v3jieLfDt9O/F6X3jLm32HzAEWHFEvKu74h2wxw==
+
+"@mongodb-js/oidc-plugin@^2.0.0", "@mongodb-js/oidc-plugin@^2.0.5":
+ version "2.0.6"
+ resolved "https://registry.npmjs.org/@mongodb-js/oidc-plugin/-/oidc-plugin-2.0.6.tgz"
+ integrity sha512-Ma38DqIKTddMQhKrHwx/T+4nFvrTAy5RVWjDQzHv4W9W/lwnZqJWxayDDKwrrd+Gg1xuj5o4GVbkr3A5hI0f1w==
+ dependencies:
+ express "^5.1.0"
+ node-fetch "^3.3.2"
+ open "^10.1.2"
+ openid-client "^6.6.3"
+
+"@mongodb-js/saslprep@^1.3.0":
+ version "1.3.2"
+ resolved "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz"
+ integrity sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==
+ dependencies:
+ sparse-bitfield "^3.0.3"
+
+"@mongodb-js/socksv5@^0.0.10":
+ version "0.0.10"
+ resolved "https://registry.npmjs.org/@mongodb-js/socksv5/-/socksv5-0.0.10.tgz"
+ integrity sha512-JDz2fLKsjMiSNUxKrCpGptsgu7DzsXfu4gnUQ3RhUaBS1d4YbLrt6HejpckAiHIAa+niBpZAeiUsoop0IihWsw==
+ dependencies:
+ ip-address "^9.0.5"
+
+"@mongosh/arg-parser@^3.23.0":
+ version "3.23.0"
+ resolved "https://registry.npmjs.org/@mongosh/arg-parser/-/arg-parser-3.23.0.tgz"
+ integrity sha512-V9lr8LEHI9XKEgBEqPAVDi+CNdl6MNBSJ8A5LAk+vfBM+bdhhtSrANIqUhuEzyV4yNyEqNfYFtqLLhjZd7/doA==
+ dependencies:
+ "@mongosh/errors" "2.4.5"
+ "@mongosh/i18n" "^2.20.0"
+ mongodb-connection-string-url "^3.0.2"
+ yargs-parser "^20.2.4"
+
+"@mongosh/errors@^2.4.5", "@mongosh/errors@2.4.5":
+ version "2.4.5"
+ resolved "https://registry.npmjs.org/@mongosh/errors/-/errors-2.4.5.tgz"
+ integrity sha512-niqLgzPv6ZG9Bx0XRJP3NCA9zZM6LbRs/z05GRwJP0B3HShYDKWi7B0D4N/u6RoEt93Y6mMa9Ok72dNSDpIEwA==
+
+"@mongosh/i18n@^2.20.0":
+ version "2.20.0"
+ resolved "https://registry.npmjs.org/@mongosh/i18n/-/i18n-2.20.0.tgz"
+ integrity sha512-g0zuKuZ5JhS/ASDizZlqLDzy1yqeMbs5tg40TxMl/55rM2EOPV1MSlfBZg7X0tyxUOATy1sLHWsUquzQSFjVjQ==
+ dependencies:
+ "@mongosh/errors" "2.4.5"
+
+"@mongosh/service-provider-core@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@mongosh/service-provider-core/-/service-provider-core-3.7.0.tgz"
+ integrity sha512-a9riCeAkV69UZjFsNmanufMxhqJ3oDbB+Zm5h2DS0YA/cnDarpGOGPEMhi0VmnzcbDK3nmZy6DPOxtx0w4cVZQ==
+ dependencies:
+ "@mongosh/errors" "2.4.5"
+ "@mongosh/shell-bson" "1.1.0"
+ bson "^6.10.4"
+ mongodb "^6.19.0"
+ mongodb-build-info "^1.8.1"
+ mongodb-connection-string-url "^3.0.2"
+
+"@mongosh/service-provider-node-driver@^3.17.5":
+ version "3.18.0"
+ resolved "https://registry.npmjs.org/@mongosh/service-provider-node-driver/-/service-provider-node-driver-3.18.0.tgz"
+ integrity sha512-tQy40wpVd/xskQTXHVLwi7v9olHxFyIqSIF06kpl/q4BPPlKxI0jCA5xdy+lk+tvDMJ80Pams0RUonRjNulVzQ==
+ dependencies:
+ "@mongodb-js/devtools-connect" "^3.9.4"
+ "@mongodb-js/oidc-plugin" "^2.0.5"
+ "@mongosh/errors" "2.4.5"
+ "@mongosh/service-provider-core" "3.7.0"
+ "@mongosh/types" "^3.14.1"
+ aws4 "^1.12.0"
+ mongodb "^6.19.0"
+ mongodb-build-info "^1.8.1"
+ mongodb-connection-string-url "^3.0.2"
+ socks "^2.8.3"
+ optionalDependencies:
+ kerberos "2.1.0"
+ mongodb-client-encryption "^6.5.0"
+
+"@mongosh/shell-bson@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@mongosh/shell-bson/-/shell-bson-1.1.0.tgz"
+ integrity sha512-eSkEvikWNPiFghiOx0puWqP28nXs/4dIHxIjbqORGEmDy/DaJXxGydYQKiUL2pebQAudoSQnzsFKyb0WQvypxw==
+ dependencies:
+ "@mongosh/errors" "^2.4.5"
+
+"@mongosh/types@^3.14.1":
+ version "3.14.1"
+ resolved "https://registry.npmjs.org/@mongosh/types/-/types-3.14.1.tgz"
+ integrity sha512-jX3Z/EmKmwPiHYSSALxAjeBHXGL1XMkEBOz0CCeE1dexCyMTyGf9H78cn+emBAVuyGQHhfo5WhyQhycNtDUZAQ==
+ dependencies:
+ "@mongodb-js/devtools-connect" "^3.9.4"
+
+"@opentelemetry/api@1.9.0":
+ version "1.9.0"
+ resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz"
+ integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==
+
+"@standard-schema/spec@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz"
+ integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==
+
+"@tootallnate/quickjs-emscripten@^0.23.0":
+ version "0.23.0"
+ resolved "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz"
+ integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==
+
+"@types/webidl-conversions@*":
+ version "7.0.3"
+ resolved "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz"
+ integrity sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==
+
+"@types/whatwg-url@^11.0.2":
+ version "11.0.5"
+ resolved "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz"
+ integrity sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==
+ dependencies:
+ "@types/webidl-conversions" "*"
+
+"@vercel/oidc@3.0.5":
+ version "3.0.5"
+ resolved "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.5.tgz"
+ integrity sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==
+
+accepts@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz"
+ integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==
+ dependencies:
+ mime-types "^3.0.0"
+ negotiator "^1.0.0"
+
+agent-base@^7.1.0, agent-base@^7.1.1, agent-base@^7.1.2:
+ version "7.1.4"
+ resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz"
+ integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
+
+ai@^5.0.72:
+ version "5.0.107"
+ resolved "https://registry.npmjs.org/ai/-/ai-5.0.107.tgz"
+ integrity sha512-laZlS9ZC/DZfSaxPgrBqI4mM+kxRvTPBBQfa74ceBFskkunZKEsaGVFNEs4cfyGa3nCCCl1WO/fjxixp4V8Zag==
+ dependencies:
+ "@ai-sdk/gateway" "2.0.18"
+ "@ai-sdk/provider" "2.0.0"
+ "@ai-sdk/provider-utils" "3.0.18"
+ "@opentelemetry/api" "1.9.0"
+
+ajv-formats@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz"
+ integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==
+ dependencies:
+ ajv "^8.0.0"
+
+ajv@^8.0.0, ajv@^8.17.1:
+ version "8.17.1"
+ resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz"
+ integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+ fast-uri "^3.0.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-styles@^4.0.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz"
+ integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==
+ dependencies:
+ call-bound "^1.0.3"
+ is-array-buffer "^3.0.5"
+
+arraybuffer.prototype.slice@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz"
+ integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==
+ dependencies:
+ array-buffer-byte-length "^1.0.1"
+ call-bind "^1.0.8"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.5"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.6"
+ is-array-buffer "^3.0.4"
+
+asn1@^0.2.6:
+ version "0.2.6"
+ resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz"
+ integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
+ dependencies:
+ safer-buffer "~2.1.0"
+
+ast-types@^0.13.4:
+ version "0.13.4"
+ resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz"
+ integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==
+ dependencies:
+ tslib "^2.0.1"
+
+async-function@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz"
+ integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==
+
+available-typed-arrays@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz"
+ integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==
+ dependencies:
+ possible-typed-array-names "^1.0.0"
+
+aws4@^1.12.0:
+ version "1.13.2"
+ resolved "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz"
+ integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==
+
+base64-js@^1.3.1:
+ version "1.5.1"
+ resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
+ integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+
+basic-ftp@^5.0.2:
+ version "5.0.5"
+ resolved "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz"
+ integrity sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==
+
+bcrypt-pbkdf@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz"
+ integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==
+ dependencies:
+ tweetnacl "^0.14.3"
+
+bindings@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz"
+ integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
+ dependencies:
+ file-uri-to-path "1.0.0"
+
+bl@^4.0.3:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz"
+ integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
+ dependencies:
+ buffer "^5.5.0"
+ inherits "^2.0.4"
+ readable-stream "^3.4.0"
+
+body-parser@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz"
+ integrity sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==
+ dependencies:
+ bytes "^3.1.2"
+ content-type "^1.0.5"
+ debug "^4.4.3"
+ http-errors "^2.0.0"
+ iconv-lite "^0.7.0"
+ on-finished "^2.4.1"
+ qs "^6.14.0"
+ raw-body "^3.0.1"
+ type-is "^2.0.1"
+
+bson@^6.10.4, "bson@^6.10.4 || ^7.0.0", bson@^6.7.0, bson@6.x:
+ version "6.10.4"
+ resolved "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz"
+ integrity sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==
+
+buffer@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz"
+ integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
+ dependencies:
+ base64-js "^1.3.1"
+ ieee754 "^1.1.13"
+
+bundle-name@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz"
+ integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==
+ dependencies:
+ run-applescript "^7.0.0"
+
+bytes@^3.1.2, bytes@~3.1.2:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz"
+ integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
+call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz"
+ integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+
+call-bind@^1.0.7, call-bind@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz"
+ integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
+ dependencies:
+ call-bind-apply-helpers "^1.0.0"
+ es-define-property "^1.0.0"
+ get-intrinsic "^1.2.4"
+ set-function-length "^1.2.2"
+
+call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz"
+ integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
+ dependencies:
+ call-bind-apply-helpers "^1.0.2"
+ get-intrinsic "^1.3.0"
+
+chownr@^1.1.1:
+ version "1.1.4"
+ resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz"
+ integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
+
+cli-table@^0.3.4:
+ version "0.3.11"
+ resolved "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz"
+ integrity sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==
+ dependencies:
+ colors "1.0.3"
+
+cliui@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz"
+ integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^7.0.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+colors@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz"
+ integrity sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==
+
+content-disposition@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz"
+ integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==
+
+content-type@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz"
+ integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+
+cookie-signature@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz"
+ integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
+
+cookie@^0.7.1:
+ version "0.7.2"
+ resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz"
+ integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
+
+cors@^2.8.5:
+ version "2.8.5"
+ resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz"
+ integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
+ dependencies:
+ object-assign "^4"
+ vary "^1"
+
+cross-spawn@^7.0.5:
+ version "7.0.6"
+ resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz"
+ integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+data-uri-to-buffer@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz"
+ integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
+
+data-uri-to-buffer@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz"
+ integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==
+
+data-view-buffer@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz"
+ integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==
+ dependencies:
+ call-bound "^1.0.3"
+ es-errors "^1.3.0"
+ is-data-view "^1.0.2"
+
+data-view-byte-length@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz"
+ integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==
+ dependencies:
+ call-bound "^1.0.3"
+ es-errors "^1.3.0"
+ is-data-view "^1.0.2"
+
+data-view-byte-offset@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz"
+ integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ is-data-view "^1.0.1"
+
+debug@^4.3.4, debug@^4.3.5, debug@^4.4.0, debug@^4.4.3, debug@4:
+ version "4.4.3"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
+decompress-response@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz"
+ integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+ dependencies:
+ mimic-response "^3.1.0"
+
+deep-extend@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
+default-browser-id@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz"
+ integrity sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==
+
+default-browser@^5.2.1:
+ version "5.4.0"
+ resolved "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz"
+ integrity sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==
+ dependencies:
+ bundle-name "^4.1.0"
+ default-browser-id "^5.0.0"
+
+define-data-property@^1.0.1, define-data-property@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz"
+ integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ gopd "^1.0.1"
+
+define-lazy-prop@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz"
+ integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==
+
+define-properties@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz"
+ integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
+ dependencies:
+ define-data-property "^1.0.1"
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
+
+degenerator@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz"
+ integrity sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==
+ dependencies:
+ ast-types "^0.13.4"
+ escodegen "^2.1.0"
+ esprima "^4.0.1"
+
+depd@^2.0.0, depd@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+detect-libc@^2.0.0:
+ version "2.1.2"
+ resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz"
+ integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
+
+dunder-proto@^1.0.0, dunder-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz"
+ integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
+ dependencies:
+ call-bind-apply-helpers "^1.0.1"
+ es-errors "^1.3.0"
+ gopd "^1.2.0"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+encodeurl@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz"
+ integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
+
+end-of-stream@^1.1.0, end-of-stream@^1.4.1:
+ version "1.4.5"
+ resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz"
+ integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==
+ dependencies:
+ once "^1.4.0"
+
+es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.9:
+ version "1.24.0"
+ resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz"
+ integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==
+ dependencies:
+ array-buffer-byte-length "^1.0.2"
+ arraybuffer.prototype.slice "^1.0.4"
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.8"
+ call-bound "^1.0.4"
+ data-view-buffer "^1.0.2"
+ data-view-byte-length "^1.0.2"
+ data-view-byte-offset "^1.0.1"
+ es-define-property "^1.0.1"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.1.1"
+ es-set-tostringtag "^2.1.0"
+ es-to-primitive "^1.3.0"
+ function.prototype.name "^1.1.8"
+ get-intrinsic "^1.3.0"
+ get-proto "^1.0.1"
+ get-symbol-description "^1.1.0"
+ globalthis "^1.0.4"
+ gopd "^1.2.0"
+ has-property-descriptors "^1.0.2"
+ has-proto "^1.2.0"
+ has-symbols "^1.1.0"
+ hasown "^2.0.2"
+ internal-slot "^1.1.0"
+ is-array-buffer "^3.0.5"
+ is-callable "^1.2.7"
+ is-data-view "^1.0.2"
+ is-negative-zero "^2.0.3"
+ is-regex "^1.2.1"
+ is-set "^2.0.3"
+ is-shared-array-buffer "^1.0.4"
+ is-string "^1.1.1"
+ is-typed-array "^1.1.15"
+ is-weakref "^1.1.1"
+ math-intrinsics "^1.1.0"
+ object-inspect "^1.13.4"
+ object-keys "^1.1.1"
+ object.assign "^4.1.7"
+ own-keys "^1.0.1"
+ regexp.prototype.flags "^1.5.4"
+ safe-array-concat "^1.1.3"
+ safe-push-apply "^1.0.0"
+ safe-regex-test "^1.1.0"
+ set-proto "^1.0.0"
+ stop-iteration-iterator "^1.1.0"
+ string.prototype.trim "^1.2.10"
+ string.prototype.trimend "^1.0.9"
+ string.prototype.trimstart "^1.0.8"
+ typed-array-buffer "^1.0.3"
+ typed-array-byte-length "^1.0.3"
+ typed-array-byte-offset "^1.0.4"
+ typed-array-length "^1.0.7"
+ unbox-primitive "^1.1.0"
+ which-typed-array "^1.1.19"
+
+es-define-property@^1.0.0, es-define-property@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz"
+ integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
+
+es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
+es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz"
+ integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
+ dependencies:
+ es-errors "^1.3.0"
+
+es-set-tostringtag@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz"
+ integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
+ dependencies:
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.6"
+ has-tostringtag "^1.0.2"
+ hasown "^2.0.2"
+
+es-to-primitive@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz"
+ integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==
+ dependencies:
+ is-callable "^1.2.7"
+ is-date-object "^1.0.5"
+ is-symbol "^1.0.4"
+
+escalade@^3.1.1:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
+escape-html@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"
+ integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+
+escodegen@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz"
+ integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==
+ dependencies:
+ esprima "^4.0.1"
+ estraverse "^5.2.0"
+ esutils "^2.0.2"
+ optionalDependencies:
+ source-map "~0.6.1"
+
+esprima@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz"
+ integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
+
+estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+etag@^1.8.1:
+ version "1.8.1"
+ resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz"
+ integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
+
+eventsource-parser@^3.0.0, eventsource-parser@^3.0.1, eventsource-parser@^3.0.6:
+ version "3.0.6"
+ resolved "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz"
+ integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==
+
+eventsource@^3.0.2:
+ version "3.0.7"
+ resolved "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz"
+ integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==
+ dependencies:
+ eventsource-parser "^3.0.1"
+
+expand-template@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz"
+ integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
+
+express-rate-limit@^7.5.0:
+ version "7.5.1"
+ resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz"
+ integrity sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==
+
+express@^5.0.1, express@^5.1.0, "express@>= 4.11":
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/express/-/express-5.2.1.tgz"
+ integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==
+ dependencies:
+ accepts "^2.0.0"
+ body-parser "^2.2.1"
+ content-disposition "^1.0.0"
+ content-type "^1.0.5"
+ cookie "^0.7.1"
+ cookie-signature "^1.2.1"
+ debug "^4.4.0"
+ depd "^2.0.0"
+ encodeurl "^2.0.0"
+ escape-html "^1.0.3"
+ etag "^1.8.1"
+ finalhandler "^2.1.0"
+ fresh "^2.0.0"
+ http-errors "^2.0.0"
+ merge-descriptors "^2.0.0"
+ mime-types "^3.0.0"
+ on-finished "^2.4.1"
+ once "^1.4.0"
+ parseurl "^1.3.3"
+ proxy-addr "^2.0.7"
+ qs "^6.14.0"
+ range-parser "^1.2.1"
+ router "^2.2.0"
+ send "^1.1.0"
+ serve-static "^2.2.0"
+ statuses "^2.0.1"
+ type-is "^2.0.1"
+ vary "^1.1.2"
+
+fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-uri@^3.0.1:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz"
+ integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==
+
+fetch-blob@^3.1.2, fetch-blob@^3.1.4:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz"
+ integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
+ dependencies:
+ node-domexception "^1.0.0"
+ web-streams-polyfill "^3.0.3"
+
+file-uri-to-path@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz"
+ integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
+
+finalhandler@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz"
+ integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==
+ dependencies:
+ debug "^4.4.0"
+ encodeurl "^2.0.0"
+ escape-html "^1.0.3"
+ on-finished "^2.4.1"
+ parseurl "^1.3.3"
+ statuses "^2.0.1"
+
+for-each@^0.3.3, for-each@^0.3.5:
+ version "0.3.5"
+ resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz"
+ integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==
+ dependencies:
+ is-callable "^1.2.7"
+
+formdata-polyfill@^4.0.10:
+ version "4.0.10"
+ resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz"
+ integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
+ dependencies:
+ fetch-blob "^3.1.2"
+
+forwarded@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz"
+ integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
+
+fresh@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz"
+ integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==
+
+fs-constants@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz"
+ integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+function.prototype.name@^1.1.6, function.prototype.name@^1.1.8:
+ version "1.1.8"
+ resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz"
+ integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.3"
+ define-properties "^1.2.1"
+ functions-have-names "^1.2.3"
+ hasown "^2.0.2"
+ is-callable "^1.2.7"
+
+functions-have-names@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz"
+ integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
+
+generator-function@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz"
+ integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
+ integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
+ dependencies:
+ call-bind-apply-helpers "^1.0.2"
+ es-define-property "^1.0.1"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.1.1"
+ function-bind "^1.1.2"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ has-symbols "^1.1.0"
+ hasown "^2.0.2"
+ math-intrinsics "^1.1.0"
+
+get-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz"
+ integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
+ dependencies:
+ dunder-proto "^1.0.1"
+ es-object-atoms "^1.0.0"
+
+get-symbol-description@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz"
+ integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==
+ dependencies:
+ call-bound "^1.0.3"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.6"
+
+get-uri@^6.0.1:
+ version "6.0.5"
+ resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz"
+ integrity sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==
+ dependencies:
+ basic-ftp "^5.0.2"
+ data-uri-to-buffer "^6.0.2"
+ debug "^4.3.4"
+
+github-from-package@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz"
+ integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
+
+globalthis@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz"
+ integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==
+ dependencies:
+ define-properties "^1.2.1"
+ gopd "^1.0.1"
+
+gopd@^1.0.1, gopd@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz"
+ integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
+
+has-bigints@^1.0.2:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz"
+ integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==
+
+has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz"
+ integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
+ dependencies:
+ es-define-property "^1.0.0"
+
+has-proto@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz"
+ integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==
+ dependencies:
+ dunder-proto "^1.0.0"
+
+has-symbols@^1.0.3, has-symbols@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz"
+ integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
+
+has-tostringtag@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz"
+ integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
+ dependencies:
+ has-symbols "^1.0.3"
+
+hasown@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
+heap-js@^2.3.0:
+ version "2.7.1"
+ resolved "https://registry.npmjs.org/heap-js/-/heap-js-2.7.1.tgz"
+ integrity sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==
+
+http-errors@^2.0.0, http-errors@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz"
+ integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==
+ dependencies:
+ depd "~2.0.0"
+ inherits "~2.0.4"
+ setprototypeof "~1.2.0"
+ statuses "~2.0.2"
+ toidentifier "~1.0.1"
+
+http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz"
+ integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==
+ dependencies:
+ agent-base "^7.1.0"
+ debug "^4.3.4"
+
+https-proxy-agent@^7.0.5, https-proxy-agent@^7.0.6:
+ version "7.0.6"
+ resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz"
+ integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
+ dependencies:
+ agent-base "^7.1.2"
+ debug "4"
+
+iconv-lite@^0.7.0, iconv-lite@~0.7.0:
+ version "0.7.0"
+ resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz"
+ integrity sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
+ieee754@^1.1.13:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
+ integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+
+inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+ini@~1.3.0:
+ version "1.3.8"
+ resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
+ integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
+internal-slot@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz"
+ integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==
+ dependencies:
+ es-errors "^1.3.0"
+ hasown "^2.0.2"
+ side-channel "^1.1.0"
+
+ip-address@^10.0.1:
+ version "10.1.0"
+ resolved "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz"
+ integrity sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==
+
+ip-address@^9.0.5:
+ version "9.0.5"
+ resolved "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz"
+ integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==
+ dependencies:
+ jsbn "1.1.0"
+ sprintf-js "^1.1.3"
+
+ipaddr.js@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
+ integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
+is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
+ version "3.0.5"
+ resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz"
+ integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.3"
+ get-intrinsic "^1.2.6"
+
+is-async-function@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz"
+ integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==
+ dependencies:
+ async-function "^1.0.0"
+ call-bound "^1.0.3"
+ get-proto "^1.0.1"
+ has-tostringtag "^1.0.2"
+ safe-regex-test "^1.1.0"
+
+is-bigint@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz"
+ integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==
+ dependencies:
+ has-bigints "^1.0.2"
+
+is-boolean-object@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz"
+ integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==
+ dependencies:
+ call-bound "^1.0.3"
+ has-tostringtag "^1.0.2"
+
+is-callable@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz"
+ integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
+
+is-data-view@^1.0.1, is-data-view@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz"
+ integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==
+ dependencies:
+ call-bound "^1.0.2"
+ get-intrinsic "^1.2.6"
+ is-typed-array "^1.1.13"
+
+is-date-object@^1.0.5, is-date-object@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz"
+ integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==
+ dependencies:
+ call-bound "^1.0.2"
+ has-tostringtag "^1.0.2"
+
+is-docker@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz"
+ integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==
+
+is-finalizationregistry@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz"
+ integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==
+ dependencies:
+ call-bound "^1.0.3"
+
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-generator-function@^1.0.10:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz"
+ integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==
+ dependencies:
+ call-bound "^1.0.4"
+ generator-function "^2.0.0"
+ get-proto "^1.0.1"
+ has-tostringtag "^1.0.2"
+ safe-regex-test "^1.1.0"
+
+is-inside-container@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz"
+ integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==
+ dependencies:
+ is-docker "^3.0.0"
+
+is-map@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz"
+ integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==
+
+is-negative-zero@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz"
+ integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==
+
+is-number-object@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz"
+ integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==
+ dependencies:
+ call-bound "^1.0.3"
+ has-tostringtag "^1.0.2"
+
+is-promise@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz"
+ integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==
+
+is-regex@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz"
+ integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==
+ dependencies:
+ call-bound "^1.0.2"
+ gopd "^1.2.0"
+ has-tostringtag "^1.0.2"
+ hasown "^2.0.2"
+
+is-set@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz"
+ integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==
+
+is-shared-array-buffer@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz"
+ integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==
+ dependencies:
+ call-bound "^1.0.3"
+
+is-string@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz"
+ integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==
+ dependencies:
+ call-bound "^1.0.3"
+ has-tostringtag "^1.0.2"
+
+is-symbol@^1.0.4, is-symbol@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz"
+ integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==
+ dependencies:
+ call-bound "^1.0.2"
+ has-symbols "^1.1.0"
+ safe-regex-test "^1.1.0"
+
+is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15:
+ version "1.1.15"
+ resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz"
+ integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==
+ dependencies:
+ which-typed-array "^1.1.16"
+
+is-weakmap@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz"
+ integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==
+
+is-weakref@^1.0.2, is-weakref@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz"
+ integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==
+ dependencies:
+ call-bound "^1.0.3"
+
+is-weakset@^2.0.3:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz"
+ integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==
+ dependencies:
+ call-bound "^1.0.3"
+ get-intrinsic "^1.2.6"
+
+is-wsl@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz"
+ integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==
+ dependencies:
+ is-inside-container "^1.0.0"
+
+isarray@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz"
+ integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+isnumber@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/isnumber/-/isnumber-1.0.0.tgz"
+ integrity sha512-JLiSz/zsZcGFXPrB4I/AGBvtStkt+8QmksyZBZnVXnnK9XdTEyz0tX8CRYljtwYDuIuZzih6DpHQdi+3Q6zHPw==
+
+jose@^6.1.0, jose@^6.1.1:
+ version "6.1.3"
+ resolved "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz"
+ integrity sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==
+
+js-yaml@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz"
+ integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
+ dependencies:
+ argparse "^2.0.1"
+
+jsbn@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz"
+ integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
+
+json-schema-traverse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz"
+ integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
+json-schema@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz"
+ integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
+
+kerberos@^2.0.1, kerberos@^2.1.0, kerberos@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.npmjs.org/kerberos/-/kerberos-2.2.2.tgz"
+ integrity sha512-42O7+/1Zatsc3MkxaMPpXcIl/ukIrbQaGoArZEAr6GcEi2qhfprOBYOPhj+YvSMJkEkdpTjApUx+2DuWaKwRhg==
+ dependencies:
+ node-addon-api "^6.1.0"
+ prebuild-install "^7.1.2"
+
+kerberos@2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/kerberos/-/kerberos-2.1.0.tgz"
+ integrity sha512-HvOl6O6cyEN/8Z4CAocHe/sekJtvt5UrxUdCuu7bXDZ2Hnsy6OpsQbISW+lpm03vrbO2ir+1QQ5Sx/vMEhHnog==
+ dependencies:
+ bindings "^1.5.0"
+ node-addon-api "^6.1.0"
+ prebuild-install "7.1.1"
+
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+lru-cache@^11.0.0, lru-cache@^11.1.0:
+ version "11.2.4"
+ resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz"
+ integrity sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==
+
+math-intrinsics@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
+ integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
+
+media-typer@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz"
+ integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==
+
+memory-pager@^1.0.2:
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz"
+ integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
+
+merge-descriptors@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz"
+ integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==
+
+mime-db@^1.54.0:
+ version "1.54.0"
+ resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz"
+ integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
+
+mime-types@^3.0.0, mime-types@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz"
+ integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==
+ dependencies:
+ mime-db "^1.54.0"
+
+mimic-response@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz"
+ integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
+minimist@^1.2.0, minimist@^1.2.3:
+ version "1.2.8"
+ resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
+ version "0.5.3"
+ resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz"
+ integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
+
+mongodb-build-info@^1.8.1:
+ version "1.8.2"
+ resolved "https://registry.npmjs.org/mongodb-build-info/-/mongodb-build-info-1.8.2.tgz"
+ integrity sha512-VJ5iB7ca3bxSyBKQGNVkGszF7U3U0IP71Kym0Mw1Y7EquCIFgbBVdEgwpLa3R02les41TXIo+x4D+pUXO6ttrw==
+ dependencies:
+ debug "^4.4.0"
+ mongodb-connection-string-url "^3.0.0"
+
+mongodb-client-encryption@^6.1.0, mongodb-client-encryption@^6.5.0, "mongodb-client-encryption@>=6.0.0 <7":
+ version "6.5.0"
+ resolved "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-6.5.0.tgz"
+ integrity sha512-Gj8EeyYKsssdko0NKhWRBGDif6uVFBbv+e+Nyn7E316UmRzApc4IP+p2NLm+av+fU+dFHVT5WqfzaQVDTh8i9w==
+ dependencies:
+ node-addon-api "^4.3.0"
+ prebuild-install "^7.1.3"
+
+mongodb-connection-string-url@^3.0.0, "mongodb-connection-string-url@^3.0.1 || ^7.0.0", mongodb-connection-string-url@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz"
+ integrity sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==
+ dependencies:
+ "@types/whatwg-url" "^11.0.2"
+ whatwg-url "^14.1.0 || ^13.0.0"
+
+mongodb-log-writer@^2.4.1, mongodb-log-writer@^2.4.4:
+ version "2.4.4"
+ resolved "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-2.4.4.tgz"
+ integrity sha512-vGOzdF9ta+wUiHsqbEcRQ1COzTIKXy4vhOWkPCvOacWCeNsFS7LLp6Vg9LT95Z/09gSjnD1WDeYhxTqZXEgcWQ==
+ dependencies:
+ heap-js "^2.3.0"
+
+mongodb-mcp-server@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/mongodb-mcp-server/-/mongodb-mcp-server-1.3.0.tgz"
+ integrity sha512-uN1fs+N8NyC/AncvZdb2LGOTSeOXkB3D6A1GlDB+U932lxsp76WNRVziVF9SNfwOo+6E0vvk9aS1mqgR5tSrXw==
+ dependencies:
+ "@modelcontextprotocol/sdk" "^1.24.2"
+ "@mongodb-js/device-id" "^0.3.1"
+ "@mongodb-js/devtools-proxy-support" "^0.5.3"
+ "@mongosh/arg-parser" "^3.23.0"
+ "@mongosh/service-provider-node-driver" "^3.17.5"
+ ai "^5.0.72"
+ bson "^6.10.4"
+ express "^5.1.0"
+ lru-cache "^11.1.0"
+ mongodb-connection-string-url "^3.0.2"
+ mongodb-log-writer "^2.4.1"
+ mongodb-redact "^1.3.0"
+ mongodb-schema "^12.6.2"
+ node-fetch "^3.3.2"
+ node-machine-id "1.1.12"
+ oauth4webapi "^3.8.0"
+ openapi-fetch "^0.15.0"
+ ts-levenshtein "^1.0.7"
+ voyage-ai-provider "^2.0.0"
+ zod "^3.25.76"
+ optionalDependencies:
+ "@mongodb-js/atlas-local" "^1.1.0"
+ kerberos "^2.2.2"
+
+mongodb-ns@^3.0.1:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/mongodb-ns/-/mongodb-ns-3.0.3.tgz"
+ integrity sha512-ctpHlSXGYlIim3JSgcsQjNkgB4CWvwWSFIxKFphSY/MoY/2i6E5rtVkOt1FBbSjEJYa2XvD4c+G2BbLfjhQqaQ==
+
+mongodb-redact@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/mongodb-redact/-/mongodb-redact-1.3.0.tgz"
+ integrity sha512-6qMkQ9RnB7Z92G8c4mCkrDGaqwekuaUreaX+XnjAl/t3oKHJ+u7C+kfFLxSueJIBc1qtI4AtK0TO4yxOHyJqTw==
+ dependencies:
+ mongodb-connection-string-url "^3.0.1 || ^7.0.0"
+ regexp.escape "^2.0.1"
+
+mongodb-schema@^12.6.2:
+ version "12.6.3"
+ resolved "https://registry.npmjs.org/mongodb-schema/-/mongodb-schema-12.6.3.tgz"
+ integrity sha512-JiAZtM9GVMTLJYJpEnAPq0/ulH9U7qBR48Bx0mOiStVGFkY3mpIlgEGOl5tVRLEvCxDKqnvtdfSSX7pWFRLlzA==
+ dependencies:
+ reservoir "^0.1.2"
+ optionalDependencies:
+ bson "^6.7.0"
+ cli-table "^0.3.4"
+ js-yaml "^4.0.0"
+ mongodb "^6.6.1"
+ mongodb-ns "^3.0.1"
+ numeral "^2.0.6"
+ progress "^2.0.3"
+ stats-lite "^2.0.0"
+ yargs "^17.6.2"
+
+mongodb@^6.19.0, mongodb@^6.6.1, mongodb@^6.9.0:
+ version "6.21.0"
+ resolved "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz"
+ integrity sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==
+ dependencies:
+ "@mongodb-js/saslprep" "^1.3.0"
+ bson "^6.10.4"
+ mongodb-connection-string-url "^3.0.2"
+
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+napi-build-utils@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz"
+ integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
+
+napi-build-utils@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz"
+ integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
+
+negotiator@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz"
+ integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
+
+netmask@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz"
+ integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==
+
+node-abi@^3.3.0:
+ version "3.85.0"
+ resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz"
+ integrity sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==
+ dependencies:
+ semver "^7.3.5"
+
+node-addon-api@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz"
+ integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
+
+node-addon-api@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz"
+ integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==
+
+node-domexception@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz"
+ integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
+
+node-fetch@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz"
+ integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==
+ dependencies:
+ data-uri-to-buffer "^4.0.0"
+ fetch-blob "^3.1.4"
+ formdata-polyfill "^4.0.10"
+
+node-machine-id@1.1.12:
+ version "1.1.12"
+ resolved "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz"
+ integrity sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==
+
+numeral@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz"
+ integrity sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==
+
+oauth4webapi@^3.8.0, oauth4webapi@^3.8.2:
+ version "3.8.3"
+ resolved "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.3.tgz"
+ integrity sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw==
+
+object-assign@^4:
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
+object-inspect@^1.13.3, object-inspect@^1.13.4:
+ version "1.13.4"
+ resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz"
+ integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
+
+object-keys@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object.assign@^4.1.7:
+ version "4.1.7"
+ resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz"
+ integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.3"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+ has-symbols "^1.1.0"
+ object-keys "^1.1.1"
+
+on-finished@^2.4.1:
+ version "2.4.1"
+ resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
+once@^1.3.1, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+open@^10.1.2:
+ version "10.2.0"
+ resolved "https://registry.npmjs.org/open/-/open-10.2.0.tgz"
+ integrity sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==
+ dependencies:
+ default-browser "^5.2.1"
+ define-lazy-prop "^3.0.0"
+ is-inside-container "^1.0.0"
+ wsl-utils "^0.1.0"
+
+openapi-fetch@^0.15.0:
+ version "0.15.0"
+ resolved "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.15.0.tgz"
+ integrity sha512-OjQUdi61WO4HYhr9+byCPMj0+bgste/LtSBEcV6FzDdONTs7x0fWn8/ndoYwzqCsKWIxEZwo4FN/TG1c1rI8IQ==
+ dependencies:
+ openapi-typescript-helpers "^0.0.15"
+
+openapi-typescript-helpers@^0.0.15:
+ version "0.0.15"
+ resolved "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.15.tgz"
+ integrity sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==
+
+openid-client@^6.6.3:
+ version "6.8.1"
+ resolved "https://registry.npmjs.org/openid-client/-/openid-client-6.8.1.tgz"
+ integrity sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==
+ dependencies:
+ jose "^6.1.0"
+ oauth4webapi "^3.8.2"
+
+own-keys@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz"
+ integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==
+ dependencies:
+ get-intrinsic "^1.2.6"
+ object-keys "^1.1.1"
+ safe-push-apply "^1.0.0"
+
+pac-proxy-agent@^7.0.2:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz"
+ integrity sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==
+ dependencies:
+ "@tootallnate/quickjs-emscripten" "^0.23.0"
+ agent-base "^7.1.2"
+ debug "^4.3.4"
+ get-uri "^6.0.1"
+ http-proxy-agent "^7.0.0"
+ https-proxy-agent "^7.0.6"
+ pac-resolver "^7.0.1"
+ socks-proxy-agent "^8.0.5"
+
+pac-resolver@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz"
+ integrity sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==
+ dependencies:
+ degenerator "^5.0.0"
+ netmask "^2.0.2"
+
+parseurl@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz"
+ integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-to-regexp@^8.0.0:
+ version "8.3.0"
+ resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz"
+ integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==
+
+pkce-challenge@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz"
+ integrity sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==
+
+possible-typed-array-names@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz"
+ integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==
+
+prebuild-install@^7.1.2, prebuild-install@^7.1.3:
+ version "7.1.3"
+ resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz"
+ integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
+ dependencies:
+ detect-libc "^2.0.0"
+ expand-template "^2.0.3"
+ github-from-package "0.0.0"
+ minimist "^1.2.3"
+ mkdirp-classic "^0.5.3"
+ napi-build-utils "^2.0.0"
+ node-abi "^3.3.0"
+ pump "^3.0.0"
+ rc "^1.2.7"
+ simple-get "^4.0.0"
+ tar-fs "^2.0.0"
+ tunnel-agent "^0.6.0"
+
+prebuild-install@7.1.1:
+ version "7.1.1"
+ resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz"
+ integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==
+ dependencies:
+ detect-libc "^2.0.0"
+ expand-template "^2.0.3"
+ github-from-package "0.0.0"
+ minimist "^1.2.3"
+ mkdirp-classic "^0.5.3"
+ napi-build-utils "^1.0.1"
+ node-abi "^3.3.0"
+ pump "^3.0.0"
+ rc "^1.2.7"
+ simple-get "^4.0.0"
+ tar-fs "^2.0.0"
+ tunnel-agent "^0.6.0"
+
+progress@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
+ integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+
+proxy-addr@^2.0.7:
+ version "2.0.7"
+ resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz"
+ integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
+ dependencies:
+ forwarded "0.2.0"
+ ipaddr.js "1.9.1"
+
+pump@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz"
+ integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+punycode@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
+ integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
+qs@^6.14.0:
+ version "6.14.0"
+ resolved "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz"
+ integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
+ dependencies:
+ side-channel "^1.1.0"
+
+range-parser@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz"
+ integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+raw-body@^3.0.0, raw-body@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz"
+ integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==
+ dependencies:
+ bytes "~3.1.2"
+ http-errors "~2.0.1"
+ iconv-lite "~0.7.0"
+ unpipe "~1.0.0"
+
+rc@^1.2.7:
+ version "1.2.8"
+ resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz"
+ integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+ dependencies:
+ deep-extend "^0.6.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
+readable-stream@^3.1.1, readable-stream@^3.4.0:
+ version "3.6.2"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
+ integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
+ version "1.0.10"
+ resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz"
+ integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==
+ dependencies:
+ call-bind "^1.0.8"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.9"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.0.0"
+ get-intrinsic "^1.2.7"
+ get-proto "^1.0.1"
+ which-builtin-type "^1.2.1"
+
+regexp.escape@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/regexp.escape/-/regexp.escape-2.0.1.tgz"
+ integrity sha512-JItRb4rmyTzmERBkAf6J87LjDPy/RscIwmaJQ3gsFlAzrmZbZU8LwBw5IydFZXW9hqpgbPlGbMhtpqtuAhMgtg==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.3"
+ es-errors "^1.3.0"
+ for-each "^0.3.3"
+ safe-regex-test "^1.0.3"
+
+regexp.prototype.flags@^1.5.4:
+ version "1.5.4"
+ resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz"
+ integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==
+ dependencies:
+ call-bind "^1.0.8"
+ define-properties "^1.2.1"
+ es-errors "^1.3.0"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ set-function-name "^2.0.2"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
+ integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+require-from-string@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
+ integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
+reservoir@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.npmjs.org/reservoir/-/reservoir-0.1.2.tgz"
+ integrity sha512-ysyw95gLBhMAzqIVrOHJ2yMrRQHAS+h97bS9r89Z7Ou10Jhl2k5KOsyjPqrxL+WfEanov0o5bAMVzQ7AKyENHA==
+
+resolve-mongodb-srv@^1.1.1:
+ version "1.1.6"
+ resolved "https://registry.npmjs.org/resolve-mongodb-srv/-/resolve-mongodb-srv-1.1.6.tgz"
+ integrity sha512-eeHKsU9+zD7NGw+QI8SVNvcawIF0rT5/WmnVC5qHHJBsUW1ZZaJ1SBaT0eEv3ex+YBg4yaQVfwsCn8pD5q1Ggw==
+ dependencies:
+ whatwg-url "^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0"
+
+router@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/router/-/router-2.2.0.tgz"
+ integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==
+ dependencies:
+ debug "^4.4.0"
+ depd "^2.0.0"
+ is-promise "^4.0.0"
+ parseurl "^1.3.3"
+ path-to-regexp "^8.0.0"
+
+run-applescript@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz"
+ integrity sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==
+
+safe-array-concat@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz"
+ integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.2"
+ get-intrinsic "^1.2.6"
+ has-symbols "^1.1.0"
+ isarray "^2.0.5"
+
+safe-buffer@^5.0.1, safe-buffer@~5.2.0:
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+safe-push-apply@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz"
+ integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==
+ dependencies:
+ es-errors "^1.3.0"
+ isarray "^2.0.5"
+
+safe-regex-test@^1.0.3, safe-regex-test@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz"
+ integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ is-regex "^1.2.1"
+
+"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0:
+ version "2.1.2"
+ resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+semver@^7.3.5:
+ version "7.7.3"
+ resolved "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz"
+ integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
+
+send@^1.1.0, send@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/send/-/send-1.2.0.tgz"
+ integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==
+ dependencies:
+ debug "^4.3.5"
+ encodeurl "^2.0.0"
+ escape-html "^1.0.3"
+ etag "^1.8.1"
+ fresh "^2.0.0"
+ http-errors "^2.0.0"
+ mime-types "^3.0.1"
+ ms "^2.1.3"
+ on-finished "^2.4.1"
+ range-parser "^1.2.1"
+ statuses "^2.0.1"
+
+serve-static@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz"
+ integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==
+ dependencies:
+ encodeurl "^2.0.0"
+ escape-html "^1.0.3"
+ parseurl "^1.3.3"
+ send "^1.2.0"
+
+set-function-length@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz"
+ integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
+ dependencies:
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.2"
+
+set-function-name@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz"
+ integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==
+ dependencies:
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ functions-have-names "^1.2.3"
+ has-property-descriptors "^1.0.2"
+
+set-proto@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz"
+ integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==
+ dependencies:
+ dunder-proto "^1.0.1"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.0.0"
+
+setprototypeof@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz"
+ integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+side-channel-list@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz"
+ integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==
+ dependencies:
+ es-errors "^1.3.0"
+ object-inspect "^1.13.3"
+
+side-channel-map@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz"
+ integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.5"
+ object-inspect "^1.13.3"
+
+side-channel-weakmap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz"
+ integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.5"
+ object-inspect "^1.13.3"
+ side-channel-map "^1.0.1"
+
+side-channel@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz"
+ integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
+ dependencies:
+ es-errors "^1.3.0"
+ object-inspect "^1.13.3"
+ side-channel-list "^1.0.0"
+ side-channel-map "^1.0.1"
+ side-channel-weakmap "^1.0.2"
+
+simple-concat@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz"
+ integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
+
+simple-get@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz"
+ integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
+ dependencies:
+ decompress-response "^6.0.0"
+ once "^1.3.1"
+ simple-concat "^1.0.0"
+
+smart-buffer@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz"
+ integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
+
+socks-proxy-agent@^8.0.4, socks-proxy-agent@^8.0.5:
+ version "8.0.5"
+ resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz"
+ integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==
+ dependencies:
+ agent-base "^7.1.2"
+ debug "^4.3.4"
+ socks "^2.8.3"
+
+socks@^2.7.1, socks@^2.7.3, socks@^2.8.3:
+ version "2.8.7"
+ resolved "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz"
+ integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==
+ dependencies:
+ ip-address "^10.0.1"
+ smart-buffer "^4.2.0"
+
+source-map@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+sparse-bitfield@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz"
+ integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==
+ dependencies:
+ memory-pager "^1.0.2"
+
+sprintf-js@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz"
+ integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
+
+ssh2@^1.15.0:
+ version "1.17.0"
+ resolved "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz"
+ integrity sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==
+ dependencies:
+ asn1 "^0.2.6"
+ bcrypt-pbkdf "^1.0.2"
+ optionalDependencies:
+ cpu-features "~0.0.10"
+ nan "^2.23.0"
+
+stats-lite@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/stats-lite/-/stats-lite-2.2.0.tgz"
+ integrity sha512-/Kz55rgUIv2KP2MKphwYT/NCuSfAlbbMRv2ZWw7wyXayu230zdtzhxxuXXcvsc6EmmhS8bSJl3uS1wmMHFumbA==
+ dependencies:
+ isnumber "~1.0.0"
+
+statuses@^2.0.1, statuses@~2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz"
+ integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
+
+stop-iteration-iterator@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz"
+ integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==
+ dependencies:
+ es-errors "^1.3.0"
+ internal-slot "^1.1.0"
+
+string_decoder@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string.prototype.trim@^1.2.10:
+ version "1.2.10"
+ resolved "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz"
+ integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.2"
+ define-data-property "^1.1.4"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.5"
+ es-object-atoms "^1.0.0"
+ has-property-descriptors "^1.0.2"
+
+string.prototype.trimend@^1.0.9:
+ version "1.0.9"
+ resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz"
+ integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.2"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+
+string.prototype.trimstart@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz"
+ integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz"
+ integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
+
+system-ca@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/system-ca/-/system-ca-2.0.1.tgz"
+ integrity sha512-9ZDV9yl8ph6Op67wDGPr4LykX86usE9x3le+XZSHfVMiiVJ5IRgmCWjLgxyz35ju9H3GDIJJZm4ogAeIfN5cQQ==
+ optionalDependencies:
+ macos-export-certificate-and-key "^1.2.0"
+ win-export-certificate-and-key "^2.1.0"
+
+tar-fs@^2.0.0:
+ version "2.1.4"
+ resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz"
+ integrity sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==
+ dependencies:
+ chownr "^1.1.1"
+ mkdirp-classic "^0.5.2"
+ pump "^3.0.0"
+ tar-stream "^2.1.4"
+
+tar-stream@^2.1.4:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz"
+ integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
+ dependencies:
+ bl "^4.0.3"
+ end-of-stream "^1.4.1"
+ fs-constants "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^3.1.1"
+
+toidentifier@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz"
+ integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
+tr46@^5.1.0:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz"
+ integrity sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==
+ dependencies:
+ punycode "^2.3.1"
+
+ts-levenshtein@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/ts-levenshtein/-/ts-levenshtein-1.0.7.tgz"
+ integrity sha512-wautEf7gl2ITJuRTTYxnlrLjzUUcwFSdg46bcu4RlzoE/zQM++TJjBFRf2Xhil49GiHqKCqmpjf1lBkWnAHj0A==
+
+tslib@^2.0.1:
+ version "2.8.1"
+ resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
+ integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
+tunnel-agent@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz"
+ integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
+ dependencies:
+ safe-buffer "^5.0.1"
+
+tweetnacl@^0.14.3:
+ version "0.14.5"
+ resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz"
+ integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==
+
+type-is@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz"
+ integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==
+ dependencies:
+ content-type "^1.0.5"
+ media-typer "^1.1.0"
+ mime-types "^3.0.0"
+
+typed-array-buffer@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz"
+ integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==
+ dependencies:
+ call-bound "^1.0.3"
+ es-errors "^1.3.0"
+ is-typed-array "^1.1.14"
+
+typed-array-byte-length@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz"
+ integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==
+ dependencies:
+ call-bind "^1.0.8"
+ for-each "^0.3.3"
+ gopd "^1.2.0"
+ has-proto "^1.2.0"
+ is-typed-array "^1.1.14"
+
+typed-array-byte-offset@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz"
+ integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==
+ dependencies:
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.8"
+ for-each "^0.3.3"
+ gopd "^1.2.0"
+ has-proto "^1.2.0"
+ is-typed-array "^1.1.15"
+ reflect.getprototypeof "^1.0.9"
+
+typed-array-length@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz"
+ integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==
+ dependencies:
+ call-bind "^1.0.7"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ is-typed-array "^1.1.13"
+ possible-typed-array-names "^1.0.0"
+ reflect.getprototypeof "^1.0.6"
+
+unbox-primitive@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz"
+ integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==
+ dependencies:
+ call-bound "^1.0.3"
+ has-bigints "^1.0.2"
+ has-symbols "^1.1.0"
+ which-boxed-primitive "^1.1.1"
+
+unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
+ integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
+
+util-deprecate@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+ integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
+vary@^1, vary@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
+ integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
+
+voyage-ai-provider@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/voyage-ai-provider/-/voyage-ai-provider-2.0.0.tgz"
+ integrity sha512-AX00egENhHOAfuHAhvmoBVQNG6+f717763CfyPefjahDTxbt6nCE0IlDXn5nkzLIu00JoM/PDFYDYQ17NYQqPw==
+ dependencies:
+ "@ai-sdk/provider" "^2.0.0"
+ "@ai-sdk/provider-utils" "^3.0.0"
+
+web-streams-polyfill@^3.0.3:
+ version "3.3.3"
+ resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz"
+ integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
+
+webidl-conversions@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz"
+ integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
+
+"whatwg-url@^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0", "whatwg-url@^14.1.0 || ^13.0.0":
+ version "14.2.0"
+ resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz"
+ integrity sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==
+ dependencies:
+ tr46 "^5.1.0"
+ webidl-conversions "^7.0.0"
+
+which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz"
+ integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==
+ dependencies:
+ is-bigint "^1.1.0"
+ is-boolean-object "^1.2.1"
+ is-number-object "^1.1.1"
+ is-string "^1.1.1"
+ is-symbol "^1.1.1"
+
+which-builtin-type@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz"
+ integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==
+ dependencies:
+ call-bound "^1.0.2"
+ function.prototype.name "^1.1.6"
+ has-tostringtag "^1.0.2"
+ is-async-function "^2.0.0"
+ is-date-object "^1.1.0"
+ is-finalizationregistry "^1.1.0"
+ is-generator-function "^1.0.10"
+ is-regex "^1.2.1"
+ is-weakref "^1.0.2"
+ isarray "^2.0.5"
+ which-boxed-primitive "^1.1.0"
+ which-collection "^1.0.2"
+ which-typed-array "^1.1.16"
+
+which-collection@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz"
+ integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==
+ dependencies:
+ is-map "^2.0.3"
+ is-set "^2.0.3"
+ is-weakmap "^2.0.2"
+ is-weakset "^2.0.3"
+
+which-typed-array@^1.1.16, which-typed-array@^1.1.19:
+ version "1.1.19"
+ resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz"
+ integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==
+ dependencies:
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.8"
+ call-bound "^1.0.4"
+ for-each "^0.3.5"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ has-tostringtag "^1.0.2"
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+wsl-utils@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz"
+ integrity sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==
+ dependencies:
+ is-wsl "^3.1.0"
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yargs-parser@^20.2.4:
+ version "20.2.9"
+ resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz"
+ integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+
+yargs-parser@^21.1.1:
+ version "21.1.1"
+ resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
+yargs@^17.6.2:
+ version "17.7.2"
+ resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz"
+ integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+ dependencies:
+ cliui "^8.0.1"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.3"
+ y18n "^5.0.5"
+ yargs-parser "^21.1.1"
+
+zod-to-json-schema@^3.25.0:
+ version "3.25.0"
+ resolved "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz"
+ integrity sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==
+
+"zod@^3.25 || ^4", "zod@^3.25 || ^4.0", zod@^3.25.76, "zod@^3.25.76 || ^4", "zod@^3.25.76 || ^4.1.8":
+ version "3.25.76"
+ resolved "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz"
+ integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==
diff --git a/Moncter/提示词/106服务器消费记录采集表分析.md b/Moncter/提示词/106服务器消费记录采集表分析.md
new file mode 100644
index 00000000..7c9e4953
--- /dev/null
+++ b/Moncter/提示词/106服务器消费记录采集表分析.md
@@ -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. 保存并启动采集任务
+
diff --git a/Moncter/提示词/QueryBuilder折叠功能说明.md b/Moncter/提示词/QueryBuilder折叠功能说明.md
new file mode 100644
index 00000000..99d01a45
--- /dev/null
+++ b/Moncter/提示词/QueryBuilder折叠功能说明.md
@@ -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
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### 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
+**更新内容**:添加折叠功能,优化界面布局
diff --git a/Moncter/提示词/可视化查询构建器使用说明.md b/Moncter/提示词/可视化查询构建器使用说明.md
new file mode 100644
index 00000000..895bdfbb
--- /dev/null
+++ b/Moncter/提示词/可视化查询构建器使用说明.md
@@ -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
diff --git a/Moncter/提示词/多集合模式使用说明.md b/Moncter/提示词/多集合模式使用说明.md
new file mode 100644
index 00000000..2ba6175d
--- /dev/null
+++ b/Moncter/提示词/多集合模式使用说明.md
@@ -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
+**状态**:前端已实现,后端待开发
diff --git a/Moncter/提示词/字段定义配置管理方案对比.md b/Moncter/提示词/字段定义配置管理方案对比.md
new file mode 100644
index 00000000..486bc084
--- /dev/null
+++ b/Moncter/提示词/字段定义配置管理方案对比.md
@@ -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
+> 字段列表
+ */
+ public function getFields(string $dataSourceType): array
+ {
+ $fieldsMap = $this->getFieldsMap();
+ return $fieldsMap[$dataSourceType] ?? [];
+ }
+
+ /**
+ * 获取所有数据源的字段映射
+ *
+ * @return array>>
+ */
+ 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
diff --git a/Moncter/提示词/当前架构设计/人物主表生成逻辑说明.md b/Moncter/提示词/当前架构设计/人物主表生成逻辑说明.md
new file mode 100644
index 00000000..d862566a
--- /dev/null
+++ b/Moncter/提示词/当前架构设计/人物主表生成逻辑说明.md
@@ -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
\ No newline at end of file
diff --git a/Moncter/提示词/当前架构设计/前端代码风格和设计思路.md b/Moncter/提示词/当前架构设计/前端代码风格和设计思路.md
new file mode 100644
index 00000000..48465d03
--- /dev/null
+++ b/Moncter/提示词/当前架构设计/前端代码风格和设计思路.md
@@ -0,0 +1,407 @@
+# 前端代码风格和设计思路
+
+## 一、技术栈
+
+### 核心技术
+- **Vue 3** (v3.4.21) - 采用 Composition API 和 `
+```
+
+### 3. 组件设计规范
+
+- **组件命名**:使用 PascalCase,如 `StatusBadge.vue`、`TaskForm.vue`
+- **单文件组件**:每个组件一个文件,文件名与组件名一致
+- **Props 定义**:使用 `defineProps` 定义,并指定类型
+- **Emits 定义**:使用 `defineEmits` 定义,并指定事件类型
+- **组件拆分**:保持组件职责单一,复杂组件拆分为多个子组件
+
+```vue
+
+```
+
+### 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('/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([])
+
+ const fetchTasks = async (params: any) => {
+ const response = await getDataCollectionTaskList(params)
+ tasks.value = response.data.tasks
+ return response.data
+ }
+
+ return {
+ tasks,
+ fetchTasks
+ }
+})
+```
+
+### 6. 样式规范
+
+- **使用 Scoped CSS**:组件样式使用 `
+```
+
+### 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('/tags/statistics', params)
+
+// POST 请求
+const response = await request.post('/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()
+
+// ✅ Emits 向上传递
+const emit = defineEmits<{
+ update: [task: DataCollectionTask]
+ delete: [id: string]
+}>()
+```
+
+## 六、开发规范总结
+
+1. **代码风格**:使用 ESLint 自动格式化,保持代码风格一致
+2. **类型安全**:充分利用 TypeScript,避免使用 `any`
+3. **组件化**:保持组件小而专一,提高可复用性
+4. **状态管理**:合理使用 Pinia,避免过度使用全局状态
+5. **API 调用**:统一使用封装的 request 方法,保持一致性
+6. **错误处理**:统一错误处理机制,提供良好的用户体验
+7. **代码注释**:关键逻辑添加注释,提高代码可读性
+
diff --git a/Moncter/提示词/当前架构设计/前端功能说明.md b/Moncter/提示词/当前架构设计/前端功能说明.md
new file mode 100644
index 00000000..c1fde74b
--- /dev/null
+++ b/Moncter/提示词/当前架构设计/前端功能说明.md
@@ -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 类型定义
+ - 建议启用严格模式以获得更好的类型检查
+
diff --git a/Moncter/提示词/当前架构设计/前端已对接API说明.md b/Moncter/提示词/当前架构设计/前端已对接API说明.md
new file mode 100644
index 00000000..5d9adb5f
--- /dev/null
+++ b/Moncter/提示词/当前架构设计/前端已对接API说明.md
@@ -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>
+```
+- **路径**: `GET /data-collection-tasks`
+- **功能**: 获取数据采集任务列表,支持按名称、状态筛选和分页
+
+#### 1.2 获取数据采集任务详情
+```typescript
+getDataCollectionTaskDetail(taskId: string): Promise>
+```
+- **路径**: `GET /data-collection-tasks/:id`
+- **功能**: 获取指定任务的详细信息
+
+#### 1.3 创建数据采集任务
+```typescript
+createDataCollectionTask(data: Partial): Promise>
+```
+- **路径**: `POST /data-collection-tasks`
+- **功能**: 创建新的数据采集任务
+
+#### 1.4 更新数据采集任务
+```typescript
+updateDataCollectionTask(taskId: string, data: Partial): Promise>
+```
+- **路径**: `PUT /data-collection-tasks/:id`
+- **功能**: 更新指定任务的信息
+
+#### 1.5 删除数据采集任务
+```typescript
+deleteDataCollectionTask(taskId: string): Promise
+```
+- **路径**: `DELETE /data-collection-tasks/:id`
+- **功能**: 删除指定任务
+
+#### 1.6 启动数据采集任务
+```typescript
+startDataCollectionTask(taskId: string): Promise
+```
+- **路径**: `POST /data-collection-tasks/:id/start`
+- **功能**: 启动指定任务
+
+#### 1.7 暂停数据采集任务
+```typescript
+pauseDataCollectionTask(taskId: string): Promise
+```
+- **路径**: `POST /data-collection-tasks/:id/pause`
+- **功能**: 暂停指定任务
+
+#### 1.8 停止数据采集任务
+```typescript
+stopDataCollectionTask(taskId: string): Promise
+```
+- **路径**: `POST /data-collection-tasks/:id/stop`
+- **功能**: 停止指定任务
+
+#### 1.9 获取任务进度
+```typescript
+getDataCollectionTaskProgress(taskId: string): Promise
+```
+- **路径**: `GET /data-collection-tasks/:id/progress`
+- **功能**: 获取任务执行进度
+
+#### 1.10 获取数据源列表
+```typescript
+getDataSources(): Promise>
+```
+- **路径**: `GET /data-collection-tasks/data-sources`
+- **功能**: 获取所有可用的数据源列表
+
+#### 1.11 获取数据库列表
+```typescript
+getDatabases(dataSourceId: string): Promise>>
+```
+- **路径**: `GET /data-collection-tasks/data-sources/:id/databases`
+- **功能**: 获取指定数据源的数据库列表
+
+#### 1.12 获取集合列表
+```typescript
+getCollections(dataSourceId: string, database: string | { name: string; id: string }): Promise>>
+```
+- **路径**: `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>>
+```
+- **路径**: `GET /data-collection-tasks/data-sources/:id/databases/:db/collections/:coll/fields`
+- **功能**: 获取指定集合的字段列表
+
+#### 1.14 获取Handler目标字段列表
+```typescript
+getHandlerTargetFields(handlerType: string): Promise>>
+```
+- **路径**: `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
+ data: Array
+ 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>
+```
+- **路径**: `GET /data-sources`
+- **功能**: 获取数据源列表,支持按类型、状态、名称筛选和分页
+
+#### 2.2 获取数据源详情
+```typescript
+getDataSourceDetail(dataSourceId: string): Promise>
+```
+- **路径**: `GET /data-sources/:id`
+- **功能**: 获取指定数据源的详细信息
+
+#### 2.3 创建数据源
+```typescript
+createDataSource(data: Partial): Promise>
+```
+- **路径**: `POST /data-sources`
+- **功能**: 创建新的数据源配置
+
+#### 2.4 更新数据源
+```typescript
+updateDataSource(dataSourceId: string, data: Partial): Promise>
+```
+- **路径**: `PUT /data-sources/:id`
+- **功能**: 更新指定数据源的配置
+
+#### 2.5 删除数据源
+```typescript
+deleteDataSource(dataSourceId: string): Promise
+```
+- **路径**: `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
+}): Promise>
+```
+- **路径**: `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>
+```
+- **路径**: `GET /tag-tasks`
+- **功能**: 获取标签任务列表,支持按名称、类型、状态筛选和分页
+
+#### 3.2 获取标签任务详情
+```typescript
+getTagTaskDetail(taskId: string): Promise>
+```
+- **路径**: `GET /tag-tasks/:id`
+- **功能**: 获取指定标签任务的详细信息
+
+#### 3.3 创建标签任务
+```typescript
+createTagTask(data: Partial): Promise>
+```
+- **路径**: `POST /tag-tasks`
+- **功能**: 创建新的标签任务
+
+#### 3.4 更新标签任务
+```typescript
+updateTagTask(taskId: string, data: Partial): Promise>
+```
+- **路径**: `PUT /tag-tasks/:id`
+- **功能**: 更新指定标签任务的信息
+
+#### 3.5 删除标签任务
+```typescript
+deleteTagTask(taskId: string): Promise
+```
+- **路径**: `DELETE /tag-tasks/:id`
+- **功能**: 删除指定标签任务
+
+#### 3.6 启动标签任务
+```typescript
+startTagTask(taskId: string): Promise
+```
+- **路径**: `POST /tag-tasks/:id/start`
+- **功能**: 启动指定标签任务
+
+#### 3.7 暂停标签任务
+```typescript
+pauseTagTask(taskId: string): Promise
+```
+- **路径**: `POST /tag-tasks/:id/pause`
+- **功能**: 暂停指定标签任务
+
+#### 3.8 停止标签任务
+```typescript
+stopTagTask(taskId: string): Promise
+```
+- **路径**: `POST /tag-tasks/:id/stop`
+- **功能**: 停止指定标签任务
+
+#### 3.9 获取任务执行记录
+```typescript
+getTagTaskExecutions(taskId: string, params?: {
+ page?: number
+ page_size?: number
+}): Promise>
+```
+- **路径**: `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>
+```
+- **路径**: `GET /tag-definitions`
+- **功能**: 获取标签定义列表,支持按名称、分类、状态筛选和分页
+
+#### 4.2 获取标签定义详情
+```typescript
+getTagDefinitionDetail(tagId: string): Promise>
+```
+- **路径**: `GET /tag-definitions/:id`
+- **功能**: 获取指定标签定义的详细信息
+
+#### 4.3 创建标签定义
+```typescript
+createTagDefinition(data: Partial): Promise>
+```
+- **路径**: `POST /tag-definitions`
+- **功能**: 创建新的标签定义
+
+#### 4.4 更新标签定义
+```typescript
+updateTagDefinition(tagId: string, data: Partial): Promise>
+```
+- **路径**: `PUT /tag-definitions/:id`
+- **功能**: 更新指定标签定义的信息
+
+#### 4.5 删除标签定义
+```typescript
+deleteTagDefinition(tagId: string): Promise
+```
+- **路径**: `DELETE /tag-definitions/:id`
+- **功能**: 删除指定标签定义
+
+#### 4.6 批量初始化标签定义
+```typescript
+batchInitTagDefinitions(data: {
+ definitions: Partial[]
+}): Promise
+```
+- **路径**: `POST /tag-definitions/batch`
+- **功能**: 批量创建标签定义
+
+---
+
+### 5. 标签查询 API (`tagQuery.ts`)
+
+#### 5.1 获取用户标签
+```typescript
+getUserTags(userIdOrPhone: string): Promise>
+```
+- **路径**: `GET /users/:userIdOrPhone/tags`
+- **功能**: 获取指定用户(通过用户ID或手机号)的所有标签
+
+#### 5.2 重新计算用户标签
+```typescript
+recalculateUserTags(userId: string): Promise
+ 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>
+```
+- **路径**: `POST /tags/filter`
+- **功能**: 根据标签条件筛选用户列表
+
+#### 5.4 获取标签统计信息
+```typescript
+getTagStatistics(params?: {
+ tag_id?: string
+ start_date?: string
+ end_date?: string
+}): Promise>
+```
+- **路径**: `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>
+```
+- **路径**: `GET /tags/history`
+- **功能**: 获取标签变更历史记录,支持按用户、标签、时间范围筛选和分页
+
+---
+
+### 6. 人群快照 API (`tagCohort.ts`)
+
+#### 6.1 获取人群快照列表
+```typescript
+getTagCohortList(params?: {
+ page?: number
+ page_size?: number
+}): Promise>
+```
+- **路径**: `GET /tag-cohorts`
+- **功能**: 获取人群快照列表,支持分页
+
+#### 6.2 获取人群快照详情
+```typescript
+getTagCohortDetail(cohortId: string): Promise>
+```
+- **路径**: `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>
+```
+- **路径**: `POST /tag-cohorts`
+- **功能**: 创建新的人群快照(支持条件筛选或直接指定用户列表)
+
+#### 6.4 删除人群快照
+```typescript
+deleteTagCohort(cohortId: string): Promise
+```
+- **路径**: `DELETE /tag-cohorts/:id`
+- **功能**: 删除指定人群快照
+
+#### 6.5 导出人群快照
+```typescript
+exportTagCohort(cohortId: string): Promise
+```
+- **路径**: `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>
+```
+- **路径**: `POST /users/search`
+- **功能**: 根据身份证、手机号、姓名搜索用户
+
+#### 7.2 解密身份证
+```typescript
+decryptIdCard(userId: string): Promise>
+```
+- **路径**: `GET /users/:userId/decrypt-id-card`
+- **功能**: 解密指定用户的身份证号(需要权限)
+
+#### 7.3 删除用户标签
+```typescript
+deleteUserTag(userId: string, tagId: string): Promise
+```
+- **路径**: `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
+
+```
+
+### 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(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)会在拦截器中提示
+ - 组件中只需处理业务逻辑,无需重复处理错误提示
+
diff --git a/Moncter/提示词/当前架构设计/数据采集业务逻辑.md b/Moncter/提示词/当前架构设计/数据采集业务逻辑.md
new file mode 100644
index 00000000..1a03c759
--- /dev/null
+++ b/Moncter/提示词/当前架构设计/数据采集业务逻辑.md
@@ -0,0 +1,1089 @@
+# 数据采集业务逻辑
+
+## 一、系统概述
+
+数据采集系统是项目的核心模块之一,负责从多个数据源采集数据并写入目标数据库。系统支持批量采集和实时监听两种模式,通过配置化的方式灵活管理采集任务。
+
+### 1.1 核心组件
+
+- **DataSyncScheduler** - 数据采集任务调度器(Workerman进程)
+- **DataCollectionTaskService** - 数据采集任务管理服务
+- **DataSourceService** - 数据源管理服务
+- **DataSourceAdapterFactory** - 数据源适配器工厂
+- **Handler** - 业务处理类(ConsumptionCollectionHandler、GenericCollectionHandler、DatabaseSyncHandler)
+- **DataSyncWorker** - 数据同步Worker(消费RabbitMQ消息队列)
+
+### 1.2 数据存储
+
+- **data_collection_tasks** - 数据采集任务集合
+- **data_sources** - 数据源配置集合
+- **consumption_records** - 消费记录集合(按时间分表)
+- **user_profile** - 用户画像集合
+- **user_phone_relations** - 手机号关联集合
+
+---
+
+## 二、任务配置体系
+
+### 2.1 配置方式
+
+系统支持两种任务配置方式:
+
+#### 2.1.1 配置文件方式
+
+配置文件:`config/data_collection_tasks.php`
+
+**适用场景**:
+- 系统级任务(如数据库实时同步)
+- 需要版本控制的配置
+- 固定不变的采集任务
+
+**配置示例**:
+```php
+'tasks' => [
+ 'database_sync' => [
+ 'name' => '数据库实时同步',
+ 'enabled' => false,
+ 'source_data_source' => 'kr_mongodb',
+ 'target_data_source' => 'sync_mongodb',
+ 'handler_class' => DatabaseSyncHandler::class,
+ 'schedule' => [
+ 'enabled' => false, // 持续运行,不需要定时调度
+ ],
+ 'sharding' => [
+ 'strategy' => 'by_database', // 按数据库分片
+ ],
+ ],
+]
+```
+
+#### 2.1.2 数据库方式
+
+集合:`data_collection_tasks`
+
+**适用场景**:
+- 通过前端界面动态创建的任务
+- 需要频繁修改的配置
+- 用户自定义的采集任务
+
+**任务结构**:
+```javascript
+{
+ task_id: String, // 任务ID(UUID)
+ name: String, // 任务名称
+ description: String, // 任务描述
+ data_source_id: String, // 源数据源ID
+ database: String, // 源数据库名
+ collection: String, // 源集合名(单集合模式)
+ collections: Array, // 源集合列表(多集合模式)
+ target_type: String, // 目标类型(consumption_record/generic)
+ target_data_source_id: String, // 目标数据源ID
+ target_database: String, // 目标数据库名
+ target_collection: String, // 目标集合名
+ mode: String, // 采集模式(batch/realtime)
+ field_mappings: Array, // 字段映射配置
+ collection_field_mappings: Object, // 多集合字段映射(key为集合名)
+ lookups: Array, // 连表查询配置
+ collection_lookups: Object, // 多集合连表查询配置
+ filter_conditions: Array, // 过滤条件
+ schedule: { // 调度计划
+ enabled: Boolean, // 是否启用定时任务
+ cron: String // Cron 表达式
+ },
+ status: String, // 任务状态(pending/running/paused/stopped/error)
+ progress: { // 任务进度
+ status: String, // 进度状态(idle/running/paused/completed/error)
+ processed_count: Number, // 已处理数量
+ success_count: Number, // 成功数量
+ error_count: Number, // 错误数量
+ total_count: Number, // 总数量
+ percentage: Number, // 完成百分比
+ start_time: Date, // 开始时间
+ end_time: Date, // 结束时间
+ last_sync_time: Date // 最后同步时间
+ },
+ statistics: { // 任务统计
+ total_processed: Number, // 总处理数量
+ total_success: Number, // 总成功数量
+ total_error: Number, // 总错误数量
+ last_run_time: Date // 最后执行时间
+ },
+ created_by: String,
+ created_at: Date,
+ updated_at: Date
+}
+```
+
+### 2.2 数据源配置
+
+数据源配置存储在 `data_sources` 集合中,支持从数据库动态加载。
+
+**数据源结构**:
+```javascript
+{
+ data_source_id: String, // 数据源ID
+ name: String, // 数据源名称
+ type: String, // 数据源类型(mongodb/mysql/postgresql)
+ host: String, // 主机地址
+ port: Number, // 端口号
+ database: String, // 数据库名
+ username: String, // 用户名
+ password: String, // 密码(加密存储)
+ auth_source: String, // 认证源(MongoDB)
+ is_tag_engine: Boolean, // 是否为标签引擎数据库
+ status: Number, // 状态(0:禁用, 1:启用)
+ options: Object, // 连接选项
+ created_at: Date,
+ updated_at: Date
+}
+```
+
+---
+
+## 三、任务执行流程
+
+### 3.1 任务创建流程
+
+```
+用户在前端创建任务
+ ↓
+填写任务配置:
+ - 数据源选择
+ - 数据库/集合选择
+ - 字段映射配置
+ - 过滤条件
+ - 调度计划
+ ↓
+API: POST /api/data-collection-tasks
+ ↓
+DataCollectionTaskService->createTask()
+ ├─→ 根据 target_type 自动处理目标数据源配置
+ │ ├─→ consumption_record: 自动使用标签数据库
+ │ └─→ generic: 使用用户指定的目标数据源
+ ├─→ 清理字段映射(移除无效项)
+ ├─→ 构建任务文档
+ └─→ 保存到 data_collection_tasks 集合
+ ↓
+如果任务状态为 running,设置 Redis 启动标志
+```
+
+### 3.2 任务启动流程
+
+```
+用户点击"启动"按钮
+ ↓
+API: POST /api/data-collection-tasks/{taskId}/start
+ ↓
+DataCollectionTaskService->startTask()
+ ├─→ 检查任务状态(不能是 running)
+ ├─→ 更新任务状态为 running
+ ├─→ 更新进度状态为 running
+ └─→ 设置 Redis 标志:data_collection_task:{taskId}:start
+ ↓
+DataSyncScheduler 进程检测到 Redis 标志
+ ├─→ 从数据库加载任务配置
+ ├─→ 转换为内部配置格式
+ └─→ 根据任务模式执行:
+ ├─→ batch 模式:根据 schedule 配置定时执行或立即执行
+ └─→ realtime 模式:立即启动,持续运行(Change Stream监听)
+```
+
+### 3.3 任务执行流程
+
+```
+DataSyncScheduler->executeTask()
+ ├─→ 获取分布式锁(防止重复执行)
+ ├─→ 查询数据源配置(从缓存或数据库)
+ ├─→ 创建数据源适配器(DataSourceAdapterFactory)
+ ├─→ 实例化 Handler(根据 target_type 选择)
+ └─→ 调用 Handler->collect(adapter, taskConfig)
+ ↓
+ Handler 执行采集逻辑
+ ├─→ batch 模式:分页查询数据,批量处理
+ └─→ realtime 模式:启动 Change Stream 监听
+```
+
+### 3.4 任务状态管理
+
+#### 3.4.1 暂停任务
+
+```
+用户点击"暂停"按钮
+ ↓
+API: POST /api/data-collection-tasks/{taskId}/pause
+ ↓
+DataCollectionTaskService->pauseTask()
+ ├─→ 检查任务状态(必须是 running)
+ ├─→ 更新任务状态为 paused
+ └─→ 设置 Redis 标志:data_collection_task:{taskId}:pause
+ ↓
+Handler 检测到暂停标志
+ ↓
+停止处理新数据,等待当前批次完成
+```
+
+#### 3.4.2 停止任务
+
+```
+用户点击"停止"按钮
+ ↓
+API: POST /api/data-collection-tasks/{taskId}/stop
+ ↓
+DataCollectionTaskService->stopTask()
+ ├─→ 更新任务状态为 stopped
+ ├─→ 更新进度状态为 stopped
+ └─→ 设置 Redis 标志:data_collection_task:{taskId}:stop
+ ↓
+Handler 检测到停止标志
+ ↓
+立即停止处理
+```
+
+---
+
+## 四、数据采集模式
+
+### 4.1 批量采集模式(Batch)
+
+**适用场景**:
+- 历史数据迁移
+- 定时增量同步
+- 一次性数据采集
+
+**执行流程**:
+```
+1. 构建查询条件
+ - 如果有 last_sync_time,只查询新数据
+ - 应用 filter_conditions 过滤条件
+
+2. 分页查询数据
+ - 使用 batch_size 控制每批数量(默认1000)
+ - 按时间排序,确保顺序处理
+
+3. 批量处理数据
+ For each batch:
+ a. 应用字段映射(field_mappings)
+ b. 执行字段转换(类型转换、值映射)
+ c. 执行连表查询(lookups,如果有)
+ d. 写入目标数据库
+ e. 更新任务进度
+
+4. 更新同步状态
+ - 记录 last_sync_time
+ - 更新任务进度和统计
+```
+
+**调度方式**:
+- **定时执行**:根据 Cron 表达式定时执行
+- **立即执行**:调度被禁用时,立即执行一次
+
+### 4.2 实时监听模式(Realtime)
+
+**适用场景**:
+- 实时数据同步
+- 数据库变更监听
+- 持续运行的数据采集
+
+**执行流程**:
+```
+1. 启动 Change Stream 监听
+ - 监听指定集合的变更事件
+ - 过滤操作类型(insert/update/delete)
+
+2. 持续监听变更
+ For each change event:
+ a. 解析变更文档
+ b. 应用字段映射和转换
+ c. 写入目标数据库
+ d. 更新同步状态
+
+3. 错误处理和重连
+ - 连接断开时自动重连
+ - 记录错误日志
+```
+
+**特点**:
+- 持续运行,不自动停止
+- 实时响应数据变更
+- 支持断点续传(记录同步位置)
+
+---
+
+## 五、Handler 处理机制
+
+### 5.1 Handler 类型
+
+系统通过 Handler 模式实现不同类型的数据采集:
+
+#### 5.1.1 ConsumptionCollectionHandler(消费记录采集)
+
+**职责**:
+- 专门处理消费记录/订单数据采集
+- 支持 KR_商城 和 KR_金融 两种数据源
+- 自动提取手机号、解析用户ID
+- 写入消费记录表(按时间分表)
+
+**处理流程**:
+```
+1. 从源数据库查询订单数据
+ - 支持单集合和多集合模式
+ - 应用过滤条件
+
+2. 字段映射和转换
+ - 提取手机号(去除单引号等特殊字符)
+ - 转换金额字段(字符串→数字)
+ - 解析时间字段(字符串→DateTime)
+ - 转换订单状态
+
+3. 身份解析
+ - 通过手机号解析 user_id
+ - 如果不存在,创建临时人
+
+4. 门店处理
+ - 通过门店名称查找或创建门店
+ - 获取或生成 store_id
+
+5. 写入消费记录
+ - 调用 ConsumptionService->createRecord()
+ - 自动按时间分表(consumption_records_YYYYMM)
+ - 更新用户统计信息(total_amount, total_count)
+ - 触发标签计算(异步)
+
+6. 更新任务进度
+```
+
+**字段映射示例**(KR_商城.21年贝蒂喜订单整合):
+```json
+{
+ "phone_number": "联系手机", // 需要去除单引号
+ "amount": "买家应付货款", // 字符串转数字
+ "actual_amount": "买家实际支付金额", // 字符串转数字
+ "consume_time": "订单付款时间", // 字符串转DateTime
+ "store_name": "店铺名称" // 自动转换为store_id
+}
+```
+
+#### 5.1.2 GenericCollectionHandler(通用数据采集)
+
+**职责**:
+- 支持动态字段映射
+- 支持批量采集和实时监听两种模式
+- 支持连表查询(lookup)
+- 支持多集合采集
+
+**处理流程**:
+```
+1. 确定要处理的集合
+ - 单集合模式:使用 collection
+ - 多集合模式:使用 collections 数组
+
+2. 批量采集模式
+ For each collection:
+ a. 构建查询条件(filter_conditions)
+ b. 分页查询数据
+ c. 应用字段映射(field_mappings 或 collection_field_mappings)
+ d. 执行连表查询(lookups 或 collection_lookups)
+ e. 写入目标数据库
+ f. 更新任务进度
+
+3. 实时监听模式
+ For each collection:
+ a. 启动 Change Stream 监听
+ b. 处理变更事件
+ c. 应用字段映射和转换
+ d. 写入目标数据库
+```
+
+**字段映射配置**:
+```json
+{
+ "field_mappings": [
+ {
+ "source_field": "源字段名",
+ "target_field": "目标字段名",
+ "transform": "转换函数名", // 可选
+ "value_mapping": [ // 可选:值映射
+ {
+ "source_value": "源值",
+ "target_value": "目标值"
+ }
+ ]
+ }
+ ]
+}
+```
+
+**连表查询配置**:
+```json
+{
+ "lookups": [
+ {
+ "from": "关联集合名",
+ "local_field": "本地字段",
+ "foreign_field": "关联字段",
+ "as": "结果字段名"
+ }
+ ]
+}
+```
+
+#### 5.1.3 DatabaseSyncHandler(数据库同步)
+
+**职责**:
+- 全量同步:首次启动时同步所有数据
+- 增量同步:使用 MongoDB Change Streams 实时监听
+- 支持按数据库分片(多进程并行)
+
+**处理流程**:
+```
+1. 全量同步(首次启动)
+ - 遍历源数据库的所有集合
+ - 批量读取数据
+ - 写入目标数据库
+ - 记录同步进度
+
+2. 增量同步(持续运行)
+ - 启动 Change Stream 监听
+ - 监听所有集合的变更
+ - 实时同步到目标数据库
+ - 记录同步位置
+```
+
+### 5.2 Handler 选择规则
+
+系统根据任务的 `target_type` 自动选择 Handler:
+
+- `target_type = 'consumption_record'` → `ConsumptionCollectionHandler`
+- `target_type = 'generic'` → `GenericCollectionHandler`
+- 配置文件中的 `database_sync` 任务 → `DatabaseSyncHandler`
+
+---
+
+## 六、数据源适配器
+
+### 6.1 适配器工厂
+
+`DataSourceAdapterFactory` 负责创建数据源适配器:
+
+```php
+$adapter = DataSourceAdapterFactory::create(
+ $dataSourceConfig['type'], // mongodb/mysql/postgresql
+ $dataSourceConfig // 数据源配置
+);
+```
+
+### 6.2 支持的适配器类型
+
+#### 6.2.1 MongoDBAdapter
+
+**功能**:
+- 连接 MongoDB 数据库
+- 执行查询操作
+- 支持 Change Stream 监听
+
+**使用场景**:
+- MongoDB 数据源采集
+- 数据库实时同步
+
+#### 6.2.2 MySQLAdapter
+
+**功能**:
+- 连接 MySQL 数据库
+- 执行 SQL 查询
+- 支持事务处理
+
+**使用场景**:
+- MySQL 数据源采集
+- 关系型数据库同步
+
+### 6.3 适配器接口
+
+所有适配器实现 `DataSourceAdapterInterface` 接口:
+
+```php
+interface DataSourceAdapterInterface
+{
+ public function connect(array $config): bool;
+ public function isConnected(): bool;
+ public function query(string $sql, array $params = []): array;
+ public function close(): void;
+}
+```
+
+---
+
+## 七、消息队列处理
+
+### 7.1 数据同步队列
+
+**队列名**:`data_sync`
+
+**消息格式**:
+```json
+{
+ "source_id": "数据源ID",
+ "data": [
+ {
+ "action": "insert|update|delete",
+ "collection": "集合名",
+ "document": {...}
+ }
+ ]
+}
+```
+
+### 7.2 DataSyncWorker 处理流程
+
+```
+Worker 启动
+ ↓
+初始化 RabbitMQ 连接
+ ├─→ 建立连接
+ ├─→ 声明队列
+ ├─→ 设置 QoS(prefetch_count = 10)
+ └─→ 开始消费消息
+ ↓
+监听消息
+ ↓
+收到消息
+ ↓
+processMessage()
+ ├─→ 解析消息(JSON)
+ ├─→ 调用 DataSyncService->syncData()
+ │ ├─→ 写入目标数据库
+ │ ├─→ 更新用户统计(如果是消费记录)
+ │ └─→ 返回同步结果
+ ├─→ 记录日志
+ └─→ 确认消息(ACK)
+ ├─→ 成功:message->ack()
+ └─→ 失败:记录错误,ack(不重试,避免重复数据)
+```
+
+### 7.3 错误处理
+
+- **业务错误**:直接确认消息,不重试(避免重复数据)
+- **系统错误**:记录错误日志,确认消息(不重试)
+
+---
+
+## 八、任务调度机制
+
+### 8.1 DataSyncScheduler 调度流程
+
+```
+Worker 启动
+ ↓
+onWorkerStart()
+ ├─→ 加载配置文件中的任务
+ ├─→ 从数据库加载数据源配置
+ ├─→ 加载数据库中的动态任务
+ └─→ 每30秒刷新一次数据库任务列表
+ ↓
+加载配置文件任务
+ ├─→ 检查任务是否启用
+ ├─→ 检查是否应该由当前 Worker 处理(分片分配)
+ └─→ 根据调度配置执行:
+ ├─→ schedule.enabled = false:立即启动(持续运行)
+ └─→ schedule.enabled = true:使用 Cron 定时执行
+ ↓
+加载数据库任务
+ ├─→ 查询所有 status = 'running' 的任务
+ ├─→ 检查 Redis 启动标志
+ └─→ 根据任务模式执行:
+ ├─→ realtime 模式:立即启动,持续运行
+ └─→ batch 模式:根据 schedule 配置执行
+ ↓
+定时刷新(每30秒)
+ ├─→ 检查新任务(status = 'running')
+ ├─→ 检查任务状态变更(启动/暂停/停止)
+ └─→ 更新运行中的任务列表
+```
+
+### 8.2 分片策略
+
+系统支持多种分片策略,实现多进程并行处理:
+
+#### 8.2.1 None(无分片)
+
+- 所有 Worker 都处理
+- 通过分布式锁保证只有一个执行
+
+#### 8.2.2 By Database(按数据库分片)
+
+- 所有 Worker 都处理
+- 每个 Worker 处理不同的数据库
+- 在 Handler 中进行数据库分配
+
+#### 8.2.3 其他分片策略
+
+- 按 Worker ID 取模分配
+- 根据 `shard_count` 配置分片数量
+
+### 8.3 分布式锁
+
+**锁键**:`lock:data_collection:{taskId}`
+
+**配置**:
+- TTL:300秒(默认)
+- 重试次数:3次
+- 重试延迟:1000毫秒
+
+**使用场景**:
+- 防止同一任务被多个 Worker 重复执行
+- 保证任务执行的唯一性
+
+---
+
+## 九、字段映射和转换
+
+### 9.1 字段映射配置
+
+**单集合模式**:
+```json
+{
+ "field_mappings": [
+ {
+ "source_field": "源字段名",
+ "target_field": "目标字段名",
+ "transform": "转换函数名",
+ "value_mapping": [
+ {
+ "source_value": "源值",
+ "target_value": "目标值"
+ }
+ ]
+ }
+ ]
+}
+```
+
+**多集合模式**:
+```json
+{
+ "collection_field_mappings": {
+ "集合1": [
+ {
+ "source_field": "源字段名",
+ "target_field": "目标字段名"
+ }
+ ],
+ "集合2": [
+ {
+ "source_field": "源字段名",
+ "target_field": "目标字段名"
+ }
+ ]
+ }
+}
+```
+
+### 9.2 支持的转换函数
+
+- `parse_phone` - 解析手机号(去除特殊字符)
+- `parse_amount` - 解析金额(字符串转数字)
+- `parse_datetime` - 解析时间(字符串转DateTime)
+- `parse_int` - 解析整数
+- `parse_float` - 解析浮点数
+- `parse_bool` - 解析布尔值
+
+### 9.3 值映射
+
+支持将源字段的值映射为目标值:
+
+```json
+{
+ "value_mapping": [
+ {
+ "source_value": "交易成功",
+ "target_value": "0"
+ },
+ {
+ "source_value": "交易关闭",
+ "target_value": "1"
+ }
+ ]
+}
+```
+
+---
+
+## 十、连表查询(Lookup)
+
+### 10.1 Lookup 配置
+
+**单集合模式**:
+```json
+{
+ "lookups": [
+ {
+ "from": "关联集合名",
+ "local_field": "本地字段",
+ "foreign_field": "关联字段",
+ "as": "结果字段名"
+ }
+ ]
+}
+```
+
+**多集合模式**:
+```json
+{
+ "collection_lookups": {
+ "集合1": [
+ {
+ "from": "关联集合名",
+ "local_field": "本地字段",
+ "foreign_field": "关联字段",
+ "as": "结果字段名"
+ }
+ ]
+ }
+}
+```
+
+### 10.2 Lookup 执行
+
+系统使用 MongoDB 的 `$lookup` 聚合管道实现连表查询:
+
+```javascript
+{
+ $lookup: {
+ from: "关联集合名",
+ localField: "本地字段",
+ foreignField: "关联字段",
+ as: "结果字段名"
+ }
+}
+```
+
+---
+
+## 十一、过滤条件
+
+### 11.1 过滤条件配置
+
+```json
+{
+ "filter_conditions": [
+ {
+ "field": "字段名",
+ "operator": "操作符",
+ "value": "值"
+ }
+ ]
+}
+```
+
+### 11.2 支持的操作符
+
+- `>` - 大于
+- `>=` - 大于等于
+- `<` - 小于
+- `<=` - 小于等于
+- `=` / `==` - 等于
+- `!=` - 不等于
+- `in` - 在列表中
+- `not_in` - 不在列表中
+- `like` - 模糊匹配(MongoDB 使用正则表达式)
+
+### 11.3 过滤条件应用
+
+系统将过滤条件转换为 MongoDB 查询条件:
+
+```php
+$filter = [];
+foreach ($filterConditions as $condition) {
+ $field = $condition['field'];
+ $operator = $condition['operator'];
+ $value = $condition['value'];
+
+ switch ($operator) {
+ case '>':
+ $filter[$field] = ['$gt' => $value];
+ break;
+ case '>=':
+ $filter[$field] = ['$gte' => $value];
+ break;
+ // ... 其他操作符
+ }
+}
+```
+
+---
+
+## 十二、任务进度跟踪
+
+### 12.1 进度更新
+
+Handler 在处理数据时,定期更新任务进度:
+
+```php
+$this->taskService->updateProgress($taskId, [
+ 'status' => 'running',
+ 'processed_count' => $processedCount,
+ 'success_count' => $successCount,
+ 'error_count' => $errorCount,
+ 'total_count' => $totalCount,
+ 'percentage' => ($processedCount / $totalCount) * 100,
+ 'last_sync_time' => new \MongoDB\BSON\UTCDateTime(time() * 1000),
+]);
+```
+
+### 12.2 进度查询
+
+**API**:`GET /api/data-collection-tasks/{taskId}/progress`
+
+**返回格式**:
+```json
+{
+ "status": "running",
+ "processed_count": 1000,
+ "success_count": 995,
+ "error_count": 5,
+ "total_count": 10000,
+ "percentage": 10.0,
+ "start_time": "2025-01-01T10:00:00.000Z",
+ "last_sync_time": "2025-01-01T10:05:00.000Z"
+}
+```
+
+---
+
+## 十三、错误处理和重试
+
+### 13.1 错误处理策略
+
+**单个记录错误**:
+- 记录错误日志
+- 错误计数 +1
+- 继续处理下一条记录
+
+**任务级错误**:
+- 更新任务状态为 `error`
+- 记录错误信息到 `progress.last_error`
+- 记录错误日志
+
+### 13.2 重试机制
+
+**批量采集**:
+- 支持断点续传(记录 `last_sync_time`)
+- 下次执行时从上次位置继续
+
+**实时监听**:
+- 连接断开时自动重连
+- 记录同步位置,避免数据丢失
+
+---
+
+## 十四、数据流图
+
+### 14.1 批量采集数据流
+
+```
+数据源(MongoDB/MySQL)
+ ↓
+DataSyncScheduler(调度器)
+ ├─→ 创建数据源适配器
+ └─→ 调用 Handler->collect()
+ ↓
+ Handler 执行采集
+ ├─→ 分页查询数据
+ ├─→ 应用字段映射
+ ├─→ 执行字段转换
+ ├─→ 执行连表查询
+ └─→ 写入目标数据库
+ ├─→ 直接写入(同步)
+ └─→ 推送到队列(异步)
+ ↓
+ DataSyncWorker 消费消息
+ ↓
+ 写入目标数据库
+```
+
+### 14.2 实时监听数据流
+
+```
+数据源(MongoDB)
+ ↓
+DataSyncScheduler(调度器)
+ ├─→ 创建数据源适配器
+ └─→ 调用 Handler->collect()
+ ↓
+ Handler 启动 Change Stream
+ ↓
+ 持续监听变更事件
+ ↓
+ 处理变更事件
+ ├─→ 解析变更文档
+ ├─→ 应用字段映射
+ └─→ 写入目标数据库
+```
+
+### 14.3 消费记录采集数据流
+
+```
+订单数据(KR_商城)
+ ↓
+ConsumptionCollectionHandler
+ ├─→ 提取手机号
+ ├─→ 字段映射和转换
+ └─→ 调用 ConsumptionService->createRecord()
+ ├─→ 身份解析(IdentifierService)
+ │ ├─→ 通过手机号解析 user_id
+ │ └─→ 如果不存在,创建临时人
+ ├─→ 门店处理(StoreService)
+ │ └─→ 查找或创建门店,获取 store_id
+ ├─→ 写入 consumption_records(按时间分表)
+ ├─→ 更新 user_profile 统计信息
+ └─→ 触发标签计算(推送到 RabbitMQ)
+ ↓
+ TagCalculationWorker 消费消息
+ ↓
+ 计算用户标签
+```
+
+---
+
+## 十五、API 接口汇总
+
+### 15.1 数据采集任务接口
+
+- `GET /api/data-collection-tasks` - 获取任务列表
+- `POST /api/data-collection-tasks` - 创建任务
+- `GET /api/data-collection-tasks/{taskId}` - 获取任务详情
+- `PUT /api/data-collection-tasks/{taskId}` - 更新任务
+- `DELETE /api/data-collection-tasks/{taskId}` - 删除任务
+- `POST /api/data-collection-tasks/{taskId}/start` - 启动任务
+- `POST /api/data-collection-tasks/{taskId}/pause` - 暂停任务
+- `POST /api/data-collection-tasks/{taskId}/stop` - 停止任务
+- `GET /api/data-collection-tasks/{taskId}/progress` - 获取任务进度
+
+### 15.2 数据源接口
+
+- `GET /api/data-sources` - 获取数据源列表
+- `POST /api/data-sources` - 创建数据源
+- `GET /api/data-sources/{dataSourceId}` - 获取数据源详情
+- `PUT /api/data-sources/{dataSourceId}` - 更新数据源
+- `DELETE /api/data-sources/{dataSourceId}` - 删除数据源
+- `POST /api/data-sources/test-connection` - 测试数据源连接
+
+### 15.3 辅助接口
+
+- `GET /api/data-collection-tasks/data-sources` - 获取数据源列表(用于选择)
+- `GET /api/data-collection-tasks/data-sources/{id}/databases` - 获取数据库列表
+- `GET /api/data-collection-tasks/data-sources/{id}/databases/{db}/collections` - 获取集合列表
+- `GET /api/data-collection-tasks/data-sources/{id}/databases/{db}/collections/{coll}/fields` - 获取字段列表
+- `GET /api/data-collection-tasks/handlers/{type}/target-fields` - 获取Handler目标字段列表
+- `POST /api/data-collection-tasks/preview-query` - 预览查询结果
+
+---
+
+## 十六、关键设计要点
+
+### 16.1 配置化设计
+
+- **任务配置**:支持配置文件和数据库两种方式
+- **字段映射**:通过配置实现灵活的字段映射
+- **数据源配置**:统一管理,支持动态加载
+
+### 16.2 高可用性
+
+- **分布式锁**:防止任务重复执行
+- **断点续传**:支持从上次位置继续
+- **错误隔离**:单个记录错误不影响整体任务
+- **自动重连**:实时监听模式支持自动重连
+
+### 16.3 可扩展性
+
+- **Handler 模式**:易于添加新的采集类型
+- **适配器模式**:易于支持新的数据源类型
+- **分片策略**:支持多进程并行处理
+
+### 16.4 可观测性
+
+- **进度跟踪**:实时跟踪任务进度
+- **日志记录**:完整的业务日志和错误日志
+- **统计信息**:记录任务执行统计
+
+---
+
+## 十七、使用示例
+
+### 17.1 创建消费记录采集任务
+
+**场景**:从 KR_商城.21年贝蒂喜订单整合 采集消费记录
+
+**配置步骤**:
+
+1. **选择数据源**:选择 KR_商城 数据源
+2. **选择数据库和集合**:`KR_商城` / `21年贝蒂喜订单整合`
+3. **选择Handler**:`消费记录处理(ConsumptionCollectionHandler)`
+4. **配置字段映射**:
+ ```json
+ {
+ "phone_number": "联系手机",
+ "amount": "买家应付货款",
+ "actual_amount": "买家实际支付金额",
+ "consume_time": "订单付款时间",
+ "store_name": "店铺名称"
+ }
+ ```
+5. **配置过滤条件**(可选):
+ ```json
+ [
+ {
+ "field": "买家实际支付金额",
+ "operator": "!=",
+ "value": "0"
+ }
+ ]
+ ```
+6. **配置调度计划**:
+ - 模式:`batch`(批量采集)
+ - 调度:启用定时任务,Cron 表达式 `0 2 * * *`(每天凌晨2点)
+7. **保存并启动任务**
+
+### 17.2 创建通用数据采集任务
+
+**场景**:从多个集合采集数据并写入目标数据库
+
+**配置步骤**:
+
+1. **选择数据源**:选择源数据源
+2. **选择数据库和集合**:支持多集合模式
+3. **选择Handler**:`通用数据采集(GenericCollectionHandler)`
+4. **配置目标数据源**:选择目标数据源、数据库、集合
+5. **配置字段映射**:为每个集合配置字段映射
+6. **配置连表查询**(可选):配置 lookup 查询
+7. **配置过滤条件**(可选)
+8. **配置调度计划**
+9. **保存并启动任务**
+
+---
+
+## 十八、总结
+
+数据采集系统提供了完整的数据采集、同步和管理功能,支持批量采集和实时监听两种模式,通过配置化的方式灵活管理采集任务。
+
+### 18.1 核心特性
+
+- ✅ **多数据源支持**:MongoDB、MySQL、PostgreSQL
+- ✅ **多种采集模式**:批量采集、实时监听
+- ✅ **灵活的字段映射**:支持字段转换、值映射
+- ✅ **连表查询支持**:支持 MongoDB lookup 查询
+- ✅ **任务管理**:完整的任务创建、执行、监控功能
+- ✅ **进度跟踪**:实时跟踪任务进度和统计
+- ✅ **错误处理**:完善的错误处理和重试机制
+
+### 18.2 使用场景
+
+1. **历史数据迁移**:批量采集历史数据
+2. **实时数据同步**:实时监听数据变更
+3. **消费记录采集**:采集订单数据并触发标签计算
+4. **数据库同步**:数据库间的实时同步
+
+---
+
+**文档生成时间**:2025-01-XX
+**项目版本**:基于当前代码库分析
+
diff --git a/Moncter/提示词/当前架构设计/标签引擎相关功能流程逻辑.md b/Moncter/提示词/当前架构设计/标签引擎相关功能流程逻辑.md
new file mode 100644
index 00000000..c24210d7
--- /dev/null
+++ b/Moncter/提示词/当前架构设计/标签引擎相关功能流程逻辑.md
@@ -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
+**项目版本**:基于当前代码库分析
+
diff --git a/Moncter/提示词/当前架构设计/采集进度100%停止机制分析报告.md b/Moncter/提示词/当前架构设计/采集进度100%停止机制分析报告.md
new file mode 100644
index 00000000..288f7d28
--- /dev/null
+++ b/Moncter/提示词/当前架构设计/采集进度100%停止机制分析报告.md
@@ -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%后停止。
+
diff --git a/Moncter/提示词/数据列表动态打标签实现方案.md b/Moncter/提示词/数据列表动态打标签实现方案.md
new file mode 100644
index 00000000..7b041b6d
--- /dev/null
+++ b/Moncter/提示词/数据列表动态打标签实现方案.md
@@ -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
+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
+**实现方案**:基于数据列表动态打标签
diff --git a/Moncter/提示词/数据列表管理_真实API接入说明.md b/Moncter/提示词/数据列表管理_真实API接入说明.md
new file mode 100644
index 00000000..5f52eb39
--- /dev/null
+++ b/Moncter/提示词/数据列表管理_真实API接入说明.md
@@ -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([])
+const collectionObjects = ref([])
+```
+
+**用途**:因为 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
+ 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
diff --git a/Moncter/提示词/数据列表管理界面使用说明.md b/Moncter/提示词/数据列表管理界面使用说明.md
new file mode 100644
index 00000000..45c8e067
--- /dev/null
+++ b/Moncter/提示词/数据列表管理界面使用说明.md
@@ -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数据
diff --git a/Moncter/提示词/最新架构逻辑.md b/Moncter/提示词/最新架构逻辑.md
new file mode 100644
index 00000000..51ac630a
--- /dev/null
+++ b/Moncter/提示词/最新架构逻辑.md
@@ -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
+**维护说明**:本文档作为系统架构的核心记忆点,如有架构变更,请及时更新本文档
diff --git a/Moncter/提示词/标签定义逻辑对比分析.md b/Moncter/提示词/标签定义逻辑对比分析.md
new file mode 100644
index 00000000..e993317a
--- /dev/null
+++ b/Moncter/提示词/标签定义逻辑对比分析.md
@@ -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
+**基于代码版本**:当前代码库分析
diff --git a/Moncter/提示词/标签定逻辑.md b/Moncter/提示词/标签定逻辑.md
new file mode 100644
index 00000000..8cbfaddd
--- /dev/null
+++ b/Moncter/提示词/标签定逻辑.md
@@ -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、根据定义标签的规则条件,直接根据用户表遍历过去进行打标签,然后将标签存储。
+
\ No newline at end of file
diff --git a/Moncter/提示词/标签定逻辑真实.md b/Moncter/提示词/标签定逻辑真实.md
new file mode 100644
index 00000000..2587d318
--- /dev/null
+++ b/Moncter/提示词/标签定逻辑真实.md
@@ -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
+**基于需求**:按照原始思路完善
diff --git a/Moncter/提示词/集合筛选功能使用技巧.md b/Moncter/提示词/集合筛选功能使用技巧.md
new file mode 100644
index 00000000..93c64aab
--- /dev/null
+++ b/Moncter/提示词/集合筛选功能使用技巧.md
@@ -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+
diff --git a/Moncter/提示词/项目完整代码逻辑分析报告.md b/Moncter/提示词/项目完整代码逻辑分析报告.md
new file mode 100644
index 00000000..f1275446
--- /dev/null
+++ b/Moncter/提示词/项目完整代码逻辑分析报告.md
@@ -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
+**项目版本**: 基于当前代码库分析
+