From 22df17f02a0ac0a0b0de6f8ae1d6f79872bbb4f4 Mon Sep 17 00:00:00 2001 From: ak Date: Wed, 12 Aug 2026 03:31:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=9C=9F=E5=AE=9E=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=20+=20=E4=BF=AE=E5=A4=8D=20yansongda=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=BB=93=E6=9E=84=20bug=20+=20=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=20AI=20=E5=AE=9E=E6=97=B6=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支付设置:支付宝/微信各自支持正式/沙箱网关环境,凭据字段补全(支付宝公钥、微信商户证书私钥/平台证书 PEM),密钥可直接粘贴 PEM 文本(yansongda 支持字符串) - 修复关键 bug:yansongda 配置结构错误(app_id 等在 default 租户外,真实网关拿不到配置),现按 alipay.default / wechat.default 正确传入,并用 _force 保证每次调用生效 - 后台 AI 设置页新增 WebSocket 实时审核进度(ai.moderation.* 事件),未连接时降级提示 - 部署文档:明确 queue:work 与 workerman:serve 等价、dev:watch 用法 - 测试:支付宝/微信配置结构回归 + AI 设置页加载 --- docs/deploy.md | 23 +++++++- .../views/ai-settings.blade.php | 56 +++++++++++++++++++ .../src/Filament/Pages/PaymentSettings.php | 48 ++++++++++++++-- .../src/Services/PaymentManager.php | 44 +++++++++------ tests/Feature/AdminPagesTest.php | 1 + tests/Feature/PaymentGatewayConfigTest.php | 54 ++++++++++++++++++ 6 files changed, 203 insertions(+), 23 deletions(-) create mode 100644 tests/Feature/PaymentGatewayConfigTest.php diff --git a/docs/deploy.md b/docs/deploy.md index b1997ed..f6a2002 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -117,17 +117,36 @@ systemctl enable --now laralog-workerman 调度器内注册的内容:`spatie/laravel-backup` 备份、缓存清理等(按需在 `routes/console.php` 增删)。 -## 队列降级(不使用 Workerman 时) +## 队列消费:workerman 与 queue:work 等价 + +队列任务(AI 审核/润色等)由**消费者**执行,两种方式消费同一个队列、功能完全等价: + +| | workerman:serve | queue:work | +|---|---|---| +| 执行任务 | ✅ | ✅ | +| 性能 | 常驻进程,快 | 每任务启动框架,慢 | +| WebSocket 进度推送 | ✅ :8787 | ❌(任务照常执行,仅后台无实时提示) | +| 适用 | 生产推荐 | 降级 / 标准 Laravel 部署 | + +**两者不要同时跑同一个队列**(虽不会重复消费,但浪费资源)。改业务代码后两者都需要重启才生效(常驻内存);`queue:work --once` 每任务新进程,适合调试。 + +### 降级方案(不用 Workerman) ```ini # /etc/systemd/system/laralog-queue.service [Service] User=www-data WorkingDirectory=/var/www/laralog -ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3 +ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3 --queue=default,ai Restart=always ``` +### 开发期热重载 + +```bash +php artisan dev:watch # 监听 app/plugins/routes/config/.env 变化,自动重启 workerman(fswatch 或 PHP 轮询兜底) +``` + ## 备份 ```bash diff --git a/plugins/neatstudio.ai-moderation/views/ai-settings.blade.php b/plugins/neatstudio.ai-moderation/views/ai-settings.blade.php index 9c8e812..2886672 100644 --- a/plugins/neatstudio.ai-moderation/views/ai-settings.blade.php +++ b/plugins/neatstudio.ai-moderation/views/ai-settings.blade.php @@ -28,4 +28,60 @@ @endif + + +
+

等待事件…

+
+
+ + diff --git a/plugins/neatstudio.payment/src/Filament/Pages/PaymentSettings.php b/plugins/neatstudio.payment/src/Filament/Pages/PaymentSettings.php index 377a234..8d8cebe 100644 --- a/plugins/neatstudio.payment/src/Filament/Pages/PaymentSettings.php +++ b/plugins/neatstudio.payment/src/Filament/Pages/PaymentSettings.php @@ -8,6 +8,8 @@ namespace Plugins\Neatstudio\Payment\Filament\Pages; use App\Models\Setting; use Filament\Actions\Action; use Filament\Schemas\Components\Section; +use Filament\Forms\Components\Select; +use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; use Filament\Forms\Concerns\InteractsWithForms; @@ -35,9 +37,14 @@ class PaymentSettings extends Page 'pay_sandbox' => (int) Setting::get('pay_sandbox', 1) === 1, 'pay_alipay_app_id' => Setting::get('pay_alipay_app_id', ''), 'pay_alipay_app_secret' => Setting::get('pay_alipay_app_secret', ''), + 'pay_alipay_public_key' => Setting::get('pay_alipay_public_key', ''), + 'pay_alipay_mode' => Setting::get('pay_alipay_mode', 'normal'), 'pay_wechat_app_id' => Setting::get('pay_wechat_app_id', ''), 'pay_wechat_mch_id' => Setting::get('pay_wechat_mch_id', ''), 'pay_wechat_mch_secret' => Setting::get('pay_wechat_mch_secret', ''), + 'pay_wechat_mch_cert' => Setting::get('pay_wechat_mch_cert', ''), + 'pay_wechat_platform_cert' => Setting::get('pay_wechat_platform_cert', ''), + 'pay_wechat_mode' => Setting::get('pay_wechat_mode', 'normal'), ]); } @@ -46,21 +53,52 @@ class PaymentSettings extends Page return $schema ->components([ Section::make('通用') + ->description('关闭沙箱后才会调用真实支付网关;支付宝/微信各自可选正式环境或官方沙箱环境(需对应环境的凭据)') ->schema([ - Toggle::make('pay_sandbox')->label('沙箱模式(无需真实密钥即可测试支付流程)')->default(true), + Toggle::make('pay_sandbox')->label('沙箱模式(模拟支付,无需真实密钥即可测试完整流程)')->default(true), ]), Section::make('支付宝') + ->description('正式/沙箱环境凭据:open.alipay.com → 沙箱环境或控制台,RSA2 密钥生成器获取应用私钥与支付宝公钥') ->columns(2) ->schema([ - TextInput::make('pay_alipay_app_id')->label('App ID')->columnSpanFull(), - TextInput::make('pay_alipay_app_secret')->label('应用私钥(app_secret_cert)')->password()->columnSpanFull(), + Select::make('pay_alipay_mode') + ->label('网关环境') + ->options(['normal' => '正式环境', 'sandbox' => '沙箱环境']) + ->default('normal'), + TextInput::make('pay_alipay_app_id') + ->label('App ID') + ->placeholder('正式 / 沙箱应用 ID') + ->columnSpan(2), + Textarea::make('pay_alipay_app_secret') + ->label('应用私钥(RSA2 PEM)') + ->rows(5) + ->helperText('以 -----BEGIN PRIVATE KEY----- 开头') + ->columnSpan(2), + Textarea::make('pay_alipay_public_key') + ->label('支付宝公钥(RSA2 PEM)') + ->rows(5) + ->helperText('以 -----BEGIN PUBLIC KEY----- 开头;用于回调验签') + ->columnSpan(2), ]), Section::make('微信支付') + ->description('微信商户平台(pay.weixin.qq.com)→ 账户中心 → API 安全:APIv3 密钥、商户 API 证书(apiclient_key.pem)、平台证书') ->columns(2) ->schema([ - TextInput::make('pay_wechat_app_id')->label('App ID'), + Select::make('pay_wechat_mode') + ->label('网关环境') + ->options(['normal' => '正式环境', 'sandbox' => '沙箱环境']) + ->default('normal'), + TextInput::make('pay_wechat_app_id')->label('App ID(小程序/公众号)'), TextInput::make('pay_wechat_mch_id')->label('商户号 MCH ID'), - TextInput::make('pay_wechat_mch_secret')->label('API 密钥')->password()->columnSpanFull(), + TextInput::make('pay_wechat_mch_secret')->label('APIv3 密钥')->password()->columnSpan(2), + Textarea::make('pay_wechat_mch_cert') + ->label('商户 API 证书私钥(apiclient_key.pem)') + ->rows(5) + ->columnSpan(2), + Textarea::make('pay_wechat_platform_cert') + ->label('微信支付平台证书(公钥 PEM)') + ->rows(5) + ->columnSpan(2), ]), ]) ->statePath('data'); diff --git a/plugins/neatstudio.payment/src/Services/PaymentManager.php b/plugins/neatstudio.payment/src/Services/PaymentManager.php index f3da66e..c7a36a0 100644 --- a/plugins/neatstudio.payment/src/Services/PaymentManager.php +++ b/plugins/neatstudio.payment/src/Services/PaymentManager.php @@ -113,31 +113,43 @@ class PaymentManager public function alipay(): Alipay { + $mode = Setting::get('pay_alipay_mode', 'normal') === 'sandbox' ? Pay::MODE_SANDBOX : Pay::MODE_NORMAL; + return Pay::alipay([ - 'default' => [ - 'notify_url' => route('pay.notify', ['channel' => 'alipay']), - 'return_url' => route('pay.return', ['channel' => 'alipay']), + '_force' => true, + 'alipay' => [ + 'default' => [ + 'mode' => $mode, + 'notify_url' => route('pay.notify', ['channel' => 'alipay']), + 'return_url' => route('pay.return', ['channel' => 'alipay']), + 'app_id' => Setting::get('pay_alipay_app_id', ''), + 'app_secret_cert' => Setting::get('pay_alipay_app_secret', ''), + 'app_public_cert_path' => null, + 'alipay_public_cert_path' => Setting::get('pay_alipay_public_key', ''), + 'alipay_root_cert_path' => null, + ], ], - 'app_id' => Setting::get('pay_alipay_app_id', ''), - 'app_secret_cert' => Setting::get('pay_alipay_app_secret', ''), - 'app_public_cert_path' => null, - 'alipay_public_cert_path' => null, - 'alipay_root_cert_path' => null, ]); } public function wechat(): Wechat { + $mode = Setting::get('pay_wechat_mode', 'normal') === 'sandbox' ? Pay::MODE_SANDBOX : Pay::MODE_NORMAL; + return Pay::wechat([ - 'default' => [ - 'notify_url' => route('pay.notify', ['channel' => 'wechat']), - 'return_url' => route('pay.return', ['channel' => 'wechat']), + '_force' => true, + 'wechat' => [ + 'default' => [ + 'mode' => $mode, + 'notify_url' => route('pay.notify', ['channel' => 'wechat']), + 'return_url' => route('pay.return', ['channel' => 'wechat']), + 'mch_id' => Setting::get('pay_wechat_mch_id', ''), + 'mch_secret_key' => Setting::get('pay_wechat_mch_secret', ''), + 'mch_secret_cert' => Setting::get('pay_wechat_mch_cert', ''), + 'mch_public_cert_path' => Setting::get('pay_wechat_platform_cert', ''), + 'mp_app_id' => Setting::get('pay_wechat_app_id', ''), + ], ], - 'mch_id' => Setting::get('pay_wechat_mch_id', ''), - 'mch_secret_key' => Setting::get('pay_wechat_mch_secret', ''), - 'mch_secret_cert' => null, - 'mch_public_cert_path' => null, - 'mp_app_id' => Setting::get('pay_wechat_app_id', ''), ]); } diff --git a/tests/Feature/AdminPagesTest.php b/tests/Feature/AdminPagesTest.php index c6e5816..d874fde 100644 --- a/tests/Feature/AdminPagesTest.php +++ b/tests/Feature/AdminPagesTest.php @@ -54,6 +54,7 @@ class AdminPagesTest extends TestCase { $this->actingAs($this->admin)->get('/admin/plugins')->assertOk(); $this->actingAs($this->admin)->get('/admin/payments')->assertOk(); + $this->actingAs($this->admin)->get('/admin/ai-settings')->assertOk(); } public function test_guest_is_redirected_to_login(): void diff --git a/tests/Feature/PaymentGatewayConfigTest.php b/tests/Feature/PaymentGatewayConfigTest.php new file mode 100644 index 0000000..3e24c2a --- /dev/null +++ b/tests/Feature/PaymentGatewayConfigTest.php @@ -0,0 +1,54 @@ +alipay(); + + $config = \Yansongda\Pay\get_provider_config('alipay'); + + $this->assertSame(\Yansongda\Pay\Pay::MODE_SANDBOX, $config['mode']); + $this->assertSame('2021000000000000', $config['app_id']); + $this->assertStringContainsString('PRIVATE KEY', $config['app_secret_cert']); + $this->assertStringContainsString('PUBLIC KEY', $config['alipay_public_cert_path']); + $this->assertStringContainsString('pay/notify', $config['notify_url']); + } + + public function test_wechat_config_lands_in_default_tenant(): void + { + Setting::set('pay_wechat_mode', 'normal'); + Setting::set('pay_wechat_mch_id', '1900000001'); + Setting::set('pay_wechat_mch_secret', 'api-v3-key'); + Setting::set('pay_wechat_mch_cert', "-----BEGIN PRIVATE KEY-----\nCCCC\n-----END PRIVATE KEY-----"); + Setting::set('pay_wechat_platform_cert', "-----BEGIN PUBLIC KEY-----\nDDDD\n-----END PUBLIC KEY-----"); + Setting::set('pay_wechat_app_id', 'wx123456'); + + app(PaymentManager::class)->wechat(); + + $config = \Yansongda\Pay\get_provider_config('wechat'); + + $this->assertSame(\Yansongda\Pay\Pay::MODE_NORMAL, $config['mode']); + $this->assertSame('1900000001', $config['mch_id']); + $this->assertSame('api-v3-key', $config['mch_secret_key']); + $this->assertStringContainsString('PRIVATE KEY', $config['mch_secret_cert']); + $this->assertStringContainsString('PUBLIC KEY', $config['mch_public_cert_path']); + $this->assertSame('wx123456', $config['mp_app_id']); + } +}