diff --git a/Cunkebao/src/pages/login/Login.tsx b/Cunkebao/src/pages/login/Login.tsx index d1db021e..5fa9b1bc 100644 --- a/Cunkebao/src/pages/login/Login.tsx +++ b/Cunkebao/src/pages/login/Login.tsx @@ -63,7 +63,7 @@ const Login: React.FC = () => { // 添加typeId参数 const loginParams = { ...values, - typeId: activeTab as number, + typeId: 1, }; const response = diff --git a/Moncter/.gitignore b/Moncter/.gitignore new file mode 100644 index 00000000..516299c3 --- /dev/null +++ b/Moncter/.gitignore @@ -0,0 +1,8 @@ +/runtime +/.idea +/.vscode +/vendor +*.log +.env +/tests/tmp +/tests/.phpunit.result.cache diff --git a/Moncter/LICENSE b/Moncter/LICENSE new file mode 100644 index 00000000..2c662929 --- /dev/null +++ b/Moncter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 walkor and contributors (see https://github.com/walkor/webman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Moncter/README.md b/Moncter/README.md new file mode 100644 index 00000000..4031784b --- /dev/null +++ b/Moncter/README.md @@ -0,0 +1,70 @@ +
+

webman

+ +基于workerman开发的超高性能PHP框架 + + +

学习

+ + + +
+ +

赞助商

+ +

特别赞助

+ + + + +

铂金赞助

+ + + + +
+ + +
+ +

请作者喝咖啡

+ + + +
+如果您觉得webman对您有所帮助,欢迎捐赠。 + + +
+ + +
+

LICENSE

+The webman is open-sourced software licensed under the MIT. +
+ +
+ + diff --git a/Moncter/app/controller/IndexController.php b/Moncter/app/controller/IndexController.php new file mode 100644 index 00000000..7d904faa --- /dev/null +++ b/Moncter/app/controller/IndexController.php @@ -0,0 +1,24 @@ + 'webman']); + } + + public function json(Request $request) + { + return json(['code' => 0, 'msg' => 'ok']); + } + +} diff --git a/Moncter/app/controller/UserController.php b/Moncter/app/controller/UserController.php new file mode 100644 index 00000000..b23f213c --- /dev/null +++ b/Moncter/app/controller/UserController.php @@ -0,0 +1,16 @@ +get('name', $default_name); + // 向浏览器返回字符串 + return response('hello ' . $name); + } +} \ No newline at end of file diff --git a/Moncter/app/functions.php b/Moncter/app/functions.php new file mode 100644 index 00000000..5c9c58da --- /dev/null +++ b/Moncter/app/functions.php @@ -0,0 +1,4 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace app\middleware; + +use Webman\MiddlewareInterface; +use Webman\Http\Response; +use Webman\Http\Request; + +/** + * Class StaticFile + * @package app\middleware + */ +class StaticFile implements MiddlewareInterface +{ + public function process(Request $request, callable $handler): Response + { + // Access to files beginning with. Is prohibited + if (strpos($request->path(), '/.') !== false) { + return response('

403 forbidden

', 403); + } + /** @var Response $response */ + $response = $handler($request); + // Add cross domain HTTP header + /*$response->withHeaders([ + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Credentials' => 'true', + ]);*/ + return $response; + } +} diff --git a/Moncter/app/model/Test.php b/Moncter/app/model/Test.php new file mode 100644 index 00000000..92d70e38 --- /dev/null +++ b/Moncter/app/model/Test.php @@ -0,0 +1,29 @@ + 'string', + 'age' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'tags' => 'array', // 支持数组类型(MongoDB 原生支持数组) + ]; + + // 自动维护时间戳(created_at/updated_at,默认启用) + // 若不需要可关闭:public $timestamps = false; + + // 自定义时间戳字段名(可选) + // const CREATED_AT = 'create_time'; + // const UPDATED_AT = 'update_time'; +} \ No newline at end of file diff --git a/Moncter/app/process/Http.php b/Moncter/app/process/Http.php new file mode 100644 index 00000000..f462c3a4 --- /dev/null +++ b/Moncter/app/process/Http.php @@ -0,0 +1,10 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace app\process; + +use FilesystemIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use SplFileInfo; +use Workerman\Timer; +use Workerman\Worker; + +/** + * Class FileMonitor + * @package process + */ +class Monitor +{ + /** + * @var array + */ + protected array $paths = []; + + /** + * @var array + */ + protected array $extensions = []; + + /** + * @var array + */ + protected array $loadedFiles = []; + + /** + * @var int + */ + protected int $ppid = 0; + + /** + * Pause monitor + * @return void + */ + public static function pause(): void + { + file_put_contents(static::lockFile(), time()); + } + + /** + * Resume monitor + * @return void + */ + public static function resume(): void + { + clearstatcache(); + if (is_file(static::lockFile())) { + unlink(static::lockFile()); + } + } + + /** + * Whether monitor is paused + * @return bool + */ + public static function isPaused(): bool + { + clearstatcache(); + return file_exists(static::lockFile()); + } + + /** + * Lock file + * @return string + */ + protected static function lockFile(): string + { + return runtime_path('monitor.lock'); + } + + /** + * FileMonitor constructor. + * @param $monitorDir + * @param $monitorExtensions + * @param array $options + */ + public function __construct($monitorDir, $monitorExtensions, array $options = []) + { + $this->ppid = function_exists('posix_getppid') ? posix_getppid() : 0; + static::resume(); + $this->paths = (array)$monitorDir; + $this->extensions = $monitorExtensions; + foreach (get_included_files() as $index => $file) { + $this->loadedFiles[$file] = $index; + if (strpos($file, 'webman-framework/src/support/App.php')) { + break; + } + } + if (!Worker::getAllWorkers()) { + return; + } + $disableFunctions = explode(',', ini_get('disable_functions')); + if (in_array('exec', $disableFunctions, true)) { + echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n"; + } else { + if ($options['enable_file_monitor'] ?? true) { + Timer::add(1, function () { + $this->checkAllFilesChange(); + }); + } + } + + $memoryLimit = $this->getMemoryLimit($options['memory_limit'] ?? null); + if ($memoryLimit && ($options['enable_memory_monitor'] ?? true)) { + Timer::add(60, [$this, 'checkMemory'], [$memoryLimit]); + } + } + + /** + * @param $monitorDir + * @return bool + */ + public function checkFilesChange($monitorDir): bool + { + static $lastMtime, $tooManyFilesCheck; + if (!$lastMtime) { + $lastMtime = time(); + } + clearstatcache(); + if (!is_dir($monitorDir)) { + if (!is_file($monitorDir)) { + return false; + } + $iterator = [new SplFileInfo($monitorDir)]; + } else { + // recursive traversal directory + $dirIterator = new RecursiveDirectoryIterator($monitorDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS); + $iterator = new RecursiveIteratorIterator($dirIterator); + } + $count = 0; + foreach ($iterator as $file) { + $count ++; + /** var SplFileInfo $file */ + if (is_dir($file->getRealPath())) { + continue; + } + // check mtime + if (in_array($file->getExtension(), $this->extensions, true) && $lastMtime < $file->getMTime()) { + $lastMtime = $file->getMTime(); + if (DIRECTORY_SEPARATOR === '/' && isset($this->loadedFiles[$file->getRealPath()])) { + echo "$file updated but cannot be reloaded because only auto-loaded files support reload.\n"; + continue; + } + $var = 0; + exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var); + if ($var) { + continue; + } + // send SIGUSR1 signal to master process for reload + if (DIRECTORY_SEPARATOR === '/') { + if ($masterPid = $this->getMasterPid()) { + echo $file . " updated and reload\n"; + posix_kill($masterPid, SIGUSR1); + } else { + echo "Master process has gone away and can not reload\n"; + } + return true; + } + echo $file . " updated and reload\n"; + return true; + } + } + if (!$tooManyFilesCheck && $count > 1000) { + echo "Monitor: There are too many files ($count files) in $monitorDir which makes file monitoring very slow\n"; + $tooManyFilesCheck = 1; + } + return false; + } + + /** + * @return int + */ + public function getMasterPid(): int + { + if ($this->ppid === 0) { + return 0; + } + if (function_exists('posix_kill') && !posix_kill($this->ppid, 0)) { + echo "Master process has gone away\n"; + return $this->ppid = 0; + } + if (PHP_OS_FAMILY !== 'Linux') { + return $this->ppid; + } + $cmdline = "/proc/$this->ppid/cmdline"; + if (!is_readable($cmdline) || !($content = file_get_contents($cmdline)) || (!str_contains($content, 'WorkerMan') && !str_contains($content, 'php'))) { + // Process not exist + $this->ppid = 0; + } + return $this->ppid; + } + + /** + * @return bool + */ + public function checkAllFilesChange(): bool + { + if (static::isPaused()) { + return false; + } + foreach ($this->paths as $path) { + if ($this->checkFilesChange($path)) { + return true; + } + } + return false; + } + + /** + * @param $memoryLimit + * @return void + */ + public function checkMemory($memoryLimit): void + { + if (static::isPaused() || $memoryLimit <= 0) { + return; + } + $masterPid = $this->getMasterPid(); + if ($masterPid <= 0) { + echo "Master process has gone away\n"; + return; + } + + $childrenFile = "/proc/$masterPid/task/$masterPid/children"; + if (!is_file($childrenFile) || !($children = file_get_contents($childrenFile))) { + return; + } + foreach (explode(' ', $children) as $pid) { + $pid = (int)$pid; + $statusFile = "/proc/$pid/status"; + if (!is_file($statusFile) || !($status = file_get_contents($statusFile))) { + continue; + } + $mem = 0; + if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) { + $mem = $match[1]; + } + $mem = (int)($mem / 1024); + if ($mem >= $memoryLimit) { + posix_kill($pid, SIGINT); + } + } + } + + /** + * Get memory limit + * @param $memoryLimit + * @return int + */ + protected function getMemoryLimit($memoryLimit): int + { + if ($memoryLimit === 0) { + return 0; + } + $usePhpIni = false; + if (!$memoryLimit) { + $memoryLimit = ini_get('memory_limit'); + $usePhpIni = true; + } + + if ($memoryLimit == -1) { + return 0; + } + $unit = strtolower($memoryLimit[strlen($memoryLimit) - 1]); + $memoryLimit = (int)$memoryLimit; + if ($unit === 'g') { + $memoryLimit = 1024 * $memoryLimit; + } else if ($unit === 'k') { + $memoryLimit = ($memoryLimit / 1024); + } else if ($unit === 'm') { + $memoryLimit = (int)($memoryLimit); + } else if ($unit === 't') { + $memoryLimit = (1024 * 1024 * $memoryLimit); + } else { + $memoryLimit = ($memoryLimit / (1024 * 1024)); + } + if ($memoryLimit < 50) { + $memoryLimit = 50; + } + if ($usePhpIni) { + $memoryLimit = (0.8 * $memoryLimit); + } + return (int)$memoryLimit; + } + +} diff --git a/Moncter/app/view/index/view.html b/Moncter/app/view/index/view.html new file mode 100644 index 00000000..67ebb26d --- /dev/null +++ b/Moncter/app/view/index/view.html @@ -0,0 +1,14 @@ + + + + + + + + webman + + + +hello + + diff --git a/Moncter/composer.json b/Moncter/composer.json new file mode 100644 index 00000000..82eaf4f0 --- /dev/null +++ b/Moncter/composer.json @@ -0,0 +1,56 @@ +{ + "name": "workerman/webman", + "type": "project", + "keywords": [ + "high performance", + "http service" + ], + "homepage": "https://www.workerman.net", + "license": "MIT", + "description": "High performance HTTP Service Framework.", + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "https://www.workerman.net", + "role": "Developer" + } + ], + "support": { + "email": "walkor@workerman.net", + "issues": "https://github.com/walkor/webman/issues", + "forum": "https://wenda.workerman.net/", + "wiki": "https://workerman.net/doc/webman", + "source": "https://github.com/walkor/webman" + }, + "require": { + "php": ">=8.1", + "workerman/webman-framework": "^2.1", + "monolog/monolog": "^2.0", + "mongodb/laravel-mongodb": "^4.0" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "autoload": { + "psr-4": { + "": "./", + "app\\": "./app", + "App\\": "./app", + "app\\View\\Components\\": "./app/view/components" + } + }, + "scripts": { + "post-package-install": [ + "support\\Plugin::install" + ], + "post-package-update": [ + "support\\Plugin::install" + ], + "pre-package-uninstall": [ + "support\\Plugin::uninstall" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/Moncter/composer.lock b/Moncter/composer.lock new file mode 100644 index 00000000..3ece18e4 --- /dev/null +++ b/Moncter/composer.lock @@ -0,0 +1,2455 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b36fd3581fc1bf43e25a6294dd7efc58", + "packages": [ + { + "name": "brick/math", + "version": "0.14.0", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.14.0" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-08-29T12:40:03+00:00" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "illuminate/bus", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/bus.git", + "reference": "5f7cd1f99b2ff7dd0ef20aead81da1390c4bc8e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/bus/zipball/5f7cd1f99b2ff7dd0ef20aead81da1390c4bc8e3", + "reference": "5f7cd1f99b2ff7dd0ef20aead81da1390c4bc8e3", + "shasum": "" + }, + "require": { + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/pipeline": "^11.0", + "illuminate/support": "^11.0", + "php": "^8.2" + }, + "suggest": { + "illuminate/queue": "Required to use closures when chaining jobs (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Bus\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Bus package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:54:20+00:00" + }, + { + "name": "illuminate/cache", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/cache.git", + "reference": "f9196623f6b75f7e69b9ac92f367491909753987" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/cache/zipball/f9196623f6b75f7e69b9ac92f367491909753987", + "reference": "f9196623f6b75f7e69b9ac92f367491909753987", + "shasum": "" + }, + "require": { + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "php": "^8.2" + }, + "provide": { + "psr/simple-cache-implementation": "1.0|2.0|3.0" + }, + "suggest": { + "ext-apcu": "Required to use the APC cache driver.", + "ext-filter": "Required to use the DynamoDb cache driver.", + "ext-memcached": "Required to use the memcache cache driver.", + "illuminate/database": "Required to use the database cache driver (^11.0).", + "illuminate/filesystem": "Required to use the file cache driver (^11.0).", + "illuminate/redis": "Required to use the redis cache driver (^11.0).", + "symfony/cache": "Required to use PSR-6 cache bridge (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Cache package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-01-27T22:47:27+00:00" + }, + { + "name": "illuminate/collections", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "856b1da953e46281ba61d7c82d337072d3ee1825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/856b1da953e46281ba61d7c82d337072d3ee1825", + "reference": "856b1da953e46281ba61d7c82d337072d3ee1825", + "shasum": "" + }, + "require": { + "illuminate/conditionable": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "php": "^8.2" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php", + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:54:20+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/conditionable.git", + "reference": "319b717e0587bd7c8a3b44464f0e27867b4bcda9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/319b717e0587bd7c8a3b44464f0e27867b4bcda9", + "reference": "319b717e0587bd7c8a3b44464f0e27867b4bcda9", + "shasum": "" + }, + "require": { + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:54:20+00:00" + }, + { + "name": "illuminate/container", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "79bf9149ad7ddd7e14326ebcdd41197d2c4ee36a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/79bf9149ad7ddd7e14326ebcdd41197d2c4ee36a", + "reference": "79bf9149ad7ddd7e14326ebcdd41197d2c4ee36a", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^11.0", + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:54:20+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "4b2a67d1663f50085bc91e6371492697a5d2d4e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/4b2a67d1663f50085bc91e6371492697a5d2d4e8", + "reference": "4b2a67d1663f50085bc91e6371492697a5d2d4e8", + "shasum": "" + }, + "require": { + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:54:20+00:00" + }, + { + "name": "illuminate/database", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/database.git", + "reference": "96abcce13f405701363d916dd312835e04848d04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/database/zipball/96abcce13f405701363d916dd312835e04848d04", + "reference": "96abcce13f405701363d916dd312835e04848d04", + "shasum": "" + }, + "require": { + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12|^0.13|^0.14", + "ext-pdo": "*", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "laravel/serializable-closure": "^1.3|^2.0", + "php": "^8.2" + }, + "suggest": { + "ext-filter": "Required to use the Postgres database driver.", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.24).", + "illuminate/console": "Required to use the database commands (^11.0).", + "illuminate/events": "Required to use the observers with Eloquent (^11.0).", + "illuminate/filesystem": "Required to use the migrations (^11.0).", + "illuminate/pagination": "Required to paginate the result set (^11.0).", + "symfony/finder": "Required to use Eloquent model factories (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Database\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Database package.", + "homepage": "https://laravel.com", + "keywords": [ + "database", + "laravel", + "orm", + "sql" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-09-29T09:23:31+00:00" + }, + { + "name": "illuminate/events", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/events.git", + "reference": "b72dab66d8e05d22dc5aa949efec150bbc73e827" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/events/zipball/b72dab66d8e05d22dc5aa949efec150bbc73e827", + "reference": "b72dab66d8e05d22dc5aa949efec150bbc73e827", + "shasum": "" + }, + "require": { + "illuminate/bus": "^11.0", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Events\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Events package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:54:20+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "e1cb9e51b9ed5d3c9bc1ab431d0a52fe42a990ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/e1cb9e51b9ed5d3c9bc1ab431d0a52fe42a990ed", + "reference": "e1cb9e51b9ed5d3c9bc1ab431d0a52fe42a990ed", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-06-28T20:10:30+00:00" + }, + { + "name": "illuminate/pipeline", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pipeline.git", + "reference": "f73bb7cab13ac8ef91094dc46976f5e992eea127" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/f73bb7cab13ac8ef91094dc46976f5e992eea127", + "reference": "f73bb7cab13ac8ef91094dc46976f5e992eea127", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^11.0", + "illuminate/support": "^11.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Pipeline\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pipeline package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:54:20+00:00" + }, + { + "name": "illuminate/support", + "version": "v11.46.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "716b5e258ee670cf143da883495b22595db12b90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/716b5e258ee670cf143da883495b22595db12b90", + "reference": "716b5e258ee670cf143da883495b22595db12b90", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-mbstring": "*", + "illuminate/collections": "^11.0", + "illuminate/conditionable": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "nesbot/carbon": "^2.72.6|^3.8.4", + "php": "^8.2", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "replace": { + "spatie/once": "*" + }, + "suggest": { + "illuminate/filesystem": "Required to use the Composer class (^11.0).", + "laravel/serializable-closure": "Required to use the once function (^1.3|^2.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.7).", + "league/uri": "Required to use the Uri class (^7.5.1).", + "ramsey/uuid": "Required to use Str::uuid() (^4.7).", + "symfony/process": "Required to use the Composer class (^7.0).", + "symfony/uid": "Required to use Str::ulid() (^7.0).", + "symfony/var-dumper": "Required to use the dd function (^7.0).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.6.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php", + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-08-11T14:50:36+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.6", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "038ce42edee619599a1debb7e81d7b3759492819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819", + "reference": "038ce42edee619599a1debb7e81d7b3759492819", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2025-10-09T13:42:30+00:00" + }, + { + "name": "mongodb/laravel-mongodb", + "version": "4.8.1", + "source": { + "type": "git", + "url": "https://github.com/mongodb/laravel-mongodb.git", + "reference": "da3a46a1b4ca25117c1d388dd6348206d04e4a9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/laravel-mongodb/zipball/da3a46a1b4ca25117c1d388dd6348206d04e4a9f", + "reference": "da3a46a1b4ca25117c1d388dd6348206d04e4a9f", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "ext-mongodb": "^1.15", + "illuminate/cache": "^10.36|^11", + "illuminate/container": "^10.0|^11", + "illuminate/database": "^10.30|^11", + "illuminate/events": "^10.0|^11", + "illuminate/support": "^10.0|^11", + "mongodb/mongodb": "^1.15", + "php": "^8.1" + }, + "conflict": { + "illuminate/bus": "< 10.37.2" + }, + "replace": { + "jenssegers/mongodb": "self.version" + }, + "require-dev": { + "doctrine/coding-standard": "12.0.x-dev", + "league/flysystem-gridfs": "^3.28", + "league/flysystem-read-only": "^3.0", + "mockery/mockery": "^1.4.4", + "mongodb/builder": "^0.2", + "orchestra/testbench": "^8.0|^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.3", + "spatie/laravel-query-builder": "^5.6" + }, + "suggest": { + "league/flysystem-gridfs": "Filesystem storage in MongoDB with GridFS", + "mongodb/builder": "Provides a fluent aggregation builder for MongoDB pipelines" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "MongoDB\\Laravel\\MongoDBServiceProvider", + "MongoDB\\Laravel\\MongoDBQueueServiceProvider", + "MongoDB\\Laravel\\MongoDBBusServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "MongoDB\\Laravel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com", + "role": "Leader" + }, + { + "name": "Jérôme Tamarelle", + "email": "jerome.tamarelle@mongodb.com", + "role": "Maintainer" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com", + "role": "Maintainer" + }, + { + "name": "Jens Segers", + "homepage": "https://jenssegers.com", + "role": "Creator" + } + ], + "description": "A MongoDB based Eloquent model and Query builder for Laravel", + "homepage": "https://github.com/mongodb/laravel-mongodb", + "keywords": [ + "database", + "eloquent", + "laravel", + "model", + "mongo", + "mongodb" + ], + "support": { + "issues": "https://www.mongodb.com/support", + "security": "https://www.mongodb.com/security", + "source": "https://github.com/mongodb/laravel-mongodb/tree/4.8.1" + }, + "time": "2024-11-20T15:01:02+00:00" + }, + { + "name": "mongodb/mongodb", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library.git", + "reference": "75da9ea3b63d97b05e0e8648d8c09a17bc54c0b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/75da9ea3b63d97b05e0e8648d8c09a17bc54c0b6", + "reference": "75da9ea3b63d97b05e0e8648d8c09a17bc54c0b6", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "ext-hash": "*", + "ext-json": "*", + "ext-mongodb": "^1.20.0", + "php": "^7.4 || ^8.0", + "psr/log": "^1.1.4|^2|^3", + "symfony/polyfill-php80": "^1.27", + "symfony/polyfill-php81": "^1.27" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0", + "rector/rector": "^1.1", + "squizlabs/php_codesniffer": "^3.7", + "symfony/phpunit-bridge": "^5.2", + "vimeo/psalm": "^5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "MongoDB\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + }, + { + "name": "Jérôme Tamarelle", + "email": "jerome.tamarelle@mongodb.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "support": { + "issues": "https://github.com/mongodb/mongo-php-library/issues", + "source": "https://github.com/mongodb/mongo-php-library/tree/1.20.0" + }, + "time": "2024-09-25T12:54:08+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "5cf826f2991858b54d5c3809bee745560a1042a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5cf826f2991858b54d5c3809bee745560a1042a7", + "reference": "5cf826f2991858b54d5c3809bee745560a1042a7", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.38 || ^9.6.19", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2024-11-12T12:43:37+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.10.3", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", + "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2025-09-06T13:39:36+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "symfony/clock", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/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-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/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": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/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-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/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": "2025-07-08T02:45:35+00:00" + }, + { + "name": "symfony/translation", + "version": "v7.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "ec25870502d0c7072d086e8ffba1420c85965174" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/ec25870502d0c7072d086e8ffba1420c85965174", + "reference": "ec25870502d0c7072d086e8ffba1420c85965174", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v7.3.4" + }, + "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": "2025-09-07T11:39:36+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-27T08:32:26+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T01:49:47+00:00" + }, + { + "name": "workerman/coroutine", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/workerman-php/coroutine.git", + "reference": "b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/workerman-php/coroutine/zipball/b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44", + "reference": "b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "psr/log": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\": "src", + "Workerman\\Coroutine\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Workerman coroutine", + "support": { + "issues": "https://github.com/workerman-php/coroutine/issues", + "source": "https://github.com/workerman-php/coroutine/tree/v1.1.4" + }, + "time": "2025-10-11T15:09:08+00:00" + }, + { + "name": "workerman/webman-framework", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/walkor/webman-framework.git", + "reference": "f803bd867f07bb0929faef060b59a19a44186bfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/webman-framework/zipball/f803bd867f07bb0929faef060b59a19a44186bfc", + "reference": "f803bd867f07bb0929faef060b59a19a44186bfc", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": ">=8.1", + "psr/container": ">=1.0", + "psr/log": "^3.0", + "workerman/workerman": "^5.1 || dev-master" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "library", + "autoload": { + "files": [ + "./src/support/helpers.php" + ], + "psr-4": { + "Webman\\": "./src", + "Support\\": "./src/support", + "support\\": "./src/support", + "Support\\View\\": "./src/support/view", + "Support\\Bootstrap\\": "./src/support/bootstrap", + "Support\\Exception\\": "./src/support/exception" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "https://www.workerman.net", + "role": "Developer" + } + ], + "description": "High performance HTTP Service Framework.", + "homepage": "https://www.workerman.net", + "keywords": [ + "High Performance", + "http service" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "https://wenda.workerman.net/", + "issues": "https://github.com/walkor/webman/issues", + "source": "https://github.com/walkor/webman-framework", + "wiki": "https://doc.workerman.net/" + }, + "time": "2025-03-10T11:52:22+00:00" + }, + { + "name": "workerman/workerman", + "version": "v5.1.4", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "ff4e17babdc92b16b3252060233c88f6c2e9a61a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/ff4e17babdc92b16b3252060233c88f6c2e9a61a", + "reference": "ff4e17babdc92b16b3252060233c88f6c2e9a61a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=8.1", + "workerman/coroutine": "^1.1 || dev-main" + }, + "conflict": { + "ext-swow": "=8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/Moncter/config/app.php b/Moncter/config/app.php new file mode 100644 index 00000000..f26e3584 --- /dev/null +++ b/Moncter/config/app.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\Request; + +return [ + 'debug' => true, + 'error_reporting' => E_ALL, + 'default_timezone' => 'Asia/Shanghai', + 'request_class' => Request::class, + 'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public', + 'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime', + 'controller_suffix' => 'Controller', + 'controller_reuse' => false, +]; diff --git a/Moncter/config/autoload.php b/Moncter/config/autoload.php new file mode 100644 index 00000000..69a8135e --- /dev/null +++ b/Moncter/config/autoload.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +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 new file mode 100644 index 00000000..95d2e87e --- /dev/null +++ b/Moncter/config/bootstrap.php @@ -0,0 +1,17 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + support\bootstrap\Session::class, +]; diff --git a/Moncter/config/container.php b/Moncter/config/container.php new file mode 100644 index 00000000..106b7b4a --- /dev/null +++ b/Moncter/config/container.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return new Webman\Container; \ No newline at end of file diff --git a/Moncter/config/database.php b/Moncter/config/database.php new file mode 100644 index 00000000..fd7cd4b5 --- /dev/null +++ b/Moncter/config/database.php @@ -0,0 +1,27 @@ + 'mysql', // 若需全局用 MongoDB,改为 'mongodb' + + 'connections' => [ + // ... 其他连接(如 mysql)保持不变 + + // MongoDB 官方连接配置 + 'mongodb' => [ + 'driver' => 'mongodb', + 'dsn' => 'mongodb://127.0.0.1:27017', // 集群可写:mongodb://node1:27017,node2:27017 + 'database' => 'Moncter', // 目标数据库名 + 'username' => 'Moncter', // 无认证则省略 + 'password' => '123456', // 无认证则省略 + 'options' => [ + 'replicaSet' => '', // 副本集名称(无则留空) + 'ssl' => false, // 是否启用 SSL + 'connectTimeoutMS' => 3000, // 连接超时 + 'socketTimeoutMS' => 5000, // 读写超时 + // 认证相关(若 MongoDB 启用认证) + 'authSource' => 'admin', // 认证数据库(默认 admin) + 'authMechanism' => 'SCRAM-SHA-256', // 认证机制(默认推荐) + ], + ], + ], +]; \ No newline at end of file diff --git a/Moncter/config/dependence.php b/Moncter/config/dependence.php new file mode 100644 index 00000000..8e964eda --- /dev/null +++ b/Moncter/config/dependence.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; \ No newline at end of file diff --git a/Moncter/config/exception.php b/Moncter/config/exception.php new file mode 100644 index 00000000..f2aede33 --- /dev/null +++ b/Moncter/config/exception.php @@ -0,0 +1,17 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + '' => support\exception\Handler::class, +]; \ No newline at end of file diff --git a/Moncter/config/log.php b/Moncter/config/log.php new file mode 100644 index 00000000..7f05de57 --- /dev/null +++ b/Moncter/config/log.php @@ -0,0 +1,32 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/webman.log', + 7, //$maxFiles + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/Moncter/config/middleware.php b/Moncter/config/middleware.php new file mode 100644 index 00000000..8e964eda --- /dev/null +++ b/Moncter/config/middleware.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; \ No newline at end of file diff --git a/Moncter/config/process.php b/Moncter/config/process.php new file mode 100644 index 00000000..892dc826 --- /dev/null +++ b/Moncter/config/process.php @@ -0,0 +1,62 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\Log; +use support\Request; +use app\process\Http; + +global $argv; + +return [ + 'webman' => [ + 'handler' => Http::class, + 'listen' => 'http://0.0.0.0:8787', + 'count' => cpu_count() * 4, + 'user' => '', + 'group' => '', + 'reusePort' => false, + 'eventLoop' => '', + 'context' => [], + 'constructor' => [ + 'requestClass' => Request::class, + 'logger' => Log::channel('default'), + 'appPath' => app_path(), + 'publicPath' => public_path() + ] + ], + // File update detection and automatic reload + 'monitor' => [ + 'handler' => app\process\Monitor::class, + 'reloadable' => false, + 'constructor' => [ + // Monitor these directories + 'monitorDir' => array_merge([ + app_path(), + config_path(), + base_path() . '/process', + base_path() . '/support', + base_path() . '/resource', + base_path() . '/.env', + ], glob(base_path() . '/plugin/*/app'), glob(base_path() . '/plugin/*/config'), glob(base_path() . '/plugin/*/api')), + // Files with these suffixes will be monitored + 'monitorExtensions' => [ + 'php', 'html', 'htm', 'env' + ], + 'options' => [ + 'enable_file_monitor' => !in_array('-d', $argv) && DIRECTORY_SEPARATOR === '/', + 'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/', + ] + ] + ] +]; diff --git a/Moncter/config/route.php b/Moncter/config/route.php new file mode 100644 index 00000000..a5064fca --- /dev/null +++ b/Moncter/config/route.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Webman\Route; + + + + + + diff --git a/Moncter/config/server.php b/Moncter/config/server.php new file mode 100644 index 00000000..054d01fb --- /dev/null +++ b/Moncter/config/server.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'event_loop' => '', + 'stop_timeout' => 2, + 'pid_file' => runtime_path() . '/webman.pid', + 'status_file' => runtime_path() . '/webman.status', + 'stdout_file' => runtime_path() . '/logs/stdout.log', + 'log_file' => runtime_path() . '/logs/workerman.log', + 'max_package_size' => 10 * 1024 * 1024 +]; diff --git a/Moncter/config/session.php b/Moncter/config/session.php new file mode 100644 index 00000000..043f8c45 --- /dev/null +++ b/Moncter/config/session.php @@ -0,0 +1,65 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Webman\Session\FileSessionHandler; +use Webman\Session\RedisSessionHandler; +use Webman\Session\RedisClusterSessionHandler; + +return [ + + 'type' => 'file', // or redis or redis_cluster + + 'handler' => FileSessionHandler::class, + + 'config' => [ + 'file' => [ + 'save_path' => runtime_path() . '/sessions', + ], + 'redis' => [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'auth' => '', + 'timeout' => 2, + 'database' => '', + 'prefix' => 'redis_session_', + ], + 'redis_cluster' => [ + 'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'], + 'timeout' => 2, + 'auth' => '', + 'prefix' => 'redis_session_', + ] + ], + + 'session_name' => 'PHPSID', + + 'auto_update_timestamp' => false, + + 'lifetime' => 7*24*60*60, + + 'cookie_lifetime' => 365*24*60*60, + + 'cookie_path' => '/', + + 'domain' => '', + + 'http_only' => true, + + 'secure' => false, + + 'same_site' => '', + + 'gc_probability' => [1, 1000], + +]; diff --git a/Moncter/config/static.php b/Moncter/config/static.php new file mode 100644 index 00000000..63136796 --- /dev/null +++ b/Moncter/config/static.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Static file settings + */ +return [ + 'enable' => true, + 'middleware' => [ // Static file Middleware + //app\middleware\StaticFile::class, + ], +]; \ No newline at end of file diff --git a/Moncter/config/translation.php b/Moncter/config/translation.php new file mode 100644 index 00000000..96589b2b --- /dev/null +++ b/Moncter/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/resource/translations', +]; \ No newline at end of file diff --git a/Moncter/config/view.php b/Moncter/config/view.php new file mode 100644 index 00000000..e3a7b856 --- /dev/null +++ b/Moncter/config/view.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\view\Raw; +use support\view\Twig; +use support\view\Blade; +use support\view\ThinkPHP; + +return [ + 'handler' => Raw::class +]; diff --git a/Moncter/go.sh b/Moncter/go.sh new file mode 100755 index 00000000..6b14ac80 --- /dev/null +++ b/Moncter/go.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -euo pipefail # 严格模式:报错立即退出、禁止未定义变量、管道错误触发退出 + +# ================= 配置项(可根据实际情况修改)================= +# PHP 脚本路径(相对路径/绝对路径均可,推荐绝对路径更稳定) +PHP_SCRIPT="start.php" +# PHP 解释器路径(默认自动查找,若提示 php 未找到,手动指定如 /usr/bin/php) +PHP_BIN=$(which php || echo "/usr/bin/php") +# ============================================================== + +# 1. 检查 PHP 解释器是否存在且可执行 +if [ ! -x "$PHP_BIN" ]; then + echo -e "\033[31m错误:未找到可执行的 PHP 解释器!\033[0m" + echo " 解决方案:" + echo " 1. 安装 PHP:sudo apt install php-cli(Ubuntu/Debian)或 sudo dnf install php-cli(CentOS/RHEL)" + echo " 2. 若已安装,手动修改脚本中的 PHP_BIN 为实际路径(通过 which php 查询)" + exit 1 +fi + +# 2. 检查 PHP 脚本是否存在 +if [ ! -f "$PHP_SCRIPT" ]; then + echo -e "\033[31m错误:未找到脚本文件 $PHP_SCRIPT!\033[0m" + echo " 请确保脚本与 $PHP_SCRIPT 在同一目录,或修改脚本中的 PHP_SCRIPT 为绝对路径" + exit 1 +fi + +# 3. 给 PHP 脚本添加执行权限(自动修复权限问题) +if [ ! -x "$PHP_SCRIPT" ]; then + echo -e "\033[33m警告:$PHP_SCRIPT 缺少执行权限,正在自动添加...\033[0m" + chmod u+x "$PHP_SCRIPT" || { + echo -e "\033[31m错误:添加执行权限失败,请用 sudo 运行脚本!\033[0m" + exit 1 + } +fi + +# 4. 执行核心命令(带日志输出优化) +echo -e "\033[32m=== 开始执行:$PHP_BIN $PHP_SCRIPT start ===\033[0m" +$PHP_BIN "$PHP_SCRIPT" start + +# 5. 执行结果判断 +if [ $? -eq 0 ]; then + echo -e "\033[32m=== 执行成功!===\033[0m" +else + echo -e "\033[31m=== 执行失败!请查看上方错误信息 ===\033[0m" + exit 1 +fi \ No newline at end of file diff --git a/Moncter/public/favicon.ico b/Moncter/public/favicon.ico new file mode 100644 index 00000000..b9f722e0 Binary files /dev/null and b/Moncter/public/favicon.ico differ diff --git a/Moncter/start.php b/Moncter/start.php new file mode 100755 index 00000000..41ad7ef2 --- /dev/null +++ b/Moncter/start.php @@ -0,0 +1,5 @@ +#!/usr/bin/env php + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Request + * @package support + */ +class Request extends \Webman\Http\Request +{ + +} \ No newline at end of file diff --git a/Moncter/support/Response.php b/Moncter/support/Response.php new file mode 100644 index 00000000..9bc4e1eb --- /dev/null +++ b/Moncter/support/Response.php @@ -0,0 +1,24 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Response + * @package support + */ +class Response extends \Webman\Http\Response +{ + +} \ No newline at end of file diff --git a/Moncter/support/bootstrap.php b/Moncter/support/bootstrap.php new file mode 100644 index 00000000..d913defd --- /dev/null +++ b/Moncter/support/bootstrap.php @@ -0,0 +1,139 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Dotenv\Dotenv; +use support\Log; +use Webman\Bootstrap; +use Webman\Config; +use Webman\Middleware; +use Webman\Route; +use Webman\Util; +use Workerman\Events\Select; +use Workerman\Worker; + +$worker = $worker ?? null; + +if (empty(Worker::$eventLoopClass)) { + Worker::$eventLoopClass = Select::class; +} + +set_error_handler(function ($level, $message, $file = '', $line = 0) { + if (error_reporting() & $level) { + throw new ErrorException($message, 0, $level, $file, $line); + } +}); + +if ($worker) { + register_shutdown_function(function ($startTime) { + if (time() - $startTime <= 0.1) { + sleep(1); + } + }, time()); +} + +if (class_exists('Dotenv\Dotenv') && file_exists(base_path(false) . '/.env')) { + if (method_exists('Dotenv\Dotenv', 'createUnsafeMutable')) { + Dotenv::createUnsafeMutable(base_path(false))->load(); + } else { + Dotenv::createMutable(base_path(false))->load(); + } +} + +Config::clear(); +support\App::loadAllConfig(['route']); +if ($timezone = config('app.default_timezone')) { + date_default_timezone_set($timezone); +} + +foreach (config('autoload.files', []) as $file) { + include_once $file; +} +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['autoload']['files'] ?? [] as $file) { + include_once $file; + } + } + foreach ($projects['autoload']['files'] ?? [] as $file) { + include_once $file; + } +} + +Middleware::load(config('middleware', [])); +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project) || $name === 'static') { + continue; + } + Middleware::load($project['middleware'] ?? []); + } + Middleware::load($projects['middleware'] ?? [], $firm); + if ($staticMiddlewares = config("plugin.$firm.static.middleware")) { + Middleware::load(['__static__' => $staticMiddlewares], $firm); + } +} +Middleware::load(['__static__' => config('static.middleware', [])]); + +foreach (config('bootstrap', []) as $className) { + if (!class_exists($className)) { + $log = "Warning: Class $className setting in config/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); +} + +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['bootstrap'] ?? [] as $className) { + if (!class_exists($className)) { + $log = "Warning: Class $className setting in config/plugin/$firm/$name/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); + } + } + foreach ($projects['bootstrap'] ?? [] as $className) { + /** @var string $className */ + if (!class_exists($className)) { + $log = "Warning: Class $className setting in plugin/$firm/config/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); + } +} + +$directory = base_path() . '/plugin'; +$paths = [config_path()]; +foreach (Util::scanDir($directory) as $path) { + if (is_dir($path = "$path/config")) { + $paths[] = $path; + } +} +Route::load($paths); + diff --git a/Moncter/windows.bat b/Moncter/windows.bat new file mode 100644 index 00000000..f07ce532 --- /dev/null +++ b/Moncter/windows.bat @@ -0,0 +1,3 @@ +CHCP 65001 +php windows.php +pause \ No newline at end of file diff --git a/Moncter/windows.php b/Moncter/windows.php new file mode 100644 index 00000000..f37a72c9 --- /dev/null +++ b/Moncter/windows.php @@ -0,0 +1,136 @@ +load(); + } else { + Dotenv::createMutable(base_path())->load(); + } +} + +App::loadAllConfig(['route']); + +$errorReporting = config('app.error_reporting'); +if (isset($errorReporting)) { + error_reporting($errorReporting); +} + +$runtimeProcessPath = runtime_path() . DIRECTORY_SEPARATOR . '/windows'; +$paths = [ + $runtimeProcessPath, + runtime_path('logs'), + runtime_path('views') +]; +foreach ($paths as $path) { + if (!is_dir($path)) { + mkdir($path, 0777, true); + } +} + +$processFiles = []; +if (config('server.listen')) { + $processFiles[] = __DIR__ . DIRECTORY_SEPARATOR . 'start.php'; +} +foreach (config('process', []) as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, ''); +} + +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['process'] ?? [] as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, "$firm.$name"); + } + } + foreach ($projects['process'] ?? [] as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, $firm); + } +} + +function write_process_file($runtimeProcessPath, $processName, $firm): string +{ + $processParam = $firm ? "plugin.$firm.$processName" : $processName; + $configParam = $firm ? "config('plugin.$firm.process')['$processName']" : "config('process')['$processName']"; + $fileContent = << true]); + if (!$resource) { + exit("Can not execute $cmd\r\n"); + } + return $resource; +} + +$resource = popen_processes($processFiles); +echo "\r\n"; +while (1) { + sleep(1); + if (!empty($monitor) && $monitor->checkAllFilesChange()) { + $status = proc_get_status($resource); + $pid = $status['pid']; + shell_exec("taskkill /F /T /PID $pid"); + proc_close($resource); + $resource = popen_processes($processFiles); + } +} diff --git a/Moncter/技术方案.md b/Moncter/技术方案.md new file mode 100644 index 00000000..25ea2751 --- /dev/null +++ b/Moncter/技术方案.md @@ -0,0 +1,348 @@ +## 用户标签引擎技术方案(以身份证为主键) + +### 一、目标与范围 +- 构建可扩展的用户标签引擎,统一以身份证为主键的人(person)进行画像与筛选。 +- 支持多数据源接入(交易、行为、社群、外呼等),并聚合多个手机号、多个微信号到同一人。 +- 提供规则驱动的人群筛选、客群快照和线索分发能力,服务销售精细化运营。 + +### 0. 需求整合清单(共识) +- 数据接入:多数据源/多数据库连接,按“数据源-表/接口”粒度定义 Job;增量水位、批量与重试。 +- 标识治理:支持弱标识(手机号等)建“临时人”,获取强标识(身份证/unionid/客户号)后合并;全链路幂等与审计。 +- 标签体系:分通道层与人层;`tag_dict` 定义口径/类型/窗口/聚合/版本;标签写入包含 window/source/version。 +- 聚合计算:通道→人层遵循 `aggregation`(sum/max/avg/any/best_of 等),支持实时触发与离线批处理。 +- 规则与人群:DSL 配置、试算/执行、审计;Redis 维护 cohort/位图,支持快照与导出。 +- 回灌与重算:规则或口径变更可对存量回评估;任务状态、错误与执行明细可观测、可重试。 +- 安全合规:身份证只存哈希(加盐),最小化数据使用,接口鉴权与导出留痕。 + +### 二、总体架构 +- 核心理念:人层(person)是唯一真相;通道层(channel)承载具体手机号/微信号等标识。 +- 组件分层: + - 数据接入层:标准化事件/明细,写入通道层标签。 + - 聚合计算层:将通道层指标按口径聚合到人层标签。 + - 规则/人群层:基于人层标签做筛选、快照、导出与分发。 + - 存储与缓存:MySQL(字典/事实/审计)+ Redis(cohort 人群集与位图)。 + +### 二点五、运行逻辑图(分层架构,数据流视角) +```mermaid +╔═════════════════════════════════════════════════════════════════════════╗ +║ 数据源层 ║ +╚═════════════════════════════════════════════════════════════════════════╝ + ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ + │交易系统│ │APP行为│ │客服系统│ │CRM │ + └─────┘ └─────┘ └─────┘ └─────┘ + │ │ │ │ + └───────┴─────────┘ + ▼ +╔═════════════════════════════════════════════════════════════════════════╗ +║ 接入层 ║ +╚═════════════════════════════════════════════════════════════════════════╝ + ┌───────────────────────────────────────┐ + │ Job调度器 增量水位 │ + └───────────────────────────────────────┘ + ▼ + ┌───────────────────────────────────────┐ + │ 标准化 校验 │ + └───────────────────────────────────────┘ + ▼ +╔═════════════════════════════════════════════════════════════════════════╗ +║ 身份层 ║ +╚═════════════════════════════════════════════════════════════════════════╝ + ┌───────────────────────────────────────┐ + │ IdentifierService │ + │ 手机号→person_id │ + └───────────────────────────────────────┘ + ▼ + ┌───────────────────────────────────────┐ + │ 临时人建表 强标识合并 │ + └───────────────────────────────────────┘ + ▼ +╔═════════════════════════════════════════════════════════════════════════╗ +║ 标签层 ║ +╚═════════════════════════════════════════════════════════════════════════╝ + ┌───────────────────────────────────────┐ + │ channel_tags │ + │ 通道标签存储 │ + └───────────────────────────────────────┘ + ▼ + ┌───────────────────────────────────────┐ + │ Aggregator │ + │ 聚合计算 │ + └───────────────────────────────────────┘ + ▼ + ┌───────────────────────────────────────┐ + │ person_tags │ + │ 人层标签存储 │ + └───────────────────────────────────────┘ + ▼ +╔═════════════════════════════════════════════════════════════════════════╗ +║ 规则层 ║ +╚═════════════════════════════════════════════════════════════════════════╝ + ┌───────────────────────────────────────┐ + │ RuleEngine │ + │ DSL执行 │ + └───────────────────────────────────────┘ + ▼ + ┌───────────────────────────────────────┐ + │ Redis Cohort │ + │ 人群集合 │ + └───────────────────────────────────────┘ + ▼ +╔═════════════════════════════════════════════════════════════════════════╗ +║ 应用层 ║ +╚═════════════════════════════════════════════════════════════════════════╝ + ┌─────┐ ┌─────┐ ┌─────┐ + │人群查询│ │快照导出│ │分发推送│ + └─────┘ └─────┘ └─────┘ + +``` + +### 二点六、运行逻辑图(时序视角) +```mermaid +sequenceDiagram + participant 业务系统 as 业务系统
(交易/APP/客服) + participant 接入服务 as 数据接入服务 + participant 身份服务 as 身份解析服务 + participant 通道标签 as channel_tags
(存储) + participant 聚合服务 as 聚合计算服务 + participant 人层标签 as person_tags
(存储) + participant 规则引擎 as 规则引擎 + participant 人群缓存 as Redis Cohort + + 业务系统->>接入服务: 1. 推送事件/批量数据 + 接入服务->>接入服务: 2. 标准化、校验、去重 + 接入服务->>身份服务: 3. 解析标识
(手机号/微信→person_id) + 身份服务-->>接入服务: 返回person_id
(不存在则建临时人) + 接入服务->>通道标签: 4. 写入通道标签
(幂等、window/source/version) + + 通道标签->>聚合服务: 5. 触发聚合事件
(实时/批量) + 聚合服务->>人层标签: 6. 按口径聚合
(sum/max/avg/any) + + 人层标签->>规则引擎: 7. 标签变更触发
(受影响person_id) + 规则引擎->>规则引擎: 8. 执行DSL规则 + 规则引擎->>人群缓存: 9. 更新cohort
(SADD/SINTER) + + 人群缓存-->>业务系统: 10. 人群查询/导出 +``` + +### 二点七、运行逻辑图(简化版,核心路径) +```mermaid +graph TB + Start([数据源
交易/行为/客服/CRM]) --> Ingest[数据接入
Job调度 + 标准化] + Ingest --> Identity[身份解析
手机号→person_id] + Identity --> Channel[通道标签
channel_tags] + Channel --> Aggregate[聚合计算
通道→人层] + Aggregate --> Person[人层标签
person_tags] + Person --> Rule[规则引擎
DSL筛选] + Rule --> Cohort[人群集合
Redis Cohort] + Cohort --> Export([应用输出
查询/快照/分发]) + + Ingest -.->|状态| Audit1[(ingest_state)] + Ingest -.->|错误| Audit2[(ingest_errors)] + Rule -.->|执行记录| Audit3[(rule_executions)] + + style Start fill:#e1f5ff + style Export fill:#e1f5ff + style Ingest fill:#fff4e1 + style Identity fill:#e8f5e9 + style Channel fill:#f3e5f5 + style Aggregate fill:#f3e5f5 + style Person fill:#f3e5f5 + style Rule fill:#fff9c4 + style Cohort fill:#fff9c4 +``` + +### 三、数据模型 +```sql +-- 人:身份证(脱敏哈希)为主键 +CREATE TABLE person ( + person_id CHAR(32) PRIMARY KEY, -- md5(uppercase(id_card_no_without_spaces)) + id_card_hash CHAR(32) UNIQUE, + name VARCHAR(64) NULL, + gender TINYINT NULL, + birthday DATE NULL, + created_at DATETIME, + updated_at DATETIME +); + +-- 标识绑定:一个人可有多个手机号/微信/外部ID +CREATE TABLE person_identifier ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + person_id CHAR(32), + id_type ENUM('phone','wechat','external','email') NOT NULL, + id_value VARCHAR(128) NOT NULL, + is_primary TINYINT DEFAULT 0, + verified TINYINT DEFAULT 0, + created_at DATETIME, + updated_at DATETIME, + UNIQUE KEY uk_type_value (id_type, id_value), + KEY idx_person (person_id) +); + +-- 标签字典 +CREATE TABLE tag_dict ( + tag_code VARCHAR(128) PRIMARY KEY, -- 例:person.trade.arpu_90d + name VARCHAR(128), + category VARCHAR(64), + level ENUM('person','channel') NOT NULL, + type ENUM('int','bool','enum','set','string','float') NOT NULL, + enum_values JSON NULL, + unit VARCHAR(16) NULL, + aggregation ENUM('sum','max','min','avg','any','best_of') NULL, -- 通道→人层口径 + description TEXT, + version INT DEFAULT 1, + status ENUM('draft','active','deprecated') DEFAULT 'active', + owner VARCHAR(64), + created_at DATETIME, + updated_at DATETIME +); + +-- 人层标签(销售筛选用) +CREATE TABLE person_tags ( + person_id CHAR(32), + tag_code VARCHAR(128), + tag_value VARCHAR(256), + confidence TINYINT DEFAULT 100, + source VARCHAR(64), + window VARCHAR(32), + version INT, + updated_at DATETIME, + PRIMARY KEY (person_id, tag_code), + KEY idx_tag (tag_code, tag_value) +); + +-- 通道层标签(手机号/微信号维度) +CREATE TABLE channel_tags ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + id_type ENUM('phone','wechat','external') NOT NULL, + id_value VARCHAR(128) NOT NULL, + tag_code VARCHAR(128), + tag_value VARCHAR(256), + source VARCHAR(64), + window VARCHAR(32), + updated_at DATETIME, + UNIQUE KEY uk_dim (id_type, id_value, tag_code), + KEY idx_tag (tag_code, tag_value) +); + +-- 规则配置与执行审计 +CREATE TABLE tag_rules ( + rule_id BIGINT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(128), + dsl JSON, + status ENUM('draft','active','paused') DEFAULT 'active', + schedule VARCHAR(64) NULL, + output_tag VARCHAR(128) NULL, + owner VARCHAR(64), + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE rule_executions ( + exec_id BIGINT PRIMARY KEY AUTO_INCREMENT, + rule_id BIGINT, + started_at DATETIME, + finished_at DATETIME, + affected_users INT, + status ENUM('success','failed','partial'), + message TEXT +); +``` + +### 四、标签规范 +- 分类: + - 基础画像、行为活跃、交易能力、风险合规、社交关系、生命周期、设备画像、地域属性。 +- 命名:`{level}.{category}.{name}_{window}` + - 人层:`person.trade.arpu_90d`,`person.behavior.active_days_30d` + - 通道层:`channel.wechat.group_join_30d`,`channel.phone.outbound_connect_7d` +- 口径:在 `tag_dict` 中声明 `type/enum/range/aggregation/window/update_freq/version`。 +- 聚合: + - 风险类:any(任一通道命中→人层命中)。 + - 活跃/价值:max/sum/avg 视业务定义。 + - 可达性:best_of(如选近30日响应率最高的手机号)。 + +### 五、规则与人群(DSL) +```json +{ + "name": "高潜付费人群", + "logic": "AND", + "conditions": [ + {"tag": "person.trade.arpu_90d", "op": "gte", "value": 500}, + {"tag": "person.behavior.active_days_30d", "op": "gte", "value": 10}, + {"tag": "person.risk.blacklist", "op": "eq", "value": false} + ], + "window": "rolling_90d", + "ttl": "24h" +} +``` + +### 六、计算与刷新策略 +- 批处理(T+1/T+0小时):生成稳定画像(交易统计、生命周期等)。 +- 准实时(秒级/分级):事件驱动更新通道层标签,触发对应人层增量聚合。 +- 回评估:字典/规则变更后对存量人群重算,并写入审计。 + +### 七、系统设计(ThinkPHP 5.1) +- 目录结构(`application/tag`): + - controller:`TagDictController`、`RuleController`、`SegmentController`、`IdentifierController` + - model:`TagDict`、`Person`、`PersonIdentifier`、`PersonTags`、`ChannelTags`、`TagRules`、`RuleExecutions` + - service: + - `IdentifierService`(标识绑定/查找/合并) + - `ChannelTagService`(通道标签写入) + - `PersonTagService`(人层聚合/重算) + - `TagQueryService`(条件解析→Redis/DB组合查询) + - `RuleEngineService`(DSL 校验/执行/审计) + - `CohortCacheService`(cohort 维护、集合/位图运算) + - command:`ExecuteRule`、`RecomputeTags`、`CohortSnapshot` + +- 路由(建议,受 `jwt` 保护): +```php +Route::group('v1/tag', function () { + Route::get('dict', 'app\\tag\\controller\\TagDictController@index'); + Route::post('dict', 'app\\tag\\controller\\TagDictController@create'); + Route::post('identifier/bind', 'app\\tag\\controller\\IdentifierController@bind'); + Route::post('rule/execute/:id', 'app\\tag\\controller\\RuleController@execute'); + Route::post('segment/query', 'app\\tag\\controller\\SegmentController@query'); + Route::post('segment/snapshot', 'app\\tag\\controller\\SegmentController@snapshot'); +})->middleware(['jwt']); +``` + +### 八、查询与筛选 +- 人层为主:`person_tags` 组合条件查询,Redis 保存常用 cohort: + - Redis 示例:`SINTER cohort:person.trade.arpu_90d:gt500 cohort:person.active_days_30d:gte10 SDIFF cohort:person.risk.blacklist:eq1` +- MySQL 组合查询示例: +```sql +SELECT DISTINCT t1.person_id +FROM person_tags t1 +JOIN person_tags t2 ON t2.person_id=t1.person_id +LEFT JOIN person_tags t3 ON t3.person_id=t1.person_id AND t3.tag_code='person.risk.blacklist' +WHERE t1.tag_code='person.trade.arpu_90d' AND CAST(t1.tag_value AS DECIMAL)>=500 + AND t2.tag_code='person.behavior.active_days_30d' AND CAST(t2.tag_value AS SIGNED)>=10 + AND (t3.tag_value IS NULL OR t3.tag_value='0') +LIMIT 50 OFFSET 0; +``` + +### 九、关键流程 +1) 事件接入:收到 `id_type + id_value`(如手机号)→ 查 `person_identifier` → 得到 `person_id`。 +2) 通道标签更新:写 `channel_tags`,并发布“聚合任务”。 +3) 人层聚合:按 `tag_dict.aggregation` 规则,更新 `person_tags`。 +4) 规则评估:对受影响的 `person_id` 运行启用中的规则,更新 cohort/输出标签。 +5) 人群产出:支持分页查询、生成快照、导出或推送 CRM/外呼系统。 + +### 十、缓存与索引 +- Redis: + - 集合/位图存 cohort,Key 规范:`cohort:{tag_code}:{op}{value}` 或区间桶。 + - TTL:默认 24h,可按规则 `ttl` 覆盖。 +- MySQL: + - `person_tags(tag_code, tag_value)`、`channel_tags(tag_code, tag_value)` 倒排索引。 + - 审计表按时间分区或冷热分离。 + +### 十一、合规与安全 +- 身份证只存哈希(不可逆),不落明文;导出脱敏。 +- 最小权限访问,接口留痕审计(规则执行、导出、查看)。 +- 口径透明:标签保留来源、窗口、置信度、版本。 + +### 十二、里程碑(落地计划) +- M1:建表与服务骨架;接入交易/行为两类数据;产出10个核心标签。 +- M2:规则试算与快照;Redis cohort;首批销售客群模板(高价值流失预警)。 +- M3:通道“最佳触达”策略;CRM/外呼对接;质量监控与看板。 + + diff --git a/Moncter/数据库列表.md b/Moncter/数据库列表.md new file mode 100644 index 00000000..2f1bc309 --- /dev/null +++ b/Moncter/数据库列表.md @@ -0,0 +1,25 @@ +KR +KR_KR +KR_LinkedIn +KR_存客宝 +KR_存客宝_四表重构KR_KR版 +KR_国外 +KR_户口 +KR_京东 +KR_酒店 +KR_卡套私域 +KR_快递 +KR_魔兽世界 +KR_企业 +KR_企业名录 +KR_人才库 +KR_商城 +KR_手机 +KR_顺丰 +KR_淘宝 +KR_腾讯 +KR_投资 +KR_微博 +KR_香港在大陆投资企业名录 +KR_销售额3000万元-5000万元企业名录 +KR_游戏 \ No newline at end of file diff --git a/Touchkebao/src/components/Upload/AudioRecorder/index.tsx b/Touchkebao/src/components/Upload/AudioRecorder/index.tsx index 1bcc705e..8e9a1dc0 100644 --- a/Touchkebao/src/components/Upload/AudioRecorder/index.tsx +++ b/Touchkebao/src/components/Upload/AudioRecorder/index.tsx @@ -10,7 +10,11 @@ import { import { uploadFile } from "@/api/common"; interface AudioRecorderProps { - onAudioUploaded: (audioData: { url: string; durationMs: number }) => void; + onAudioUploaded: (audioData: { + url: string; + name: string; + durationMs?: number; + }) => void; className?: string; disabled?: boolean; maxDuration?: number; // 最大录音时长(秒) @@ -206,6 +210,7 @@ const AudioRecorder: React.FC = ({ // 调用回调函数,传递音频URL和时长(毫秒) onAudioUploaded({ url: filePath, + name: audioFile.name, durationMs: recordingTime * 1000, // 将秒转换为毫秒 }); diff --git a/Touchkebao/src/components/Upload/SimpleFileUpload/index.tsx b/Touchkebao/src/components/Upload/SimpleFileUpload/index.tsx index 014c3dd1..b751bddb 100644 --- a/Touchkebao/src/components/Upload/SimpleFileUpload/index.tsx +++ b/Touchkebao/src/components/Upload/SimpleFileUpload/index.tsx @@ -3,7 +3,7 @@ import React, { useRef } from "react"; import { message } from "antd"; interface SimpleFileUploadProps { - onFileUploaded?: (filePath: string) => void; + onFileUploaded?: (filePath: { name: string; url: string }) => void; maxSize?: number; // 最大文件大小(MB) type?: number; // 1: 图片, 2: 视频, 3: 音频, 4: 文件 slot?: React.ReactNode; @@ -51,7 +51,10 @@ const SimpleFileUpload: React.FC = ({ try { const fileUrl = await uploadFile(file); - onFileUploaded?.(fileUrl); + onFileUploaded?.({ + name: file.name, + url: fileUrl, + }); message.success("文件上传成功"); } catch (error: any) { console.error("文件上传失败:", error); diff --git a/Touchkebao/src/pages/404/index.module.scss b/Touchkebao/src/pages/404/index.module.scss new file mode 100644 index 00000000..e486dbd0 --- /dev/null +++ b/Touchkebao/src/pages/404/index.module.scss @@ -0,0 +1,75 @@ +.not-found-container { + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: 20px; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); +} + +.not-found-content { + text-align: center; + max-width: 500px; + width: 100%; +} + +.error-code { + font-size: 120px; + font-weight: bold; + color: #1890ff; + line-height: 1; + margin-bottom: 20px; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); + + @media (max-width: 768px) { + font-size: 80px; + } +} + +.error-title { + font-size: 28px; + font-weight: 600; + color: #333; + margin: 20px 0 16px; + + @media (max-width: 768px) { + font-size: 24px; + } +} + +.error-description { + font-size: 16px; + color: #666; + margin-bottom: 40px; + line-height: 1.6; + + @media (max-width: 768px) { + font-size: 14px; + margin-bottom: 30px; + } +} + +.action-buttons { + display: flex; + gap: 16px; + justify-content: center; + flex-wrap: wrap; + + @media (max-width: 768px) { + flex-direction: column; + gap: 12px; + } +} + +.action-btn { + display: flex; + align-items: center; + gap: 8px; + min-width: 140px; + + @media (max-width: 768px) { + width: 100%; + justify-content: center; + } +} + diff --git a/Touchkebao/src/pages/404/index.tsx b/Touchkebao/src/pages/404/index.tsx new file mode 100644 index 00000000..edbeec11 --- /dev/null +++ b/Touchkebao/src/pages/404/index.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; +import { Button } from "antd-mobile"; +import { ArrowLeftOutlined, HomeOutlined } from "@ant-design/icons"; +import Layout from "@/components/Layout/Layout"; +import styles from "./index.module.scss"; + +const NotFound: React.FC = () => { + const navigate = useNavigate(); + + const handleGoHome = () => { + navigate("/"); + }; + + const handleGoBack = () => { + navigate(-1); + }; + + return ( + +
+
+ {/* 404 图标 */} +
404
+ + {/* 错误提示 */} +

页面未找到

+

+ 抱歉,您访问的页面不存在或已被删除 +

+ + {/* 操作按钮 */} +
+ + +
+
+
+
+ ); +}; + +export default NotFound; diff --git a/Touchkebao/src/pages/login/Login.tsx b/Touchkebao/src/pages/login/Login.tsx index 2e83787f..898de6d4 100644 --- a/Touchkebao/src/pages/login/Login.tsx +++ b/Touchkebao/src/pages/login/Login.tsx @@ -89,6 +89,7 @@ const Login: React.FC = () => { const loginParams = { ...values, verifySessionId: verify.verifySessionId, + typeId: 1, }; const response = diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx index 33307856..8ae096e8 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx @@ -34,7 +34,6 @@ const { sendCommand } = useWebSocketStore.getState(); const MessageEnter: React.FC = ({ contract }) => { const [inputValue, setInputValue] = useState(""); - const [showMaterialModal, setShowMaterialModal] = useState(false); const EnterModule = useWeChatStore(state => state.EnterModule); const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox); const updateEnterModule = useWeChatStore(state => state.updateEnterModule); @@ -254,20 +253,43 @@ const MessageEnter: React.FC = ({ contract }) => { FILE: 5, }; const handleFileUploaded = ( - filePath: string | { url: string; durationMs: number }, + filePath: { url: string; name: string; durationMs?: number }, fileType: number, ) => { + console.log("handleFileUploaded: ", fileType, filePath); + // msgType(1:文本 3:图片 43:视频 47:动图表情包(gif、其他表情包) 49:小程序/其他:图文、文件) let msgType = 1; + let content: any = ""; if ([FileType.TEXT].includes(fileType)) { - msgType = getMsgTypeByFileFormat(filePath as string); + msgType = getMsgTypeByFileFormat(filePath.url); } else if ([FileType.IMAGE].includes(fileType)) { msgType = 3; + content = filePath.url; } else if ([FileType.AUDIO].includes(fileType)) { msgType = 34; + content = JSON.stringify({ + url: filePath.url, + durationMs: filePath.durationMs, + }); } else if ([FileType.FILE].includes(fileType)) { - msgType = 49; + msgType = getMsgTypeByFileFormat(filePath.url); + if (msgType === 3) { + content = filePath.url; + } + if (msgType === 43) { + content = filePath.url; + } + + if (msgType === 49) { + content = JSON.stringify({ + type: "file", + title: filePath.name, + url: filePath.url, + }); + } } + const messageId = +Date.now(); const params = { wechatAccountId: contract.wechatAccountId, @@ -275,10 +297,37 @@ const MessageEnter: React.FC = ({ contract }) => { wechatFriendId: contract?.chatroomId ? 0 : contract.id, msgSubType: 0, msgType, - content: [FileType.AUDIO].includes(fileType) - ? JSON.stringify(filePath) - : filePath, + content: content, + seq: messageId, }; + + // 构造本地消息对象 + const localMessage: ChatRecord = { + id: messageId, // 使用时间戳作为临时ID + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract?.chatroomId ? 0 : contract.id, + wechatChatroomId: contract?.chatroomId ? contract.id : 0, + tenantId: 0, + accountId: 0, + synergyAccountId: 0, + content: params.content, + msgType: msgType, + msgSubType: 0, + msgSvrId: "", + isSend: true, // 标记为发送中 + createTime: new Date().toISOString(), + isDeleted: false, + deleteTime: "", + sendStatus: 1, + wechatTime: Date.now(), + origin: 0, + msgId: 0, + recalled: false, + seq: messageId, + }; + // 先插入本地数据 + addMessage(localMessage); + sendCommand("CmdSendMessage", params); }; @@ -349,10 +398,10 @@ const MessageEnter: React.FC = ({ contract }) => {
- handleFileUploaded(filePath, FileType.FILE) + onFileUploaded={fileInfo => + handleFileUploaded(fileInfo, FileType.FILE) } - maxSize={1} + maxSize={10} type={4} slot={ , - , - ]} - width={800} - > -
- {/* 左侧素材分类 */} -
-
-

公共素材

-
-
-
- 暗黑4 -
-
- 针对老客户的... -
-
- D2辅助 -
-
- ROS反馈演示... -
-
- 一键宏产品素... -
-
-
-

部门素材

-
-
- - {/* 右侧内容区域 */} -
-
- -
-
- 请选择左侧素材分类 -
-
-
- + 、 ); }; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/SmallProgramMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/SmallProgramMessage/index.tsx index 44787540..51711685 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/SmallProgramMessage/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/SmallProgramMessage/index.tsx @@ -26,7 +26,7 @@ const SmallProgramMessage: React.FC = ({ const messageData = JSON.parse(trimmedContent); // 处理文章类型消息 - if (messageData.type === "link" && messageData.title && messageData.url) { + if (messageData.type === "link") { const { title, desc, thumbPath, url } = messageData; return ( @@ -68,7 +68,7 @@ const SmallProgramMessage: React.FC = ({ } // 处理小程序消息 - 统一使用parseWeappMsgStr解析 - if (messageData.type === "miniprogram" && messageData.contentXml) { + if (messageData.type === "miniprogram") { try { const parsedData = parseWeappMsgStr(trimmedContent); @@ -144,109 +144,115 @@ const SmallProgramMessage: React.FC = ({ } } - // 验证传统JSON格式的小程序数据结构 - if ( - messageData && - typeof messageData === "object" && - (messageData.title || messageData.appName) - ) { - return ( -
-
- {messageData.thumb && ( - 小程序缩略图 { - const target = e.target as HTMLImageElement; - target.style.display = "none"; - }} - /> - )} -
-
- {messageData.title || "小程序消息"} -
- {messageData.appName && ( -
- {messageData.appName} + //处理文档类型消息 + + if (messageData.type === "file") { + const { url, title } = messageData; + // 增强的文件消息处理 + const isFileUrl = + url.startsWith("http") || + url.startsWith("https") || + url.startsWith("file://") || + /\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|zip|rar|7z)$/i.test(url); + + if (isFileUrl) { + // 尝试从URL中提取文件名 + const fileName = + title || url.split("/").pop()?.split("?")[0] || "文件"; + const fileExtension = fileName.split(".").pop()?.toLowerCase(); + + // 根据文件类型选择图标 + let fileIcon = "📄"; + if (fileExtension) { + const iconMap: { [key: string]: string } = { + pdf: "📕", + doc: "📘", + docx: "📘", + xls: "📗", + xlsx: "📗", + ppt: "📙", + pptx: "📙", + txt: "📝", + zip: "🗜️", + rar: "🗜️", + "7z": "🗜️", + jpg: "🖼️", + jpeg: "🖼️", + png: "🖼️", + gif: "🖼️", + mp4: "🎬", + avi: "🎬", + mov: "🎬", + mp3: "🎵", + wav: "🎵", + flac: "🎵", + }; + fileIcon = iconMap[fileExtension] || "📄"; + } + + return ( +
+
+
{fileIcon}
+
+
+ {fileName.length > 20 + ? fileName.substring(0, 20) + "..." + : fileName}
- )} +
{ + try { + window.open(messageData.url, "_blank"); + } catch (e) { + console.error("文件打开失败:", e); + } + }} + > + 点击查看 +
+
-
- ); - } - } - - // 增强的文件消息处理 - const isFileUrl = - content.startsWith("http") || - content.startsWith("https") || - content.startsWith("file://") || - /\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|zip|rar|7z)$/i.test(content); - - if (isFileUrl) { - // 尝试从URL中提取文件名 - const fileName = content.split("/").pop()?.split("?")[0] || "文件"; - const fileExtension = fileName.split(".").pop()?.toLowerCase(); - - // 根据文件类型选择图标 - let fileIcon = "📄"; - if (fileExtension) { - const iconMap: { [key: string]: string } = { - pdf: "📕", - doc: "📘", - docx: "📘", - xls: "📗", - xlsx: "📗", - ppt: "📙", - pptx: "📙", - txt: "📝", - zip: "🗜️", - rar: "🗜️", - "7z": "🗜️", - jpg: "🖼️", - jpeg: "🖼️", - png: "🖼️", - gif: "🖼️", - mp4: "🎬", - avi: "🎬", - mov: "🎬", - mp3: "🎵", - wav: "🎵", - flac: "🎵", - }; - fileIcon = iconMap[fileExtension] || "📄"; + ); + } } - return ( -
-
-
{fileIcon}
-
-
- {fileName.length > 20 - ? fileName.substring(0, 20) + "..." - : fileName} -
-
{ - try { - window.open(content, "_blank"); - } catch (e) { - console.error("文件打开失败:", e); - } - }} - > - 点击查看 -
-
-
-
- ); + // 验证传统JSON格式的小程序数据结构 + // if ( + // messageData && + // typeof messageData === "object" && + // (messageData.title || messageData.appName) + // ) { + // return ( + //
+ //
+ // {messageData.thumb && ( + // 小程序缩略图 { + // const target = e.target as HTMLImageElement; + // target.style.display = "none"; + // }} + // /> + // )} + //
+ //
+ // {messageData.title || "小程序消息"} + //
+ // {messageData.appName && ( + //
+ // {messageData.appName} + //
+ // )} + //
+ //
+ //
+ // ); + // } } return renderErrorMessage("[小程序/文件消息]"); diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.tsx index 62e41990..91f5324b 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.tsx @@ -39,6 +39,7 @@ const TransmitModal: React.FC = () => { // 从 Zustand store 获取更新方法 const openTransmitModal = useContactStore(state => state.openTransmitModal); + const setTransmitModal = useContactStore(state => state.setTransmitModal); const updateSelectedChatRecords = useWeChatStore( state => state.updateSelectedChatRecords, @@ -142,11 +143,11 @@ const TransmitModal: React.FC = () => { updateTransmitModal(false)} + onCancel={() => setTransmitModal(false)} width={"60%"} className={styles.transmitModal} footer={[ - , + +
+
+ + ); +}; + +export default AddFriends; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss new file mode 100644 index 00000000..20d9a3d3 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss @@ -0,0 +1,153 @@ +.popChatRoomModal { + .ant-modal-content { + height: 500px; + } + + .ant-modal-body { + height: calc(500px - 110px); + overflow: hidden; + padding: 16px; + } +} + +.modalContent { + height: 100%; + display: flex; + flex-direction: column; +} + +.searchContainer { + margin-bottom: 16px; + + .searchInput { + width: 100%; + } +} + +.contentBody { + flex: 1; + display: flex; + gap: 16px; + min-height: 0; +} + +.contactList, +.selectedList { + flex: 1; + display: flex; + flex-direction: column; + border: 1px solid #d9d9d9; + border-radius: 6px; + overflow: hidden; +} + +.listHeader { + padding: 12px 16px; + background-color: #fafafa; + border-bottom: 1px solid #d9d9d9; + font-weight: 500; + font-size: 14px; + color: #262626; +} + +.listContent { + flex: 1; + overflow-y: auto; + padding: 8px; + min-height: 0; +} + +.contactItem, +.selectedItem { + display: flex; + align-items: center; + padding: 8px 12px; + border-radius: 4px; + margin-bottom: 4px; + transition: background-color 0.2s; + + &:hover { + background-color: #f5f5f5; + } + + &:last-child { + margin-bottom: 0; + } +} + +.paginationContainer { + padding: 12px; + border-top: 1px solid #d9d9d9; + background-color: #fafafa; +} + +.selectedItem { + justify-content: space-between; +} + +.contactInfo { + display: flex; + align-items: center; + gap: 8px; + flex: 1; + min-width: 0; +} + +.contactName { + font-size: 14px; + color: #262626; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.conRemark { + font-size: 12px; + color: #8c8c8c; +} + +.removeIcon { + color: #8c8c8c; + cursor: pointer; + padding: 4px; + border-radius: 2px; + transition: all 0.2s; + + &:hover { + color: #ff4d4f; + background-color: #fff2f0; + } +} + +.loadingContainer { + display: flex; + justify-content: center; + align-items: center; + height: 200px; + flex-direction: column; + gap: 12px; + color: #8c8c8c; +} + +// 响应式设计 +@media (max-width: 768px) { + .popChatRoomModal { + .ant-modal-content { + max-height: 600px; + } + + .ant-modal-body { + max-height: calc(600px - 110px); + } + } + + .contentBody { + flex-direction: column; + gap: 12px; + } + + .contactList, + .selectedList { + min-height: 200px; + } +} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx new file mode 100644 index 00000000..68a99575 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx @@ -0,0 +1,348 @@ +import React, { useState, useEffect, useMemo } from "react"; +import { + Modal, + Input, + Button, + Avatar, + Checkbox, + Empty, + Spin, + message, + Pagination, +} from "antd"; +import { SearchOutlined, CloseOutlined, UserOutlined } from "@ant-design/icons"; +import styles from "./index.module.scss"; +import { ContactManager } from "@/utils/dbAction"; +import { useUserStore } from "@/store/module/user"; +import { useCustomerStore } from "@/store/module/weChat/customer"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; +import { Contact } from "@/utils/db"; + +interface PopChatRoomProps { + visible: boolean; + onCancel: () => void; +} + +const PopChatRoom: React.FC = ({ visible, onCancel }) => { + const [searchValue, setSearchValue] = useState(""); + const [allContacts, setAllContacts] = useState([]); + const [selectedContacts, setSelectedContacts] = useState([]); + const [loading, setLoading] = useState(false); + const [page, setPage] = useState(1); + const [showNameModal, setShowNameModal] = useState(false); + const [chatroomName, setChatroomName] = useState(""); + const pageSize = 10; + const { sendCommand } = useWebSocketStore(); + const currentUserId = useUserStore(state => state.user?.id) || 0; + const currentCustomer = useCustomerStore(state => state.currentCustomer); + + // 加载联系人数据(只加载好友,不包含群聊) + const loadContacts = async () => { + setLoading(true); + try { + const allContactsData = + await ContactManager.getUserContacts(currentUserId); + // 过滤出好友类型,排除群聊 + const friendsOnly = (allContactsData as Contact[]).filter( + contact => contact.type === "friend", + ); + setAllContacts(friendsOnly); + } catch (err) { + console.error("加载联系人数据失败:", err); + message.error("加载联系人数据失败"); + } finally { + setLoading(false); + } + }; + + // 重置状态 + useEffect(() => { + if (visible) { + setSearchValue(""); + setSelectedContacts([]); + setPage(1); + loadContacts(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [visible]); + + // 过滤联系人 - 支持名称和拼音搜索 + const filteredContacts = useMemo(() => { + if (!searchValue.trim()) return allContacts; + + const keyword = searchValue.toLowerCase(); + return allContacts.filter(contact => { + const name = (contact.nickname || "").toLowerCase(); + const remark = (contact.conRemark || "").toLowerCase(); + const quanPin = (contact as any).quanPin?.toLowerCase?.() || ""; + const pinyin = (contact as any).pinyin?.toLowerCase?.() || ""; + return ( + name.includes(keyword) || + remark.includes(keyword) || + quanPin.includes(keyword) || + pinyin.includes(keyword) + ); + }); + }, [allContacts, searchValue]); + + const paginatedContacts = useMemo(() => { + const start = (page - 1) * pageSize; + const end = start + pageSize; + return filteredContacts.slice(start, end); + }, [filteredContacts, page]); + + // 处理联系人选择 + const handleContactSelect = (contact: Contact) => { + setSelectedContacts(prev => { + if (isContactSelected(contact.id)) { + return prev.filter(item => item.id !== contact.id); + } + return [...prev, contact]; + }); + }; + + // 移除已选择的联系人 + const handleRemoveSelected = (contactId: number) => { + setSelectedContacts(prev => + prev.filter(contact => contact.id !== contactId), + ); + }; + + // 检查联系人是否已选择 + const isContactSelected = (contactId: number) => { + return selectedContacts.some(contact => contact.id === contactId); + }; + + // 处理取消 + const handleCancel = () => { + setSearchValue(""); + setSelectedContacts([]); + setPage(1); + setChatroomName(""); + setShowNameModal(false); + onCancel(); + }; + + // 处理创建群聊 - 先显示输入群名称的弹窗 + const handleCreateGroup = () => { + if (selectedContacts.length === 0) { + message.warning("请至少选择一个联系人"); + return; + } + + if (!currentCustomer?.id) { + message.error("请先选择客服账号"); + return; + } + + // 显示输入群名称的弹窗 + setShowNameModal(true); + }; + + // 确认创建群聊 + const handleConfirmCreate = () => { + if (!chatroomName.trim()) { + message.warning("请输入群聊名称"); + return; + } + + if (!currentCustomer?.id) { + message.error("请先选择客服账号"); + return; + } + + // 获取选中的好友ID列表 + const friendIds = selectedContacts.map(contact => contact.id); + + try { + // 发送创建群聊命令 + sendCommand("CmdChatroomCreate", { + wechatAccountId: currentCustomer.id, + chatroomName: chatroomName.trim(), + wechatFriendIds: friendIds, + }); + + message.success("群聊创建请求已发送"); + handleCancel(); + } catch (error) { + console.error("创建群聊失败:", error); + message.error("创建群聊失败,请重试"); + } + }; + + // 取消输入群名称 + const handleCancelNameInput = () => { + setShowNameModal(false); + setChatroomName(""); + }; + + return ( + <> + + 取消 + , + , + ]} + > +
+ {/* 搜索框 */} +
+ } + value={searchValue} + onChange={e => setSearchValue(e.target.value)} + className={styles.searchInput} + disabled={loading} + allowClear + /> +
+ +
+ {/* 左侧联系人列表 */} +
+
+ 联系人 ({filteredContacts.length}) +
+
+ {loading ? ( +
+ + 加载联系人中... +
+ ) : filteredContacts.length > 0 ? ( + paginatedContacts.map(contact => ( +
+ handleContactSelect(contact)} + > +
+ } + /> +
+
{contact.nickname}
+ {contact.conRemark && ( +
+ {contact.conRemark} +
+ )} +
+
+
+
+ )) + ) : ( + + )} +
+ {filteredContacts.length > 0 && ( +
+ setPage(p)} + showSizeChanger={false} + /> +
+ )} +
+ + {/* 右侧已选择列表 */} +
+
+ 已选联系人 ({selectedContacts.length}) +
+
+ {selectedContacts.length > 0 ? ( + selectedContacts.map(contact => ( +
+
+ } + /> +
+
{contact.nickname}
+ {contact.conRemark && ( +
+ {contact.conRemark} +
+ )} +
+
+ handleRemoveSelected(contact.id)} + /> +
+ )) + ) : ( + + )} +
+
+
+
+
+ + {/* 输入群名称的弹窗 */} + + 取消 + , + , + ]} + > +
+
请输入群聊名称。
+ setChatroomName(e.target.value)} + onPressEnter={handleConfirmCreate} + autoFocus + /> +
+
+ + ); +}; + +export default PopChatRoom; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/SidebarMenu.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/SidebarMenu.module.scss index 6562e218..4ab2bed0 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/SidebarMenu.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/SidebarMenu.module.scss @@ -13,6 +13,8 @@ margin-bottom: 16px; padding: 0; background: #fff; + display: flex; + gap: 10px; } .tabsContainer { diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/index.tsx index f596b9c9..3699b995 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/index.tsx @@ -1,9 +1,16 @@ import React, { useState, useEffect } from "react"; -import { Input, Skeleton } from "antd"; -import { SearchOutlined } from "@ant-design/icons"; +import { Input, Skeleton, Button, Dropdown, MenuProps } from "antd"; +import { + SearchOutlined, + PlusOutlined, + UserAddOutlined, + TeamOutlined, +} from "@ant-design/icons"; import WechatFriends from "./WechatFriends"; import MessageList from "./MessageList/index"; import FriendsCircle from "./FriendsCicle"; +import AddFriends from "./AddFriends"; +import PopChatRoom from "./PopChatRoom"; import styles from "./SidebarMenu.module.scss"; import { useContactStore } from "@/store/module/weChat/contacts"; import { useCustomerStore } from "@/store/module/weChat/customer"; @@ -28,6 +35,9 @@ const SidebarMenu: React.FC = ({ loading = false }) => { const [activeTab, setActiveTab] = useState("chats"); const [switchingTab, setSwitchingTab] = useState(false); // tab切换加载状态 + const [isAddFriendModalVisible, setIsAddFriendModalVisible] = useState(false); + const [isCreateGroupModalVisible, setIsCreateGroupModalVisible] = + useState(false); // 监听 currentContact 变化,自动切换到聊天tab并选中会话 useEffect(() => { @@ -68,6 +78,26 @@ const SidebarMenu: React.FC = ({ loading = false }) => { clearSearchKeyword(); }; + // 下拉菜单项 + const menuItems: MenuProps["items"] = [ + { + key: "addFriend", + label: "添加好友", + icon: , + onClick: () => { + setIsAddFriendModalVisible(true); + }, + }, + { + key: "createGroup", + label: "发起群聊", + icon: , + onClick: () => { + setIsCreateGroupModalVisible(true); + }, + }, + ]; + // 渲染骨架屏 const renderSkeleton = () => (
@@ -126,6 +156,15 @@ const SidebarMenu: React.FC = ({ loading = false }) => { onClear={handleClearSearch} allowClear /> + {currentCustomer && ( + + + + )}
{/* 标签页切换 */} @@ -181,6 +220,16 @@ const SidebarMenu: React.FC = ({ loading = false }) => {
{renderHeader()}
{renderContent()}
+ {/* 添加好友弹窗 */} + setIsAddFriendModalVisible(false)} + /> + {/* 发起群聊弹窗 */} + setIsCreateGroupModalVisible(false)} + />
); }; diff --git a/Touchkebao/src/router/index.tsx b/Touchkebao/src/router/index.tsx index 117681a7..877aa58c 100644 --- a/Touchkebao/src/router/index.tsx +++ b/Touchkebao/src/router/index.tsx @@ -1,6 +1,7 @@ import React from "react"; import { BrowserRouter, useRoutes, RouteObject } from "react-router-dom"; import PermissionRoute from "./permissionRoute"; +import NotFound from "@/pages/404"; // 动态导入所有 module 下的 ts/tsx 路由模块 const modules = import.meta.glob("./module/*.{ts,tsx}", { eager: true }); @@ -31,7 +32,15 @@ function wrapWithPermission( return route; } -const routes = allRoutes.map(wrapWithPermission); +// 添加 404 路由(通配符路由,必须放在最后) +const routes = [ + ...allRoutes.map(wrapWithPermission), + { + path: "*", + element: , + auth: false, + }, +]; const AppRoutes = () => useRoutes(routes); diff --git a/Touchkebao/src/store/module/dataCenter/index.ts b/Touchkebao/src/store/module/dataCenter/index.ts index fe83e150..1d87121d 100644 --- a/Touchkebao/src/store/module/dataCenter/index.ts +++ b/Touchkebao/src/store/module/dataCenter/index.ts @@ -31,7 +31,7 @@ import { * 微信聊天状态管理 Store * 使用 Zustand 管理微信聊天相关的状态和操作 */ -export const useWeChatStore = create()( +export const useDataCenterStore = create()( persist( (set, get) => ({ showChatRecordModel: false, @@ -153,7 +153,7 @@ export const useWeChatStore = create()( contract: ContractData | weChatGroup, isExist?: boolean, ) => { - const state = useWeChatStore.getState(); + const state = useDataCenterStore.getState(); // 切换联系人时清空当前消息,等待重新加载 set({ currentMessages: [], openTransmitModal: false }); @@ -193,7 +193,7 @@ export const useWeChatStore = create()( // ==================== 消息加载方法 ==================== /** 加载聊天消息 */ loadChatMessages: async (Init: boolean, To?: number) => { - const state = useWeChatStore.getState(); + const state = useDataCenterStore.getState(); const contact = state.currentContract; set({ messagesLoading: true }); set({ isLoadingData: Init }); @@ -258,7 +258,7 @@ export const useWeChatStore = create()( keyword: string; Count?: number; }) => { - const state = useWeChatStore.getState(); + const state = useDataCenterStore.getState(); const contact = state.currentContract; set({ messagesLoading: true }); @@ -302,7 +302,7 @@ export const useWeChatStore = create()( // ==================== 消息接收处理 ==================== /** 接收新消息处理 */ receivedMsg: async message => { - const currentContract = useWeChatStore.getState().currentContract; + const currentContract = useDataCenterStore.getState().currentContract; // 判断是群聊还是私聊 const getMessageId = message?.wechatChatroomId || message.wechatFriendId; @@ -468,13 +468,16 @@ export const useWeChatStore = create()( // ==================== 便捷选择器导出 ==================== /** 获取当前联系人的 Hook */ export const useCurrentContact = () => - useWeChatStore(state => state.currentContract); + useDataCenterStore(state => state.currentContract); /** 获取当前消息列表的 Hook */ export const useCurrentMessages = () => - useWeChatStore(state => state.currentMessages); + useDataCenterStore(state => state.currentMessages); /** 获取消息加载状态的 Hook */ export const useMessagesLoading = () => - useWeChatStore(state => state.messagesLoading); + useDataCenterStore(state => state.messagesLoading); /** 获取复选框显示状态的 Hook */ export const useShowCheckbox = () => - useWeChatStore(state => state.showCheckbox); + useDataCenterStore(state => state.showCheckbox); + +export const useUpdateTransmitModal = (open: boolean) => + useDataCenterStore(state => state.updateTransmitModal(open)); diff --git a/Touchkebao/src/store/module/websocket/msgManage.ts b/Touchkebao/src/store/module/websocket/msgManage.ts index bbb1e4e1..82058dc9 100644 --- a/Touchkebao/src/store/module/websocket/msgManage.ts +++ b/Touchkebao/src/store/module/websocket/msgManage.ts @@ -53,7 +53,7 @@ const messageHandlers: Record = { } }, CmdSendMessageResult: message => { - updateMessage(message.friendMessageId, { + updateMessage(message.friendMessageId || message.chatroomMessageId, { sendStatus: 0, }); },