This commit is contained in:
wong
2026-01-05 11:04:08 +08:00
parent 8beb2cb6fe
commit f096e07ec8
19 changed files with 4352 additions and 27 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@ SuperAdmin/.specstory/
Cunkebao/dist
Touchkebao/.specstory/
Serverruntime/
Moncter/提示词/

24
Moncter/.env.example Normal file
View File

@@ -0,0 +1,24 @@
# 存客宝标签系统 - 环境变量配置示例
# 复制此文件为 .env 并修改相应的配置值,不要提交到版本控制系统
# ============================================
# 加密配置
# ============================================
# AES 加密密钥至少32字符建议使用随机生成的强密钥
# 生产环境请务必修改此密钥,并妥善保管
ENCRYPTION_AES_KEY=your-32-byte-secret-key-here-12345678
# 哈希盐值(用于身份证哈希,增强安全性)
# 生产环境请务必修改此盐值
ENCRYPTION_HASH_SALT=your-hash-salt-here-change-in-production
# ============================================
# 应用配置
# ============================================
# 应用环境development/production
APP_ENV=development
# 应用调试模式true/false
APP_DEBUG=true

View File

@@ -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"
}
}
}
}
}

Binary file not shown.

View File

@@ -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);
}
}

View File

@@ -1,16 +1,557 @@
<?php
namespace app\controller;
use app\repository\UserProfileRepository;
use app\service\UserService;
use app\utils\ApiResponseHelper;
use app\utils\DataMaskingHelper;
use app\utils\LoggerHelper;
use support\Request;
use support\Response;
class UserController
{
public function hello(Request $request)
/**
* 创建用户
*
* POST /api/users
*
* 请求体示例:
* {
* "id_card": "110101199001011234",
* "id_card_type": "身份证",
* "name": "张三",
* "phone": "13800138000",
* "email": "zhangsan@example.com",
* "gender": 1,
* "birthday": "1990-01-01",
* "address": "北京市朝阳区"
* }
*/
public function store(Request $request): Response
{
$default_name = 'webman';
// 从get请求里获得name参数如果没有传递name参数则返回$default_name
$name = $request->get('name', $default_name);
// 向浏览器返回字符串
return response('hello ' . $name);
try {
LoggerHelper::logRequest('POST', '/api/users');
$rawBody = $request->rawBody();
// 调试:记录原始请求体
if (empty($rawBody)) {
return ApiResponseHelper::error('请求体为空,请确保 Content-Type 为 application/json 并发送有效的 JSON 数据', 400);
}
$body = json_decode($rawBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$errorMsg = '请求体必须是有效的 JSON 格式';
$jsonError = json_last_error_msg();
if ($jsonError) {
$errorMsg .= ': ' . $jsonError;
}
// 开发环境输出更多调试信息
if (getenv('APP_DEBUG') === 'true') {
$errorMsg .= ' (原始请求体: ' . substr($rawBody, 0, 200) . ')';
}
return ApiResponseHelper::error($errorMsg, 400);
}
$userService = new UserService(new UserProfileRepository());
$result = $userService->createUser($body);
return ApiResponseHelper::success($result, '用户创建成功');
} catch (\InvalidArgumentException $e) {
return ApiResponseHelper::error($e->getMessage(), 400);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
}
/**
* 查询用户信息
*
* GET /api/users/{user_id}?decrypt_id_card=1
*
* @param Request $request
* @return Response
*/
public function show(Request $request): Response
{
try {
// 从请求路径中解析 user_id
$path = $request->path();
if (preg_match('#/api/users/([^/]+)#', $path, $matches)) {
$userId = $matches[1];
} else {
$userId = $request->get('user_id');
if (!$userId) {
throw new \InvalidArgumentException('缺少 user_id 参数');
}
}
LoggerHelper::logRequest('GET', $path, ['user_id' => $userId]);
// 检查是否需要解密身份证(需要权限控制,这里简单用参数控制)
$decryptIdCard = (bool)$request->get('decrypt_id_card', false);
$userService = new UserService(new UserProfileRepository());
$user = $userService->getUserById($userId, $decryptIdCard);
if (!$user) {
return ApiResponseHelper::error('用户不存在', 404, 404);
}
// 如果不需要解密身份证,对敏感字段进行脱敏
if (!$decryptIdCard) {
$user = DataMaskingHelper::maskArray($user, ['phone', 'email']);
}
LoggerHelper::logBusiness('get_user_info', [
'user_id' => $userId,
'decrypt_id_card' => $decryptIdCard,
]);
return ApiResponseHelper::success($user);
} catch (\InvalidArgumentException $e) {
return ApiResponseHelper::error($e->getMessage(), 400);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
/**
* 更新用户信息
*
* PUT /api/users/{user_id}
*
* 请求体示例:
* {
* "name": "张三",
* "phone": "13800138000",
* "email": "zhangsan@example.com",
* "gender": 1,
* "birthday": "1990-01-01",
* "address": "北京市朝阳区",
* "status": 0
* }
*/
public function update(Request $request): Response
{
try {
// 从请求路径中解析 user_id
$path = $request->path();
if (preg_match('#/api/users/([^/]+)#', $path, $matches)) {
$userId = $matches[1];
} else {
$userId = $request->get('user_id');
if (!$userId) {
throw new \InvalidArgumentException('缺少 user_id 参数');
}
}
LoggerHelper::logRequest('PUT', $path, ['user_id' => $userId]);
$rawBody = $request->rawBody();
// 调试:记录原始请求体
if (empty($rawBody)) {
return ApiResponseHelper::error('请求体为空,请确保 Content-Type 为 application/json 并发送有效的 JSON 数据', 400);
}
$body = json_decode($rawBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$errorMsg = '请求体必须是有效的 JSON 格式';
$jsonError = json_last_error_msg();
if ($jsonError) {
$errorMsg .= ': ' . $jsonError;
}
// 开发环境输出更多调试信息
if (getenv('APP_DEBUG') === 'true') {
$errorMsg .= ' (原始请求体: ' . substr($rawBody, 0, 200) . ')';
}
return ApiResponseHelper::error($errorMsg, 400);
}
if (empty($body)) {
return ApiResponseHelper::error('请求体不能为空', 400);
}
$userService = new UserService(new UserProfileRepository());
$result = $userService->updateUser($userId, $body);
// 脱敏处理
$result = DataMaskingHelper::maskArray($result, ['phone', 'email']);
return ApiResponseHelper::success($result, '用户更新成功');
} catch (\InvalidArgumentException $e) {
return ApiResponseHelper::error($e->getMessage(), 400);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
/**
* 解密身份证号
*
* GET /api/users/{user_id}/decrypt-id-card
*/
public function decryptIdCard(Request $request): Response
{
try {
// 从请求路径中解析 user_id
$path = $request->path();
if (preg_match('#/api/users/([^/]+)/decrypt-id-card#', $path, $matches)) {
$userId = $matches[1];
} else {
$userId = $request->get('user_id');
if (!$userId) {
throw new \InvalidArgumentException('缺少 user_id 参数');
}
}
LoggerHelper::logRequest('GET', $path, ['user_id' => $userId]);
$userService = new UserService(new UserProfileRepository());
$user = $userService->getUserById($userId, true); // 强制解密
if (!$user) {
return ApiResponseHelper::error('用户不存在', 404, 404);
}
LoggerHelper::logBusiness('decrypt_id_card', [
'user_id' => $userId,
]);
return ApiResponseHelper::success([
'user_id' => $user['user_id'],
'id_card' => $user['id_card'] ?? ''
]);
} catch (\InvalidArgumentException $e) {
return ApiResponseHelper::error($e->getMessage(), 400);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
/**
* 删除用户(软删除)
*
* DELETE /api/users/{user_id}
*/
public function destroy(Request $request): Response
{
try {
// 从请求路径中解析 user_id
$path = $request->path();
if (preg_match('#/api/users/([^/]+)#', $path, $matches)) {
$userId = $matches[1];
} else {
$userId = $request->get('user_id');
if (!$userId) {
throw new \InvalidArgumentException('缺少 user_id 参数');
}
}
LoggerHelper::logRequest('DELETE', $path, ['user_id' => $userId]);
$userService = new UserService(new UserProfileRepository());
$userService->deleteUser($userId);
return ApiResponseHelper::success(null, '用户删除成功');
} catch (\InvalidArgumentException $e) {
return ApiResponseHelper::error($e->getMessage(), 400);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
/**
* 搜索用户(支持多种搜索条件组合)
*
* POST /api/users/search
*
* 支持以下搜索方式:
* 1. 基础字段搜索:姓名、手机号、邮箱、身份证号等
* 2. 标签筛选:根据用户标签筛选
* 3. 组合搜索:基础字段 + 标签筛选
*
* 请求体示例1姓名模糊搜索
* {
* "name": "张三",
* "page": 1,
* "page_size": 20
* }
*
* 请求体示例2组合搜索姓名 + 手机号):
* {
* "name": "张",
* "phone": "138",
* "page": 1,
* "page_size": 20
* }
*
* 请求体示例3根据标签筛选
* {
* "tag_conditions": [
* {
* "tag_code": "high_consumer",
* "operator": "=",
* "value": "high"
* }
* ],
* "logic": "AND",
* "page": 1,
* "page_size": 20
* }
*
* 请求体示例4组合搜索基础字段 + 标签):
* {
* "name": "张",
* "min_total_amount": 1000,
* "tag_conditions": [
* {
* "tag_code": "active_user",
* "operator": "=",
* "value": "active"
* }
* ],
* "page": 1,
* "page_size": 20
* }
*/
public function search(Request $request): Response
{
try {
LoggerHelper::logRequest('POST', '/api/users/search');
$rawBody = $request->rawBody();
// 调试:记录原始请求体
if (empty($rawBody)) {
return ApiResponseHelper::error('请求体为空,请确保 Content-Type 为 application/json 并发送有效的 JSON 数据', 400);
}
$body = json_decode($rawBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$errorMsg = '请求体必须是有效的 JSON 格式';
$jsonError = json_last_error_msg();
if ($jsonError) {
$errorMsg .= ': ' . $jsonError;
}
// 开发环境输出更多调试信息
if (getenv('APP_DEBUG') === 'true') {
$errorMsg .= ' (原始请求体: ' . substr($rawBody, 0, 200) . ')';
}
return ApiResponseHelper::error($errorMsg, 400);
}
$page = (int)($body['page'] ?? 1);
$pageSize = (int)($body['page_size'] ?? 20);
if ($page < 1) {
$page = 1;
}
if ($pageSize < 1 || $pageSize > 100) {
$pageSize = 20;
}
$userService = new UserService(new UserProfileRepository());
// 情况1仅根据身份证号查找返回单个用户不分页
if (!empty($body['id_card']) && empty($body['tag_conditions']) && empty($body['name']) && empty($body['phone']) && empty($body['email'])) {
$user = $userService->findUserByIdCard($body['id_card']);
if (!$user) {
return ApiResponseHelper::error('未找到该身份证号对应的用户', 404, 404);
}
// 脱敏处理
$user = DataMaskingHelper::maskArray($user, ['phone', 'email']);
LoggerHelper::logBusiness('search_user_by_id_card', [
'found' => true,
]);
return ApiResponseHelper::success($user);
}
// 情况2根据标签筛选用户可能结合基础字段搜索
if (!empty($body['tag_conditions'])) {
$tagService = new \app\service\TagService(
new \app\repository\TagDefinitionRepository(),
new UserProfileRepository(),
new \app\repository\UserTagRepository(),
new \app\repository\TagHistoryRepository(),
new \app\service\TagRuleEngine\SimpleRuleEngine()
);
$conditions = $body['tag_conditions'];
$logic = $body['logic'] ?? 'AND';
$includeUserInfo = true; // 标签筛选需要用户信息
// 验证条件格式
foreach ($conditions as $condition) {
if (!isset($condition['tag_code']) || !isset($condition['operator']) || !isset($condition['value'])) {
throw new \InvalidArgumentException('每个条件必须包含 tag_code、operator 和 value 字段');
}
}
// 先根据标签筛选用户
$tagResult = $tagService->filterUsersByTags(
$conditions,
$logic,
1, // 先获取所有符合条件的用户ID
10000, // 临时设置大值获取所有用户ID
true
);
$userIds = array_column($tagResult['users'], 'user_id');
if (empty($userIds)) {
return ApiResponseHelper::success([
'users' => [],
'total' => 0,
'page' => $page,
'page_size' => $pageSize,
'total_pages' => 0,
]);
}
// 如果有基础字段搜索条件,进一步筛选
$baseConditions = [];
if (!empty($body['name'])) {
$baseConditions['name'] = $body['name'];
}
if (!empty($body['phone'])) {
$baseConditions['phone'] = $body['phone'];
$baseConditions['phone_exact'] = $body['phone_exact'] ?? false;
}
if (!empty($body['email'])) {
$baseConditions['email'] = $body['email'];
$baseConditions['email_exact'] = $body['email_exact'] ?? false;
}
if (isset($body['gender']) && $body['gender'] !== '') {
$baseConditions['gender'] = $body['gender'];
}
if (isset($body['status']) && $body['status'] !== '') {
$baseConditions['status'] = $body['status'];
}
if (isset($body['min_total_amount'])) {
$baseConditions['min_total_amount'] = $body['min_total_amount'];
}
if (isset($body['max_total_amount'])) {
$baseConditions['max_total_amount'] = $body['max_total_amount'];
}
if (isset($body['min_total_count'])) {
$baseConditions['min_total_count'] = $body['min_total_count'];
}
if (isset($body['max_total_count'])) {
$baseConditions['max_total_count'] = $body['max_total_count'];
}
// 如果有基础字段条件,需要进一步筛选
if (!empty($baseConditions)) {
$baseConditions['user_ids'] = $userIds; // 限制在标签筛选的用户范围内
$result = $userService->searchUsers($baseConditions, $page, $pageSize);
} else {
// 没有基础字段条件,直接使用标签筛选结果并分页
$total = count($userIds);
$offset = ($page - 1) * $pageSize;
$pagedUserIds = array_slice($userIds, $offset, $pageSize);
// 获取用户详细信息
$users = [];
foreach ($pagedUserIds as $userId) {
$user = $userService->getUserById($userId, false);
if ($user) {
$users[] = $user;
}
}
$result = [
'users' => $users,
'total' => $total,
'page' => $page,
'page_size' => $pageSize,
'total_pages' => (int)ceil($total / $pageSize),
];
}
// 对返回的用户信息进行脱敏处理
if (isset($result['users']) && is_array($result['users'])) {
foreach ($result['users'] as &$user) {
$user = DataMaskingHelper::maskArray($user, ['phone', 'email']);
}
unset($user);
}
LoggerHelper::logBusiness('search_users_by_tags', [
'conditions_count' => count($conditions),
'base_conditions' => !empty($baseConditions),
'result_count' => $result['total'] ?? 0,
]);
return ApiResponseHelper::success($result);
}
// 情况3仅基础字段搜索无标签条件
$baseConditions = [];
if (!empty($body['name'])) {
$baseConditions['name'] = $body['name'];
}
if (!empty($body['phone'])) {
$baseConditions['phone'] = $body['phone'];
$baseConditions['phone_exact'] = $body['phone_exact'] ?? false;
}
if (!empty($body['email'])) {
$baseConditions['email'] = $body['email'];
$baseConditions['email_exact'] = $body['email_exact'] ?? false;
}
if (!empty($body['id_card'])) {
$baseConditions['id_card'] = $body['id_card'];
}
if (isset($body['gender']) && $body['gender'] !== '') {
$baseConditions['gender'] = $body['gender'];
}
if (isset($body['status']) && $body['status'] !== '') {
$baseConditions['status'] = $body['status'];
}
if (isset($body['min_total_amount'])) {
$baseConditions['min_total_amount'] = $body['min_total_amount'];
}
if (isset($body['max_total_amount'])) {
$baseConditions['max_total_amount'] = $body['max_total_amount'];
}
if (isset($body['min_total_count'])) {
$baseConditions['min_total_count'] = $body['min_total_count'];
}
if (isset($body['max_total_count'])) {
$baseConditions['max_total_count'] = $body['max_total_count'];
}
if (empty($baseConditions)) {
return ApiResponseHelper::error('请提供至少一个搜索条件', 400);
}
$result = $userService->searchUsers($baseConditions, $page, $pageSize);
// 对返回的用户信息进行脱敏处理
if (isset($result['users']) && is_array($result['users'])) {
foreach ($result['users'] as &$user) {
$user = DataMaskingHelper::maskArray($user, ['phone', 'email']);
}
unset($user);
}
LoggerHelper::logBusiness('search_users_by_base_fields', [
'conditions' => array_keys($baseConditions),
'result_count' => $result['total'] ?? 0,
]);
return ApiResponseHelper::success($result);
} catch (\InvalidArgumentException $e) {
return ApiResponseHelper::error($e->getMessage(), 400);
} catch (\Throwable $e) {
return ApiResponseHelper::exception($e);
}
}
}

View File

@@ -27,7 +27,12 @@
"php": ">=8.1",
"workerman/webman-framework": "^2.1",
"monolog/monolog": "^2.0",
"mongodb/laravel-mongodb": "^4.0"
"mongodb/laravel-mongodb": "^4.0",
"vlucas/phpdotenv": "^5.6",
"predis/predis": "^2.0",
"dragonmantank/cron-expression": "^3.6",
"php-amqplib/php-amqplib": "^3.7",
"ramsey/uuid": "^4.7"
},
"suggest": {
"ext-event": "For better performance. "

896
Moncter/composer.lock generated
View File

@@ -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",

View File

@@ -14,7 +14,6 @@
return [
'files' => [
base_path() . '/app/functions.php',
base_path() . '/support/Request.php',
base_path() . '/support/Response.php',
]

View File

@@ -14,4 +14,5 @@
return [
support\bootstrap\Session::class,
support\bootstrap\MongoDB::class,
];

View File

@@ -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', // 认证机制(默认推荐)
],
],

View File

@@ -28,5 +28,10 @@ return [
],
]
],
'processors' => [
[
'class' => app\utils\LogMaskingProcessor::class,
],
],
],
];

View File

@@ -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,
],
];

View File

@@ -15,7 +15,106 @@
use Webman\Route;
// 数据库连接测试接口
Route::get('/api/test/db', [app\controller\IndexController::class, 'testDb']);
// ============================================
// 用户相关接口RESTful
// ============================================
Route::post('/api/users', [app\controller\UserController::class, 'store']); // 创建用户
Route::get('/api/users/{user_id}', [app\controller\UserController::class, 'show']); // 查询用户
Route::put('/api/users/{user_id}', [app\controller\UserController::class, 'update']); // 更新用户
Route::delete('/api/users/{user_id}', [app\controller\UserController::class, 'destroy']); // 删除用户
Route::get('/api/users/{user_id}/decrypt-id-card', [app\controller\UserController::class, 'decryptIdCard']); // 解密身份证
Route::post('/api/users/search', [app\controller\UserController::class, 'search']); // 搜索用户(复杂查询)
// ============================================
// 用户标签相关接口RESTful
// ============================================
Route::get('/api/users/{user_id}/tags', [app\controller\TagController::class, 'listByUser']); // 查询用户标签
Route::put('/api/users/{user_id}/tags', [app\controller\TagController::class, 'calculate']); // 更新/计算用户标签
Route::delete('/api/users/{user_id}/tags/{tag_id}', [app\controller\TagController::class, 'destroy']); // 删除用户标签
// ============================================
// 消费记录相关接口
// ============================================
Route::post('/api/consumption/record', [app\controller\ConsumptionController::class, 'store']); // 创建消费记录
// ============================================
// 标签定义相关接口(管理接口)
// ============================================
Route::post('/api/tags/filter', [app\controller\TagController::class, 'filter']); // 根据标签筛选用户
Route::get('/api/tags/statistics', [app\controller\TagController::class, 'statistics']); // 获取标签统计信息
Route::get('/api/tags/history', [app\controller\TagController::class, 'history']); // 获取标签历史记录
Route::post('/api/tag-definitions/batch', [app\controller\TagController::class, 'init']); // 批量初始化标签定义
Route::get('/api/tag-definitions', [app\controller\TagDefinitionController::class, 'list']); // 获取标签定义列表
Route::post('/api/tag-definitions', [app\controller\TagDefinitionController::class, 'create']); // 创建标签定义
Route::get('/api/tag-definitions/{tag_id}', [app\controller\TagDefinitionController::class, 'detail']); // 获取标签定义详情
Route::put('/api/tag-definitions/{tag_id}', [app\controller\TagDefinitionController::class, 'update']); // 更新标签定义
Route::delete('/api/tag-definitions/{tag_id}', [app\controller\TagDefinitionController::class, 'delete']); // 删除标签定义
// ============================================
// 标签任务管理接口
// ============================================
Route::post('/api/tag-tasks', [app\controller\TagTaskController::class, 'create']); // 创建标签任务
Route::put('/api/tag-tasks/{task_id}', [app\controller\TagTaskController::class, 'update']); // 更新标签任务
Route::delete('/api/tag-tasks/{task_id}', [app\controller\TagTaskController::class, 'delete']); // 删除标签任务
Route::get('/api/tag-tasks', [app\controller\TagTaskController::class, 'list']); // 标签任务列表
Route::get('/api/tag-tasks/{task_id}', [app\controller\TagTaskController::class, 'detail']); // 标签任务详情
Route::get('/api/tag-tasks/{task_id}/executions', [app\controller\TagTaskController::class, 'executions']); // 获取任务执行记录
Route::post('/api/tag-tasks/{task_id}/start', [app\controller\TagTaskController::class, 'start']); // 启动标签任务
Route::post('/api/tag-tasks/{task_id}/pause', [app\controller\TagTaskController::class, 'pause']); // 暂停标签任务
Route::post('/api/tag-tasks/{task_id}/stop', [app\controller\TagTaskController::class, 'stop']); // 停止标签任务
// ============================================
// 身份合并相关接口场景4手机号发现身份证后合并
// ============================================
Route::post('/api/person-merge/phone-to-id-card', [app\controller\PersonMergeController::class, 'mergePhoneToIdCard']); // 合并手机号到身份证
Route::post('/api/person-merge/temporary-to-formal', [app\controller\PersonMergeController::class, 'mergeTemporaryToFormal']); // 合并临时人到正式人
// ============================================
// 数据库同步相关接口
// ============================================
Route::get('/database-sync/dashboard', [app\controller\DatabaseSyncController::class, 'dashboard']); // 同步进度看板页面
Route::get('/api/database-sync/progress', [app\controller\DatabaseSyncController::class, 'progress']); // 查询同步进度
Route::get('/api/database-sync/stats', [app\controller\DatabaseSyncController::class, 'stats']); // 查询同步统计
Route::post('/api/database-sync/reset', [app\controller\DatabaseSyncController::class, 'reset']); // 重置同步进度
Route::post('/api/database-sync/skip-error', [app\controller\DatabaseSyncController::class, 'skipError']); // 跳过错误数据库
// ============================================
// 数据采集任务管理接口
// ============================================
Route::post('/api/data-collection-tasks', [app\controller\DataCollectionTaskController::class, 'create']); // 创建任务
Route::put('/api/data-collection-tasks/{task_id}', [app\controller\DataCollectionTaskController::class, 'update']); // 更新任务
Route::delete('/api/data-collection-tasks/{task_id}', [app\controller\DataCollectionTaskController::class, 'delete']); // 删除任务
Route::get('/api/data-collection-tasks', [app\controller\DataCollectionTaskController::class, 'list']); // 任务列表
Route::get('/api/data-collection-tasks/data-sources', [app\controller\DataCollectionTaskController::class, 'getDataSources']); // 获取数据源列表
Route::get('/api/data-collection-tasks/{task_id}', [app\controller\DataCollectionTaskController::class, 'detail']); // 任务详情
Route::get('/api/data-collection-tasks/{task_id}/progress', [app\controller\DataCollectionTaskController::class, 'progress']); // 任务进度
Route::post('/api/data-collection-tasks/{task_id}/start', [app\controller\DataCollectionTaskController::class, 'start']); // 启动任务
Route::post('/api/data-collection-tasks/{task_id}/pause', [app\controller\DataCollectionTaskController::class, 'pause']); // 暂停任务
Route::post('/api/data-collection-tasks/{task_id}/stop', [app\controller\DataCollectionTaskController::class, 'stop']); // 停止任务
Route::get('/api/data-collection-tasks/data-sources/{data_source_id}/databases', [app\controller\DataCollectionTaskController::class, 'getDatabases']); // 获取数据库列表
Route::get('/api/data-collection-tasks/data-sources/{data_source_id}/databases/{database}/collections', [app\controller\DataCollectionTaskController::class, 'getCollections']); // 获取集合列表
Route::get('/api/data-collection-tasks/data-sources/{data_source_id}/databases/{database}/collections/{collection}/fields', [app\controller\DataCollectionTaskController::class, 'getFields']); // 获取字段列表
Route::get('/api/data-collection-tasks/handlers/{handler_type}/target-fields', [app\controller\DataCollectionTaskController::class, 'getHandlerTargetFields']); // 获取Handler的目标字段列表
Route::post('/api/data-collection-tasks/preview-query', [app\controller\DataCollectionTaskController::class, 'previewQuery']); // 预览查询结果
// ============================================
// 数据源管理接口
// ============================================
Route::get('/api/data-sources', [app\controller\DataSourceController::class, 'list']); // 获取数据源列表
Route::get('/api/data-sources/{data_source_id}', [app\controller\DataSourceController::class, 'detail']); // 获取数据源详情
Route::post('/api/data-sources', [app\controller\DataSourceController::class, 'create']); // 创建数据源
Route::put('/api/data-sources/{data_source_id}', [app\controller\DataSourceController::class, 'update']); // 更新数据源
Route::delete('/api/data-sources/{data_source_id}', [app\controller\DataSourceController::class, 'delete']); // 删除数据源
Route::post('/api/data-sources/test-connection', [app\controller\DataSourceController::class, 'testConnection']); // 测试数据源连接
// ============================================
// 人群快照相关接口
// ============================================
Route::get('/api/tag-cohorts', [app\controller\TagCohortController::class, 'list']); // 获取人群快照列表
Route::get('/api/tag-cohorts/{cohort_id}', [app\controller\TagCohortController::class, 'detail']); // 获取人群快照详情
Route::post('/api/tag-cohorts', [app\controller\TagCohortController::class, 'create']); // 创建人群快照
Route::delete('/api/tag-cohorts/{cohort_id}', [app\controller\TagCohortController::class, 'delete']); // 删除人群快照
Route::post('/api/tag-cohorts/{cohort_id}/export', [app\controller\TagCohortController::class, 'export']); // 导出人群快照

54
Moncter/env.txt Normal file
View File

@@ -0,0 +1,54 @@
# ============================================
# 加密配置
# ============================================
# AES 加密密钥至少32字符建议使用随机生成的强密钥
# 生产环境请务必修改此密钥,并妥善保管
ENCRYPTION_AES_KEY=your-32-byte-secret-key-here-12345678
# 哈希盐值(用于身份证哈希,增强安全性)
# 生产环境请务必修改此盐值
ENCRYPTION_HASH_SALT=your-hash-salt-here-change-in-production
# ============================================
# 应用配置
# ============================================
# 应用环境development/production
APP_ENV=development
# 应用调试模式true/false
APP_DEBUG=true
# ============================================
# 以下为:超级主机资源数据库
# ============================================
#主机标签数据库
TAG_MONGODB_DRIVER = "mongodb"
TAG_MONGODB_DNS = mongodb://192.168.1.106:27017
TAG_MONGODB_DATABASE = ckb
TAG_MONGODB_USER = ckb
TAG_MONGODB_AUTH = ckb
TAG_MONGODB_PASSWORD = 123456
#主机同步KR数据库
SYNC_MONGODB_HOST = 192.168.1.106
SYNC_MONGODB_PORT = 27017
SYNC_MONGODB_AUTH = KR
SYNC_MONGODB_USER = KR
SYNC_MONGODB_PASS = 123456
# ============================================
# 以下为:爬虫抓取的业务数据库
# ============================================
#卡若的数据库
KR_MONGODB_HOST = 192.168.2.8
KR_MONGODB_PORT = 27017
KR_MONGODB_DATABASE = admin
KR_MONGODB_USER = admin
KR_MONGODB_PASSWORD = key123456
KR_MONGODB_AUTH_SOURCE=admin

38
Moncter/start_debug.sh Normal file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
# 数据采集任务调试启动脚本(显示实时日志)
# 使用方法: chmod +x start_debug.sh && ./start_debug.sh
set -e
echo "=================================================="
echo " 数据采集任务 - 调试模式启动"
echo " 实时显示所有日志输出"
echo "=================================================="
echo ""
# 检查 PHP
if ! command -v php &> /dev/null; then
echo "❌ 错误: 未找到 PHP请先安装 PHP"
exit 1
fi
echo "✓ PHP 版本: $(php -v | head -n 1)"
echo ""
# 停止已有进程
if [ -f "runtime/webman.pid" ]; then
echo "🛑 停止已运行的进程..."
php start.php stop
sleep 2
fi
# 以调试模式启动(不使用 daemon 模式,输出到终端)
echo "🚀 启动 Workerman调试模式..."
echo " 提示: 按 Ctrl+C 停止"
echo "=================================================="
echo ""
# 使用 start 而不是 start -ddaemon这样输出会显示在终端
php start.php start

View File

@@ -29,9 +29,17 @@ if (empty(Worker::$eventLoopClass)) {
}
set_error_handler(function ($level, $message, $file = '', $line = 0) {
// 忽略 MongoDB Laravel 的废弃警告E_USER_DEPRECATED = 16384
// 这些警告不影响功能只是提示使用新的API
if ($level === E_USER_DEPRECATED && strpos($message, 'Using "$collection" property is deprecated') !== false) {
return true; // 忽略此警告
}
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
}
return false;
});
if ($worker) {

2492
Moncter/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,4 @@ dist/
build/
yarn.lock
.env
.DS_Store
.specstory/
.cursorindexingignore
.DS_Store