From ca0bf11b55824cd939f5435efc25207cb3b7fd87 Mon Sep 17 00:00:00 2001
From: ASUS <515617283@qq.com>
Date: Fri, 15 Aug 2025 10:06:23 +0800
Subject: [PATCH] =?UTF-8?q?=E5=91=BD=E4=BB=A4=E8=A1=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/command/Logzip.php | 76 ++++
app/command/OrderTask.php | 62 ++++
app/command/SpinningTask3.php | 71 ++++
app/command/TempCashOutTask.php | 89 +++++
app/command/TempOrderTask.php | 106 ++++++
composer.json | 3 +-
composer.lock | 514 ++++++++++++++++++++++++++-
config/plugin/webman/console/app.php | 24 ++
webman | 73 ++++
9 files changed, 1016 insertions(+), 2 deletions(-)
create mode 100644 app/command/Logzip.php
create mode 100644 app/command/OrderTask.php
create mode 100644 app/command/SpinningTask3.php
create mode 100644 app/command/TempCashOutTask.php
create mode 100644 app/command/TempOrderTask.php
create mode 100644 config/plugin/webman/console/app.php
create mode 100644 webman
diff --git a/app/command/Logzip.php b/app/command/Logzip.php
new file mode 100644
index 0000000..18992ac
--- /dev/null
+++ b/app/command/Logzip.php
@@ -0,0 +1,76 @@
+addArgument('name', InputArgument::OPTIONAL, 'Name description');
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $dir_arr = [
+ 'runtime/admin/log/',
+ 'runtime/api/log/',
+ 'runtime/czg/log/',
+ 'runtime/log/',
+ ];
+ $yestermonth = date("Ym");
+ // 如果今天是1号
+ if(date('d') == 01 || date('d') == 1) {
+ $yestermonth = date("Ym", strtotime('-1 month'));
+ }
+ foreach ($dir_arr as $dir_a) {
+ $file_arr = getYesterdayFiles($dir_a . $yestermonth . '/');
+ if(!empty($file_arr['files'])) {
+ foreach ($file_arr['files'] as $k => $file_p) {
+ $file = $dir_a . $yestermonth . '/' . $file_p;
+ $zip_filename = str_replace('.log', '.zip', $file_p);
+ $file_zip = $dir_a . $yestermonth . '/' . $zip_filename;
+ $is_close = false;
+ if(!file_exists($file_zip)) {
+ $zip = new ZipArchive();
+ if($zip->open($file_zip, ZipArchive::CREATE) === TRUE) {
+ if(file_exists($file)) {
+ if($zip->addFile($file, $file_p)) {
+ $is_close = true;
+ print_r($file . "\r\n");
+ }
+ }
+ if ($is_close) {
+ if($zip->close()) {
+ if($is_close) {
+ unlink($file);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ $output->writeln('ok');
+ return self::SUCCESS;
+ }
+
+}
diff --git a/app/command/OrderTask.php b/app/command/OrderTask.php
new file mode 100644
index 0000000..db33634
--- /dev/null
+++ b/app/command/OrderTask.php
@@ -0,0 +1,62 @@
+addArgument('name', InputArgument::OPTIONAL, 'Name description');
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ try {
+ Log::info("未支付订单删除开始");
+ // 获取3小时前的时间
+ $threeHoursAgo = date('Y-m-d H:i:s', strtotime('-3 hours'));
+ // 开启事务确保数据一致性
+ DatabaseRoute::transactionXa(function () use ($threeHoursAgo, $output) {
+ $deletedOrders = DatabaseRoute::deleteAllDbDirect('orders', function ($query) use ($threeHoursAgo) {
+ return $query->where('create_time', '<=', $threeHoursAgo)
+ ->where('status', '<>', 1);
+ });
+ Log::info("删除了 {$deletedOrders} 条未支付订单");
+ $deletedPayments = DatabaseRoute::deleteAllDbDirect('pay_details', function ($query) use ($threeHoursAgo) {
+ return $query->where('create_time', '<=', $threeHoursAgo)
+ ->where('state', '<>', 1);
+ });
+ Log::info("删除了 {$deletedPayments} 条未支付支付记录");
+ $output->writeln("清理完成: 删除 {$deletedOrders} 个订单, {$deletedPayments} 个支付记录");
+ });
+ } catch (\Exception $e) {
+ Log::error("未支付订单清理失败: " . $e->getMessage());
+ $output->writeln("清理失败: " . $e->getMessage() . "");
+ return 1; // 返回错误码
+ }
+ // 返回成功码
+ $output->writeln(0);
+ return self::SUCCESS;
+ }
+
+}
diff --git a/app/command/SpinningTask3.php b/app/command/SpinningTask3.php
new file mode 100644
index 0000000..0536372
--- /dev/null
+++ b/app/command/SpinningTask3.php
@@ -0,0 +1,71 @@
+addArgument('name', InputArgument::OPTIONAL, 'Name description');
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $params = 1;
+ if($input->hasOption('params')) {
+ $params = $input->getOption('params');
+ }
+ // 计算时间范围(当前时间 - N*5分钟 到 当前时间 - (N*5+15)分钟)
+ $now = time();
+ $offsetMinutes = (int)$params * -5; // N*5分钟前
+ $fiveMinutesAgo = date('Y-m-d H:i:s', strtotime("{$offsetMinutes} minutes", $now));
+ $tenMinutesAgo = date('Y-m-d H:i:s', strtotime("-15 minutes", strtotime($fiveMinutesAgo)));
+ Log::info("大转盘到账补偿时间范围:{$tenMinutesAgo}-----{$fiveMinutesAgo}");
+ $list = DatabaseRoute::getAllDbData('disc_spinning_record', function ($query) use($fiveMinutesAgo, $tenMinutesAgo) {
+ return $query->whereNull('target')
+ ->whereNull('target_id')
+ ->where('type', 2)
+ ->where('create_time', '>=', $tenMinutesAgo) // 大于等于(N*5+15)分钟前
+ ->where('create_time', '<=', $fiveMinutesAgo);
+ })->select();
+ if($list) {
+ $list = $list->toArray();
+ Log::info('需要补偿的总条数' . count($list));
+ if(count($list) > 0) {
+ // 推进队列
+ $this->execAsync($list);
+ }
+ }
+ $output->writeln("大转盘到账补偿机制结束");
+ Log::write("大转盘到账补偿机制结束");
+ $output->writeln('Hello SpinningTask3');
+ return self::SUCCESS;
+ }
+ public function execAsync($list)
+ {
+ foreach ($list as $k => $value) {
+ pushQueue(DiscCompensateJob::class, $value);
+ }
+ }
+}
diff --git a/app/command/TempCashOutTask.php b/app/command/TempCashOutTask.php
new file mode 100644
index 0000000..fd66c2a
--- /dev/null
+++ b/app/command/TempCashOutTask.php
@@ -0,0 +1,89 @@
+addArgument('name', InputArgument::OPTIONAL, 'Name description');
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ try {
+ $time = date('Y-m-d H:i:s', strtotime('-15 minutes'));
+
+ // 查询待处理的提现订单(你需要根据你的业务逻辑调整 where 条件)
+ $cashOuts = DatabaseRoute::getAllDbData('cash_out', function ($query) use ($time) {
+ return $query->where([
+ ['state', 'in', [0, 4]],
+ ['user_type', '=', 1],
+ ['create_at', '<', $time],
+ ['order_number', '!=', ''],
+ ])->whereNotNull('order_number');
+ })->select()->toArray();
+
+ Log::info('定时查询提现订单 待处理订单: ' . count($cashOuts));
+
+ $sucOrderList = [];
+ $failOrderList = [];
+
+ foreach ($cashOuts as $cashOut) {
+ try {
+ // 调用支付平台接口(需你自定义服务类)
+ $baseResp = WuYouPayUtils::queryExtractOrder(
+ $cashOut['order_number'],
+ $cashOut['user_id'],
+ $cashOut['user_type'] != 2,
+ $cashOut['money']
+ );
+
+ // 执行回调(需你自定义服务类)
+ $result = Order::executeExtractCallback($cashOut, $baseResp);
+
+ if ($result === 1) {
+ $sucOrderList[] = $cashOut['order_number'];
+ } else {
+ $failOrderList[] = $cashOut['order_number'];
+ }
+
+ } catch (\Throwable $e) {
+ Log::error('提现定时任务查询出错: ' . $e->getMessage());
+ }
+ }
+
+ Log::info('定时查询提现订单 提现结束, 成功:' . count($sucOrderList) . '条, 失败:' . count($failOrderList) . '条');
+ Log::info('定时查询提现订单 成功:' . json_encode($sucOrderList) . ', 失败:' . json_encode($failOrderList));
+ } catch (\Exception $e) {
+ Log::error("定时查询提现订单失败: " . $e->getMessage());
+ Log::info($e->getTraceAsString());
+ $output->writeln("定时查询提现订单失败: " . $e->getMessage() . "");
+ return 1; // 返回错误码
+ }
+ return self::SUCCESS;
+ }
+
+}
diff --git a/app/command/TempOrderTask.php b/app/command/TempOrderTask.php
new file mode 100644
index 0000000..21049d5
--- /dev/null
+++ b/app/command/TempOrderTask.php
@@ -0,0 +1,106 @@
+addArgument('name', InputArgument::OPTIONAL, 'Name description');
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ try {
+ Log::info('订单表数据处理开始');
+
+ // 查询待处理的支付明细(state=0,create_time 早于 15 分钟前)
+ $payDetailsList = DatabaseRoute::getAllDbData('pay_details', function ($query) {
+ return $query
+ ->where('state', 0)
+ ->where('create_time', '<', date('Y-m-d H:i:s', strtotime('-15 minutes')))
+ ->order('create_time', 'asc')
+ ->limit(1800);
+ })->select()->toArray();
+
+ if (empty($payDetailsList)) {
+ return 0;
+ }
+
+ Log::info('待处理数据' . count($payDetailsList) . '条');
+
+ foreach ($payDetailsList as $details) {
+ try {
+ usleep(100 * 1000); // sleep 100ms
+
+ // 根据 orderId 查询 Orders 表
+ $order = DatabaseRoute::getAllDbData('orders', function ($query) use ($details) {
+ return $query->where('orders_no', $details['order_id'])
+ ->where('user_id', $details['user_id']);
+ })->find();
+
+ // 调用支付平台查询订单状态
+ $baseResp = WuYouPayUtils::queryOrder(
+ $details['trade_no'],
+ $details['user_id'],
+ (string)$details['money'],
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X)...'
+ );
+
+ Log::info('baseResp: ' . json_encode($baseResp));
+
+ if (empty($baseResp['code']) || $baseResp['code'] != 200) {
+ Log::info('code 错误跳过');
+ continue;
+ }
+
+ if (($baseResp['payStatus'] ?? '') === 'SUCCESS' || ($baseResp['payStatus2'] ?? '') === 'SUCCESS') {
+ Log::info('payDetails: ' . json_encode($details));
+ Log::info('order: ' . json_encode($order));
+ Orders::updateOrderStatus($details, $order, $order['user_id']);
+ } else {
+ Log::info('订单未支付,修改状态: ' . $details['trade_no']);
+ DatabaseRoute::getDb('orders', $order['user_id'], true, true)->where([
+ 'orders_id' => $order['orders_id']
+ ])->update([
+ 'status' => $order ? 3 : 2
+ ]);
+ }
+ } catch (\Throwable $e) {
+ Log::error('订单数据处理异常:' . $e->getMessage());
+ }
+ }
+
+ Log::info('订单表数据处理完毕');
+ } catch (\Exception $e) {
+ Log::error("订单表数据处理失败: " . $e->getMessage());
+ Log::info($e->getTraceAsString());
+ $output->writeln("订单表数据处理失败: " . $e->getMessage() . "");
+ return 1; // 返回错误码
+ }
+ return self::SUCCESS;
+ }
+
+}
diff --git a/composer.json b/composer.json
index 3a8558d..2e9387a 100644
--- a/composer.json
+++ b/composer.json
@@ -33,7 +33,8 @@
"firebase/php-jwt": "^6.11",
"ext-bcmath": "*",
"webman/redis": "^2.1",
- "illuminate/events": "^11.45"
+ "illuminate/events": "^11.45",
+ "webman/console": "^2.1"
},
"suggest": {
"ext-event": "For better performance. "
diff --git a/composer.lock b/composer.lock
index 9716524..8d709cd 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "e847739a76e8232a82ad829ae648038b",
+ "content-hash": "b513a600f1b1dbb2119a8038831ab9e0",
"packages": [
{
"name": "carbonphp/carbon-doctrine-types",
@@ -1573,6 +1573,106 @@
],
"time": "2024-09-25T14:21:43+00:00"
},
+ {
+ "name": "symfony/console",
+ "version": "v7.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44",
+ "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/string": "^7.2"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<6.4",
+ "symfony/dotenv": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/lock": "<6.4",
+ "symfony/process": "<6.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/lock": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/stopwatch": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "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": "Eases the creation of beautiful and testable command line interfaces",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "cli",
+ "command-line",
+ "console",
+ "terminal"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/console/tree/v7.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-24T10:34:04+00:00"
+ },
{
"name": "symfony/deprecation-contracts",
"version": "v3.5.1",
@@ -1731,6 +1831,177 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "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.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.31.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": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "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.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
{
"name": "symfony/polyfill-mbstring",
"version": "v1.31.0",
@@ -1985,6 +2256,188 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.5.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": "2024-09-25T14:20:29+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v7.2.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/a214fe7d62bd4df2a76447c67c6b26e1d5e74931",
+ "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/translation-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/emoji": "^7.1",
+ "symfony/error-handler": "^6.4|^7.0",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/intl": "^6.4|^7.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
+ "symfony/var-exporter": "^6.4|^7.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/v7.2.6"
+ },
+ "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": "2025-04-20T20:18:16+00:00"
+ },
{
"name": "symfony/translation",
"version": "v7.3.1",
@@ -2561,6 +3014,65 @@
],
"time": "2024-11-21T00:49:12+00:00"
},
+ {
+ "name": "webman/console",
+ "version": "v2.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webman-php/console.git",
+ "reference": "bbee274a5f091eaf90e7a257dd0fbfef47da9a17"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webman-php/console/zipball/bbee274a5f091eaf90e7a257dd0fbfef47da9a17",
+ "reference": "bbee274a5f091eaf90e7a257dd0fbfef47da9a17",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "doctrine/inflector": "^2.0",
+ "symfony/console": ">=5.0"
+ },
+ "require-dev": {
+ "workerman/webman": "^1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Webman\\Console\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "walkor",
+ "email": "walkor@workerman.net",
+ "homepage": "http://www.workerman.net",
+ "role": "Developer"
+ }
+ ],
+ "description": "Webman console",
+ "homepage": "http://www.workerman.net",
+ "keywords": [
+ "webman console"
+ ],
+ "support": {
+ "email": "walkor@workerman.net",
+ "forum": "http://www.workerman.net/questions",
+ "issues": "https://github.com/webman-php/console/issues",
+ "source": "https://github.com/webman-php/console",
+ "wiki": "http://www.workerman.net/doc/webman"
+ },
+ "time": "2025-03-06T08:23:07+00:00"
+ },
{
"name": "webman/redis",
"version": "v2.1.2",
diff --git a/config/plugin/webman/console/app.php b/config/plugin/webman/console/app.php
new file mode 100644
index 0000000..074e986
--- /dev/null
+++ b/config/plugin/webman/console/app.php
@@ -0,0 +1,24 @@
+ true,
+
+ 'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build',
+
+ 'phar_filename' => 'webman.phar',
+
+ 'bin_filename' => 'webman.bin',
+
+ 'signature_algorithm'=> Phar::SHA256, //set the signature algorithm for a phar and apply it. The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL.
+
+ 'private_key_file' => '', // The file path for certificate or OpenSSL private key file.
+
+ 'exclude_pattern' => '#^(?!.*(composer.json|/.github/|/.idea/|/.git/|/.setting/|/runtime/|/vendor-bin/|/build/|/vendor/webman/admin/))(.*)$#',
+
+ 'exclude_files' => [
+ '.env', 'LICENSE', 'composer.json', 'composer.lock', 'start.php', 'webman.phar', 'webman.bin'
+ ],
+
+ 'custom_ini' => '
+memory_limit = 256M
+ ',
+];
diff --git a/webman b/webman
new file mode 100644
index 0000000..f0fa4f9
--- /dev/null
+++ b/webman
@@ -0,0 +1,73 @@
+#!/usr/bin/env php
+setName('webman cli');
+$cli->installInternalCommands();
+if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) {
+ $cli->installCommands($command_path);
+}
+
+foreach (config('plugin', []) as $firm => $projects) {
+ if (isset($projects['app'])) {
+ foreach (['', '/app'] as $app) {
+ if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) {
+ $command_path = base_path() . "/plugin/$firm{$app}/$command_str";
+ $cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str");
+ }
+ }
+ }
+ foreach ($projects as $name => $project) {
+ if (!is_array($project)) {
+ continue;
+ }
+ foreach ($project['command'] ?? [] as $class_name) {
+ $reflection = new \ReflectionClass($class_name);
+ if ($reflection->isAbstract()) {
+ continue;
+ }
+ $properties = $reflection->getStaticProperties();
+ $name = $properties['defaultName'];
+ if (!$name) {
+ throw new RuntimeException("Command {$class_name} has no defaultName");
+ }
+ $description = $properties['defaultDescription'] ?? '';
+ $command = Container::get($class_name);
+ $command->setName($name)->setDescription($description);
+ $cli->add($command);
+ }
+ }
+}
+
+$cli->run();