From 1f832477c0faa996f1979d582e192165f602840d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=9D=E5=8D=9C?= Date: Fri, 13 Mar 2026 22:40:56 +0000 Subject: [PATCH] feat: platform orders export require download=1 safety valve --- .../Admin/PlatformOrderController.php | 5 +++ .../admin/platform_orders/index.blade.php | 1 + ...dminPlatformOrderExportBmpaFiltersTest.php | 4 +-- ...formOrderExportDownloadSafetyValveTest.php | 33 +++++++++++++++++++ ...formOrderExportReceiptStatusFilterTest.php | 4 +-- ...OrderExportReconcileMismatchFilterTest.php | 2 +- ...rderExportRefundInconsistentFilterTest.php | 2 +- ...tRefundInconsistentToleranceConfigTest.php | 2 +- ...formOrderExportRefundReceiptFieldsTest.php | 2 +- ...tformOrderExportRefundStatusFilterTest.php | 4 +-- .../Feature/AdminPlatformOrderExportTest.php | 8 ++--- 11 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 tests/Feature/AdminPlatformOrderExportDownloadSafetyValveTest.php diff --git a/app/Http/Controllers/Admin/PlatformOrderController.php b/app/Http/Controllers/Admin/PlatformOrderController.php index d49b326..8ed604d 100644 --- a/app/Http/Controllers/Admin/PlatformOrderController.php +++ b/app/Http/Controllers/Admin/PlatformOrderController.php @@ -977,6 +977,11 @@ class PlatformOrderController extends Controller { $this->ensurePlatformAdmin($request); + // 安全阀:必须显式声明 download=1,避免浏览器预取/误触发导致频繁导出 + if ((string) $request->query('download', '') !== '1') { + abort(400, 'download=1 required'); + } + $filters = [ 'status' => trim((string) $request->query('status', '')), 'payment_status' => trim((string) $request->query('payment_status', '')), diff --git a/resources/views/admin/platform_orders/index.blade.php b/resources/views/admin/platform_orders/index.blade.php index f07a46b..ff04157 100644 --- a/resources/views/admin/platform_orders/index.blade.php +++ b/resources/views/admin/platform_orders/index.blade.php @@ -440,6 +440,7 @@ @endif
+ diff --git a/tests/Feature/AdminPlatformOrderExportBmpaFiltersTest.php b/tests/Feature/AdminPlatformOrderExportBmpaFiltersTest.php index 2a20c5f..e48060e 100644 --- a/tests/Feature/AdminPlatformOrderExportBmpaFiltersTest.php +++ b/tests/Feature/AdminPlatformOrderExportBmpaFiltersTest.php @@ -77,7 +77,7 @@ class AdminPlatformOrderExportBmpaFiltersTest extends TestCase 'placed_at' => now(), ]); - $res = $this->get('/admin/platform-orders/export?bmpa_failed_only=1'); + $res = $this->get('/admin/platform-orders/export?download=1&bmpa_failed_only=1'); $res->assertOk(); $content = $res->streamedContent(); @@ -147,7 +147,7 @@ class AdminPlatformOrderExportBmpaFiltersTest extends TestCase ], ]); - $res = $this->get('/admin/platform-orders/export?bmpa_error_keyword=' . urlencode('回执')); + $res = $this->get('/admin/platform-orders/export?download=1&bmpa_error_keyword=' . urlencode('回执')); $res->assertOk(); $content = $res->streamedContent(); diff --git a/tests/Feature/AdminPlatformOrderExportDownloadSafetyValveTest.php b/tests/Feature/AdminPlatformOrderExportDownloadSafetyValveTest.php new file mode 100644 index 0000000..565b221 --- /dev/null +++ b/tests/Feature/AdminPlatformOrderExportDownloadSafetyValveTest.php @@ -0,0 +1,33 @@ +seed(); + + $this->post('/admin/login', [ + 'email' => 'platform.admin@demo.local', + 'password' => 'Platform@123456', + ])->assertRedirect('/admin'); + } + + public function test_export_should_require_download_flag(): void + { + $this->loginAsPlatformAdmin(); + + $this->get('/admin/platform-orders/export') + ->assertStatus(400) + ->assertSee('download=1 required'); + + $this->get('/admin/platform-orders/export?download=1') + ->assertOk(); + } +} diff --git a/tests/Feature/AdminPlatformOrderExportReceiptStatusFilterTest.php b/tests/Feature/AdminPlatformOrderExportReceiptStatusFilterTest.php index 31038ea..ebc8789 100644 --- a/tests/Feature/AdminPlatformOrderExportReceiptStatusFilterTest.php +++ b/tests/Feature/AdminPlatformOrderExportReceiptStatusFilterTest.php @@ -107,14 +107,14 @@ class AdminPlatformOrderExportReceiptStatusFilterTest extends TestCase 'meta' => [], ]); - $res1 = $this->get('/admin/platform-orders/export?receipt_status=has'); + $res1 = $this->get('/admin/platform-orders/export?download=1&receipt_status=has'); $res1->assertOk(); $content1 = $res1->streamedContent(); $this->assertStringContainsString('PO_EXPORT_RECEIPT_HAS_0001', $content1); $this->assertStringContainsString('PO_EXPORT_RECEIPT_HAS_0002', $content1); $this->assertStringNotContainsString('PO_EXPORT_RECEIPT_NONE_0003', $content1); - $res2 = $this->get('/admin/platform-orders/export?receipt_status=none'); + $res2 = $this->get('/admin/platform-orders/export?download=1&receipt_status=none'); $res2->assertOk(); $content2 = $res2->streamedContent(); $this->assertStringContainsString('PO_EXPORT_RECEIPT_NONE_0003', $content2); diff --git a/tests/Feature/AdminPlatformOrderExportReconcileMismatchFilterTest.php b/tests/Feature/AdminPlatformOrderExportReconcileMismatchFilterTest.php index 5826b08..3b0f5bf 100644 --- a/tests/Feature/AdminPlatformOrderExportReconcileMismatchFilterTest.php +++ b/tests/Feature/AdminPlatformOrderExportReconcileMismatchFilterTest.php @@ -88,7 +88,7 @@ class AdminPlatformOrderExportReconcileMismatchFilterTest extends TestCase ], ]); - $res = $this->get('/admin/platform-orders/export?reconcile_mismatch=1'); + $res = $this->get('/admin/platform-orders/export?download=1&reconcile_mismatch=1'); $res->assertOk(); $content = $res->streamedContent(); diff --git a/tests/Feature/AdminPlatformOrderExportRefundInconsistentFilterTest.php b/tests/Feature/AdminPlatformOrderExportRefundInconsistentFilterTest.php index 4bf969f..578a409 100644 --- a/tests/Feature/AdminPlatformOrderExportRefundInconsistentFilterTest.php +++ b/tests/Feature/AdminPlatformOrderExportRefundInconsistentFilterTest.php @@ -115,7 +115,7 @@ class AdminPlatformOrderExportRefundInconsistentFilterTest extends TestCase ], ]); - $res = $this->get('/admin/platform-orders/export?refund_inconsistent=1'); + $res = $this->get('/admin/platform-orders/export?download=1&refund_inconsistent=1'); $res->assertOk(); $content = $res->streamedContent(); diff --git a/tests/Feature/AdminPlatformOrderExportRefundInconsistentToleranceConfigTest.php b/tests/Feature/AdminPlatformOrderExportRefundInconsistentToleranceConfigTest.php index 41d6e5d..d100dee 100644 --- a/tests/Feature/AdminPlatformOrderExportRefundInconsistentToleranceConfigTest.php +++ b/tests/Feature/AdminPlatformOrderExportRefundInconsistentToleranceConfigTest.php @@ -142,7 +142,7 @@ class AdminPlatformOrderExportRefundInconsistentToleranceConfigTest extends Test ], ]); - $res = $this->get('/admin/platform-orders/export?refund_inconsistent=1'); + $res = $this->get('/admin/platform-orders/export?download=1&refund_inconsistent=1'); $res->assertOk(); $content = $res->streamedContent(); diff --git a/tests/Feature/AdminPlatformOrderExportRefundReceiptFieldsTest.php b/tests/Feature/AdminPlatformOrderExportRefundReceiptFieldsTest.php index d919c2f..b66492b 100644 --- a/tests/Feature/AdminPlatformOrderExportRefundReceiptFieldsTest.php +++ b/tests/Feature/AdminPlatformOrderExportRefundReceiptFieldsTest.php @@ -78,7 +78,7 @@ class AdminPlatformOrderExportRefundReceiptFieldsTest extends TestCase ], ]); - $res = $this->get('/admin/platform-orders/export'); + $res = $this->get('/admin/platform-orders/export?download=1'); $res->assertOk(); $content = $res->streamedContent(); diff --git a/tests/Feature/AdminPlatformOrderExportRefundStatusFilterTest.php b/tests/Feature/AdminPlatformOrderExportRefundStatusFilterTest.php index cb517fe..a06c958 100644 --- a/tests/Feature/AdminPlatformOrderExportRefundStatusFilterTest.php +++ b/tests/Feature/AdminPlatformOrderExportRefundStatusFilterTest.php @@ -107,14 +107,14 @@ class AdminPlatformOrderExportRefundStatusFilterTest extends TestCase 'meta' => [], ]); - $res1 = $this->get('/admin/platform-orders/export?refund_status=has'); + $res1 = $this->get('/admin/platform-orders/export?download=1&refund_status=has'); $res1->assertOk(); $content1 = $res1->streamedContent(); $this->assertStringContainsString('PO_EXPORT_REFUND_HAS_0001', $content1); $this->assertStringContainsString('PO_EXPORT_REFUND_HAS_0002', $content1); $this->assertStringNotContainsString('PO_EXPORT_REFUND_NONE_0003', $content1); - $res2 = $this->get('/admin/platform-orders/export?refund_status=none'); + $res2 = $this->get('/admin/platform-orders/export?download=1&refund_status=none'); $res2->assertOk(); $content2 = $res2->streamedContent(); $this->assertStringContainsString('PO_EXPORT_REFUND_NONE_0003', $content2); diff --git a/tests/Feature/AdminPlatformOrderExportTest.php b/tests/Feature/AdminPlatformOrderExportTest.php index 7f8c436..96d0cc9 100644 --- a/tests/Feature/AdminPlatformOrderExportTest.php +++ b/tests/Feature/AdminPlatformOrderExportTest.php @@ -112,7 +112,7 @@ class AdminPlatformOrderExportTest extends TestCase ], ]); - $res = $this->get('/admin/platform-orders/export'); + $res = $this->get('/admin/platform-orders/export?download=1'); $res->assertOk(); $res->assertHeader('content-type', 'text/csv; charset=UTF-8'); @@ -131,14 +131,14 @@ class AdminPlatformOrderExportTest extends TestCase $this->assertStringContainsString('退款总额', $content); // include_meta=1 时应包含 meta(JSON) 列 - $res2 = $this->get('/admin/platform-orders/export?include_meta=1'); + $res2 = $this->get('/admin/platform-orders/export?download=1&include_meta=1'); $res2->assertOk(); $content2 = $res2->streamedContent(); $this->assertStringContainsString('原始meta(JSON)', $content2); $this->assertStringContainsString('subscription_activation_error', $content2); // batch_synced_24h=1 导出应只包含 24h 内批量同步过的订单 - $res3 = $this->get('/admin/platform-orders/export?batch_synced_24h=1'); + $res3 = $this->get('/admin/platform-orders/export?download=1&batch_synced_24h=1'); $res3->assertOk(); $content3 = $res3->streamedContent(); $this->assertStringContainsString('PO_EXPORT_BATCH_RECENT', $content3); @@ -198,7 +198,7 @@ class AdminPlatformOrderExportTest extends TestCase 'activated_at' => now(), ]); - $res4 = $this->get('/admin/platform-orders/export?site_subscription_id=' . $sub->id); + $res4 = $this->get('/admin/platform-orders/export?download=1&site_subscription_id=' . $sub->id); $res4->assertOk(); $content4 = $res4->streamedContent(); $this->assertStringContainsString('PO_EXPORT_SUB_FILTER_0001', $content4);