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']);
+ }
+}