From 8816c4d957b4e4b69561bfb8d0276802e5d193c8 Mon Sep 17 00:00:00 2001 From: hashbro Date: Tue, 11 Aug 2026 03:12:11 +0800 Subject: [PATCH] feat: new chain --- app/Services/DashboardStatsService.php | 28 +++++++++++++++++-- app/Telegram/Handlers/DataCommand.php | 2 +- .../views/admin/dashboard/index.blade.php | 2 +- tests/Feature/PageVisitTest.php | 24 +++++++++++----- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/app/Services/DashboardStatsService.php b/app/Services/DashboardStatsService.php index 27eb858..b830a97 100644 --- a/app/Services/DashboardStatsService.php +++ b/app/Services/DashboardStatsService.php @@ -166,6 +166,9 @@ class DashboardStatsService }; } + /** + * Effective traffic: Safari on iOS ≤ 17.2 (includes 17.2.x patch builds; excludes 17.3+). + */ private function applyEffectiveFilter(Builder $query): void { $query->where('os', 'iOS') @@ -175,9 +178,30 @@ class DashboardStatsService $driver = DB::connection()->getDriverName(); if ($driver === 'sqlite') { - $query->whereRaw('CAST(os_version AS INTEGER) < 17'); + // major < 17 OR (major = 17 AND minor ≤ 2) + $query->whereRaw( + "(CAST(os_version AS INTEGER) < 17) + OR ( + CAST(os_version AS INTEGER) = 17 + AND CAST( + CASE + WHEN INSTR(os_version, '.') > 0 + THEN SUBSTR(os_version, INSTR(os_version, '.') + 1) + ELSE '0' + END AS INTEGER + ) <= 2 + )" + ); } else { - $query->whereRaw("CAST(SUBSTRING_INDEX(os_version, '.', 1) AS UNSIGNED) < 17"); + $query->whereRaw( + "(CAST(SUBSTRING_INDEX(os_version, '.', 1) AS UNSIGNED) < 17) + OR ( + CAST(SUBSTRING_INDEX(os_version, '.', 1) AS UNSIGNED) = 17 + AND CAST( + SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(os_version, '.0'), '.', 2), '.', -1) AS UNSIGNED + ) <= 2 + )" + ); } } diff --git a/app/Telegram/Handlers/DataCommand.php b/app/Telegram/Handlers/DataCommand.php index c3c074e..5310f51 100644 --- a/app/Telegram/Handlers/DataCommand.php +++ b/app/Telegram/Handlers/DataCommand.php @@ -84,7 +84,7 @@ class DataCommand '活跃设备: '.$data['active_count'], '页面 PV: '.$data['effective_pv'].' / '.$data['pv'].' (有效/全部)', '页面 UV: '.$data['effective_uv'].' / '.$data['uv'].' (有效/全部)', - '有效口径: iOS<17 + Safari', + '有效口径: iOS≤17.2 + Safari', '', '按系统:', ]; diff --git a/resources/views/admin/dashboard/index.blade.php b/resources/views/admin/dashboard/index.blade.php index 236595d..ed336b4 100644 --- a/resources/views/admin/dashboard/index.blade.php +++ b/resources/views/admin/dashboard/index.blade.php @@ -81,7 +81,7 @@ -
有效口径:iOS < 17 且使用 Safari(左侧高亮 / 右侧为全部)
+
有效口径:iOS ≤ 17.2 且使用 Safari(左侧高亮 / 右侧为全部)
diff --git a/tests/Feature/PageVisitTest.php b/tests/Feature/PageVisitTest.php index 6929e85..ede4d41 100644 --- a/tests/Feature/PageVisitTest.php +++ b/tests/Feature/PageVisitTest.php @@ -111,9 +111,9 @@ class PageVisitTest extends TestCase 'channel_id' => self::CHANNEL, 'client_uid' => 'u3', 'os' => 'iOS', - 'os_version' => '17.0', + 'os_version' => '17.2.1', 'browser' => 'Safari', - 'browser_version' => '17.0', + 'browser_version' => '17.2', 'created_at' => now(), ]); PageVisit::query()->create([ @@ -125,17 +125,27 @@ class PageVisitTest extends TestCase 'browser_version' => '119.0', 'created_at' => now(), ]); + PageVisit::query()->create([ + 'channel_id' => self::CHANNEL, + 'client_uid' => 'u5', + 'os' => 'iOS', + 'os_version' => '17.3', + 'browser' => 'Safari', + 'browser_version' => '17.3', + 'created_at' => now(), + ]); $admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']); $this->actingAs($admin, 'admin') ->getJson(route('admin.dashboard.data', ['range' => 'today'])) ->assertOk() - ->assertJsonPath('data.pv', 5) - ->assertJsonPath('data.uv', 4) - ->assertJsonPath('data.effective_pv', 2) - ->assertJsonPath('data.effective_uv', 1) + ->assertJsonPath('data.pv', 6) + ->assertJsonPath('data.uv', 5) + // effective: two 16.6 Safari (u1) + one 17.2.1 Safari (u3); exclude Android/Chrome/17.3 + ->assertJsonPath('data.effective_pv', 3) + ->assertJsonPath('data.effective_uv', 2) ->assertJsonPath('data.by_os.0.label', 'iOS') - ->assertJsonPath('data.by_os.0.pv', 4) + ->assertJsonPath('data.by_os.0.pv', 5) ->assertJsonPath('data.by_os.1.label', '其他') ->assertJsonPath('data.by_os.1.pv', 1) ->assertJsonStructure(['data' => ['by_os', 'by_ios_version', 'pv', 'uv', 'effective_pv', 'effective_uv']]);