From 8bcd5298fcd7ab2a729bf39b3c28a990012081bd Mon Sep 17 00:00:00 2001 From: WuYongJie <549727955@qq.com> Date: Tue, 4 Aug 2026 15:37:59 +0800 Subject: [PATCH] fix: refresh operations replica snapshots --- app/Business/UpstreamBusinessInformation.php | 95 +++++++++++++----- app/Http/Controllers/Business/Pages.php | 23 ++++- modules/EcommerceFinance/Providers/Main.php | 2 + .../OperationsReplicaSnapshotRefresher.php | 37 +++++++ resources/views/business/pages/show.blade.php | 5 + ...OperationsReplicaSnapshotRefresherTest.php | 98 +++++++++++++++++++ 6 files changed, 231 insertions(+), 29 deletions(-) create mode 100644 modules/EcommerceFinance/Services/OperationsReplicaSnapshotRefresher.php create mode 100644 tests/Unit/EcommerceFinance/OperationsReplicaSnapshotRefresherTest.php diff --git a/app/Business/UpstreamBusinessInformation.php b/app/Business/UpstreamBusinessInformation.php index 2eefb48a..86a70b1c 100644 --- a/app/Business/UpstreamBusinessInformation.php +++ b/app/Business/UpstreamBusinessInformation.php @@ -3,6 +3,7 @@ namespace App\Business; use Illuminate\Pagination\LengthAwarePaginator; +use InvalidArgumentException; use Modules\EcommerceFinance\Services\UpstreamQueryCacheFactory; use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery; @@ -39,11 +40,42 @@ class UpstreamBusinessInformation ); } + /** + * 将已登记的经营分析副本库查询还原成不经过页面缓存的固定只读查询。 + * + * 生产后台刷新器使用这个入口生成共享快照;页面仍然只能通过 + * UpstreamQueryCache 读取快照,不能绕过缓存直接访问上游。 + * + * @return array{data: array>, total: int} + */ + public function snapshot(string $scope, array $parameters): array + { + $page = max((int) ($parameters['page'] ?? 1), 1); + $perPage = min(max((int) ($parameters['perPage'] ?? 50), 1), 100); + $query = new UpstreamReadOnlyQuery(self::SOURCE); + + return match ($scope) { + 'company-information.read' => $this->companySnapshot($query, $page, $perPage), + 'store-information.read' => $this->storeSnapshot($query, $page, $perPage), + 'trademark-information.read' => $this->trademarkSnapshot($query, $page, $perPage), + default => throw new InvalidArgumentException('未知的经营分析副本库快照类型:' . $scope), + }; + } + private function companyPage(int $page, int $perPage): array { - return $this->remember('company-information.read', $page, $perPage, function (UpstreamReadOnlyQuery $query) use ($page, $perPage) { - $offset = ($page - 1) * $perPage; - $rows = $query->select(<<remember( + 'company-information.read', + $page, + $perPage, + fn () => $this->snapshot('company-information.read', compact('page', 'perPage')), + ); + } + + private function companySnapshot(UpstreamReadOnlyQuery $query, int $page, int $perPage): array + { + $offset = ($page - 1) * $perPage; + $rows = $query->select(<< array_map(fn (array $row) => $this->mapCompany($row), $rows), - 'total' => (int) $query->scalar('SELECT COUNT(*) FROM rpt.fys_companyInfo'), - ]; - }); + return [ + 'data' => array_map(fn (array $row) => $this->mapCompany($row), $rows), + 'total' => (int) $query->scalar('SELECT COUNT(*) FROM rpt.fys_companyInfo'), + ]; } private function storePage(int $page, int $perPage): array { - return $this->remember('store-information.read', $page, $perPage, function (UpstreamReadOnlyQuery $query) use ($page, $perPage) { - $offset = ($page - 1) * $perPage; - $rows = $query->select(<<remember( + 'store-information.read', + $page, + $perPage, + fn () => $this->snapshot('store-information.read', compact('page', 'perPage')), + ); + } + + private function storeSnapshot(UpstreamReadOnlyQuery $query, int $page, int $perPage): array + { + $offset = ($page - 1) * $perPage; + $rows = $query->select(<< array_map(fn (array $row) => $this->mapStore($row), $rows), - 'total' => (int) $query->scalar('SELECT COUNT(*) FROM rpt.fys_shopInfo'), - ]; - }); + return [ + 'data' => array_map(fn (array $row) => $this->mapStore($row), $rows), + 'total' => (int) $query->scalar('SELECT COUNT(*) FROM rpt.fys_shopInfo'), + ]; } private function trademarkPage(int $page, int $perPage): array { - return $this->remember('trademark-information.read', $page, $perPage, function (UpstreamReadOnlyQuery $query) use ($page, $perPage) { - $offset = ($page - 1) * $perPage; - $rows = $query->select(<<remember( + 'trademark-information.read', + $page, + $perPage, + fn () => $this->snapshot('trademark-information.read', compact('page', 'perPage')), + ); + } + + private function trademarkSnapshot(UpstreamReadOnlyQuery $query, int $page, int $perPage): array + { + $offset = ($page - 1) * $perPage; + $rows = $query->select(<< array_map(fn (array $row) => $this->mapTrademark($row), $rows), - 'total' => (int) $query->scalar('SELECT COUNT(*) FROM rpt.fys_TrademarkInfo WHERE isDelete = 0'), - ]; - }); + return [ + 'data' => array_map(fn (array $row) => $this->mapTrademark($row), $rows), + 'total' => (int) $query->scalar('SELECT COUNT(*) FROM rpt.fys_TrademarkInfo WHERE isDelete = 0'), + ]; } private function remember(string $scope, int $page, int $perPage, callable $resolver): array diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 6a8b1e1d..b21688b2 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -145,6 +145,7 @@ class Pages extends Controller $rows = collect(); $tabValue = null; $tabColumn = null; + $upstreamError = null; if ($page && $content === null) { $descriptor = TableRegistry::describe( @@ -155,11 +156,21 @@ class Pages extends Controller if ($descriptor) { if ($descriptor->readOnly) { - $rows = app(UpstreamBusinessInformation::class)->paginate( - $section['slug'], - $page['slug'], - 50, - ); + try { + $rows = app(UpstreamBusinessInformation::class)->paginate( + $section['slug'], + $page['slug'], + 50, + ); + } catch (ServiceUnavailableHttpException $exception) { + // 首份快照尚未生成或某个来源暂时不可用时,保留页面、 + // 交代原因并允许后台刷新,而不是把整个业务页变成通用 503。 + $rows = new LengthAwarePaginator([], 0, 50, 1, [ + 'path' => request()->url(), + 'query' => request()->query(), + ]); + $upstreamError = $exception->getMessage(); + } return [ 'section' => $section, @@ -169,6 +180,7 @@ class Pages extends Controller 'content' => $content, 'descriptor' => $descriptor, 'rows' => $rows, + 'upstreamError' => $upstreamError, 'tabColumn' => null, 'tabValue' => null, 'edgeBanding' => $this->edgeBandingContent($page['slug']), @@ -205,6 +217,7 @@ class Pages extends Controller 'content' => $content, 'descriptor' => $descriptor, 'rows' => $rows, + 'upstreamError' => $upstreamError, 'tabColumn' => $tabColumn, 'tabValue' => $tabValue, 'edgeBanding' => $page ? $this->edgeBandingContent($page['slug']) : null, diff --git a/modules/EcommerceFinance/Providers/Main.php b/modules/EcommerceFinance/Providers/Main.php index 7009f87d..d3f51ced 100644 --- a/modules/EcommerceFinance/Providers/Main.php +++ b/modules/EcommerceFinance/Providers/Main.php @@ -4,6 +4,7 @@ namespace Modules\EcommerceFinance\Providers; use Illuminate\Support\ServiceProvider as Provider; use Modules\EcommerceFinance\Services\JianSunSnapshotRefresher; +use Modules\EcommerceFinance\Services\OperationsReplicaSnapshotRefresher; use Modules\EcommerceFinance\Services\UpstreamSnapshotRefresherRegistry; use Modules\EcommerceFinance\Services\UpstreamSourceRegistry; @@ -22,6 +23,7 @@ class Main extends Provider $app->make(UpstreamSourceRegistry::class) ); $registry->register('jiansun', fn () => $app->make(JianSunSnapshotRefresher::class)); + $registry->register('operations_replica', fn () => $app->make(OperationsReplicaSnapshotRefresher::class)); return $registry; }); diff --git a/modules/EcommerceFinance/Services/OperationsReplicaSnapshotRefresher.php b/modules/EcommerceFinance/Services/OperationsReplicaSnapshotRefresher.php new file mode 100644 index 00000000..69bf8cb2 --- /dev/null +++ b/modules/EcommerceFinance/Services/OperationsReplicaSnapshotRefresher.php @@ -0,0 +1,37 @@ +caches->forSource(self::SOURCE)->refresh( + $scope, + $parameters, + fn () => $this->information->snapshot($scope, $parameters), + ); + } +} diff --git a/resources/views/business/pages/show.blade.php b/resources/views/business/pages/show.blade.php index ef40d2e6..302737ca 100644 --- a/resources/views/business/pages/show.blade.php +++ b/resources/views/business/pages/show.blade.php @@ -401,6 +401,11 @@ @if ($errors->has('import')) @endif + + @if (!empty($upstreamError)) + + @endif + @if ($tabs->isNotEmpty())