From 7dd2e2e40aff05ca03ee74cf00c7557386ec7aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=9D=E5=8D=9C?= Date: Tue, 17 Mar 2026 18:24:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20warning=20flash=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=8F=AF=E9=80=89=E9=93=BE=E6=8E=A5=E5=B9=B6=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E9=87=8D=E5=A4=8D=E6=89=B9=E9=87=8F=E6=8A=95=E9=80=92?= =?UTF-8?q?=E5=8F=AF=E7=9B=B4=E8=BE=BE=E4=B8=8A=E6=AC=A1=E5=A4=8D=E7=9B=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Admin/PlatformOrderController.php | 53 ++++++++++++++++--- resources/views/admin/layouts/app.blade.php | 13 ++++- ...shWarningShouldSupportOptionalLinkTest.php | 51 ++++++++++++++++++ 3 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 tests/Feature/AdminFlashWarningShouldSupportOptionalLinkTest.php diff --git a/app/Http/Controllers/Admin/PlatformOrderController.php b/app/Http/Controllers/Admin/PlatformOrderController.php index 3e8eaef..d959c9a 100644 --- a/app/Http/Controllers/Admin/PlatformOrderController.php +++ b/app/Http/Controllers/Admin/PlatformOrderController.php @@ -1605,9 +1605,21 @@ class PlatformOrderController extends Controller // 注意:当前阶段仍由 Job 内逐条写 meta 与错误原因;后续可再升级为分片 Job + 结果聚合。 $orderIds = $orders->pluck('id')->map(fn ($id) => (int) $id)->values()->all(); + // run_id:用于把一次批量执行关联起来,便于后续追溯/筛选/可观测。 + // 说明:run_id 在投递阶段就生成,这样运营在页面成功提示里即可复制 run_id 并进入批次复盘页。 + $runId = 'BAS' . now()->format('YmdHis') . str_pad((string) random_int(1, 9999), 4, '0', STR_PAD_LEFT); + // 幂等/防抖(最小实现):避免运营短时间内重复点击导致重复投递同一批次。 // 说明:这里做“短 TTL 的一次性锁”,不引入新表;后续可演进为批次表 + 幂等 key。 // key 口径:scope + filters + ids + limit(同一集合的重复点击会被拦截)。 + $lockKey = \App\Support\BatchDispatchLock::makeKey( + 'admin:bas:dispatch', + $scope, + (string) $filterSummary, + $orderIds, + (int) $limit, + ); + if (! \App\Support\BatchDispatchLock::acquire( 'admin:bas:dispatch', $scope, @@ -1615,14 +1627,22 @@ class PlatformOrderController extends Controller $orderIds, (int) $limit, 60, - '1', + (string) $runId, )) { - return redirect()->back()->with('warning', '检测到刚刚已提交过同一批次的 BAS 任务(1 分钟内)。为避免重复投递,本次未再次提交。'); - } + $existing = (string) (\App\Support\BatchDispatchLock::getExistingValue($lockKey) ?? ''); + $existing = trim($existing); - // run_id:用于把一次批量执行关联起来,便于后续追溯/筛选/可观测。 - // 说明:run_id 在投递阶段就生成,这样运营在页面成功提示里即可复制 run_id 并进入批次复盘页。 - $runId = 'BAS' . now()->format('YmdHis') . str_pad((string) random_int(1, 9999), 4, '0', STR_PAD_LEFT); + $res = redirect()->back()->with('warning', '检测到刚刚已提交过同一批次的 BAS 任务(1 分钟内)。为避免重复投递,本次未再次提交。'); + + // 若锁内已有 run_id(作为 value 存储),则给运营一个直达复盘入口。 + if ($existing !== '' && str_starts_with($existing, 'BAS')) { + $res = $res + ->with('warning_link_href', '/admin/platform-batches/show?type=bas&run_id=' . urlencode($existing)) + ->with('warning_link_label', '进入上次批次复盘'); + } + + return $res; + } \App\Jobs\BatchActivateSubscriptionsJob::dispatch( $orderIds, @@ -1744,6 +1764,14 @@ class PlatformOrderController extends Controller // 幂等/防抖(最小实现):避免运营短时间内重复点击导致重复投递同一批次。 // 说明:这里做“短 TTL 的一次性锁”,不引入新表;后续可演进为批次表 + 幂等 key。 // key 口径:scope + filters + ids + limit(同一集合的重复点击会被拦截)。 + $lockKey = \App\Support\BatchDispatchLock::makeKey( + 'admin:bmpa:dispatch', + $scope, + (string) $filterSummary, + $orderIds, + (int) $limit, + ); + if (! \App\Support\BatchDispatchLock::acquire( 'admin:bmpa:dispatch', $scope, @@ -1753,7 +1781,18 @@ class PlatformOrderController extends Controller 60, (string) $runId, )) { - return redirect()->back()->with('warning', '检测到刚刚已提交过同一批次的 BMPA 任务(1 分钟内)。为避免重复投递,本次未再次提交。'); + $existing = (string) (\App\Support\BatchDispatchLock::getExistingValue($lockKey) ?? ''); + $existing = trim($existing); + + $res = redirect()->back()->with('warning', '检测到刚刚已提交过同一批次的 BMPA 任务(1 分钟内)。为避免重复投递,本次未再次提交。'); + + if ($existing !== '' && str_starts_with($existing, 'BMPA')) { + $res = $res + ->with('warning_link_href', '/admin/platform-batches/show?type=bmpa&run_id=' . urlencode($existing)) + ->with('warning_link_label', '进入上次批次复盘'); + } + + return $res; } \App\Jobs\BatchMarkPaidAndActivateJob::dispatch( diff --git a/resources/views/admin/layouts/app.blade.php b/resources/views/admin/layouts/app.blade.php index 57f3889..52be026 100644 --- a/resources/views/admin/layouts/app.blade.php +++ b/resources/views/admin/layouts/app.blade.php @@ -91,7 +91,18 @@ @endif @if(session('warning')) -
{{ session('warning') }}
+
+ {{ session('warning') }} + @if(session('warning_link_href')) + @php + $flashWarningLinkHref = \App\Support\BackUrl::sanitizeForLinks((string) session('warning_link_href')); + $flashWarningLinkLabel = (string) (session('warning_link_label') ?: '查看'); + @endphp + @if($flashWarningLinkHref !== '') + {{ $flashWarningLinkLabel }} + @endif + @endif +
@endif @if(session('error'))
{{ session('error') }}
diff --git a/tests/Feature/AdminFlashWarningShouldSupportOptionalLinkTest.php b/tests/Feature/AdminFlashWarningShouldSupportOptionalLinkTest.php new file mode 100644 index 0000000..e4e1cbe --- /dev/null +++ b/tests/Feature/AdminFlashWarningShouldSupportOptionalLinkTest.php @@ -0,0 +1,51 @@ +seed(); + + $this->post('/admin/login', [ + 'email' => 'platform.admin@demo.local', + 'password' => 'Platform@123456', + ])->assertRedirect('/admin'); + } + + public function test_flash_warning_should_render_optional_link_when_session_keys_present(): void + { + $this->loginAsPlatformAdmin(); + + $res = $this->withSession([ + 'warning' => '检测到重复提交', + 'warning_link_href' => '/admin/platform-batches/show?type=bmpa&run_id=BMPA202603171234560001', + 'warning_link_label' => '进入上次批次复盘', + ])->get('/admin'); + + $res->assertOk(); + $res->assertSee('检测到重复提交'); + $res->assertSee('进入上次批次复盘'); + $res->assertSee('/admin/platform-batches/show?type=bmpa&run_id=BMPA202603171234560001', false); + } + + public function test_flash_warning_link_should_be_sanitized_to_relative_path(): void + { + $this->loginAsPlatformAdmin(); + + $res = $this->withSession([ + 'warning' => 'warn', + 'warning_link_href' => 'https://evil.example.com/x', + 'warning_link_label' => '查看', + ])->get('/admin'); + + $res->assertOk(); + $res->assertDontSee('https://evil.example.com/x'); + } +}