From 3e781226591d946778822b4fcc6b223e50a8889c Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 20 Nov 2025 11:45:53 +0800 Subject: [PATCH 01/51] =?UTF-8?q?composer=E4=BE=9D=E8=B5=96=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/composer.json | 48 +- Server/composer.lock | 4299 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 4320 insertions(+), 27 deletions(-) diff --git a/Server/composer.json b/Server/composer.json index 61d3fa7c0..1f70969d1 100644 --- a/Server/composer.json +++ b/Server/composer.json @@ -13,21 +13,59 @@ { "name": "liu21st", "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" } ], "require": { - "php": ">=5.4.0", - "topthink/framework": "5.0.*" + "php": ">=5.6.0", + "topthink/framework": "5.1.41", + "topthink/think-installer": "2.*", + "topthink/think-captcha": "^2.0", + "topthink/think-helper": "^3.0", + "topthink/think-image": "^1.0", + "topthink/think-queue": "^2.0", + "topthink/think-worker": "^2.0", + "textalk/websocket": "^1.5", + "aliyuncs/oss-sdk-php": "^2.6", + "monolog/monolog": "^1.27", + "guzzlehttp/guzzle": "^6.5", + "overtrue/wechat": "~4.6", + "endroid/qr-code": "^3.9", + "phpoffice/phpspreadsheet": "^1.29", + "workerman/workerman": "^3.5", + "workerman/gateway-worker": "^3.0", + "hashids/hashids": "^2.0", + "khanamiryan/qrcode-detector-decoder": "^1.0", + "lizhichao/word": "^2.0", + "adbario/php-dot-notation": "^2.2" + }, + "require-dev": { + "symfony/var-dumper": "^3.4|^4.4", + "topthink/think-migration": "^2.0", + "phpunit/phpunit": "^5.0|^6.0" }, "autoload": { "psr-4": { "app\\": "application" - } + }, + "files": [ + "application/common.php" + ], + "classmap": [] }, "extra": { "think-path": "thinkphp" }, "config": { - "preferred-install": "dist" - } + "preferred-install": "dist", + "allow-plugins": { + "topthink/think-installer": true, + "easywechat-composer/easywechat-composer": true + } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/Server/composer.lock b/Server/composer.lock index 50bf3ceaf..56ff4a97b 100644 --- a/Server/composer.lock +++ b/Server/composer.lock @@ -4,48 +4,3832 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ba81097bd3bedb9a9265465b0f578307", + "content-hash": "5bca28194f84a7d13965d1851e6ce38a", "packages": [ { - "name": "topthink/framework", - "version": "v5.0.25", + "name": "adbario/php-dot-notation", + "version": "2.5.0", "source": { "type": "git", - "url": "https://github.com/top-think/framework.git", - "reference": "643c58ed1bd22a2823ce5e95b3b68a5075f9087c" + "url": "https://github.com/adbario/php-dot-notation.git", + "reference": "081e2cca50c84bfeeea2e3ef9b2c8d206d80ccae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/top-think/framework/zipball/643c58ed1bd22a2823ce5e95b3b68a5075f9087c", - "reference": "643c58ed1bd22a2823ce5e95b3b68a5075f9087c", + "url": "https://api.github.com/repos/adbario/php-dot-notation/zipball/081e2cca50c84bfeeea2e3ef9b2c8d206d80ccae", + "reference": "081e2cca50c84bfeeea2e3ef9b2c8d206d80ccae", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^5.5 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.7|^6.6|^7.5|^8.5|^9.5", + "squizlabs/php_codesniffer": "^3.6" + }, + "type": "library", + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Adbar\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Riku Särkinen", + "email": "riku@adbar.io" + } + ], + "description": "PHP dot notation access to arrays", + "homepage": "https://github.com/adbario/php-dot-notation", + "keywords": [ + "ArrayAccess", + "dotnotation" + ], + "support": { + "issues": "https://github.com/adbario/php-dot-notation/issues", + "source": "https://github.com/adbario/php-dot-notation/tree/2.5.0" + }, + "time": "2022-10-14T20:31:46+00:00" + }, + { + "name": "aliyuncs/oss-sdk-php", + "version": "v2.7.2", + "source": { + "type": "git", + "url": "https://github.com/aliyun/aliyun-oss-php-sdk.git", + "reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aliyun/aliyun-oss-php-sdk/zipball/483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5", + "reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "php-coveralls/php-coveralls": "*", + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "OSS\\": "src/OSS" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aliyuncs", + "homepage": "http://www.aliyun.com" + } + ], + "description": "Aliyun OSS SDK for PHP", + "homepage": "http://www.aliyun.com/product/oss/", + "support": { + "issues": "https://github.com/aliyun/aliyun-oss-php-sdk/issues", + "source": "https://github.com/aliyun/aliyun-oss-php-sdk/tree/v2.7.2" + }, + "time": "2024-10-28T10:41:12+00:00" + }, + { + "name": "bacon/bacon-qr-code", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.1", + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8" + }, + "time": "2022-12-07T17:46:57+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "dasprid/enum", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.7" + }, + "time": "2025-09-16T12:23:56+00:00" + }, + { + "name": "easywechat-composer/easywechat-composer", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/mingyoung/easywechat-composer.git", + "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mingyoung/easywechat-composer/zipball/3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd", + "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=7.0" + }, + "require-dev": { + "composer/composer": "^1.0 || ^2.0", + "phpunit/phpunit": "^6.5 || ^7.0" + }, + "type": "composer-plugin", + "extra": { + "class": "EasyWeChatComposer\\Plugin" + }, + "autoload": { + "psr-4": { + "EasyWeChatComposer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "张铭阳", + "email": "mingyoungcheung@gmail.com" + } + ], + "description": "The composer plugin for EasyWeChat", + "support": { + "issues": "https://github.com/mingyoung/easywechat-composer/issues", + "source": "https://github.com/mingyoung/easywechat-composer/tree/1.4.1" + }, + "time": "2021-07-05T04:03:22+00:00" + }, + { + "name": "endroid/qr-code", + "version": "3.9.7", + "source": { + "type": "git", + "url": "https://github.com/endroid/qr-code.git", + "reference": "94563d7b3105288e6ac53a67ae720e3669fac1f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/endroid/qr-code/zipball/94563d7b3105288e6ac53a67ae720e3669fac1f6", + "reference": "94563d7b3105288e6ac53a67ae720e3669fac1f6", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^2.0", + "khanamiryan/qrcode-detector-decoder": "^1.0.5", + "myclabs/php-enum": "^1.5", + "php": "^7.3||^8.0", + "symfony/options-resolver": "^3.4||^4.4||^5.0", + "symfony/property-access": "^3.4||^4.4||^5.0" + }, + "require-dev": { + "endroid/quality": "^1.5.2", + "setasign/fpdf": "^1.8" + }, + "suggest": { + "ext-gd": "Required for generating PNG images", + "roave/security-advisories": "Avoids installation of package versions with vulnerabilities", + "setasign/fpdf": "Required to use the FPDF writer.", + "symfony/security-checker": "Checks your composer.lock for vulnerabilities" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Endroid\\QrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeroen van den Enden", + "email": "info@endroid.nl" + } + ], + "description": "Endroid QR Code", + "homepage": "https://github.com/endroid/qr-code", + "keywords": [ + "bundle", + "code", + "endroid", + "php", + "qr", + "qrcode" + ], + "support": { + "issues": "https://github.com/endroid/qr-code/issues", + "source": "https://github.com/endroid/qr-code/tree/3.9.7" + }, + "funding": [ + { + "url": "https://github.com/endroid", + "type": "github" + } + ], + "time": "2021-04-20T19:10:54+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.19.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0" + }, + "time": "2025-10-17T16:34:55+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.5.8", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a52f0440530b54fa079ce76e8c5d196a42cad981", + "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.9", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.17" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/6.5.8" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-06-20T22:16:07+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e", + "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-05-21T12:31:43+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b", + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b", "shasum": "" }, "require": { "php": ">=5.4.0", - "topthink/think-installer": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" }, "require-dev": { - "johnkary/phpunit-speedtrap": "^1.0", - "mikey179/vfsstream": "~1.6", - "phpdocumentor/reflection-docblock": "^2.0", - "phploc/phploc": "2.*", - "phpunit/phpunit": "4.8.*", - "sebastian/phpcpd": "2.*" + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.9.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-04-17T16:00:37+00:00" + }, + { + "name": "hashids/hashids", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/vinkla/hashids.git", + "reference": "7a945a5192d4a5c8888364970feece9bc26179df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vinkla/hashids/zipball/7a945a5192d4a5c8888364970feece9bc26179df", + "reference": "7a945a5192d4a5c8888364970feece9bc26179df", + "shasum": "" + }, + "require": { + "php": "^5.6.4 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.3" + }, + "suggest": { + "ext-bcmatch": "Required to use BC Math arbitrary precision mathematics (*).", + "ext-gmp": "Required to use GNU multiple precision mathematics (*)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } }, - "type": "think-framework", "autoload": { "psr-4": { - "think\\": "library/think" + "Hashids\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Akimov", + "email": "ivan@barreleye.com", + "homepage": "https://twitter.com/IvanAkimov" + }, + { + "name": "Vincent Klaiber", + "email": "hello@vinkla.com", + "homepage": "https://vinkla.com" + } + ], + "description": "Generate short, unique, non-sequential ids (like YouTube and Bitly) from numbers", + "homepage": "http://hashids.org/php", + "keywords": [ + "bitly", + "decode", + "encode", + "hash", + "hashid", + "hashids", + "ids", + "obfuscate", + "youtube" + ], + "support": { + "issues": "https://github.com/vinkla/hashids/issues", + "source": "https://github.com/vinkla/hashids/tree/2.0.4" + }, + "time": "2017-10-28T11:24:20+00:00" + }, + { + "name": "khanamiryan/qrcode-detector-decoder", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/khanamiryan/php-qrcode-detector-decoder.git", + "reference": "45326fb83a2a375065dbb3a134b5b8a5872da569" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/khanamiryan/php-qrcode-detector-decoder/zipball/45326fb83a2a375065dbb3a134b5b8a5872da569", + "reference": "45326fb83a2a375065dbb3a134b5b8a5872da569", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 | ^7.5 | ^8.0 | ^9.0", + "rector/rector": "^0.13.6", + "symplify/easy-coding-standard": "^11.0" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Common/customFunctions.php" + ], + "psr-4": { + "Zxing\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "Apache-2.0" + ], + "authors": [ + { + "name": "Ashot Khanamiryan", + "email": "a.khanamiryan@gmail.com", + "homepage": "https://github.com/khanamiryan", + "role": "Developer" + } + ], + "description": "QR code decoder / reader", + "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder/", + "keywords": [ + "barcode", + "qr", + "zxing" + ], + "support": { + "issues": "https://github.com/khanamiryan/php-qrcode-detector-decoder/issues", + "source": "https://github.com/khanamiryan/php-qrcode-detector-decoder/tree/1.0.6" + }, + "time": "2022-06-29T09:25:13+00:00" + }, + { + "name": "lizhichao/word", + "version": "v2.1", + "source": { + "type": "git", + "url": "https://github.com/lizhichao/VicWord.git", + "reference": "f17172d45f505e7140da0bde2103defc13255326" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lizhichao/VicWord/zipball/f17172d45f505e7140da0bde2103defc13255326", + "reference": "f17172d45f505e7140da0bde2103defc13255326", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lizhichao\\Word\\": "Lib" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "Apache-2.0" ], + "authors": [ + { + "name": "tanszhe", + "email": "1018595261@qq.com" + } + ], + "description": "This is a participle library", + "support": { + "issues": "https://github.com/lizhichao/VicWord/issues", + "source": "https://github.com/lizhichao/VicWord/tree/master" + }, + "time": "2020-07-30T07:33:06+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "2.2.6", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f", + "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f", + "shasum": "" + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": "^7.4 || ^8.0", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.9", + "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.4", + "phpunit/phpunit": "^8.5.8 || ^9.4.2", + "vimeo/psalm": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.2.6" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2022-11-25T18:57:19+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, + { + "name": "monolog/monolog", + "version": "1.27.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "904713c5929655dc9b97288b69cfeedad610c9a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/904713c5929655dc9b97288b69cfeedad610c9a1", + "reference": "904713c5929655dc9b97288b69cfeedad610c9a1", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpstan/phpstan": "^0.12.59", + "phpunit/phpunit": "~4.5", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/1.27.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2022-06-09T08:53:42+00:00" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.5", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/e7be26966b7398204a234f8673fdad5ac6277802", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2 || ^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "https://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2025-01-14T11:49:03+00:00" + }, + { + "name": "overtrue/socialite", + "version": "2.0.24", + "source": { + "type": "git", + "url": "https://github.com/overtrue/socialite.git", + "reference": "ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/socialite/zipball/ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec", + "reference": "ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^5.0|^6.0|^7.0", + "php": ">=5.6", + "symfony/http-foundation": "^2.7|^3.0|^4.0|^5.0" + }, + "require-dev": { + "mockery/mockery": "~1.2", + "phpunit/phpunit": "^6.0|^7.0|^8.0|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Overtrue\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "A collection of OAuth 2 packages that extracts from laravel/socialite.", + "keywords": [ + "login", + "oauth", + "qq", + "social", + "wechat", + "weibo" + ], + "support": { + "issues": "https://github.com/overtrue/socialite/issues", + "source": "https://github.com/overtrue/socialite/tree/2.0.24" + }, + "funding": [ + { + "url": "https://www.patreon.com/overtrue", + "type": "patreon" + } + ], + "time": "2021-05-13T16:04:48+00:00" + }, + { + "name": "overtrue/wechat", + "version": "4.9.0", + "source": { + "type": "git", + "url": "https://github.com/w7corp/easywechat.git", + "reference": "92791f5d957269c633b9aa175f842f6006f945b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/w7corp/easywechat/zipball/92791f5d957269c633b9aa175f842f6006f945b1", + "reference": "92791f5d957269c633b9aa175f842f6006f945b1", + "shasum": "" + }, + "require": { + "easywechat-composer/easywechat-composer": "^1.1", + "ext-fileinfo": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.2 || ^7.0", + "monolog/monolog": "^1.22 || ^2.0", + "overtrue/socialite": "~2.0", + "php": ">=7.2", + "pimple/pimple": "^3.0", + "psr/simple-cache": "^1.0", + "symfony/cache": "^3.3 || ^4.3 || ^5.0", + "symfony/event-dispatcher": "^4.3 || ^5.0", + "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/psr-http-message-bridge": "^0.3 || ^1.0 || ^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.15", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2.3", + "phpstan/phpstan": "^0.12.0", + "phpunit/phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/Kernel/Support/Helpers.php", + "src/Kernel/Helpers.php" + ], + "psr-4": { + "EasyWeChat\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "微信SDK", + "keywords": [ + "easywechat", + "sdk", + "wechat", + "weixin", + "weixin-sdk" + ], + "support": { + "issues": "https://github.com/w7corp/easywechat/issues", + "source": "https://github.com/w7corp/easywechat/tree/4.9.0" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "abandoned": "w7corp/easywechat", + "time": "2023-04-28T03:30:34+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.30.1", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "fa8257a579ec623473eabfe49731de5967306c4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c", + "reference": "fa8257a579ec623473eabfe49731de5967306c4c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": ">=7.4.0 <8.5.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^1.0 || ^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1" + }, + "time": "2025-10-26T16:01:04+00:00" + }, + { + "name": "phrity/net-uri", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/phrity-net-uri.git", + "reference": "3f458e0c4d1ddc0e218d7a5b9420127c63925f43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/phrity-net-uri/zipball/3f458e0c4d1ddc0e218d7a5b9420127c63925f43", + "reference": "3f458e0c4d1ddc0e218d7a5b9420127c63925f43", + "shasum": "" + }, + "require": { + "php": "^7.4 | ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 | ^2.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0 | ^10.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phrity\\Net\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "PSR-7 Uri and PSR-17 UriFactory implementation", + "homepage": "https://phrity.sirn.se/net-uri", + "keywords": [ + "psr-17", + "psr-7", + "uri", + "uri factory" + ], + "support": { + "issues": "https://github.com/sirn-se/phrity-net-uri/issues", + "source": "https://github.com/sirn-se/phrity-net-uri/tree/1.3.0" + }, + "time": "2023-08-21T10:33:06+00:00" + }, + { + "name": "phrity/util-errorhandler", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/phrity-util-errorhandler.git", + "reference": "483228156e06673963902b1cc1e6bd9541ab4d5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/phrity-util-errorhandler/zipball/483228156e06673963902b1cc1e6bd9541ab4d5e", + "reference": "483228156e06673963902b1cc1e6bd9541ab4d5e", + "shasum": "" + }, + "require": { + "php": "^7.4 | ^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0 | ^10.0 | ^11.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phrity\\Util\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "Inline error handler; catch and resolve errors for code block.", + "homepage": "https://phrity.sirn.se/util-errorhandler", + "keywords": [ + "error", + "warning" + ], + "support": { + "issues": "https://github.com/sirn-se/phrity-util-errorhandler/issues", + "source": "https://github.com/sirn-se/phrity-util-errorhandler/tree/1.1.1" + }, + "time": "2024-09-12T06:49:16+00:00" + }, + { + "name": "pimple/pimple", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a70f552d338f9266eec6606c1f0b324da5514c96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a70f552d338f9266eec6606c1f0b324da5514c96", + "reference": "a70f552d338f9266eec6606c1f0b324da5514c96", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1 || ^2.0" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "https://pimple.symfony.com", + "keywords": [ + "container", + "dependency injection" + ], + "support": { + "source": "https://github.com/silexphp/Pimple/tree/v3.6.0" + }, + "time": "2025-11-12T12:31:38+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+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/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "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/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://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/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/cache", + "version": "v4.3.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "8794ccf68ac341fc19311919d2287f7557bfccba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/8794ccf68ac341fc19311919d2287f7557bfccba", + "reference": "8794ccf68ac341fc19311919d2287f7557bfccba", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "symfony/cache-contracts": "^1.1", + "symfony/service-contracts": "^1.1", + "symfony/var-exporter": "^4.2" + }, + "conflict": { + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<3.4", + "symfony/var-dumper": "<3.4" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0", + "symfony/cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", + "psr/simple-cache": "^1.0", + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.1", + "symfony/var-dumper": "^4.1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "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": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v4.3.11" + }, + "time": "2020-01-27T09:15:09+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "a872a66e0bf7bac179c89bc96c7098bef1949f81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/a872a66e0bf7bac179c89bc96c7098bef1949f81", + "reference": "a872a66e0bf7bac179c89bc96c7098bef1949f81", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/cache": "^1.0|^2.0|^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "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 caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v1.10.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": "2022-01-02T09:41:36+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-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/v2.5.4" + }, + "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:11:13+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/72982eb416f61003e9bb6e91f8b3213600dcf9e9", + "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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 that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.45" + }, + "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:11:13+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", + "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.4" + }, + "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:11:13+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v5.4.50", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "1a0706e8b8041046052ea2695eb8aeee04f97609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1a0706e8b8041046052ea2695eb8aeee04f97609", + "reference": "1a0706e8b8041046052ea2695eb8aeee04f97609", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "predis/predis": "^1.0|^2.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "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": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v5.4.50" + }, + "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-11-03T12:58:48+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", + "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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 an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.4.45" + }, + "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:11:13+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "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 intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/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-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/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-10T14:38:51+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "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 for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "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-php73", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "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\\Php73\\": "" + }, + "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 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/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-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/property-access", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "111e7ed617509f1a9139686055d234aad6e388e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/111e7ed617509f1a9139686055d234aad6e388e0", + "reference": "111e7ed617509f1a9139686055d234aad6e388e0", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16", + "symfony/property-info": "^5.2|^6.0" + }, + "require-dev": { + "symfony/cache": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/cache-implementation": "To cache access methods." + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "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 functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v5.4.45" + }, + "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:11:13+00:00" + }, + { + "name": "symfony/property-info", + "version": "v5.4.48", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "a0396295ad585f95fccd690bc6a281e5bd303902" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/a0396295ad585f95fccd690bc6a281e5bd303902", + "reference": "a0396295ad585f95fccd690bc6a281e5bd303902", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<4.4" + }, + "require-dev": { + "doctrine/annotations": "^1.10.4|^2", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/serializer": "^4.4|^5.0|^6.0" + }, + "suggest": { + "phpdocumentor/reflection-docblock": "To use the PHPDoc", + "psr/cache-implementation": "To cache results", + "symfony/doctrine-bridge": "To use Doctrine metadata", + "symfony/serializer": "To use Serializer metadata" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v5.4.48" + }, + "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-11-25T16:14:41+00:00" + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e", + "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/http-foundation": "^5.4 || ^6.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "psr/log": "^1.1 || ^2 || ^3", + "symfony/browser-kit": "^5.4 || ^6.0", + "symfony/config": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/framework-bundle": "^5.4 || ^6.0", + "symfony/http-kernel": "^5.4 || ^6.0", + "symfony/phpunit-bridge": "^6.2" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "http://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/symfony/psr-http-message-bridge/issues", + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1" + }, + "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": "2023-07-26T11:53:26+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/191afdcb5804db960d26d8566b7e9a2843cab3a0", + "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "psr/container": "", + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "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 writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v1.1.2" + }, + "time": "2019-05-28T07:50:59+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "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": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.47" + }, + "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-11-10T20:33:58+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v4.4.43", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "4a7a3a3d55c471d396e6d28011368b7b83cb518b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/4a7a3a3d55c471d396e6d28011368b7b83cb518b", + "reference": "4a7a3a3d55c471d396e6d28011368b7b83cb518b", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/var-dumper": "^4.4.9|^5.0.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "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": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v4.4.43" + }, + "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": "2022-05-27T11:44:32+00:00" + }, + { + "name": "textalk/websocket", + "version": "1.6.3", + "source": { + "type": "git", + "url": "https://github.com/Textalk/websocket-php.git", + "reference": "67de79745b1a357caf812bfc44e0abf481cee012" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Textalk/websocket-php/zipball/67de79745b1a357caf812bfc44e0abf481cee012", + "reference": "67de79745b1a357caf812bfc44e0abf481cee012", + "shasum": "" + }, + "require": { + "php": "^7.4 | ^8.0", + "phrity/net-uri": "^1.0", + "phrity/util-errorhandler": "^1.0", + "psr/http-message": "^1.0", + "psr/log": "^1.0 | ^2.0 | ^3.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "WebSocket\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Fredrik Liljegren" + }, + { + "name": "Sören Jensen" + } + ], + "description": "WebSocket client and server", + "support": { + "issues": "https://github.com/Textalk/websocket-php/issues", + "source": "https://github.com/Textalk/websocket-php/tree/1.6.3" + }, + "time": "2022-11-07T18:59:33+00:00" + }, + { + "name": "topthink/framework", + "version": "v5.1.12", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "f879603ee321af8fde56d8855445cf98bc81b042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/f879603ee321af8fde56d8855445cf98bc81b042", + "reference": "f879603ee321af8fde56d8855445cf98bc81b042", + "shasum": "" + }, + "require": { + "php": ">=5.6.0", + "topthink/think-installer": "~1.0" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsstream": "~1.6", + "phpdocumentor/reflection-docblock": "^2.0", + "phploc/phploc": "2.*", + "phpunit/phpunit": "^5.0|^6.0", + "sebastian/phpcpd": "2.*", + "squizlabs/php_codesniffer": "2.*" + }, + "type": "think-framework", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], "authors": [ { "name": "liu21st", "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" } ], "description": "the new thinkphp framework", @@ -55,7 +3839,144 @@ "orm", "thinkphp" ], - "time": "2022-10-25T14:59:38+00:00" + "support": { + "issues": "https://github.com/top-think/framework/issues", + "source": "https://github.com/top-think/framework/tree/5.1" + }, + "time": "2018-04-26T03:46:41+00:00" + }, + { + "name": "topthink/think-captcha", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-captcha.git", + "reference": "54c8a51552f99ff9ea89ea9c272383a8f738ceee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-captcha/zipball/54c8a51552f99ff9ea89ea9c272383a8f738ceee", + "reference": "54c8a51552f99ff9ea89ea9c272383a8f738ceee", + "shasum": "" + }, + "require": { + "topthink/framework": "5.1.*" + }, + "type": "library", + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\captcha\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "captcha package for thinkphp5", + "support": { + "issues": "https://github.com/top-think/think-captcha/issues", + "source": "https://github.com/top-think/think-captcha/tree/2.0" + }, + "time": "2017-12-31T16:37:49+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.11", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "1d6ada9b9f3130046bf6922fe1bd159c8d88a33c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/1d6ada9b9f3130046bf6922fe1bd159c8d88a33c", + "reference": "1d6ada9b9f3130046bf6922fe1bd159c8d88a33c", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package", + "support": { + "issues": "https://github.com/top-think/think-helper/issues", + "source": "https://github.com/top-think/think-helper/tree/v3.1.11" + }, + "time": "2025-04-07T06:55:59+00:00" + }, + { + "name": "topthink/think-image", + "version": "v1.0.8", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-image.git", + "reference": "d1d748cbb2fe2f29fca6138cf96cb8b5113892f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-image/zipball/d1d748cbb2fe2f29fca6138cf96cb8b5113892f1", + "reference": "d1d748cbb2fe2f29fca6138cf96cb8b5113892f1", + "shasum": "" + }, + "require": { + "ext-gd": "*" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "topthink/framework": "^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Image Package", + "support": { + "issues": "https://github.com/top-think/think-image/issues", + "source": "https://github.com/top-think/think-image/tree/v1.0.8" + }, + "time": "2024-08-07T10:06:35+00:00" }, { "name": "topthink/think-installer", @@ -96,17 +4017,351 @@ "email": "448901948@qq.com" } ], + "support": { + "issues": "https://github.com/top-think/think-installer/issues", + "source": "https://github.com/top-think/think-installer/tree/v1.0.14" + }, "time": "2021-03-25T08:34:02+00:00" + }, + { + "name": "topthink/think-queue", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-queue.git", + "reference": "465320c9cb7811df22d4ff8f29f58ead7d104348" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-queue/zipball/465320c9cb7811df22d4ff8f29f58ead7d104348", + "reference": "465320c9cb7811df22d4ff8f29f58ead7d104348", + "shasum": "" + }, + "require": { + "topthink/think-helper": ">=1.0.4", + "topthink/think-installer": ">=1.0.10" + }, + "type": "think-extend", + "extra": { + "think-config": { + "queue": "src/config.php" + } + }, + "autoload": { + "files": [ + "src/common.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Queue Package", + "support": { + "issues": "https://github.com/top-think/think-queue/issues", + "source": "https://github.com/top-think/think-queue/tree/master" + }, + "time": "2018-05-04T05:29:53+00:00" + }, + { + "name": "topthink/think-worker", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-worker.git", + "reference": "7b7a64b2911cc11298b11677508f1a1936a2fb70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-worker/zipball/7b7a64b2911cc11298b11677508f1a1936a2fb70", + "reference": "7b7a64b2911cc11298b11677508f1a1936a2fb70", + "shasum": "" + }, + "require": { + "topthink/framework": "5.1.*", + "workerman/workerman": "^3.3.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/config.php" + ], + "psr-4": { + "think\\worker\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "workerman extend for thinkphp5", + "support": { + "issues": "https://github.com/top-think/think-worker/issues", + "source": "https://github.com/top-think/think-worker/tree/2.0" + }, + "time": "2018-06-28T06:04:53+00:00" + }, + { + "name": "workerman/gateway-worker", + "version": "v3.0.22", + "source": { + "type": "git", + "url": "https://github.com/walkor/GatewayWorker.git", + "reference": "a615036c482d11f68b693998575e804752ef9068" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/a615036c482d11f68b693998575e804752ef9068", + "reference": "a615036c482d11f68b693998575e804752ef9068", + "shasum": "" + }, + "require": { + "workerman/workerman": ">=3.5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "GatewayWorker\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "keywords": [ + "communication", + "distributed" + ], + "support": { + "issues": "https://github.com/walkor/GatewayWorker/issues", + "source": "https://github.com/walkor/GatewayWorker/tree/v3.0.22" + }, + "funding": [ + { + "url": "https://opencollective.com/walkor", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "time": "2021-12-23T13:13:09+00:00" + }, + { + "name": "workerman/workerman", + "version": "v3.5.35", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "3cc0adae51ba36db38b11e7996c64250d356dbe7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/3cc0adae51ba36db38b11e7996c64250d356dbe7", + "reference": "3cc0adae51ba36db38b11e7996c64250d356dbe7", + "shasum": "" + }, + "require": { + "php": "^5.3||^7.0" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://wenda.workerman.net/", + "issues": "https://github.com/walkor/workerman/issues", + "source": "https://github.com/walkor/workerman", + "wiki": "http://doc.workerman.net/" + }, + "funding": [ + { + "url": "https://opencollective.com/workerman", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "time": "2023-09-13T14:30:13+00:00" + } + ], + "packages-dev": [ + { + "name": "symfony/var-dumper", + "version": "v3.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "0719f6cf4633a38b2c1585140998579ce23b4b7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0719f6cf4633a38b2c1585140998579ce23b4b7d", + "reference": "0719f6cf4633a38b2c1585140998579ce23b4b7d", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "require-dev": { + "ext-iconv": "*", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "ext-symfony_debug": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "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": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v3.4.47" + }, + "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": "2020-10-24T10:57:07+00:00" + }, + { + "name": "topthink/think-migration", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-migration.git", + "reference": "70c89850ca29c2eab988c7c3475d1d5331901bb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-migration/zipball/70c89850ca29c2eab988c7c3475d1d5331901bb8", + "reference": "70c89850ca29c2eab988c7c3475d1d5331901bb8", + "shasum": "" + }, + "require": { + "topthink/framework": "5.1.*" + }, + "type": "library", + "autoload": { + "files": [ + "src/config.php" + ], + "psr-4": { + "Phinx\\": "phinx/src/Phinx", + "think\\migration\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "support": { + "issues": "https://github.com/top-think/think-migration/issues", + "source": "https://github.com/top-think/think-migration/tree/2.0" + }, + "time": "2017-12-31T16:32:22+00:00" } ], - "packages-dev": [], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "dev", "stability-flags": [], - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=5.4.0" + "php": ">=5.6.0" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.3.0" } From 5a5160b92d8c77c193ea1f8bc044e5d18bf27b63 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 20 Nov 2025 11:48:31 +0800 Subject: [PATCH 02/51] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/common.php | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Server/application/common.php b/Server/application/common.php index 37a12739f..8785f12c9 100644 --- a/Server/application/common.php +++ b/Server/application/common.php @@ -75,7 +75,18 @@ if (!function_exists('requestCurl')) { if (!function_exists('dataBuild')) { function dataBuild($array) { - return is_array($array) ? http_build_query($array) : $array; + if (!is_array($array)) { + return $array; + } + + // 处理嵌套数组 + foreach ($array as $key => $value) { + if (is_array($value)) { + $array[$key] = json_encode($value); + } + } + + return http_build_query($array); } } @@ -550,14 +561,15 @@ if (!function_exists('exit_data')) { exit(); } } - -/** - * 调试打印变量但不终止程序 - * @return void - */ -function dump() -{ - call_user_func_array(['app\\common\\helper\\Debug', 'dump'], func_get_args()); +if (!function_exists('dump')) { + /** + * 调试打印变量但不终止程序 + * @return void + */ + function dump() + { + call_user_func_array(['app\\common\\helper\\Debug', 'dump'], func_get_args()); + } } if (!function_exists('artificialAllotWechatFriend')) { From 13ae8bd76f4235ec0de33146ed7448f810b7dc1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 1 Dec 2025 10:38:27 +0800 Subject: [PATCH 03/51] Enhance message filtering to support WeChat red packet messages, allowing for sender identification in chat records. --- Touchkebao/src/utils/filter.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Touchkebao/src/utils/filter.ts b/Touchkebao/src/utils/filter.ts index 22887a8bd..c2fa1a6bc 100644 --- a/Touchkebao/src/utils/filter.ts +++ b/Touchkebao/src/utils/filter.ts @@ -40,6 +40,18 @@ export const messageFilter = (message: string) => { case !!parsed.linkUrl: return "[链接]"; + // 微信红包消息:包含 paymsgid 或 nativeurl 中包含红包链接 + case !!( + parsed.paymsgid || + (parsed.nativeurl && + parsed.nativeurl.includes( + "wxpay://c2cbizmessagehandler/hongbao/receivehongbao", + )) + ): + return parsed.sendertitle + ? `[微信红包] ${parsed.sendertitle}` + : "[微信红包]"; + // 文本消息:包含 text 或 content case !!(parsed.text || parsed.content): return parsed.text || parsed.content; From b9bc0a71a9ef4901ec5d1a172c48ab86909e66cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 1 Dec 2025 10:56:05 +0800 Subject: [PATCH 04/51] =?UTF-8?q?=E5=9C=A8MessageRecord=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E5=A2=9E=E5=8A=A0=E5=BE=AE=E4=BF=A1=E8=BD=AC=E8=B4=A6?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=94=AF=E6=8C=81=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E8=BF=87=E6=BB=A4=E9=80=BB=E8=BE=91=E3=80=82?= =?UTF-8?q?=E8=B0=83=E6=95=B4RedPacketMessage=E6=A0=B7=E5=BC=8F=E4=BB=A5?= =?UTF-8?q?=E8=8E=B7=E5=BE=97=E6=9B=B4=E5=A5=BD=E7=9A=84=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RedPacketMessage.module.scss | 2 +- .../TransferMessage.module.scss | 124 ++++++++++++++++++ .../components/TransferMessage/index.tsx | 71 ++++++++++ .../components/MessageRecord/index.tsx | 9 ++ Touchkebao/src/utils/filter.ts | 7 + 5 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss create mode 100644 Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/RedPacketMessage.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/RedPacketMessage.module.scss index 1d8603b03..11aa32a0b 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/RedPacketMessage.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/RedPacketMessage.module.scss @@ -2,7 +2,7 @@ .redPacketMessage { background: transparent; box-shadow: none; - max-width: 300px; + width: 260px; } .redPacketCard { diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss new file mode 100644 index 000000000..1f5aa75f8 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss @@ -0,0 +1,124 @@ +// 转账消息样式 +.transferMessage { + background: transparent; + box-shadow: none; + width: 260px; + max-width: 260px; +} + +.transferCard { + position: relative; + display: flex; + flex-direction: column; + padding: 12px; + background: #ff9500; // 橙色背景 + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 8px rgba(255, 149, 0, 0.2); + + &:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(255, 149, 0, 0.3); + } + + &:active { + transform: translateY(0); + } +} + +.transferHeader { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 5px; +} + +.transferIcon { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: #ffffff; + border-radius: 50%; + flex-shrink: 0; + font-size: 18px; + color: #ff9500; +} + +.transferAmount { + font-size: 20px; + font-weight: 600; + color: #ffffff; + line-height: 1.2; + flex: 1; +} + +.transferStatus { + font-size: 13px; + color: #ffffff; + margin-bottom: 5px; + opacity: 0.95; +} + +.transferDivider { + height: 1px; + background: rgba(255, 255, 255, 0.3); + margin-bottom: 6px; +} + +.transferFooter { + display: flex; + align-items: center; + justify-content: flex-start; +} + +.transferLabel { + font-size: 12px; + color: rgba(255, 255, 255, 0.9); + font-weight: 400; + line-height: 1.4; +} + +// 消息文本样式(用于错误提示) +.messageText { + line-height: 1.4; + white-space: pre-wrap; + word-break: break-word; + color: #8c8c8c; + font-size: 13px; +} + +// 响应式设计 +@media (max-width: 768px) { + .transferMessage { + max-width: 200px; + } + + .transferCard { + padding: 12px; + } + + .transferIcon { + width: 28px; + height: 28px; + + svg { + width: 18px; + height: 18px; + } + } + + .transferAmount { + font-size: 18px; + } + + .transferStatus { + font-size: 12px; + } + + .transferLabel { + font-size: 11px; + } +} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx new file mode 100644 index 000000000..c46c39261 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import { SwapOutlined } from "@ant-design/icons"; +import styles from "./TransferMessage.module.scss"; + +interface TransferData { + title?: string; + feedesc?: string; + payMemo?: string; + transferid?: string; + transcationid?: string; + invalidtime?: string; + paysubtype?: string; + [key: string]: any; +} + +interface TransferMessageProps { + content: string; +} + +const TransferMessage: React.FC = ({ content }) => { + const renderErrorMessage = (fallbackText: string) => ( +
{fallbackText}
+ ); + + if (typeof content !== "string" || !content.trim()) { + return renderErrorMessage("[转账消息 - 无效内容]"); + } + + try { + const trimmedContent = content.trim(); + const jsonData: TransferData = JSON.parse(trimmedContent); + + // 验证是否为转账消息 + const isTransfer = + jsonData.title === "微信转账" || + (jsonData.transferid && jsonData.feedesc); + + if (!isTransfer) { + return renderErrorMessage("[转账消息 - 格式错误]"); + } + + const amount = jsonData.feedesc || "¥0.00"; + const memo = jsonData.payMemo || ""; + // 根据转账状态显示不同的提示文字 + // 可以根据实际业务逻辑判断状态,这里默认显示"待朋友确认收钱" + const statusText = "待朋友确认收钱"; + + return ( +
+
+
+
+ +
+
{amount}
+
+
{statusText}
+
+
+ 微信转账 +
+
+
+ ); + } catch (e) { + console.warn("转账消息解析失败:", e); + return renderErrorMessage("[转账消息 - 解析失败]"); + } +}; + +export default TransferMessage; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index ccd4759b4..ad11cec54 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -8,6 +8,7 @@ import ClickMenu from "./components/ClickMeau"; import LocationMessage from "./components/LocationMessage"; import SystemRecommendRemarkMessage from "./components/SystemRecommendRemarkMessage/index"; import RedPacketMessage from "./components/RedPacketMessage"; +import TransferMessage from "./components/TransferMessage"; import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { formatWechatTime } from "@/utils/common"; import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji"; @@ -274,6 +275,14 @@ const MessageRecord: React.FC = ({ contract }) => { return ; } + // 判断是否为转账消息 + if ( + jsonData.title === "微信转账" || + (jsonData.transferid && jsonData.feedesc) + ) { + return ; + } + if (jsonData.type === "file" && msg && contract) { return ( { ? `[微信红包] ${parsed.sendertitle}` : "[微信红包]"; + // 微信转账消息:包含 title="微信转账" 或 transferid + feedesc + case !!( + parsed.title === "微信转账" || + (parsed.transferid && parsed.feedesc) + ): + return parsed.feedesc ? `[微信转账] ${parsed.feedesc}` : "[微信转账]"; + // 文本消息:包含 text 或 content case !!(parsed.text || parsed.content): return parsed.text || parsed.content; From 1ca5b30c1582e98f4f1b4d04a6abb631996f0c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 1 Dec 2025 10:59:50 +0800 Subject: [PATCH 05/51] Refactor TransferMessage component to improve layout and styling. Adjust icon size and add status display for better user experience. --- .../TransferMessage/TransferMessage.module.scss | 12 +++++------- .../components/TransferMessage/index.tsx | 9 ++++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss index 1f5aa75f8..731e418cc 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss @@ -38,20 +38,18 @@ display: flex; align-items: center; justify-content: center; - width: 32px; - height: 32px; - background: #ffffff; + width: 36px; + height: 36px; border-radius: 50%; flex-shrink: 0; font-size: 18px; - color: #ff9500; + color: #ffffff; + border: 2px solid #ffffff; } .transferAmount { - font-size: 20px; - font-weight: 600; + font-size: 16px; color: #ffffff; - line-height: 1.2; flex: 1; } diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx index c46c39261..be9f93121 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx @@ -50,11 +50,14 @@ const TransferMessage: React.FC = ({ content }) => {
- + +
+
+
{amount}
+
{statusText}
-
{amount}
-
{statusText}
+
微信转账 From 8659049ae9eb388c8e736dbb8f12fceff59a02ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 1 Dec 2025 12:23:19 +0800 Subject: [PATCH 06/51] =?UTF-8?q?=E5=A2=9E=E5=BC=BAMessageRecord=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=BB=A5=E6=94=AF=E6=8C=81RedPacketMessage=E5=92=8CTr?= =?UTF-8?q?ansferMessage=E7=9A=84=E9=A2=9D=E5=A4=96=E9=81=93=E5=85=B7?= =?UTF-8?q?=EF=BC=8C=E4=BB=8E=E8=80=8C=E6=9B=B4=E5=A5=BD=E5=9C=B0=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=8D=95=E5=87=BB=E4=BA=8B=E4=BB=B6=E5=92=8C=E5=A5=97?= =?UTF-8?q?=E6=8E=A5=E5=AD=97=E8=AF=B7=E6=B1=82=E3=80=82=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=A6=81=E7=94=A8=E7=9A=84=E8=BD=AC=E6=8E=A5=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E7=9A=84=E6=A0=B7=E5=BC=8F=E4=BB=A5=E6=94=B9=E5=96=84=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/RedPacketMessage/index.tsx | 44 ++++++++- .../TransferMessage.module.scss | 10 ++ .../components/TransferMessage/index.tsx | 92 +++++++++++++++++-- .../components/MessageRecord/index.tsx | 13 ++- 4 files changed, 148 insertions(+), 11 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/index.tsx index 75bf86ff5..1d462cafc 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/index.tsx @@ -1,4 +1,6 @@ import React from "react"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; +import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import styles from "./RedPacketMessage.module.scss"; interface RedPacketData { @@ -10,9 +12,15 @@ interface RedPacketData { interface RedPacketMessageProps { content: string; + msg?: ChatRecord; + contract?: ContractData | weChatGroup; } -const RedPacketMessage: React.FC = ({ content }) => { +const RedPacketMessage: React.FC = ({ + content, + msg, + contract, +}) => { const renderErrorMessage = (fallbackText: string) => (
{fallbackText}
); @@ -39,10 +47,42 @@ const RedPacketMessage: React.FC = ({ content }) => { const title = jsonData.sendertitle || "恭喜发财,大吉大利"; const paymsgid = jsonData.paymsgid || ""; + const nativeurl = jsonData.nativeurl || ""; + + // 处理红包点击事件,发送 socket 请求 + const handleRedPacketClick = () => { + if (!contract || !paymsgid || !nativeurl) { + console.warn("红包点击失败:缺少必要参数", { + contract, + paymsgid, + nativeurl, + }); + return; + } + + const isGroup = !!contract.chatroomId; + const wechatFriendId = isGroup ? 0 : contract.id; + + // 发送 socket 请求 + useWebSocketStore.getState().sendCommand("CmdOpenLuckyMoney", { + wechatAccountId: contract.wechatAccountId, + wechatFriendId: wechatFriendId, + paymsgid: paymsgid, + nativeurl: nativeurl, + }); + + console.log("发送红包打开请求:", { + cmdType: "CmdOpenLuckyMoney", + wechatAccountId: contract.wechatAccountId, + wechatFriendId: wechatFriendId, + paymsgid: paymsgid, + nativeurl: nativeurl, + }); + }; return (
-
+
🧧
{title}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss index 731e418cc..006691c61 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/TransferMessage.module.scss @@ -25,6 +25,16 @@ &:active { transform: translateY(0); } + + &.transferDisabled { + opacity: 0.5; + cursor: not-allowed; + + &:hover { + transform: none; + box-shadow: 0 2px 8px rgba(255, 149, 0, 0.2); + } + } } .transferHeader { diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx index be9f93121..7c6f2f68e 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage/index.tsx @@ -1,5 +1,7 @@ import React from "react"; import { SwapOutlined } from "@ant-design/icons"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; +import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import styles from "./TransferMessage.module.scss"; interface TransferData { @@ -15,9 +17,16 @@ interface TransferData { interface TransferMessageProps { content: string; + msg?: ChatRecord; + contract?: ContractData | weChatGroup; } -const TransferMessage: React.FC = ({ content }) => { +const TransferMessage: React.FC = ({ + content, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + msg, // 保留参数以保持与 RedPacketMessage 组件的一致性,未来可能会用到 + contract, +}) => { const renderErrorMessage = (fallbackText: string) => (
{fallbackText}
); @@ -40,14 +49,85 @@ const TransferMessage: React.FC = ({ content }) => { } const amount = jsonData.feedesc || "¥0.00"; - const memo = jsonData.payMemo || ""; - // 根据转账状态显示不同的提示文字 - // 可以根据实际业务逻辑判断状态,这里默认显示"待朋友确认收钱" - const statusText = "待朋友确认收钱"; + + // 判断转账状态 + const getTransferStatus = ( + data: TransferData, + ): { text: string; canClick: boolean } => { + const paySubType = data.paysubtype || ""; + + switch (paySubType) { + case "1": + return { text: "待朋友确认收钱", canClick: true }; + case "2": + return { text: "已过期", canClick: false }; + case "3": + return { text: "已领取", canClick: false }; + case "4": + return { text: "已退回", canClick: false }; + default: + // 默认情况:可能是待领取 + return { text: "待朋友确认收钱", canClick: true }; + } + }; + + const { text: statusText, canClick } = getTransferStatus(jsonData); + + // 处理转账点击事件,发送 socket 请求 + const handleTransferClick = () => { + // 如果状态不允许点击,直接返回 + if (!canClick) { + console.log("转账状态不允许点击:", statusText); + return; + } + + if ( + !contract || + !jsonData.transferid || + !jsonData.transcationid || + !jsonData.invalidtime || + !jsonData.paysubtype + ) { + console.warn("转账点击失败:缺少必要参数", { + contract, + transferid: jsonData.transferid, + transcationid: jsonData.transcationid, + invalidtime: jsonData.invalidtime, + paysubtype: jsonData.paysubtype, + }); + return; + } + + const isGroup = !!contract.chatroomId; + const wechatFriendId = isGroup ? 0 : contract.id; + + // 发送 socket 请求 + useWebSocketStore.getState().sendCommand("CmdReceiveTransMoney", { + wechatAccountId: contract.wechatAccountId, + wechatFriendId: wechatFriendId, + transcationid: jsonData.transcationid, + transferid: jsonData.transferid, + invalidtime: jsonData.invalidtime, + paysubtype: jsonData.paysubtype, + }); + + console.log("发送转账接收请求:", { + cmdType: "CmdReceiveTransMoney", + wechatAccountId: contract.wechatAccountId, + wechatFriendId: wechatFriendId, + transcationid: jsonData.transcationid, + transferid: jsonData.transferid, + invalidtime: jsonData.invalidtime, + paysubtype: jsonData.paysubtype, + }); + }; return (
-
+
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index ad11cec54..44b01c977 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -256,7 +256,6 @@ const MessageRecord: React.FC = ({ contract }) => { msg?: ChatRecord, contract?: ContractData | weChatGroup, ) => { - console.log("红包"); if (isLegacyEmojiContent(trimmedContent)) { return renderEmojiContent(rawContent); } @@ -272,7 +271,13 @@ const MessageRecord: React.FC = ({ contract }) => { "wxpay://c2cbizmessagehandler/hongbao/receivehongbao", ) ) { - return ; + return ( + + ); } // 判断是否为转账消息 @@ -280,7 +285,9 @@ const MessageRecord: React.FC = ({ contract }) => { jsonData.title === "微信转账" || (jsonData.transferid && jsonData.feedesc) ) { - return ; + return ( + + ); } if (jsonData.type === "file" && msg && contract) { From 7d9f94984cfec45d1b8dcf5cff48d51ba24ffe14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 1 Dec 2025 14:23:10 +0800 Subject: [PATCH 07/51] =?UTF-8?q?=E5=9C=A8MessageRecord=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E5=AE=9E=E7=8E=B0parseSystemMessage=E5=AE=9E=E7=94=A8?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=EF=BC=8C=E4=BB=8E=E7=B3=BB=E7=BB=9F=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E4=B8=AD=E6=8F=90=E5=8F=96=E7=BA=AF=E6=96=87=E6=9C=AC?= =?UTF-8?q?=EF=BC=8C=E9=80=9A=E8=BF=87=E5=88=A0=E9=99=A4HTML=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E5=B9=B6=E4=B8=BA=E7=9B=B8=E5=85=B3=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BA=A2=E5=8C=85=E5=9B=BE=E6=A0=87=E6=9D=A5?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=B6=88=E6=81=AF=E6=B8=B2=E6=9F=93=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/MessageRecord/index.tsx | 17 ++++++---- Touchkebao/src/utils/filter.ts | 34 +++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index 44b01c977..23ea4a23f 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -12,6 +12,7 @@ import TransferMessage from "./components/TransferMessage"; import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { formatWechatTime } from "@/utils/common"; import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji"; +import { parseSystemMessage } from "@/utils/filter"; import styles from "./com.module.scss"; import { useWeChatStore } from "@/store/module/weChat/weChat"; import { useContactStore } from "@/store/module/weChat/contacts"; @@ -856,13 +857,15 @@ const MessageRecord: React.FC = ({ contract }) => { {group.messages .filter(v => [10000].includes(v.msgType)) - .map(msg => ( -
- ))} + .map(msg => { + // 解析系统消息,提取纯文本(移除img标签和_wc_custom_link_标签) + const parsedText = parseSystemMessage(msg.content); + return ( +
+ {parsedText} +
+ ); + })} {group.messages .filter(v => [570425393, 90000].includes(v.msgType)) diff --git a/Touchkebao/src/utils/filter.ts b/Touchkebao/src/utils/filter.ts index 199c2ce80..04058d51a 100644 --- a/Touchkebao/src/utils/filter.ts +++ b/Touchkebao/src/utils/filter.ts @@ -86,3 +86,37 @@ export const messageFilter = (message: string) => { return message; } }; + +/** + * 解析系统消息中的HTML标签,提取纯文本 + * 例如: 你领取了许老板的<_wc_custom_link_>红包。 + * 提取为:🧧 你领取了许老板的红包 + */ +export const parseSystemMessage = (html: string): string => { + if (!html) return ""; + + let text = html; + + // 移除所有 标签 + text = text.replace(/]*>/gi, ""); + + // 提取 <_wc_custom_link_> 标签内的文本内容 + // 匹配 <_wc_custom_link_ ...>内容 + text = text.replace( + /<_wc_custom_link_[^>]*>(.*?)<\/_wc_custom_link_>/gi, + "$1", + ); + + // 清理多余的空格(将多个连续空格替换为单个空格) + text = text.replace(/\s+/g, " "); + + // 去除首尾空格 + text = text.trim(); + + // 如果消息内容包含红包相关关键词,在前面添加🧧图标 + if (/红包|hongbao/i.test(text)) { + text = `🧧 ${text}`; + } + + return text; +}; From 57c398f537e5f692bd09ca4d6664ca256fb2ffd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 1 Dec 2025 14:27:39 +0800 Subject: [PATCH 08/51] =?UTF-8?q?=E6=9B=B4=E6=96=B0MessageRecord=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E7=9A=84=E6=B6=88=E6=81=AF=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BB=A5=E5=8C=85=E6=8B=AC=E5=85=B6?= =?UTF-8?q?=E4=BB=96=E6=B6=88=E6=81=AF=E7=B1=BB=E5=9E=8B=EF=BC=88-10001?= =?UTF-8?q?=EF=BC=89=EF=BC=8C=E4=BB=A5=E6=94=B9=E8=BF=9B=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E7=9A=84=E5=A4=84=E7=90=86=E5=B9=B6=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ChatWindow/components/MessageRecord/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index 23ea4a23f..c6a78cb80 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -856,7 +856,7 @@ const MessageRecord: React.FC = ({ contract }) => { {groupMessagesByTime(currentMessages).map((group, groupIndex) => ( {group.messages - .filter(v => [10000].includes(v.msgType)) + .filter(v => [10000, -10001].includes(v.msgType)) .map(msg => { // 解析系统消息,提取纯文本(移除img标签和_wc_custom_link_标签) const parsedText = parseSystemMessage(msg.content); @@ -893,7 +893,7 @@ const MessageRecord: React.FC = ({ contract }) => { })}
{group.time}
{group.messages - .filter(v => ![10000, 570425393, 90000].includes(v.msgType)) + .filter(v => ![10000, 570425393, 90000, -10001].includes(v.msgType)) .map(msg => { return renderMessage(msg); })} From 2cc21840026a84c85096cd31e4638d45a3de4c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 1 Dec 2025 14:34:19 +0800 Subject: [PATCH 09/51] =?UTF-8?q?=E5=A2=9E=E5=BC=BAMessageRecord=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=BB=A5=E7=AE=A1=E7=90=86=E6=B6=88=E6=81=AF=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E6=9C=9F=E9=97=B4=E7=9A=84=E6=BB=9A=E5=8A=A8=E8=A1=8C?= =?UTF-8?q?=E4=B8=BA=E3=80=82=E4=B8=BA=E6=B6=88=E6=81=AF=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E5=92=8C=E5=8A=A0=E8=BD=BD=E7=8A=B6=E6=80=81=E5=BC=95=E5=85=A5?= =?UTF-8?q?=E5=BC=95=E7=94=A8=EF=BC=8C=E9=80=9A=E8=BF=87=E5=9C=A8=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E6=9B=B4=E5=A4=9A=E6=B6=88=E6=81=AF=E6=97=B6=E4=BF=9D?= =?UTF-8?q?=E7=95=99=E6=BB=9A=E5=8A=A8=E4=BD=8D=E7=BD=AE=E6=9D=A5=E7=A1=AE?= =?UTF-8?q?=E4=BF=9D=E5=B9=B3=E6=BB=91=E7=9A=84=E7=94=A8=E6=88=B7=E4=BD=93?= =?UTF-8?q?=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/MessageRecord/index.tsx | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index c6a78cb80..c534fe9d8 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -150,6 +150,7 @@ type GroupRenderItem = { const MessageRecord: React.FC = ({ contract }) => { const messagesEndRef = useRef(null); + const messagesContainerRef = useRef(null); // 右键菜单状态 const [contextMenu, setContextMenu] = useState({ visible: false, @@ -171,6 +172,8 @@ const MessageRecord: React.FC = ({ contract }) => { const isLoadingData = useWeChatStore(state => state.isLoadingData); const showCheckbox = useWeChatStore(state => state.showCheckbox); const prevMessagesRef = useRef(currentMessages); + const isLoadingMoreRef = useRef(false); + const scrollPositionRef = useRef(0); const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox); const updateEnterModule = useWeChatStore(state => state.updateEnterModule); const currentCustomer = useCustomerStore(state => @@ -460,7 +463,10 @@ const MessageRecord: React.FC = ({ contract }) => { } }); - if (currentMessages.length > prevLength && !hasVideoStateChange) { + // 如果正在加载更早的消息,不自动滚动到底部 + if (isLoadingMoreRef.current && currentMessages.length > prevLength) { + // 不滚动,等待加载完成后在另一个 useEffect 中恢复滚动位置 + } else if (currentMessages.length > prevLength && !hasVideoStateChange) { scrollToBottom(); } else if (isLoadingData && !hasVideoStateChange) { scrollToBottom(); @@ -470,6 +476,22 @@ const MessageRecord: React.FC = ({ contract }) => { prevMessagesRef.current = currentMessages; }, [currentMessages, isLoadingData]); + // 监听加载状态,当加载完成时恢复滚动位置 + useEffect(() => { + if (!messagesLoading && isLoadingMoreRef.current) { + // 等待DOM更新后恢复滚动位置 + requestAnimationFrame(() => { + const container = messagesContainerRef.current; + if (container) { + const scrollHeight = container.scrollHeight; + const newScrollTop = scrollHeight - scrollPositionRef.current; + container.scrollTop = newScrollTop; + } + isLoadingMoreRef.current = false; + }); + } + }, [messagesLoading]); + const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; @@ -762,6 +784,12 @@ const MessageRecord: React.FC = ({ contract }) => { if (messagesLoading || !currentMessagesHasMore) { return; } + // 保存当前滚动位置(距离底部的距离) + const container = messagesContainerRef.current; + if (container) { + scrollPositionRef.current = container.scrollHeight - container.scrollTop; + isLoadingMoreRef.current = true; + } loadChatMessages(false); }; @@ -840,7 +868,7 @@ const MessageRecord: React.FC = ({ contract }) => { }; return ( -
+
loadMoreMessages()} From 75aab08a72e24ed34139e867c9c1ce32148478e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 1 Dec 2025 14:43:33 +0800 Subject: [PATCH 10/51] =?UTF-8?q?=E5=9C=A8MessageList=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E5=AE=9E=E7=8E=B0=E5=AF=B9=E4=B8=A2=E5=A4=B1=E5=A4=B4?= =?UTF-8?q?=E5=83=8F=E6=88=96=E6=98=B5=E7=A7=B0=E7=9A=84=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=EF=BC=8C=E5=B9=B6=E7=9B=B8=E5=BA=94=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=BC=9A=E8=AF=9D=E7=8A=B6=E6=80=81=E5=92=8C=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=EF=BC=8C=E4=BB=A5=E7=A1=AE=E4=BF=9D=E5=87=86?= =?UTF-8?q?=E7=A1=AE=E6=98=BE=E7=A4=BA=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SidebarMenu/MessageList/index.tsx | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx index 1e57ed746..1e08ff0ab 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx @@ -805,6 +805,59 @@ const MessageList: React.FC = () => { // 设置当前会话 setCurrentContact(session as any); + // 如果头像或昵称缺失,则尝试拉取详情并更新列表 + const needFetchDetail = + !session.avatar || + !(session.conRemark || session.nickname || session.wechatId); + + if (needFetchDetail) { + try { + let detailResult: any = null; + if (session.type === "friend") { + detailResult = await getWechatFriendDetail({ id: session.id }); + } else { + detailResult = await getWechatChatroomDetail({ id: session.id }); + } + + const detail = detailResult?.detail; + if (detail) { + // 1. 更新会话列表中的该条记录(驱动 UI 立即更新头像和昵称) + setSessionState(prev => + prev.map(s => + s.id === session.id && s.type === session.type + ? { + ...s, + avatar: + session.type === "group" + ? detail.chatroomAvatar || s.avatar + : detail.avatar || s.avatar, + nickname: detail.nickname || s.nickname, + conRemark: detail.conRemark || s.conRemark, + wechatId: detail.wechatId || s.wechatId, + } + : s, + ), + ); + + // 2. 同步到会话数据库,保证刷新后数据仍然是最新的 + await MessageManager.updateSession({ + userId: currentUserId, + id: session.id, + type: session.type, + avatar: + session.type === "group" + ? detail.chatroomAvatar || session.avatar + : detail.avatar || session.avatar, + nickname: detail.nickname || session.nickname, + conRemark: detail.conRemark || session.conRemark, + wechatId: detail.wechatId || session.wechatId, + }); + } + } catch (e) { + console.error("点击会话补拉详情失败:", e); + } + } + // 标记为已读(不更新时间和排序) if (session.config.unreadCount > 0) { // 立即更新UI(只更新未读数量) From 11a4ec8c676bb918fce001001d6e8ea5cba155cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 1 Dec 2025 15:09:25 +0800 Subject: [PATCH 11/51] =?UTF-8?q?=E5=9C=A8MessageList=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E6=B7=BB=E5=8A=A0=E6=9C=AA=E7=9F=A5=E8=81=94=E7=B3=BB?= =?UTF-8?q?=E4=BA=BA=E8=A1=A5=E5=85=85=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E6=8B=89=E5=8F=96=E7=BC=BA=E5=A4=B1=E7=9A=84=E5=A4=B4?= =?UTF-8?q?=E5=83=8F=E5=92=8C=E6=98=B5=E7=A7=B0=E4=BF=A1=E6=81=AF=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E6=9B=B4=E6=96=B0=E4=BC=9A=E8=AF=9D=E5=92=8C=E8=81=94?= =?UTF-8?q?=E7=B3=BB=E4=BA=BA=E6=95=B0=E6=8D=AE=E5=BA=93=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF=E7=9A=84?= =?UTF-8?q?=E5=87=86=E7=A1=AE=E6=80=A7=E5=92=8C=E5=AE=8C=E6=95=B4=E6=80=A7?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SidebarMenu/MessageList/index.tsx | 181 ++++++++++++------ 1 file changed, 127 insertions(+), 54 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx index 1e08ff0ab..efbcbad8a 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx @@ -47,6 +47,7 @@ const MessageList: React.FC = () => { } = useMessageStore(); const [filteredSessions, setFilteredSessions] = useState([]); const [syncing, setSyncing] = useState(false); // 同步状态 + const hasEnrichedRef = useRef(false); // 是否已做过未知联系人补充 // 右键菜单相关状态 const [contextMenu, setContextMenu] = useState<{ @@ -296,7 +297,130 @@ const MessageList: React.FC = () => { }; }, [contextMenu.visible]); - // ==================== 数据加载 ==================== + // ==================== 数据加载 & 未知联系人补充 ==================== + + // 同步完成后,检查是否存在“未知联系人”或缺失头像/昵称的会话,并异步补充详情 + const enrichUnknownContacts = async () => { + if (!currentUserId) return; + if (hasEnrichedRef.current) return; // 避免重复执行 + + // 只在会话有数据时执行 + if (!sessions || sessions.length === 0) return; + + const needEnrich = sessions.filter(s => { + const noName = !s.conRemark && !s.nickname && !s.wechatId; + const isUnknownNickname = s.nickname === "未知联系人"; + const noAvatar = !s.avatar; + return noName || isUnknownNickname || noAvatar; + }); + + if (needEnrich.length === 0) { + hasEnrichedRef.current = true; + return; + } + + hasEnrichedRef.current = true; + + // 逐个异步拉取详情,失败不打断整体流程 + for (const session of needEnrich) { + try { + let detailResult: any = null; + if (session.type === "friend") { + detailResult = await getWechatFriendDetail({ id: session.id }); + } else { + detailResult = await getWechatChatroomDetail({ id: session.id }); + } + + const detail = detailResult?.detail; + if (!detail) continue; + + // 更新会话列表 UI + setSessionState(prev => + prev.map(s => + s.id === session.id && s.type === session.type + ? { + ...s, + avatar: + session.type === "group" + ? detail.chatroomAvatar || s.avatar + : detail.avatar || s.avatar, + nickname: detail.nickname || s.nickname, + conRemark: detail.conRemark || s.conRemark, + wechatId: detail.wechatId || s.wechatId, + } + : s, + ), + ); + + // 同步到会话数据库 + await MessageManager.updateSession({ + userId: currentUserId, + id: session.id, + type: session.type, + avatar: + session.type === "group" + ? detail.chatroomAvatar || session.avatar + : detail.avatar || session.avatar, + nickname: detail.nickname || session.nickname, + conRemark: detail.conRemark || session.conRemark, + wechatId: detail.wechatId || session.wechatId, + }); + + // 同步到联系人数据库(方便后续搜索、其它页面使用) + const contactBase: any = { + serverId: `${session.type}_${session.id}`, + userId: currentUserId, + id: session.id, + type: session.type, + wechatAccountId: detail.wechatAccountId, + nickname: detail.nickname || "", + conRemark: detail.conRemark || "", + avatar: + session.type === "group" + ? detail.chatroomAvatar || "" + : detail.avatar || "", + lastUpdateTime: new Date().toISOString(), + sortKey: "", + searchKey: (detail.conRemark || detail.nickname || "").toLowerCase(), + }; + + if (session.type === "group") { + Object.assign(contactBase, { + chatroomId: detail.chatroomId, + chatroomOwner: detail.chatroomOwner, + selfDisplayName: detail.selfDisplyName, + notice: detail.notice, + }); + } else { + Object.assign(contactBase, { + wechatFriendId: detail.id, + wechatId: detail.wechatId, + alias: detail.alias, + gender: detail.gender, + region: detail.region, + signature: detail.signature, + phone: detail.phone, + quanPin: detail.quanPin, + groupId: detail.groupId, + }); + } + + // 使用 upsert 逻辑:如果已存在就更新,不存在则新增 + const existContact = await ContactManager.getContactByIdAndType( + currentUserId, + session.id, + session.type, + ); + if (existContact) { + await ContactManager.updateContact(contactBase); + } else { + await ContactManager.addContact(contactBase); + } + } catch (error) { + console.error("补拉未知联系人详情失败:", error, session); + } + } + }; // 与服务器同步数据(优化版:逐页同步,立即更新UI) const syncWithServer = async () => { @@ -379,6 +503,8 @@ const MessageList: React.FC = () => { console.log( `会话同步完成: 成功${successCount}页, 失败${failCount}页, 共处理${totalProcessed}条数据`, ); + // 同步完成后,异步补充未知联系人信息 + enrichUnknownContacts(); } catch (error) { console.error("同步服务器数据失败:", error); } finally { @@ -805,59 +931,6 @@ const MessageList: React.FC = () => { // 设置当前会话 setCurrentContact(session as any); - // 如果头像或昵称缺失,则尝试拉取详情并更新列表 - const needFetchDetail = - !session.avatar || - !(session.conRemark || session.nickname || session.wechatId); - - if (needFetchDetail) { - try { - let detailResult: any = null; - if (session.type === "friend") { - detailResult = await getWechatFriendDetail({ id: session.id }); - } else { - detailResult = await getWechatChatroomDetail({ id: session.id }); - } - - const detail = detailResult?.detail; - if (detail) { - // 1. 更新会话列表中的该条记录(驱动 UI 立即更新头像和昵称) - setSessionState(prev => - prev.map(s => - s.id === session.id && s.type === session.type - ? { - ...s, - avatar: - session.type === "group" - ? detail.chatroomAvatar || s.avatar - : detail.avatar || s.avatar, - nickname: detail.nickname || s.nickname, - conRemark: detail.conRemark || s.conRemark, - wechatId: detail.wechatId || s.wechatId, - } - : s, - ), - ); - - // 2. 同步到会话数据库,保证刷新后数据仍然是最新的 - await MessageManager.updateSession({ - userId: currentUserId, - id: session.id, - type: session.type, - avatar: - session.type === "group" - ? detail.chatroomAvatar || session.avatar - : detail.avatar || session.avatar, - nickname: detail.nickname || session.nickname, - conRemark: detail.conRemark || session.conRemark, - wechatId: detail.wechatId || session.wechatId, - }); - } - } catch (e) { - console.error("点击会话补拉详情失败:", e); - } - } - // 标记为已读(不更新时间和排序) if (session.config.unreadCount > 0) { // 立即更新UI(只更新未读数量) From d3fae3bbd0a3ee8693116e1eb6b89d7097cab9a5 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Mon, 1 Dec 2025 15:42:54 +0800 Subject: [PATCH 12/51] =?UTF-8?q?=E9=98=9F=E5=88=97=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/README_scheduler.md | 254 ++++++++++ Server/application/command.php | 3 + .../command/TaskSchedulerCommand.php | 457 ++++++++++++++++++ Server/config/task_scheduler.php | 301 ++++++++++++ 4 files changed, 1015 insertions(+) create mode 100644 Server/README_scheduler.md create mode 100644 Server/application/command/TaskSchedulerCommand.php create mode 100644 Server/config/task_scheduler.php diff --git a/Server/README_scheduler.md b/Server/README_scheduler.md new file mode 100644 index 000000000..4c485684f --- /dev/null +++ b/Server/README_scheduler.md @@ -0,0 +1,254 @@ +# 统一任务调度器使用说明 + +## 概述 + +统一任务调度器(TaskSchedulerCommand)是一个集中管理所有定时任务的调度系统,支持: +- ✅ 单条 crontab 配置管理所有任务 +- ✅ 多进程并发执行任务 +- ✅ 自动根据 cron 表达式判断任务执行时间 +- ✅ 任务锁机制,防止重复执行 +- ✅ 完善的日志记录 + +## 安装配置 + +### 1. 配置文件 + +任务配置位于 `config/task_scheduler.php`,格式如下: + +```php +'任务标识' => [ + 'command' => '命令名称', // 必填:执行的命令 + 'schedule' => 'cron表达式', // 必填:cron表达式 + 'options' => ['--option=value'], // 可选:命令参数 + 'enabled' => true, // 可选:是否启用 + 'max_concurrent' => 1, // 可选:最大并发数 + 'timeout' => 3600, // 可选:超时时间(秒) + 'log_file' => 'custom.log', // 可选:自定义日志文件 +] +``` + +### 2. Cron 表达式格式 + +标准 cron 格式:`分钟 小时 日 月 星期` + +示例: +- `*/1 * * * *` - 每分钟执行 +- `*/5 * * * *` - 每5分钟执行 +- `*/30 * * * *` - 每30分钟执行 +- `0 2 * * *` - 每天凌晨2点执行 +- `0 3 */3 * *` - 每3天的3点执行 + +### 3. Crontab 配置 + +**只需要在 crontab 中添加一条任务:** + +```bash +# 每分钟执行一次调度器(调度器内部会根据 cron 表达式判断哪些任务需要执行) +* * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think scheduler:run >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/scheduler.log 2>&1 +``` + +### 4. 系统要求 + +- PHP >= 5.6.0 +- 推荐启用 `pcntl` 扩展以支持多进程并发(非必需,未启用时使用单进程顺序执行) + +检查 pcntl 扩展: +```bash +php -m | grep pcntl +``` + +## 使用方法 + +### 手动执行调度器 + +```bash +# 执行调度器(会自动判断当前时间需要执行的任务) +php think scheduler:run +``` + +### 查看任务配置 + +```bash +# 查看所有已注册的命令 +php think list +``` + +### 启用/禁用任务 + +编辑 `config/task_scheduler.php`,设置 `'enabled' => false` 即可禁用任务。 + +## 功能特性 + +### 1. 多进程并发执行 + +- 默认最大并发数:10 个进程 +- 自动管理进程池 +- 自动清理僵尸进程 + +### 2. 任务锁机制 + +- 每个任务在执行时会设置锁(5分钟内不重复执行) +- 防止任务重复执行 +- 锁存储在缓存中,自动过期 + +### 3. 日志记录 + +- 调度器日志:`runtime/log/scheduler.log` +- 每个任务的日志:`runtime/log/{log_file}` +- 任务执行开始和结束都有标记 + +### 4. 超时控制 + +- 默认超时时间:3600 秒(1小时) +- 可在配置中为每个任务单独设置超时时间 +- 超时后自动终止任务 + +## 配置示例 + +### 高频任务(每分钟) + +```php +'wechat_friends_active' => [ + 'command' => 'wechatFriends:list', + 'schedule' => '*/1 * * * *', + 'options' => ['--isDel=0'], + 'enabled' => true, +], +``` + +### 中频任务(每5分钟) + +```php +'device_active' => [ + 'command' => 'device:list', + 'schedule' => '*/5 * * * *', + 'options' => ['--isDel=0'], + 'enabled' => true, +], +``` + +### 每日任务 + +```php +'wechat_calculate_score' => [ + 'command' => 'wechat:calculate-score', + 'schedule' => '0 2 * * *', // 每天凌晨2点 + 'options' => [], + 'enabled' => true, +], +``` + +### 定期任务(每3天) + +```php +'sync_all_friends' => [ + 'command' => 'sync:allFriends', + 'schedule' => '0 3 */3 * *', // 每3天的3点 + 'options' => [], + 'enabled' => true, +], +``` + +## 从旧配置迁移 + +### 旧配置(多条 crontab) + +```bash +*/5 * * * * cd /path && php think device:list --isDel=0 >> log1.log 2>&1 +*/1 * * * * cd /path && php think wechatFriends:list >> log2.log 2>&1 +``` + +### 新配置(单条 crontab + 配置文件) + +**Crontab:** +```bash +* * * * * cd /path && php think scheduler:run >> scheduler.log 2>&1 +``` + +**config/task_scheduler.php:** +```php +'device_active' => [ + 'command' => 'device:list', + 'schedule' => '*/5 * * * *', + 'options' => ['--isDel=0'], + 'log_file' => 'log1.log', +], +'wechat_friends' => [ + 'command' => 'wechatFriends:list', + 'schedule' => '*/1 * * * *', + 'log_file' => 'log2.log', +], +``` + +## 监控和调试 + +### 查看调度器日志 + +```bash +tail -f runtime/log/scheduler.log +``` + +### 查看任务执行日志 + +```bash +tail -f runtime/log/crontab_device_active.log +``` + +### 检查任务是否在执行 + +```bash +# 查看进程 +ps aux | grep "php think" +``` + +### 手动测试任务 + +```bash +# 直接执行某个任务 +php think device:list --isDel=0 +``` + +## 注意事项 + +1. **时间同步**:确保服务器时间准确,调度器依赖系统时间判断任务执行时间 +2. **资源限制**:根据服务器性能调整 `maxConcurrent` 参数 +3. **日志清理**:定期清理日志文件,避免占用过多磁盘空间 +4. **任务冲突**:如果任务执行时间较长,建议调整执行频率或增加并发数 +5. **缓存依赖**:任务锁使用缓存,确保缓存服务正常运行 + +## 故障排查 + +### 任务未执行 + +1. 检查任务是否启用:`'enabled' => true` +2. 检查 cron 表达式是否正确 +3. 检查调度器是否正常运行:查看 `scheduler.log` +4. 检查任务锁:任务可能在5分钟内重复执行被跳过 + +### 任务执行失败 + +1. 查看任务日志:`runtime/log/{log_file}` +2. 检查命令是否正确:手动执行命令测试 +3. 检查权限:确保有执行权限和日志写入权限 + +### 多进程不工作 + +1. 检查 pcntl 扩展:`php -m | grep pcntl` +2. 检查系统限制:`ulimit -u` 查看最大进程数 +3. 查看调度器日志中的错误信息 + +## 性能优化建议 + +1. **合理设置并发数**:根据服务器 CPU 核心数和内存大小调整 +2. **错开高频任务**:避免所有任务在同一分钟执行 +3. **优化任务执行时间**:减少任务执行时长 +4. **使用队列**:对于耗时任务,建议使用队列异步处理 + +## 更新日志 + +### v1.0.0 (2024-01-XX) +- 初始版本 +- 支持多进程并发执行 +- 支持 cron 表达式调度 +- 支持任务锁机制 + diff --git a/Server/application/command.php b/Server/application/command.php index b5c4d0e55..ce06453b8 100644 --- a/Server/application/command.php +++ b/Server/application/command.php @@ -42,4 +42,7 @@ return [ 'wechat:calculate-score' => 'app\command\CalculateWechatAccountScoreCommand', // 统一计算微信账号健康分 'wechat:update-score' => 'app\command\UpdateWechatAccountScoreCommand', // 更新微信账号评分记录 + + // 统一任务调度器 + 'scheduler:run' => 'app\command\TaskSchedulerCommand', // 统一任务调度器,支持多进程并发执行 ]; diff --git a/Server/application/command/TaskSchedulerCommand.php b/Server/application/command/TaskSchedulerCommand.php new file mode 100644 index 000000000..06a1dbafe --- /dev/null +++ b/Server/application/command/TaskSchedulerCommand.php @@ -0,0 +1,457 @@ +> /path/to/log/scheduler.log 2>&1 + */ +class TaskSchedulerCommand extends Command +{ + /** + * 任务配置 + */ + protected $tasks = []; + + /** + * 最大并发进程数 + */ + protected $maxConcurrent = 10; + + /** + * 当前运行的进程数 + */ + protected $runningProcesses = []; + + /** + * 日志目录 + */ + protected $logDir = ''; + + protected function configure() + { + $this->setName('scheduler:run') + ->setDescription('统一任务调度器,支持多进程并发执行所有定时任务'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln('=========================================='); + $output->writeln('任务调度器启动'); + $output->writeln('时间: ' . date('Y-m-d H:i:s')); + $output->writeln('=========================================='); + + // 检查是否支持 pcntl 扩展 + if (!function_exists('pcntl_fork')) { + $output->writeln('错误:系统不支持 pcntl 扩展,无法使用多进程功能'); + $output->writeln('提示:将使用单进程顺序执行任务'); + $this->maxConcurrent = 1; + } + + // 加载任务配置 + $this->tasks = Config::get('task_scheduler', []); + if (empty($this->tasks)) { + $output->writeln('错误:未找到任务配置'); + return false; + } + + // 设置日志目录 + $this->logDir = runtime_path() . 'log' . DIRECTORY_SEPARATOR; + if (!is_dir($this->logDir)) { + mkdir($this->logDir, 0755, true); + } + + // 获取当前时间 + $currentTime = time(); + $currentMinute = date('i', $currentTime); + $currentHour = date('H', $currentTime); + $currentDay = date('d', $currentTime); + $currentMonth = date('m', $currentTime); + $currentWeekday = date('w', $currentTime); // 0=Sunday, 6=Saturday + + $output->writeln("当前时间: {$currentHour}:{$currentMinute}"); + $output->writeln("已加载 " . count($this->tasks) . " 个任务配置"); + + // 筛选需要执行的任务 + $tasksToRun = []; + foreach ($this->tasks as $taskId => $task) { + if (!isset($task['enabled']) || !$task['enabled']) { + continue; + } + + if ($this->shouldRun($task['schedule'], $currentMinute, $currentHour, $currentDay, $currentMonth, $currentWeekday)) { + $tasksToRun[$taskId] = $task; + } + } + + if (empty($tasksToRun)) { + $output->writeln('当前时间没有需要执行的任务'); + return true; + } + + $output->writeln("找到 " . count($tasksToRun) . " 个需要执行的任务"); + + // 执行任务 + if ($this->maxConcurrent > 1 && function_exists('pcntl_fork')) { + $this->executeConcurrent($tasksToRun, $output); + } else { + $this->executeSequential($tasksToRun, $output); + } + + // 清理僵尸进程 + $this->cleanupZombieProcesses(); + + $output->writeln('=========================================='); + $output->writeln('任务调度器执行完成'); + $output->writeln('=========================================='); + + return true; + } + + /** + * 判断任务是否应该执行 + * + * @param string $schedule cron表达式,格式:分钟 小时 日 月 星期 + * @param int $minute 当前分钟 + * @param int $hour 当前小时 + * @param int $day 当前日期 + * @param int $month 当前月份 + * @param int $weekday 当前星期 + * @return bool + */ + protected function shouldRun($schedule, $minute, $hour, $day, $month, $weekday) + { + $parts = preg_split('/\s+/', trim($schedule)); + if (count($parts) < 5) { + return false; + } + + list($scheduleMinute, $scheduleHour, $scheduleDay, $scheduleMonth, $scheduleWeekday) = $parts; + + // 解析分钟 + if (!$this->matchCronField($scheduleMinute, $minute)) { + return false; + } + + // 解析小时 + if (!$this->matchCronField($scheduleHour, $hour)) { + return false; + } + + // 解析日期 + if (!$this->matchCronField($scheduleDay, $day)) { + return false; + } + + // 解析月份 + if (!$this->matchCronField($scheduleMonth, $month)) { + return false; + } + + // 解析星期(注意:cron中0和7都表示星期日) + if ($scheduleWeekday !== '*') { + $scheduleWeekday = str_replace('7', '0', $scheduleWeekday); + if (!$this->matchCronField($scheduleWeekday, $weekday)) { + return false; + } + } + + return true; + } + + /** + * 匹配cron字段 + * + * @param string $field cron字段表达式 + * @param int $value 当前值 + * @return bool + */ + protected function matchCronField($field, $value) + { + // 通配符 + if ($field === '*') { + return true; + } + + // 列表(逗号分隔) + if (strpos($field, ',') !== false) { + $values = explode(',', $field); + foreach ($values as $v) { + if ($this->matchCronField(trim($v), $value)) { + return true; + } + } + return false; + } + + // 范围(如 1-5) + if (strpos($field, '-') !== false) { + list($start, $end) = explode('-', $field); + return $value >= (int)$start && $value <= (int)$end; + } + + // 步长(如 */5 或 0-59/5) + if (strpos($field, '/') !== false) { + $parts = explode('/', $field); + $base = $parts[0]; + $step = (int)$parts[1]; + + if ($base === '*') { + return $value % $step === 0; + } else { + // 处理范围步长,如 0-59/5 + if (strpos($base, '-') !== false) { + list($start, $end) = explode('-', $base); + if ($value >= (int)$start && $value <= (int)$end) { + return ($value - (int)$start) % $step === 0; + } + return false; + } else { + return $value % $step === 0; + } + } + } + + // 精确匹配 + return (int)$field === $value; + } + + /** + * 并发执行任务(多进程) + * + * @param array $tasks 任务列表 + * @param Output $output 输出对象 + */ + protected function executeConcurrent($tasks, Output $output) + { + $output->writeln('使用多进程并发执行任务(最大并发数:' . $this->maxConcurrent . ')'); + + foreach ($tasks as $taskId => $task) { + // 等待可用进程槽 + while (count($this->runningProcesses) >= $this->maxConcurrent) { + $this->waitForProcesses(); + usleep(100000); // 等待100ms + } + + // 检查任务是否已经在运行(防止重复执行) + $lockKey = "scheduler_task_lock:{$taskId}"; + $lockTime = Cache::get($lockKey); + if ($lockTime && (time() - $lockTime) < 300) { // 5分钟内不重复执行 + $output->writeln("任务 {$taskId} 正在运行中,跳过"); + continue; + } + + // 创建子进程 + $pid = pcntl_fork(); + + if ($pid == -1) { + // 创建进程失败 + $output->writeln("创建子进程失败:{$taskId}"); + Log::error("任务调度器:创建子进程失败", ['task' => $taskId]); + continue; + } elseif ($pid == 0) { + // 子进程:执行任务 + $this->runTask($taskId, $task); + exit(0); + } else { + // 父进程:记录子进程PID + $this->runningProcesses[$pid] = [ + 'task_id' => $taskId, + 'start_time' => time(), + ]; + $output->writeln("启动任务:{$taskId} (PID: {$pid})"); + + // 设置任务锁 + Cache::set($lockKey, time(), 600); // 10分钟过期 + } + } + + // 等待所有子进程完成 + while (!empty($this->runningProcesses)) { + $this->waitForProcesses(); + usleep(500000); // 等待500ms + } + } + + /** + * 顺序执行任务(单进程) + * + * @param array $tasks 任务列表 + * @param Output $output 输出对象 + */ + protected function executeSequential($tasks, Output $output) + { + $output->writeln('使用单进程顺序执行任务'); + + foreach ($tasks as $taskId => $task) { + $output->writeln("执行任务:{$taskId}"); + $this->runTask($taskId, $task); + } + } + + /** + * 执行单个任务 + * + * @param string $taskId 任务ID + * @param array $task 任务配置 + */ + protected function runTask($taskId, $task) + { + $startTime = microtime(true); + $logFile = $this->logDir . ($task['log_file'] ?? "scheduler_{$taskId}.log"); + + // 确保日志目录存在 + $logDir = dirname($logFile); + if (!is_dir($logDir)) { + mkdir($logDir, 0755, true); + } + + // 构建命令 + $thinkPath = root_path() . 'think'; + $command = "php {$thinkPath} {$task['command']}"; + if (!empty($task['options'])) { + foreach ($task['options'] as $option) { + $command .= ' ' . escapeshellarg($option); + } + } + + // 添加日志重定向 + $command .= " >> " . escapeshellarg($logFile) . " 2>&1"; + + // 记录任务开始 + $logMessage = "\n" . str_repeat('=', 60) . "\n"; + $logMessage .= "任务开始执行: {$taskId}\n"; + $logMessage .= "执行时间: " . date('Y-m-d H:i:s') . "\n"; + $logMessage .= "命令: {$command}\n"; + $logMessage .= str_repeat('=', 60) . "\n"; + file_put_contents($logFile, $logMessage, FILE_APPEND); + + // 执行命令 + $descriptorspec = [ + 0 => ['file', (PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null'), 'r'], // stdin + 1 => ['file', $logFile, 'a'], // stdout + 2 => ['file', $logFile, 'a'], // stderr + ]; + + $process = @proc_open($command, $descriptorspec, $pipes, root_path()); + + if (is_resource($process)) { + // 关闭管道 + if (isset($pipes[0])) @fclose($pipes[0]); + if (isset($pipes[1])) @fclose($pipes[1]); + if (isset($pipes[2])) @fclose($pipes[2]); + + // 设置超时 + $timeout = $task['timeout'] ?? 3600; + $startWaitTime = time(); + + // 等待进程完成或超时 + while (true) { + $status = proc_get_status($process); + + if (!$status['running']) { + break; + } + + // 检查超时 + if ((time() - $startWaitTime) > $timeout) { + if (function_exists('proc_terminate')) { + proc_terminate($process, SIGTERM); + // 等待进程终止 + sleep(2); + $status = proc_get_status($process); + if ($status['running']) { + // 强制终止 + proc_terminate($process, SIGKILL); + } + } + Log::warning("任务执行超时", [ + 'task' => $taskId, + 'timeout' => $timeout, + ]); + break; + } + + usleep(500000); // 等待500ms + } + + // 关闭进程 + proc_close($process); + } else { + // 如果 proc_open 失败,尝试直接执行(后台执行) + if (PHP_OS_FAMILY === 'Windows') { + pclose(popen("start /B " . $command, "r")); + } else { + exec($command . ' > /dev/null 2>&1 &'); + } + } + + $endTime = microtime(true); + $duration = round($endTime - $startTime, 2); + + // 记录任务完成 + $logMessage = "\n" . str_repeat('=', 60) . "\n"; + $logMessage .= "任务执行完成: {$taskId}\n"; + $logMessage .= "完成时间: " . date('Y-m-d H:i:s') . "\n"; + $logMessage .= "执行时长: {$duration} 秒\n"; + $logMessage .= str_repeat('=', 60) . "\n"; + file_put_contents($logFile, $logMessage, FILE_APPEND); + + Log::info("任务执行完成", [ + 'task' => $taskId, + 'duration' => $duration, + ]); + } + + /** + * 等待进程完成 + */ + protected function waitForProcesses() + { + foreach ($this->runningProcesses as $pid => $info) { + $status = 0; + $result = pcntl_waitpid($pid, $status, WNOHANG); + + if ($result == $pid || $result == -1) { + // 进程已结束 + unset($this->runningProcesses[$pid]); + + $duration = time() - $info['start_time']; + Log::info("子进程执行完成", [ + 'pid' => $pid, + 'task' => $info['task_id'], + 'duration' => $duration, + ]); + } + } + } + + /** + * 清理僵尸进程 + */ + protected function cleanupZombieProcesses() + { + if (!function_exists('pcntl_waitpid')) { + return; + } + + $status = 0; + while (($pid = pcntl_waitpid(-1, $status, WNOHANG)) > 0) { + // 清理僵尸进程 + } + } +} + diff --git a/Server/config/task_scheduler.php b/Server/config/task_scheduler.php new file mode 100644 index 000000000..7fa7ea374 --- /dev/null +++ b/Server/config/task_scheduler.php @@ -0,0 +1,301 @@ + [ + // 'command' => '命令名称', // 必填:执行的 ThinkPHP 命令(见 application/command.php) + // 'schedule' => 'cron表达式', // 必填:cron 表达式,如 '*/5 * * * *' 表示每5分钟 + // 'options' => ['--option=value'], // 可选:命令参数(原来 crontab 里的 --xxx=yyy) + // 'enabled' => true, // 可选:是否启用,默认 true + // 'max_concurrent'=> 1, // 可选:单任务最大并发数(目前由调度器统一控制,可预留) + // 'timeout' => 3600, // 可选:超时时间(秒),默认 3600 + // 'log_file' => 'custom.log', // 可选:日志文件名,默认使用任务标识 + // ] + + // =========================== + // 高频任务(每分钟或更频繁) + // =========================== + + // 同步微信好友列表(未删除好友),用于保持系统中好友数据实时更新 + 'wechat_friends_active' => [ + 'command' => 'wechatFriends:list', + 'schedule' => '*/1 * * * *', // 每1分钟 + 'options' => ['--isDel=0'], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_wechatFriends_active.log', + ], + + // 拉取“添加好友任务”列表,驱动自动加好友的任务队列 + 'friend_task' => [ + 'command' => 'friendTask:list', + 'schedule' => '*/1 * * * *', // 每1分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_friendTask.log', + ], + + // 同步微信好友私聊消息列表,写入消息表,供客服工作台使用 + 'message_friends' => [ + 'command' => 'message:friendsList', + 'schedule' => '*/1 * * * *', // 每1分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_messageFriends.log', + ], + + // 同步微信群聊消息列表,写入消息表,供群聊记录与风控分析 + 'message_chatroom' => [ + 'command' => 'message:chatroomList', + 'schedule' => '*/1 * * * *', // 每1分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_messageChatroom.log', + ], + + // 客服端消息提醒任务,负责给在线客服推送新消息通知 + 'kf_notice' => [ + 'command' => 'kf:notice', + 'schedule' => '*/1 * * * *', // 每1分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'kf_notice.log', + ], + + // =========================== + // 中频任务(每 2-5 分钟) + // =========================== + + // 同步微信设备列表(未删除设备),用于设备管理与监控 + 'device_active' => [ + 'command' => 'device:list', + 'schedule' => '*/5 * * * *', // 每5分钟 + 'options' => ['--isDel=0'], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_device_active.log', + ], + + // 同步微信群聊列表(未删除群),用于群管理与后续任务分配 + 'wechat_chatroom_active' => [ + 'command' => 'wechatChatroom:list', + 'schedule' => '*/5 * * * *', // 每5分钟 + 'options' => ['--isDel=0'], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_wechatChatroom_active.log', + ], + + // 同步微信群成员列表(群好友),维持群成员明细数据 + 'group_friends' => [ + 'command' => 'groupFriends:list', + 'schedule' => '*/5 * * * *', // 每5分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_groupFriends.log', + ], + + // 同步“微信客服列表”,获取绑定到公司的微信号,用于工作台与分配规则 + 'wechat_list' => [ + 'command' => 'wechatList:list', + 'schedule' => '*/5 * * * *', // 每5分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_wechatList.log', + ], + + // 同步公司账号列表(企业/租户账号),供后台管理与统计 + 'account_list' => [ + 'command' => 'account:list', + 'schedule' => '*/5 * * * *', // 每5分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_account.log', + ], + + // 内容采集任务,将外部或设备内容同步到系统内容库 + 'content_collect' => [ + 'command' => 'content:collect', + 'schedule' => '*/5 * * * *', // 每5分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_contentCollect.log', + ], + + // 工作台:自动点赞好友/客户朋友圈,提高账号活跃度 + 'workbench_auto_like' => [ + 'command' => 'workbench:autoLike', + 'schedule' => '*/6 * * * *', // 每6分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_workbench_autoLike.log', + ], + + // 工作台:自动建群任务,按规则批量创建微信群 + 'workbench_group_create' => [ + 'command' => 'workbench:groupCreate', + 'schedule' => '*/5 * * * *', // 每5分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'workbench_groupCreate.log', + ], + + // 工作台:自动导入通讯录到系统,生成加粉/建群等任务 + 'workbench_import_contact' => [ + 'command' => 'workbench:import-contact', + 'schedule' => '*/5 * * * *', // 每5分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'import_contact.log', + ], + + // =========================== + // 低频任务(每 2 分钟) + // =========================== + + // 清洗并同步微信原始数据到存客宝业务表(数据治理任务) + 'sync_wechat_data' => [ + 'command' => 'sync:wechatData', + 'schedule' => '*/2 * * * *', // 每2分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'sync_wechat_data.log', + ], + + // 工作台:流量分发任务,把流量池中的线索按规则分配给微信号或员工 + 'workbench_traffic_distribute' => [ + 'command' => 'workbench:trafficDistribute', + 'schedule' => '*/2 * * * *', // 每2分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'traffic_distribute.log', + ], + + // 工作台:朋友圈同步任务,拉取并落库朋友圈内容 + 'workbench_moments' => [ + 'command' => 'workbench:moments', + 'schedule' => '*/2 * * * *', // 每2分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'workbench_moments.log', + ], + + // 预防性切换好友任务,监控频繁/风控风险,自动切换加人对象,保护微信号 + 'switch_friends' => [ + 'command' => 'switch:friends', + 'schedule' => '*/2 * * * *', // 每2分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'switch_friends.log', + ], + + // =========================== + // 低频任务(每 30 分钟) + // =========================== + + // 拉取设备通话记录(语音/电话),用于质检、统计或标签打分 + 'call_recording' => [ + 'command' => 'call-recording:list', + 'schedule' => '*/30 * * * *', // 每30分钟 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'call_recording.log', + ], + + // =========================== + // 每日 / 每几天任务 + // =========================== + + // 每日 1:00 同步“已删除设备”列表,补齐历史状态 + 'device_deleted' => [ + 'command' => 'device:list', + 'schedule' => '0 1 * * *', // 每天1点 + 'options' => ['--isDel=1'], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_device_deleted.log', + ], + + // 每日 1:10 同步“已停用设备”列表,更新停用状态 + 'device_stopped' => [ + 'command' => 'device:list', + 'schedule' => '10 1 * * *', // 每天1:10 + 'options' => ['--isDel=2'], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_device_stopped.log', + ], + + // 每日 1:30 同步“已删除微信好友”,用于历史恢复与报表 + 'wechat_friends_deleted' => [ + 'command' => 'wechatFriends:list', + 'schedule' => '30 1 * * *', // 每天1:30 + 'options' => ['--isDel=1'], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_wechatFriends_deleted.log', + ], + + // 每日 1:30 同步“已删除微信群聊”,用于统计与留痕 + 'wechat_chatroom_deleted' => [ + 'command' => 'wechatChatroom:list', + 'schedule' => '30 1 * * *', // 每天1:30 + 'options' => ['--isDel=1'], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'crontab_wechatChatroom_deleted.log', + ], + + // 每日 2:00 统一计算所有微信账号健康分(基础分 + 动态分) + 'wechat_calculate_score' => [ + 'command' => 'wechat:calculate-score', + 'schedule' => '0 2 * * *', // 每天2点 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'calculate_score.log', + ], + + // 每 3 天执行的全量任务 + + // 每 3 天 3:00 全量同步所有在线好友,做一次大规模校准 + 'sync_all_friends' => [ + 'command' => 'sync:allFriends', + 'schedule' => '0 3 */3 * *', // 每3天的3点 + 'options' => [], + 'enabled' => true, + 'max_concurrent' => 1, + 'log_file' => 'all_friends.log', + ], + + // 已禁用的任务(注释掉的任务) + // 'workbench_group_push' => [ + // 'command' => 'workbench:groupPush', + // 'schedule' => '*/2 * * * *', + // 'options' => [], + // 'enabled' => false, + // 'log_file' => 'workbench_groupPush.log', + // ], +]; + From 44edfe7a810fc68d166cf41221aed47596aba683 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Mon, 1 Dec 2025 16:25:15 +0800 Subject: [PATCH 13/51] =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/MessageController.php | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/Server/application/api/controller/MessageController.php b/Server/application/api/controller/MessageController.php index 6b47ebe48..734f3f7df 100644 --- a/Server/application/api/controller/MessageController.php +++ b/Server/application/api/controller/MessageController.php @@ -61,10 +61,8 @@ class MessageController extends BaseController // 发送请求获取好友列表 $result = requestCurl($this->baseUrl . 'api/WechatFriend/listWechatFriendForMsgPagination', $params, 'POST', $header, 'json'); $response = handleApiResponse($result); - // 获取同步消息标志 $syncMessages = $this->request->param('syncMessages', true); - // 如果需要同步消息,则获取每个好友的消息 if ($syncMessages && !empty($response['results'])) { $from = strtotime($fromTime) * 1000; @@ -90,7 +88,6 @@ class MessageController extends BaseController // 调用获取消息的接口 $messageResult = requestCurl($this->baseUrl . 'api/FriendMessage/searchMessage', $messageParams, 'GET', $header, 'json'); $messageResponse = handleApiResponse($messageResult); - // 保存消息到数据库 if (!empty($messageResponse)) { foreach ($messageResponse as $item) { @@ -353,12 +350,6 @@ class MessageController extends BaseController { // 检查消息是否已存在 $exists = WechatMessageModel::where('id', $item['id']) ->find(); - - // 如果消息已存在,直接返回 - if ($exists) { - return; - } - // 将毫秒时间戳转换为秒级时间戳 $createTime = isset($item['createTime']) ? strtotime($item['createTime']) : null; $deleteTime = !empty($item['isDeleted']) ? strtotime($item['deleteTime']) : null; @@ -387,7 +378,8 @@ class MessageController extends BaseController 'wechatTime' => $wechatTime ]; - //已被删除 + + //已被删除 if ($item['msgType'] == 10000 && strpos($item['content'],'开启了朋友验证') !== false) { Db::table('s2_wechat_friend')->where('id',$item['wechatFriendId'])->update(['isDeleted'=> 1,'deleteTime' => $wechatTime]); }else{ @@ -425,8 +417,19 @@ class MessageController extends BaseController } } } - // 创建新记录 - $res = WechatMessageModel::create($data); + + $id = ''; + if (empty($exists)){ + // 创建新记录 + $res = WechatMessageModel::create($data); + $id= $res['id']; + }else{ + $id = $data['id']; + unset($data['id']); + $res = $exists->save($data); + } + + // 1 文字 3图片 47动态图片 34语言 43视频 42名片 40/20链接 49文件 if (!empty($res) && empty($item['isSend']) && in_array($item['msgType'],[1,3,20,34,40,42,43,47,49])){ @@ -439,13 +442,14 @@ class MessageController extends BaseController 'companyId' => $friend['companyId'], 'trafficPoolId' => $trafficPoolId, 'source' => 0, - 'uniqueId' => $res['id'], + 'uniqueId' => $id, 'sourceData' => json_encode([]), 'remark' => '用户发送了消息', 'createTime' => time(), 'updateTime' => time() ]; - Db::name('user_portrait')->insert($data); + Db::name('user_portrait')->insert($data); + } } } @@ -461,11 +465,7 @@ class MessageController extends BaseController { // 检查消息是否已存在 $exists = WechatMessageModel::where('id', $item['id'])->find(); - - // 如果消息已存在,直接返回 - if ($exists) { - return true; - } + // 处理发送者信息 $sender = $item['sender'] ?? []; @@ -515,7 +515,12 @@ class MessageController extends BaseController // 创建新记录 try { - WechatMessageModel::create($data); + if(empty($exists)){ + WechatMessageModel::create($data); + }else{ + unset($data['id']); + $exists->save($data); + } return true; } catch (\Exception $e) { return false; From 0bc6c3a22a225f380ab67588c330a211eb53ff52 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Mon, 1 Dec 2025 16:41:40 +0800 Subject: [PATCH 14/51] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/TaskSchedulerCommand.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Server/application/command/TaskSchedulerCommand.php b/Server/application/command/TaskSchedulerCommand.php index 06a1dbafe..b91bb06d3 100644 --- a/Server/application/command/TaskSchedulerCommand.php +++ b/Server/application/command/TaskSchedulerCommand.php @@ -61,10 +61,23 @@ class TaskSchedulerCommand extends Command $this->maxConcurrent = 1; } - // 加载任务配置 + // 加载任务配置(优先使用框架配置,其次直接引入配置文件,避免加载失败) $this->tasks = Config::get('task_scheduler', []); + + // 如果通过 Config 没有读到,再尝试直接 include 配置文件 if (empty($this->tasks)) { - $output->writeln('错误:未找到任务配置'); + // 以项目根目录为基准查找 config/task_scheduler.php + $configFile = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'task_scheduler.php'; + if (is_file($configFile)) { + $config = include $configFile; + if (is_array($config) && !empty($config)) { + $this->tasks = $config; + } + } + } + + if (empty($this->tasks)) { + $output->writeln('错误:未找到任务配置(task_scheduler),请检查 config/task_scheduler.php 是否存在且返回数组'); return false; } From ff7097f2d961005f75b6ab543042d6dddc6d7fc6 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Mon, 1 Dec 2025 16:46:23 +0800 Subject: [PATCH 15/51] 1 --- Server/application/command/TaskSchedulerCommand.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Server/application/command/TaskSchedulerCommand.php b/Server/application/command/TaskSchedulerCommand.php index b91bb06d3..0ad3cc48b 100644 --- a/Server/application/command/TaskSchedulerCommand.php +++ b/Server/application/command/TaskSchedulerCommand.php @@ -81,8 +81,8 @@ class TaskSchedulerCommand extends Command return false; } - // 设置日志目录 - $this->logDir = runtime_path() . 'log' . DIRECTORY_SEPARATOR; + // 设置日志目录(注意要使用全局函数,避免命名空间影响) + $this->logDir = \runtime_path() . 'log' . DIRECTORY_SEPARATOR; if (!is_dir($this->logDir)) { mkdir($this->logDir, 0755, true); } @@ -333,7 +333,8 @@ class TaskSchedulerCommand extends Command } // 构建命令 - $thinkPath = root_path() . 'think'; + // 使用项目根目录下的 think 脚本(同命令行 php think) + $thinkPath = \root_path() . 'think'; $command = "php {$thinkPath} {$task['command']}"; if (!empty($task['options'])) { foreach ($task['options'] as $option) { From 2939b63998d03aaabded67d04f48b102b205173b Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Mon, 1 Dec 2025 16:47:57 +0800 Subject: [PATCH 16/51] 1 --- .../application/command/TaskSchedulerCommand.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Server/application/command/TaskSchedulerCommand.php b/Server/application/command/TaskSchedulerCommand.php index 0ad3cc48b..9cad5c145 100644 --- a/Server/application/command/TaskSchedulerCommand.php +++ b/Server/application/command/TaskSchedulerCommand.php @@ -81,8 +81,12 @@ class TaskSchedulerCommand extends Command return false; } - // 设置日志目录(注意要使用全局函数,避免命名空间影响) - $this->logDir = \runtime_path() . 'log' . DIRECTORY_SEPARATOR; + // 设置日志目录(ThinkPHP5 中无 runtime_path 辅助函数,直接使用 ROOT_PATH/runtime/log) + if (!defined('ROOT_PATH')) { + // CLI 下正常情况下 ROOT_PATH 已在入口脚本 define,这里兜底一次 + define('ROOT_PATH', dirname(__DIR__, 2)); + } + $this->logDir = ROOT_PATH . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR; if (!is_dir($this->logDir)) { mkdir($this->logDir, 0755, true); } @@ -334,7 +338,10 @@ class TaskSchedulerCommand extends Command // 构建命令 // 使用项目根目录下的 think 脚本(同命令行 php think) - $thinkPath = \root_path() . 'think'; + if (!defined('ROOT_PATH')) { + define('ROOT_PATH', dirname(__DIR__, 2)); + } + $thinkPath = ROOT_PATH . DIRECTORY_SEPARATOR . 'think'; $command = "php {$thinkPath} {$task['command']}"; if (!empty($task['options'])) { foreach ($task['options'] as $option) { @@ -360,7 +367,7 @@ class TaskSchedulerCommand extends Command 2 => ['file', $logFile, 'a'], // stderr ]; - $process = @proc_open($command, $descriptorspec, $pipes, root_path()); + $process = @proc_open($command, $descriptorspec, $pipes, ROOT_PATH); if (is_resource($process)) { // 关闭管道 From 1dbdee155e3f5af2023a294266a7f45d8e67ac50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Mon, 1 Dec 2025 16:58:58 +0800 Subject: [PATCH 17/51] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=AB=98=E9=A2=91?= =?UTF-8?q?=E8=BD=AE=E8=AF=A2=E6=8E=A5=E5=8F=A3=E7=99=BD=E5=90=8D=E5=8D=95?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E8=AF=B7=E6=B1=82=E6=88=AA=E6=B5=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BB=A5=E6=94=AF=E6=8C=81=E7=89=B9=E5=AE=9A?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E7=9A=84=E6=97=A0=E6=88=AA=E6=B5=81=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E3=80=82=E5=85=81=E8=AE=B8=E9=80=9A=E8=BF=87=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=8E=A7=E5=88=B6=E6=88=AA=E6=B5=81=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E6=8F=90=E5=8D=87=E8=AF=B7=E6=B1=82=E5=A4=84=E7=90=86?= =?UTF-8?q?=E7=9A=84=E7=81=B5=E6=B4=BB=E6=80=A7=E5=92=8C=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Touchkebao/src/api/request.ts | 37 +++++++++++++++++++++++++--------- Touchkebao/src/api/request2.ts | 34 +++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/Touchkebao/src/api/request.ts b/Touchkebao/src/api/request.ts index adfa7ea50..35e7bbe5c 100644 --- a/Touchkebao/src/api/request.ts +++ b/Touchkebao/src/api/request.ts @@ -10,6 +10,13 @@ const { token } = useUserStore.getState(); const DEFAULT_DEBOUNCE_GAP = 1000; const debounceMap = new Map(); +// 需要高频轮询、不走截流的接口白名单(按实际接口路径调整) +const NO_DEBOUNCE_URLS = [ + "/wechat/friend/list", // 好友列表 + "/wechat/group/list", // 群组列表 + "/wechat/message/list", // 消息列表 +]; + const instance: AxiosInstance = axios.create({ baseURL: (import.meta as any).env?.VITE_API_BASE_URL || "/api", timeout: 20000, @@ -64,21 +71,31 @@ export function request( url: string, data?: any, method: Method = "GET", - config?: AxiosRequestConfig, + // 允许通过 config.debounce 控制是否开启截流,默认开启 + config?: AxiosRequestConfig & { debounce?: boolean }, debounceGap?: number, ): Promise { const gap = typeof debounceGap === "number" ? debounceGap : DEFAULT_DEBOUNCE_GAP; - const key = `${method}_${url}_${JSON.stringify(data)}`; - const now = Date.now(); - const last = debounceMap.get(key) || 0; - if (gap > 0 && now - last < gap) { - // Toast.show({ content: '请求过于频繁,请稍后再试', position: 'top' }); - return Promise.reject("请求过于频繁,请稍后再试"); - } - debounceMap.set(key, now); - const axiosConfig: AxiosRequestConfig = { + const enableDebounce = config?.debounce !== false; + const isInNoDebounceList = NO_DEBOUNCE_URLS.some(pattern => + url.includes(pattern), + ); + const shouldDebounce = enableDebounce && !isInNoDebounceList; + + if (shouldDebounce) { + const key = `${method}_${url}_${JSON.stringify(data)}`; + const now = Date.now(); + const last = debounceMap.get(key) || 0; + if (gap > 0 && now - last < gap) { + // Toast.show({ content: '请求过于频繁,请稍后再试', position: 'top' }); + return Promise.reject("请求过于频繁,请稍后再试"); + } + debounceMap.set(key, now); + } + + const axiosConfig: AxiosRequestConfig & { debounce?: boolean } = { url, method, ...config, diff --git a/Touchkebao/src/api/request2.ts b/Touchkebao/src/api/request2.ts index deb3d9bf5..13732be02 100644 --- a/Touchkebao/src/api/request2.ts +++ b/Touchkebao/src/api/request2.ts @@ -9,11 +9,20 @@ import { useUserStore } from "@/store/module/user"; const DEFAULT_DEBOUNCE_GAP = 1000; const debounceMap = new Map(); +// 需要高频轮询、不走截流的接口白名单(按实际接口路径调整) +const NO_DEBOUNCE_URLS = [ + "/wechat/friend/list", + "/wechat/group/list", + "/wechat/message/list", +]; + interface RequestConfig extends AxiosRequestConfig { - headers: { + headers?: { Client?: string; "Content-Type"?: string; }; + // 是否开启截流,默认开启 + debounce?: boolean; } const instance: AxiosInstance = axios.create({ @@ -63,14 +72,23 @@ export function request( ): Promise { const gap = typeof debounceGap === "number" ? debounceGap : DEFAULT_DEBOUNCE_GAP; - const key = `${method}_${url}_${JSON.stringify(data)}`; - const now = Date.now(); - const last = debounceMap.get(key) || 0; - if (gap > 0 && now - last < gap) { - // Toast.show({ content: '请求过于频繁,请稍后再试', position: 'top' }); - return Promise.reject("请求过于频繁,请稍后再试"); + + const enableDebounce = config?.debounce !== false; + const isInNoDebounceList = NO_DEBOUNCE_URLS.some(pattern => + url.includes(pattern), + ); + const shouldDebounce = enableDebounce && !isInNoDebounceList; + + if (shouldDebounce) { + const key = `${method}_${url}_${JSON.stringify(data)}`; + const now = Date.now(); + const last = debounceMap.get(key) || 0; + if (gap > 0 && now - last < gap) { + // Toast.show({ content: '请求过于频繁,请稍后再试', position: 'top' }); + return Promise.reject("请求过于频繁,请稍后再试"); + } + debounceMap.set(key, now); } - debounceMap.set(key, now); const axiosConfig: RequestConfig = { url, From 2c37314c4fdf49d19660eb6c694677f041e45379 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Mon, 1 Dec 2025 17:41:57 +0800 Subject: [PATCH 18/51] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/api/controller/MessageController.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Server/application/api/controller/MessageController.php b/Server/application/api/controller/MessageController.php index 734f3f7df..0f7f6d3d1 100644 --- a/Server/application/api/controller/MessageController.php +++ b/Server/application/api/controller/MessageController.php @@ -350,6 +350,12 @@ class MessageController extends BaseController { // 检查消息是否已存在 $exists = WechatMessageModel::where('id', $item['id']) ->find(); + + if ($exists['sendStatus'] == 0){ + return true; + } + + // 将毫秒时间戳转换为秒级时间戳 $createTime = isset($item['createTime']) ? strtotime($item['createTime']) : null; $deleteTime = !empty($item['isDeleted']) ? strtotime($item['deleteTime']) : null; @@ -466,6 +472,9 @@ class MessageController extends BaseController // 检查消息是否已存在 $exists = WechatMessageModel::where('id', $item['id'])->find(); + if ($exists['sendStatus'] == 0){ + return true; + } // 处理发送者信息 $sender = $item['sender'] ?? []; From 354d6c4f6276400e278cf14be896b38135bb82a8 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Mon, 1 Dec 2025 18:09:06 +0800 Subject: [PATCH 19/51] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/api/controller/MessageController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/application/api/controller/MessageController.php b/Server/application/api/controller/MessageController.php index 0f7f6d3d1..98b4fe499 100644 --- a/Server/application/api/controller/MessageController.php +++ b/Server/application/api/controller/MessageController.php @@ -351,7 +351,7 @@ class MessageController extends BaseController // 检查消息是否已存在 $exists = WechatMessageModel::where('id', $item['id']) ->find(); - if ($exists['sendStatus'] == 0){ + if (!empty($exists) && $exists['sendStatus'] == 0){ return true; } @@ -472,7 +472,7 @@ class MessageController extends BaseController // 检查消息是否已存在 $exists = WechatMessageModel::where('id', $item['id'])->find(); - if ($exists['sendStatus'] == 0){ + if (!empty($exists) && $exists['sendStatus'] == 0){ return true; } From 5f5b567749ac255355957a6691ef26ba0c1d32ee Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Tue, 2 Dec 2025 10:51:27 +0800 Subject: [PATCH 20/51] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/api/controller/MessageController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/application/api/controller/MessageController.php b/Server/application/api/controller/MessageController.php index 98b4fe499..87575f319 100644 --- a/Server/application/api/controller/MessageController.php +++ b/Server/application/api/controller/MessageController.php @@ -75,7 +75,7 @@ class MessageController extends BaseController 'keyword' => '', 'msgType' => '', 'accountId' => '', - 'count' => 100, + 'count' => 20, 'messageId' => '', 'olderData' => true, 'wechatAccountId' => $friend['wechatAccountId'], @@ -238,7 +238,7 @@ class MessageController extends BaseController 'keyword' => '', 'msgType' =>'', 'accountId' => '', - 'count' => 100, + 'count' => 20, 'messageId' => '', 'olderData' => true, 'wechatId' => '', From b5d62145217d07351de9a30d0d34a15ce0815201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Tue, 2 Dec 2025 15:28:57 +0800 Subject: [PATCH 21/51] Remove pin/unpin chat session API methods and update message list handling for pinning sessions. --- Touchkebao/src/pages/pc/ckbox/api.ts | 10 ---------- Touchkebao/src/pages/pc/ckbox/weChat/api.ts | 10 ---------- .../SidebarMenu/MessageList/index.tsx | 17 +++++++++++++---- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/api.ts b/Touchkebao/src/pages/pc/ckbox/api.ts index f5717fa7d..07cac7e89 100644 --- a/Touchkebao/src/pages/pc/ckbox/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/api.ts @@ -255,16 +255,6 @@ export const deleteChatSession = (chatId: string): Promise => { return request2(`/v1/chats/${chatId}`, {}, "DELETE"); }; -// 置顶聊天会话 -export const pinChatSession = (chatId: string): Promise => { - return request2(`/v1/chats/${chatId}/pin`, {}, "PUT"); -}; - -// 取消置顶聊天会话 -export const unpinChatSession = (chatId: string): Promise => { - return request2(`/v1/chats/${chatId}/unpin`, {}, "PUT"); -}; - // 静音聊天会话 export const muteChatSession = (chatId: string): Promise => { return request2(`/v1/chats/${chatId}/mute`, {}, "PUT"); diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/api.ts b/Touchkebao/src/pages/pc/ckbox/weChat/api.ts index 7e77433e4..3ceefe75b 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/weChat/api.ts @@ -305,16 +305,6 @@ export const deleteChatSession = (chatId: string): Promise => { return request2(`/v1/chats/${chatId}`, {}, "DELETE"); }; -// 置顶聊天会话 -export const pinChatSession = (chatId: string): Promise => { - return request2(`/v1/chats/${chatId}/pin`, {}, "PUT"); -}; - -// 取消置顶聊天会话 -export const unpinChatSession = (chatId: string): Promise => { - return request2(`/v1/chats/${chatId}/unpin`, {}, "PUT"); -}; - // 静音聊天会话 export const muteChatSession = (chatId: string): Promise => { return request2(`/v1/chats/${chatId}/mute`, {}, "PUT"); diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx index efbcbad8a..52ffa850a 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx @@ -128,10 +128,19 @@ const MessageList: React.FC = () => { }); // 2. 后台调用API - await updateConfig({ - id: session.id, - config: { top: newPinned, chat: true }, - }); + const params: any = { + type: "CmdPinToTop", + wechatAccountId: session.wechatAccountId, + isTop: newPinned, + }; + + if (session.type === "friend") { + params.wechatFriendId = session.id; + } else if (session.type === "group") { + params.wechatChatroomId = session.id; + } + + await dataProcessing(params); // 3. 后台更新数据库 await MessageManager.togglePin( From 242065d2980d17fcd1e363b76e57774da4df5d3e Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Tue, 2 Dec 2025 17:47:22 +0800 Subject: [PATCH 22/51] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=88=86=E7=BB=84?= =?UTF-8?q?=E5=8F=8A=E6=B6=88=E6=81=AF=E7=BD=AE=E9=A1=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/chukebao/config/route.php | 8 +- .../chukebao/controller/DataProcessing.php | 36 +++ .../chukebao/controller/MessageController.php | 11 +- .../controller/WechatGroupController.php | 264 +++++++++++++++++- .../application/chukebao/model/ChatGroups.php | 16 ++ 5 files changed, 316 insertions(+), 19 deletions(-) create mode 100644 Server/application/chukebao/model/ChatGroups.php diff --git a/Server/application/chukebao/config/route.php b/Server/application/chukebao/config/route.php index 56e17f519..ce1a477bf 100644 --- a/Server/application/chukebao/config/route.php +++ b/Server/application/chukebao/config/route.php @@ -42,7 +42,13 @@ Route::group('v1/', function () { }); //微信分组 - Route::get('wechatGroup/list', 'app\chukebao\controller\WechatGroupController@getList'); // 微信分组 + Route::group('wechatGroup/', function () { + Route::get('list', 'app\chukebao\controller\WechatGroupController@getList'); // 获取分组列表 + Route::post('add', 'app\chukebao\controller\WechatGroupController@create'); // 新增分组 + Route::post('update', 'app\chukebao\controller\WechatGroupController@update'); // 更新分组 + Route::delete('delete', 'app\chukebao\controller\WechatGroupController@delete'); // 删除分组(假删除) + Route::post('move', 'app\chukebao\controller\WechatGroupController@move'); // 移动分组(好友/群移动到指定分组) + }); diff --git a/Server/application/chukebao/controller/DataProcessing.php b/Server/application/chukebao/controller/DataProcessing.php index fdde86b7a..af8070cad 100644 --- a/Server/application/chukebao/controller/DataProcessing.php +++ b/Server/application/chukebao/controller/DataProcessing.php @@ -36,6 +36,7 @@ class DataProcessing extends BaseController 'CmdChatroomOperate', //修改群信息 {chatroomName(群名)、announce(公告)、extra(公告)、wechatAccountId、wechatChatroomId} 'CmdNewMessage', //接收消息 'CmdSendMessageResult', //更新消息状态 + 'CmdPinToTop', //置顶 ]; if (empty($type) || empty($wechatAccountId)) { @@ -164,6 +165,41 @@ class DataProcessing extends BaseController $msg = '更新消息状态成功'; break; + case 'CmdPinToTop': //置顶 + $wechatFriendId = $this->request->param('wechatFriendId', 0); + $wechatChatroomId = $this->request->param('wechatChatroomId', 0); + $isTop = $this->request->param('isTop', null); + + if ($isTop === null) { + return ResponseHelper::error('isTop不能为空'); + } + + if (empty($wechatFriendId) && empty($wechatChatroomId)) { + return ResponseHelper::error('wechatFriendId或chatroomId至少提供一个'); + } + + + if (!empty($wechatFriendId)){ + $data = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find(); + $msg = $isTop == 1 ? '已置顶' : '取消置顶'; + if(empty($data)){ + return ResponseHelper::error('好友不存在'); + } + } + + + if (!empty($wechatChatroomId)){ + $data = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find(); + $msg = $isTop == 1 ? '已置顶' : '取消置顶'; + if(empty($data)){ + return ResponseHelper::error('群聊不存在'); + } + } + + $data->updateTime = time(); + $data->isTop = $isTop; + $data->save(); + break; } return ResponseHelper::success('',$msg,$codee); } diff --git a/Server/application/chukebao/controller/MessageController.php b/Server/application/chukebao/controller/MessageController.php index 93f491a99..5f59e1e05 100644 --- a/Server/application/chukebao/controller/MessageController.php +++ b/Server/application/chukebao/controller/MessageController.php @@ -20,7 +20,7 @@ class MessageController extends BaseController $friends = Db::table('s2_wechat_friend') ->where(['accountId' => $accountId, 'isDeleted' => 0]) - ->column('id,nickname,avatar,conRemark,labels,groupId,wechatAccountId,wechatId,extendFields,phone,region'); + ->column('id,nickname,avatar,conRemark,labels,groupId,wechatAccountId,wechatId,extendFields,phone,region,isTop'); // 构建好友子查询 @@ -31,7 +31,7 @@ class MessageController extends BaseController // 优化后的查询:使用MySQL兼容的查询方式 $unionQuery = " - (SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime,m.wechatAccountId, 2 as msgType, wc.nickname, wc.chatroomAvatar as avatar, wc.chatroomId + (SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime,m.wechatAccountId, 2 as msgType, wc.nickname, wc.chatroomAvatar as avatar, wc.chatroomId, wc.isTop FROM s2_wechat_chatroom wc INNER JOIN s2_wechat_message m ON wc.id = m.wechatChatroomId AND m.type = 2 INNER JOIN ( @@ -43,7 +43,7 @@ class MessageController extends BaseController WHERE wc.accountId = {$accountId} AND wc.isDeleted = 0 ) UNION ALL - (SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime, 1 as msgType, 1 as nickname, 1 as avatar, 1 as chatroomId, 1 as wechatAccountId + (SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime, 1 as msgType, 1 as nickname, 1 as avatar, 1 as chatroomId, 1 as wechatAccountId, 0 as isTop FROM s2_wechat_message m INNER JOIN ( SELECT wechatFriendId, MAX(wechatTime) as maxTime, MAX(id) as maxId @@ -122,6 +122,7 @@ class MessageController extends BaseController $v['extendFields'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['extendFields'] : []; $v['region'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['region'] : ''; $v['phone'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['phone'] : ''; + $v['isTop'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['isTop'] : 0; $v['labels'] = !empty($friends[$v['wechatFriendId']]) ? json_decode($friends[$v['wechatFriendId']]['labels'], true) : []; $unreadCount = isset($friendUnreadMap[$v['wechatFriendId']]) ? (int)$friendUnreadMap[$v['wechatFriendId']] : 0; @@ -136,7 +137,7 @@ class MessageController extends BaseController $v['id'] = !empty($v['wechatFriendId']) ? $v['wechatFriendId'] : $v['wechatChatroomId']; $v['config'] = [ - 'top' => false, + 'top' => !empty($v['isTop']) ? true : false, 'unreadCount' => $unreadCount, 'chat' => true, 'msgTime' => $v['wechatTime'], @@ -150,7 +151,7 @@ class MessageController extends BaseController 'wechatTime' => $wechatTime ]; - unset($v['wechatFriendId'], $v['wechatChatroomId']); + unset($v['wechatFriendId'], $v['wechatChatroomId'],$v['isTop']); } unset($v); diff --git a/Server/application/chukebao/controller/WechatGroupController.php b/Server/application/chukebao/controller/WechatGroupController.php index 0ee08b91c..8ff0b75ff 100644 --- a/Server/application/chukebao/controller/WechatGroupController.php +++ b/Server/application/chukebao/controller/WechatGroupController.php @@ -4,27 +4,32 @@ namespace app\chukebao\controller; use library\ResponseHelper; use think\Db; +use app\chukebao\model\ChatGroups; class WechatGroupController extends BaseController { - public function getList(){ - - $accountId = $this->getUserInfo('s2_accountId'); - $userId = $this->getUserInfo('id'); + /** + * 获取分组列表 + * @return \think\response\Json + * @throws \Exception + */ + public function getList() + { + // 公司维度分组,不强制校验 userId $companyId = $this->getUserInfo('companyId'); - $query = Db::table('s2_wechat_group') - ->where(function ($query) use ($accountId,$companyId) { - $query->where('accountId', $accountId)->whereOr('departmentId', $companyId); - }) - ->whereIn('groupType',[1,2]) - ->order('groupType desc,sortIndex desc,id desc'); - $list = $query->select(); + $query = ChatGroups::where([ + 'companyId' => $companyId, + 'isDel' => 0, + ]) + ->order('groupType desc,sort desc,id desc'); + $total = $query->count(); + $list = $query->select(); - - // 处理每个好友的数据 + // 处理每个分组的数据 + $list = is_array($list) ? $list : $list->toArray(); foreach ($list as $k => &$v) { $v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : ''; } @@ -32,4 +37,237 @@ class WechatGroupController extends BaseController return ResponseHelper::success(['list'=>$list,'total'=>$total]); } + + /** + * 新增分组 + * @return \think\response\Json + * @throws \Exception + */ + public function create() + { + $groupName = $this->request->param('groupName', ''); + $groupMemo = $this->request->param('groupMemo', ''); + $groupType = $this->request->param('groupType', 1); + $sort = $this->request->param('sort', 0); + $companyId = $this->getUserInfo('companyId'); + + // 只校验公司维度 + if (empty($companyId)) { + return ResponseHelper::error('请先登录'); + } + + if (empty($groupName)) { + return ResponseHelper::error('分组名称不能为空'); + } + + // 验证分组类型 + if (!in_array($groupType, [1, 2])) { + return ResponseHelper::error('无效的分组类型'); + } + + Db::startTrans(); + try { + $chatGroup = new ChatGroups(); + $chatGroup->groupName = $groupName; + $chatGroup->groupMemo = $groupMemo; + $chatGroup->groupType = $groupType; + $chatGroup->sort = $sort; + $chatGroup->userId = $this->getUserInfo('id'); + $chatGroup->companyId = $companyId; + $chatGroup->createTime = time(); + $chatGroup->isDel = 0; + $chatGroup->save(); + + Db::commit(); + return ResponseHelper::success(['id' => $chatGroup->id], '创建成功'); + } catch (\Exception $e) { + Db::rollback(); + return ResponseHelper::error('创建失败:' . $e->getMessage()); + } + } + + /** + * 更新分组 + * @return \think\response\Json + * @throws \Exception + */ + public function update() + { + $id = $this->request->param('id', 0); + $groupName = $this->request->param('groupName', ''); + $groupMemo = $this->request->param('groupMemo', ''); + $groupType = $this->request->param('groupType', 1); + $sort = $this->request->param('sort', 0); + $companyId = $this->getUserInfo('companyId'); + + if (empty($companyId)) { + return ResponseHelper::error('请先登录'); + } + + if (empty($id)) { + return ResponseHelper::error('参数缺失'); + } + + if (empty($groupName)) { + return ResponseHelper::error('分组名称不能为空'); + } + + // 验证分组类型 + if (!in_array($groupType, [1, 2])) { + return ResponseHelper::error('无效的分组类型'); + } + + // 检查分组是否存在 + $chatGroup = ChatGroups::where([ + 'id' => $id, + 'companyId' => $companyId, + 'isDel' => 0, + ])->find(); + + if (empty($chatGroup)) { + return ResponseHelper::error('该分组不存在或已删除'); + } + + Db::startTrans(); + try { + $chatGroup->groupName = $groupName; + $chatGroup->groupMemo = $groupMemo; + $chatGroup->groupType = $groupType; + $chatGroup->sort = $sort; + $chatGroup->save(); + + Db::commit(); + return ResponseHelper::success('', '更新成功'); + } catch (\Exception $e) { + Db::rollback(); + return ResponseHelper::error('更新失败:' . $e->getMessage()); + } + } + + /** + * 删除分组(假删除) + * @return \think\response\Json + * @throws \Exception + */ + public function delete() + { + $id = $this->request->param('id', 0); + $companyId = $this->getUserInfo('companyId'); + + if (empty($companyId)) { + return ResponseHelper::error('请先登录'); + } + + if (empty($id)) { + return ResponseHelper::error('参数缺失'); + } + + // 检查分组是否存在 + $chatGroup = ChatGroups::where([ + 'id' => $id, + 'companyId' => $companyId, + 'isDel' => 0, + ])->find(); + + if (empty($chatGroup)) { + return ResponseHelper::error('该分组不存在或已删除'); + } + + Db::startTrans(); + try { + // 1. 假删除当前分组 + $chatGroup->isDel = 1; + $chatGroup->deleteTime = time(); + $chatGroup->save(); + + // 2. 重置该分组下所有好友的分组ID(s2_wechat_friend.groupIds -> 0) + Db::table('s2_wechat_friend') + ->where('groupIds', $id) + ->update(['groupIds' => 0]); + + // 3. 重置该分组下所有微信群的分组ID(s2_wechat_chatroom.groupIds -> 0) + Db::table('s2_wechat_chatroom') + ->where('groupIds', $id) + ->update(['groupIds' => 0]); + + Db::commit(); + return ResponseHelper::success('', '删除成功'); + } catch (\Exception $e) { + Db::rollback(); + return ResponseHelper::error('删除失败:' . $e->getMessage()); + } + } + + /** + * 移动分组(将好友或群移动到指定分组) + * @return \think\response\Json + * @throws \Exception + */ + public function move() + { + // type: friend 好友, chatroom 群 + $type = $this->request->param('type', 'friend'); + $targetId = (int)$this->request->param('groupId', 0); + // 仅支持单个ID移动 + $idParam = $this->request->param('id', 0); + $companyId = $this->getUserInfo('companyId'); + + if (empty($companyId)) { + return ResponseHelper::error('请先登录'); + } + + if (empty($targetId)) { + return ResponseHelper::error('目标分组ID不能为空'); + } + + // 仅允许单个 ID,禁止批量 + $moveId = (int)$idParam; + if (empty($moveId)) { + return ResponseHelper::error('需要移动的ID不能为空'); + } + + // 校验目标分组是否存在且属于当前公司 + $targetGroup = ChatGroups::where([ + 'id' => $targetId, + 'companyId' => $companyId, + 'isDel' => 0, + ])->find(); + + if (empty($targetGroup)) { + return ResponseHelper::error('目标分组不存在或已删除'); + } + + // 校验分组类型与移动对象类型是否匹配 + // groupType: 1=好友分组, 2=群分组 + if ($type === 'friend' && (int)$targetGroup->groupType !== 1) { + return ResponseHelper::error('目标分组类型错误(需要好友分组)'); + } + if ($type === 'chatroom' && (int)$targetGroup->groupType !== 2) { + return ResponseHelper::error('目标分组类型错误(需要群分组)'); + } + + Db::startTrans(); + try { + if ($type === 'friend') { + // 移动单个好友到指定分组:更新 s2_wechat_friend.groupIds + Db::table('s2_wechat_friend') + ->where('id', $moveId) + ->update(['groupIds' => $targetId]); + } elseif ($type === 'chatroom') { + // 移动单个群到指定分组:更新 s2_wechat_chatroom.groupIds + Db::table('s2_wechat_chatroom') + ->where('id', $moveId) + ->update(['groupIds' => $targetId]); + } else { + Db::rollback(); + return ResponseHelper::error('无效的类型参数'); + } + + Db::commit(); + return ResponseHelper::success('', '移动成功'); + } catch (\Exception $e) { + Db::rollback(); + return ResponseHelper::error('移动失败:' . $e->getMessage()); + } + } } \ No newline at end of file diff --git a/Server/application/chukebao/model/ChatGroups.php b/Server/application/chukebao/model/ChatGroups.php new file mode 100644 index 000000000..b9a84fba5 --- /dev/null +++ b/Server/application/chukebao/model/ChatGroups.php @@ -0,0 +1,16 @@ + Date: Wed, 3 Dec 2025 09:46:19 +0800 Subject: [PATCH 23/51] =?UTF-8?q?=E9=87=8D=E6=9E=84ChatWindow=E5=92=8CMess?= =?UTF-8?q?ageEnter=E7=BB=84=E4=BB=B6=E4=BB=A5=E6=8F=90=E9=AB=98=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E5=92=8C=E5=8F=AF=E7=BB=B4=E6=8A=A4=E6=80=A7=E3=80=82?= =?UTF-8?q?=E5=88=A9=E7=94=A8useCallback=E5=92=8CuseMemo=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E5=A4=87=E5=BF=98=E5=BD=95=E5=8C=96=EF=BC=8C=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=B1=BB=E5=9E=8B=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E7=AE=80=E5=8C=96=E6=B6=88=E6=81=AF=E5=91=88=E7=8E=B0?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E3=80=82=E5=9C=A8MessageList=E4=B8=AD?= =?UTF-8?q?=E5=BC=95=E5=85=A5SessionItem=E7=BB=84=E4=BB=B6=E4=BB=A5?= =?UTF-8?q?=E6=9B=B4=E5=A5=BD=E5=9C=B0=E7=AE=A1=E7=90=86=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/MessageEnter/index.tsx | 674 ++++++++++-------- .../components/MessageRecord/index.tsx | 648 +++++++++-------- .../weChat/components/ChatWindow/index.tsx | 167 +++-- .../SidebarMenu/MessageList/index.tsx | 109 +-- 4 files changed, 900 insertions(+), 698 deletions(-) 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 e057a7281..b2838f7ec 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 @@ -1,15 +1,5 @@ -import React, { useEffect, useState, useRef } from "react"; -import { - Layout, - Input, - Button, - Modal, - message, - Tooltip, - AutoComplete, - Input as AntInput, - Spin, -} from "antd"; +import React, { useEffect, useState, useCallback } from "react"; +import { Layout, Button, message, Tooltip } from "antd"; import { SendOutlined, FolderOutlined, @@ -36,7 +26,6 @@ import { import { useContactStore } from "@/store/module/weChat/contacts"; import SelectMap from "./components/selectMap"; const { Footer } = Layout; -const { TextArea } = Input; interface MessageEnterProps { contract: ContractData | weChatGroup; @@ -44,6 +33,159 @@ interface MessageEnterProps { const { sendCommand } = useWebSocketStore.getState(); +const FileType = { + TEXT: 1, + IMAGE: 2, + VIDEO: 3, + AUDIO: 4, + FILE: 5, +}; + +const IMAGE_FORMATS = [ + "jpg", + "jpeg", + "png", + "gif", + "bmp", + "webp", + "svg", + "ico", +]; + +const VIDEO_FORMATS = [ + "mp4", + "avi", + "mov", + "wmv", + "flv", + "mkv", + "webm", + "3gp", + "rmvb", +]; + +// 根据文件格式判断消息类型(纯函数,放在组件外避免重复创建) +const getMsgTypeByFileFormat = (filePath: string): number => { + const extension = filePath.toLowerCase().split(".").pop() || ""; + + if (IMAGE_FORMATS.includes(extension)) { + return 3; // 图片 + } + + if (VIDEO_FORMATS.includes(extension)) { + return 43; // 视频 + } + + // 其他格式默认为文件 + return 49; // 文件 +}; + +const InputToolbar = React.memo( + ({ + isAiAssist, + isAiTakeover, + isLoadingAiChat, + onEmojiSelect, + onFileUploaded, + onAudioUploaded, + onOpenMap, + onManualTriggerAi, + onOpenChatRecord, + }: { + isAiAssist: boolean; + isAiTakeover: boolean; + isLoadingAiChat: boolean; + onEmojiSelect: (emoji: EmojiInfo) => void; + onFileUploaded: ( + filePath: { url: string; name: string; durationMs?: number }, + fileType: number, + ) => void; + onAudioUploaded: (audioData: { + name: string; + url: string; + durationMs?: number; + }) => void; + onOpenMap: () => void; + onManualTriggerAi: () => void; + onOpenChatRecord: () => void; + }) => { + return ( +
+
+ + onFileUploaded(fileInfo, FileType.FILE)} + maxSize={10} + type={4} + slot={ +
+
+ +
+ +  聊天记录 +
+
+
+ ); + }, +); + +const MemoSelectMap: React.FC> = + React.memo(props => ); + const MessageEnter: React.FC = ({ contract }) => { const [inputValue, setInputValue] = useState(""); const EnterModule = useWeChatStore(state => state.EnterModule); @@ -78,270 +220,267 @@ const MessageEnter: React.FC = ({ contract }) => { const isAiTakeover = aiQuoteMessageContent === 2; // AI接管 // 取消AI生成 - const handleCancelAi = () => { - // 清除AI请求定时器和队列 + const handleCancelAi = useCallback(() => { clearAiRequestQueue("用户手动取消"); - // 停止AI加载状态 updateIsLoadingAiChat(false); - // 清空AI回复内容 updateQuoteMessageContent(""); message.info("已取消AI生成"); - }; + }, [updateIsLoadingAiChat, updateQuoteMessageContent]); // 监听输入框变化 - 用户开始输入时取消AI - const handleInputChange = (e: React.ChangeEvent) => { - const newValue = e.target.value; - setInputValue(newValue); + const handleInputChange = useCallback( + (e: React.ChangeEvent) => { + const newValue = e.target.value; + setInputValue(newValue); - // 如果用户开始输入(且不是AI填充的内容),取消AI请求 - if (newValue && newValue !== quoteMessageContent) { - if (isLoadingAiChat) { + // 如果用户开始输入(且不是AI填充的内容),取消AI请求 + if (newValue && newValue !== quoteMessageContent && isLoadingAiChat) { console.log("👤 用户开始输入,取消AI生成"); clearAiRequestQueue("用户开始输入"); updateIsLoadingAiChat(false); updateQuoteMessageContent(""); } - } - }; + }, + [ + isLoadingAiChat, + quoteMessageContent, + updateIsLoadingAiChat, + updateQuoteMessageContent, + ], + ); // 手动触发AI生成 - const handleManualTriggerAi = async () => { + const handleManualTriggerAi = useCallback(async () => { const success = await manualTriggerAi(); if (success) { message.success("AI正在生成回复..."); } else { message.warning("无法生成AI回复,请检查消息记录"); } - }; + }, []); // 发送消息(支持传入内容参数,避免闭包问题) - const handleSend = async (content?: string) => { - const messageContent = content || inputValue; // 优先使用传入的内容 + const handleSend = useCallback( + async (content?: string) => { + const messageContent = content || inputValue; // 优先使用传入的内容 - if (!messageContent || !messageContent.trim()) { - console.warn("消息内容为空,取消发送"); - return; - } + if (!messageContent || !messageContent.trim()) { + console.warn("消息内容为空,取消发送"); + return; + } - // 用户主动发送消息时,取消AI请求 - if (!content && isLoadingAiChat) { - console.log("👤 用户主动发送消息,取消AI生成"); - clearAiRequestQueue("用户主动发送"); - updateIsLoadingAiChat(false); - } + // 用户主动发送消息时,取消AI请求 + if (!content && isLoadingAiChat) { + console.log("👤 用户主动发送消息,取消AI生成"); + clearAiRequestQueue("用户主动发送"); + updateIsLoadingAiChat(false); + } - console.log("handleSend", messageContent); - const messageId = +Date.now(); - // 构造本地消息对象 - 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: messageContent, - msgType: 1, - 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); + console.log("handleSend", messageContent); + const messageId = +Date.now(); + // 构造本地消息对象 + 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: messageContent, + msgType: 1, + 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); - // 再发送消息到服务器 - const params = { - wechatAccountId: contract.wechatAccountId, - wechatChatroomId: contract?.chatroomId ? contract.id : 0, - wechatFriendId: contract?.chatroomId ? 0 : contract.id, - msgSubType: 0, - msgType: 1, - content: messageContent, - seq: messageId, - }; - sendCommand("CmdSendMessage", params); + // 再发送消息到服务器 + const params = { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract?.chatroomId ? contract.id : 0, + wechatFriendId: contract?.chatroomId ? 0 : contract.id, + msgSubType: 0, + msgType: 1, + content: messageContent, + seq: messageId, + }; + sendCommand("CmdSendMessage", params); - // 清空输入框和AI回复内容 - setInputValue(""); - updateQuoteMessageContent(""); - }; + // 清空输入框和AI回复内容 + setInputValue(""); + updateQuoteMessageContent(""); + }, + [ + addMessage, + contract.id, + contract.wechatAccountId, + contract?.chatroomId, + inputValue, + isLoadingAiChat, + updateIsLoadingAiChat, + updateQuoteMessageContent, + ], + ); // AI 消息处理 - 只处理AI辅助模式 // AI接管模式已经在weChat.ts中直接发送,不经过此组件 // 快捷语填充:当 quoteMessageContent 更新时,填充到输入框 useEffect(() => { if (quoteMessageContent) { - if (isAiAssist) { - // AI辅助模式:直接填充输入框 - setInputValue(quoteMessageContent); - } else { - // 快捷语模式:直接填充输入框(用户主动点击快捷语,应该替换当前内容) - setInputValue(quoteMessageContent); - } + // AI辅助模式 & 快捷语模式:都直接填充输入框 + setInputValue(quoteMessageContent); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [quoteMessageContent, aiQuoteMessageContent, isAiAssist]); - const handleKeyPress = (e: React.KeyboardEvent) => { - if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey) { - e.preventDefault(); - handleSend(); - } - // Ctrl+Enter 换行由 TextArea 自动处理,不需要阻止默认行为 - }; + const handleKeyPress = useCallback( + (e: React.KeyboardEvent) => { + // 中文等输入法候选阶段,忽略 Enter,避免误触发送和卡顿感 + if ((e.nativeEvent as any).isComposing) { + return; + } + + if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey) { + e.preventDefault(); + handleSend(); + } + // Ctrl+Enter 换行由浏览器原生 textarea 处理,不需要阻止默认行为 + }, + [handleSend], + ); // 处理表情选择 - const handleEmojiSelect = (emoji: EmojiInfo) => { + const handleEmojiSelect = useCallback((emoji: EmojiInfo) => { setInputValue(prevValue => prevValue + `[${emoji.name}]`); - }; + }, []); - // 根据文件格式判断消息类型 - const getMsgTypeByFileFormat = (filePath: string): number => { - const extension = filePath.toLowerCase().split(".").pop() || ""; + const handleFileUploaded = useCallback( + ( + filePath: { url: string; name: string; durationMs?: number }, + fileType: number, + ) => { + console.log("handleFileUploaded: ", fileType, filePath); - // 图片格式 - const imageFormats = [ - "jpg", - "jpeg", - "png", - "gif", - "bmp", - "webp", - "svg", - "ico", - ]; - if (imageFormats.includes(extension)) { - return 3; // 图片 - } - - // 视频格式 - const videoFormats = [ - "mp4", - "avi", - "mov", - "wmv", - "flv", - "mkv", - "webm", - "3gp", - "rmvb", - ]; - if (videoFormats.includes(extension)) { - return 43; // 视频 - } - - // 其他格式默认为文件 - return 49; // 文件 - }; - const FileType = { - TEXT: 1, - IMAGE: 2, - VIDEO: 3, - AUDIO: 4, - FILE: 5, - }; - const handleFileUploaded = ( - 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.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 = getMsgTypeByFileFormat(filePath.url); - if (msgType === 3) { + // msgType(1:文本 3:图片 43:视频 47:动图表情包(gif、其他表情包) 49:小程序/其他:图文、文件) + let msgType = 1; + let content: any = ""; + if ([FileType.TEXT].includes(fileType)) { + msgType = getMsgTypeByFileFormat(filePath.url); + } else if ([FileType.IMAGE].includes(fileType)) { + msgType = 3; content = filePath.url; - } - if (msgType === 43) { - content = filePath.url; - } - - if (msgType === 49) { + } else if ([FileType.AUDIO].includes(fileType)) { + msgType = 34; content = JSON.stringify({ - type: "file", - title: filePath.name, url: filePath.url, + durationMs: filePath.durationMs, }); + } else if ([FileType.FILE].includes(fileType)) { + 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 messageId = +Date.now(); - const params = { - wechatAccountId: contract.wechatAccountId, - wechatChatroomId: contract?.chatroomId ? contract.id : 0, - wechatFriendId: contract?.chatroomId ? 0 : contract.id, - msgSubType: 0, - msgType, - content: content, - seq: messageId, - }; + const params = { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract?.chatroomId ? contract.id : 0, + wechatFriendId: contract?.chatroomId ? 0 : contract.id, + msgSubType: 0, + msgType, + 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); + // 构造本地消息对象 + 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); - }; + sendCommand("CmdSendMessage", params); + }, + [addMessage, contract.wechatAccountId, contract.chatroomId, contract.id], + ); - const handleCancelAction = () => { + const handleCancelAction = useCallback(() => { + if (!EnterModule) return; updateShowCheckbox(false); updateEnterModule("common"); - }; - const handTurnRignt = () => { + }, [EnterModule, updateShowCheckbox, updateEnterModule]); + + const handTurnRignt = useCallback(() => { setTransmitModal(true); - }; - const openChatRecordModel = () => { + }, [setTransmitModal]); + + const openChatRecordModel = useCallback(() => { updateShowChatRecordModel(!showChatRecordModel); - }; + }, [showChatRecordModel, updateShowChatRecordModel]); const [mapVisible, setMapVisible] = useState(false); + const handleOpenMap = useCallback(() => { + setMapVisible(true); + }, []); + + const handleAudioUploaded = useCallback( + (audioData: { name: string; url: string; durationMs?: number }) => { + handleFileUploaded( + { + name: audioData.name, + url: audioData.url, + durationMs: audioData.durationMs, + }, + FileType.AUDIO, + ); + }, + [handleFileUploaded], + ); + return ( <> {/* 聊天输入 */} @@ -394,96 +533,27 @@ const MessageEnter: React.FC = ({ contract }) => { <> {["common"].includes(EnterModule) && (
-
-
- - - handleFileUploaded(fileInfo, FileType.FILE) - } - maxSize={10} - type={4} - slot={ -
-
- -
- -  聊天记录 -
-
-
+
-