From 916fb30f37dddc7673e7bef0dd284b6fac56aa29 Mon Sep 17 00:00:00 2001 From: renshen <1005457301@qq.com> Date: Wed, 7 Oct 2026 14:05:50 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E7=AD=9B=E9=80=89=EF=BC=9A=E7=BA=AF=E6=95=B0=E5=AD=97=E5=8D=95?= =?UTF-8?q?=E5=8F=B7=E5=BD=93=E6=95=B0=E7=BB=84=E9=94=AE=E4=BC=9A=E8=A2=AB?= =?UTF-8?q?=20PHP=20=E8=BD=AC=E6=88=90=20int?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit batchValues() 原来拿值本身当数组键去重,纯数字订单号(如 3317080368285022883)会被 PHP 转成 int,后续 whereIn 就成了数字和长文本比较,走不上 so_id / outer_oi_id 的 前缀索引,实测退化成全表扫描(70 秒以上)。 改成键加 v 前缀、返回原字符串,ctype_digit 显式传字符串。新增用例覆盖前导零单号 0001234 与 1234,防止再回归。 --- .../Repositories/OrderDataMirrorRepository.php | 10 +++++++--- .../Repositories/OrderDataRepository.php | 7 +++++-- .../OrderDataMirrorRepositoryTest.php | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/modules/EcommerceFinance/Repositories/OrderDataMirrorRepository.php b/modules/EcommerceFinance/Repositories/OrderDataMirrorRepository.php index ae7f20ac..3bbca0dd 100644 --- a/modules/EcommerceFinance/Repositories/OrderDataMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/OrderDataMirrorRepository.php @@ -377,7 +377,7 @@ class OrderDataMirrorRepository $prefix = $single !== null ? $this->likePrefix($single) : null; $digits = []; foreach ($needles as $needle) { - if (ctype_digit($needle)) { + if (ctype_digit((string) $needle)) { $digits[] = (int) $needle; } } @@ -773,12 +773,16 @@ class OrderDataMirrorRepository continue; } - $values[$part] = true; + // 键加 'v' 前缀:纯数字单号直接当数组键会被 PHP 转成 int, + // 后面按数值去比对就用不上 so_id / outer_oi_id 的前缀索引了。 + if (! isset($values['v' . $part])) { + $values['v' . $part] = $part; + } if (count($values) >= self::BATCH_FILTER_LIMIT) { break; } } - return array_keys($values); + return array_values($values); } } diff --git a/modules/EcommerceFinance/Repositories/OrderDataRepository.php b/modules/EcommerceFinance/Repositories/OrderDataRepository.php index 7a9b4747..d0de3447 100644 --- a/modules/EcommerceFinance/Repositories/OrderDataRepository.php +++ b/modules/EcommerceFinance/Repositories/OrderDataRepository.php @@ -569,12 +569,15 @@ class OrderDataRepository continue; } - $values[$part] = true; + // 键加 'v' 前缀:纯数字单号当数组键会被 PHP 转成 int,会丢掉原字符串。 + if (! isset($values['v' . $part])) { + $values['v' . $part] = $part; + } if (count($values) >= 50) { break; } } - return array_keys($values); + return array_values($values); } } diff --git a/tests/Unit/EcommerceFinance/OrderDataMirrorRepositoryTest.php b/tests/Unit/EcommerceFinance/OrderDataMirrorRepositoryTest.php index e4c77006..33b06697 100644 --- a/tests/Unit/EcommerceFinance/OrderDataMirrorRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/OrderDataMirrorRepositoryTest.php @@ -232,6 +232,20 @@ class OrderDataMirrorRepositoryTest extends TestCase $this->assertSame(['SO-2026-0001', 'SO-2026-0003'], $this->sortedOnlineOrders($skus['rows'])); } + public function test_batch_order_numbers_keep_their_exact_text_form(): void + { + $db = DB::connection('business_mirror'); + $this->insertOrder($db, 1, '0001234', '拼多多-甲店'); + $this->insertOrder($db, 2, '1234', '拼多多-乙店'); + + $result = $this->repository($db)->afterSales([ + 'from' => '', 'to' => '', 'order_no' => "0001234\n9999999", 'shop_name' => '', 'sku_id' => '', + ], 1, 25); + + // 纯数字单号不能被 PHP 当数组键转成 int,否则前导零的单号会串到别的订单。 + $this->assertSame(['0001234'], $this->sortedOnlineOrders($result['rows'])); + } + public function test_promotion_expenses_filters_by_multiple_shops(): void { $db = DB::connection('business_mirror'); From a9da8d4447fe45ee1cb91593efb91bcbe4855d2a Mon Sep 17 00:00:00 2001 From: WuYongJie <549727955@qq.com> Date: Wed, 7 Oct 2026 14:08:20 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8F=91=E8=B4=A7?= =?UTF-8?q?=E9=95=9C=E5=83=8F=E5=AD=97=E6=AE=B5=E6=A0=A1=E5=87=86=E4=B8=8E?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=88=97=E8=A1=A8=E8=80=97=E6=97=B6=20(#283)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0000_reconcile_shipping_merged_columns.php | 23 ++++++++++++++ .../Console/SyncJushuitanShipping.php | 4 ++- .../Repositories/ShippingOrderRepository.php | 15 +++++++-- .../ShippingMergedColumnsMigrationTest.php | 31 +++++++++++++++++++ .../ShippingReplicaSynchronizerTest.php | 2 +- 5 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2026_10_07_150000_reconcile_shipping_merged_columns.php create mode 100644 tests/Unit/EcommerceFinance/ShippingMergedColumnsMigrationTest.php diff --git a/database/migrations/2026_10_07_150000_reconcile_shipping_merged_columns.php b/database/migrations/2026_10_07_150000_reconcile_shipping_merged_columns.php new file mode 100644 index 00000000..78a0fa28 --- /dev/null +++ b/database/migrations/2026_10_07_150000_reconcile_shipping_merged_columns.php @@ -0,0 +1,23 @@ +hasTable('jst_oms_shipping')) { + throw new RuntimeException('发货镜像表不存在,不能将合并字段迁移标为完成。'); + } + $migration = require base_path('modules/EcommerceFinance/Database/Migrations/2026_10_05_000001_add_merged_cols_to_jst_oms_shipping.php'); + $migration->up(); + } + + public function down(): void + { + // 字段属于原迁移;校准回滚保留原迁移及镜像数据。 + } +}; diff --git a/modules/EcommerceFinance/Console/SyncJushuitanShipping.php b/modules/EcommerceFinance/Console/SyncJushuitanShipping.php index a27f74ea..b14b5e00 100644 --- a/modules/EcommerceFinance/Console/SyncJushuitanShipping.php +++ b/modules/EcommerceFinance/Console/SyncJushuitanShipping.php @@ -61,7 +61,9 @@ class SyncJushuitanShipping extends Command $expressRows = (int) ($result['processed'] ?? 0); $autoRows = (int) ($result['autoship'] ?? 0); - $summary = sprintf('发货明细 %d 条,代发仓 %d 条,合计处理 %d 条明细', $expressRows, $autoRows, $expressRows + $autoRows); + $outboundRows = (int) ($result['outbound'] ?? 0); + $purchaseOutRows = (int) ($result['purchase_out'] ?? 0); + $summary = sprintf('发货明细 %d 条,代发仓 %d 条,销售出库补漏 %d 条,采购出库补漏 %d 条,合计处理 %d 条明细', $expressRows, $autoRows, $outboundRows, $purchaseOutRows, $expressRows + $autoRows + $outboundRows + $purchaseOutRows); $packageSummary = isset($result['unique_packages']) ? sprintf('唯一包裹源键 %d 个,包裹记录处理 %d 次;', $result['unique_packages'], $result['packages'] ?? 0) : ''; diff --git a/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php b/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php index 91915fb3..c3bdc35a 100644 --- a/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php +++ b/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php @@ -49,12 +49,18 @@ class ShippingOrderRepository // 合并:按包裹(source_id)归并 $base = $this->filteredQuery($filters); $total = (clone $base)->distinct()->count('source_id'); + $pageKeys = (clone $base)->select('source_id')->selectRaw('MAX(ship_time) as ship_time') + ->groupBy('source_id')->orderByDesc('ship_time')->orderByDesc('source_id') + ->forPage($page, $perPage)->pluck('source_id')->all(); + if ($pageKeys === []) { + return ['data' => [], 'total' => $total]; + } $rows = (clone $base) + ->whereIn('source_id', $pageKeys) ->groupBy('source_id') ->selectRaw($this->mergedSelectRaw()) ->orderByDesc('ship_time') ->orderByDesc('source_id') - ->forPage($page, $perPage) ->get()->map(fn ($r) => (array) $r)->all(); return ['data' => $rows, 'total' => $total]; @@ -157,7 +163,12 @@ class ShippingOrderRepository if ($last !== null) { $query->where('source_id', '>', $last); } - $rows = $query->groupBy('source_id')->selectRaw($this->mergedSelectRaw()) + $keys = (clone $query)->select('source_id')->distinct() + ->orderBy('source_id')->limit($batchSize)->pluck('source_id')->all(); + if ($keys === []) { + return; + } + $rows = $query->whereIn('source_id', $keys)->groupBy('source_id')->selectRaw($this->mergedSelectRaw()) ->orderBy('source_id')->limit($batchSize)->get()->all(); if ($rows === []) { return; diff --git a/tests/Unit/EcommerceFinance/ShippingMergedColumnsMigrationTest.php b/tests/Unit/EcommerceFinance/ShippingMergedColumnsMigrationTest.php new file mode 100644 index 00000000..69dc805f --- /dev/null +++ b/tests/Unit/EcommerceFinance/ShippingMergedColumnsMigrationTest.php @@ -0,0 +1,31 @@ +getSchemaBuilder(); + $columns = ['m_name', 'm_volume_cm3', 'm_length_cm', 'm_width_cm', 'm_height_cm']; + $schema->table('jst_oms_shipping', fn ($table) => $table->dropColumn($columns)); + $migration = require base_path('database/migrations/2026_10_07_150000_reconcile_shipping_merged_columns.php'); + $migration->up(); + $migration->up(); + foreach ($columns as $column) { + $this->assertTrue($schema->hasColumn('jst_oms_shipping', $column)); + } + } + + public function test_missing_target_is_reported_instead_of_marked_complete(): void + { + DB::connection('business_mirror')->getSchemaBuilder()->drop('jst_oms_shipping'); + $migration = require base_path('database/migrations/2026_10_07_150000_reconcile_shipping_merged_columns.php'); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('发货镜像表不存在'); + $migration->up(); + } +} diff --git a/tests/Unit/EcommerceFinance/ShippingReplicaSynchronizerTest.php b/tests/Unit/EcommerceFinance/ShippingReplicaSynchronizerTest.php index ab2b853f..aada17b3 100644 --- a/tests/Unit/EcommerceFinance/ShippingReplicaSynchronizerTest.php +++ b/tests/Unit/EcommerceFinance/ShippingReplicaSynchronizerTest.php @@ -844,7 +844,7 @@ class ShippingReplicaSynchronizerTest extends TestCase $this->artisan('jushuitan:sync-shipping', [ '--from' => '2026-09-01', '--to' => '2026-09-05', '--skip-freight' => true, - ])->expectsOutputToContain('唯一包裹源键 2 个,包裹记录处理 2 次;发货明细 1 条,代发仓 1 条,合计处理 2 条明细') + ])->expectsOutputToContain('唯一包裹源键 2 个,包裹记录处理 2 次;发货明细 1 条,代发仓 1 条,销售出库补漏 0 条,采购出库补漏 0 条,合计处理 2 条明细') ->assertExitCode(0); } From c4a976751d7f4fc888ae528ebbecb4f5459d67fc Mon Sep 17 00:00:00 2001 From: renshen <1005457301@qq.com> Date: Wed, 7 Oct 2026 14:21:27 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=94=AE=E5=90=8E=E9=A1=B5=E9=9D=A2=E4=B8=80=E7=9B=B4=E8=BD=AC?= =?UTF-8?q?=E5=9C=88=EF=BC=9A=E5=BA=97=E9=93=BA=E5=80=99=E9=80=89=E6=94=B9?= =?UTF-8?q?=E6=88=90=E5=AE=9A=E6=97=B6=E9=A2=84=E7=83=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一次把店铺候选的完整计算放在 app()->terminating() 里,但服务器是 mod_php, 响应要等整个请求(含终止回调)结束才算完成,浏览器就一直转圈、侧边栏停在骨架屏。 线上实测:发布后第一个打开订单售后费用明细的人,页面会卡两分钟以上。 改成:页面只返回毫秒级的店铺目录兜底,完整候选(目录 ∪ 订单镜像店铺)由新增的 order-data:warm-shop-candidates 每小时预热一次,并在发布脚本的 cron 里注册。 --- .../Commands/WarmOrderDataShopCandidates.php | 30 +++++++++++ deploy/activate-incremental-release.sh | 3 ++ deploy/activate-release.sh | 3 ++ .../OrderDataMirrorRepository.php | 52 +++++++++---------- .../Deployment/BusinessMirrorScheduleTest.php | 5 ++ .../OrderDataMirrorRepositoryTest.php | 16 ++++-- 6 files changed, 79 insertions(+), 30 deletions(-) create mode 100644 app/Console/Commands/WarmOrderDataShopCandidates.php diff --git a/app/Console/Commands/WarmOrderDataShopCandidates.php b/app/Console/Commands/WarmOrderDataShopCandidates.php new file mode 100644 index 00000000..2ff0f41e --- /dev/null +++ b/app/Console/Commands/WarmOrderDataShopCandidates.php @@ -0,0 +1,30 @@ +refreshShopNamesCache(); + $this->info(sprintf('店铺候选已预热,共 %d 个店铺。', count($names))); + + return self::SUCCESS; + } catch (Throwable $exception) { + Log::error('店铺候选预热失败。', ['exception' => $exception::class]); + $this->error('店铺候选预热失败:' . $exception->getMessage()); + + return self::FAILURE; + } + } +} diff --git a/deploy/activate-incremental-release.sh b/deploy/activate-incremental-release.sh index d9b7e7e3..038b0f15 100644 --- a/deploy/activate-incremental-release.sh +++ b/deploy/activate-incremental-release.sh @@ -181,6 +181,9 @@ CRON_TZ=Asia/Shanghai * * * * * root /usr/bin/flock -n /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-catalog --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-purchase-prices --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan upstream:snapshots:refresh-all --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-prices --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-tables --quiet' >/dev/null 2>&1 # 店铺经营报表按 15 分钟刷新;单轮覆盖 50000 行回看窗口,避免空闲时持续重读千万级账单。 7,22,37,52 * * * * root /usr/bin/flock -w 600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-store-report --max-batches=12 --quiet >/dev/null 2>&1 +# 订单售后费用明细的店铺候选:完整列表要扫两张百万级订单宽表(约 2 分钟),放定时任务预热; +# 页面冷缓存时先用毫秒级的店铺目录兜底,避免请求一直不结束、页面一直转圈。 +45 * * * * root /usr/bin/flock -w 600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan order-data:warm-shop-candidates --quiet >/dev/null 2>&1 0 0 * * * root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-third-party-warehouse-storage --max-batches=0 --quiet && /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:warm-report-caches --quiet' >/dev/null 2>&1 0 0 * * * root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-jushuitan-cost-price-history --quiet >/dev/null 2>&1 0 5 * * * root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-purchase-prices --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-tables operations_replica.rpt.jst_Orders --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-ding-online-daifa-price --quiet' >/dev/null 2>&1 diff --git a/deploy/activate-release.sh b/deploy/activate-release.sh index 241950e2..6260783d 100644 --- a/deploy/activate-release.sh +++ b/deploy/activate-release.sh @@ -185,6 +185,9 @@ CRON_TZ=Asia/Shanghai * * * * * root /usr/bin/flock -n /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-catalog --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-purchase-prices --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan upstream:snapshots:refresh-all --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-prices --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-tables --quiet' >/dev/null 2>&1 # 店铺经营报表按 15 分钟刷新;单轮覆盖 50000 行回看窗口,避免空闲时持续重读千万级账单。 7,22,37,52 * * * * root /usr/bin/flock -w 600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-store-report --max-batches=12 --quiet >/dev/null 2>&1 +# 订单售后费用明细的店铺候选:完整列表要扫两张百万级订单宽表(约 2 分钟),放定时任务预热; +# 页面冷缓存时先用毫秒级的店铺目录兜底,避免请求一直不结束、页面一直转圈。 +45 * * * * root /usr/bin/flock -w 600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan order-data:warm-shop-candidates --quiet >/dev/null 2>&1 0 0 * * * root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-third-party-warehouse-storage --max-batches=0 --quiet && /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:warm-report-caches --quiet' >/dev/null 2>&1 0 0 * * * root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-jushuitan-cost-price-history --quiet >/dev/null 2>&1 0 5 * * * root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-purchase-prices --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-tables operations_replica.rpt.jst_Orders --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-ding-online-daifa-price --quiet' >/dev/null 2>&1 diff --git a/modules/EcommerceFinance/Repositories/OrderDataMirrorRepository.php b/modules/EcommerceFinance/Repositories/OrderDataMirrorRepository.php index 3bbca0dd..c1771179 100644 --- a/modules/EcommerceFinance/Repositories/OrderDataMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/OrderDataMirrorRepository.php @@ -58,13 +58,32 @@ class OrderDataMirrorRepository } // 完整候选要扫订单宽表(shop_name 是 LONGTEXT,非淘系约 30 秒、淘系约 - // 2 分钟),不能卡在页面请求里。冷缓存时先返回毫秒级的店铺目录, - // 完整列表放到响应结束后算好再缓存 24 小时。 - $this->warmShopNamesCache(); + // 2 分钟)。mod_php 下"响应结束后再算"并不会让浏览器提前结束请求, + // 页面会一直转圈,所以这里只返回毫秒级的店铺目录,完整列表交给 + // order-data:warm-shop-candidates 定时预热。 + return $this->shopDirectoryNames(); + } - $directory = $this->shopDirectoryNames(); + /** + * 重新计算并缓存完整店铺候选(店铺目录 ∪ 订单镜像里出现过的店铺)。 + * + * 只允许定时任务调用:单次要扫两张百万级宽表。 + * + * @return array + */ + public function refreshShopNamesCache(): array + { + $names = array_values(array_unique(array_merge( + $this->computedShopNames(), + $this->shopDirectoryNames(), + ))); + sort($names, SORT_STRING); - return $directory !== [] ? $directory : $this->computedShopNames(); + if ($names !== []) { + Cache::put(self::SHOP_NAMES_CACHE_KEY, $names, now()->addHours(24)); + } + + return $names; } /** 聚水潭店铺目录(约 1300 行):毫秒级返回,作为冷缓存时的候选兜底。 */ @@ -162,32 +181,11 @@ class OrderDataMirrorRepository return array_keys($names); } - private function warmShopNamesCache(): void - { - app()->terminating(function (): void { - $lock = Cache::lock(self::SHOP_NAMES_CACHE_KEY . ':warm', 300); - if (! $lock->get()) { - return; - } - - try { - $names = $this->computedShopNames(); - if ($names !== []) { - Cache::put(self::SHOP_NAMES_CACHE_KEY, $names, now()->addHours(24)); - } - } catch (Throwable $exception) { - report($exception); - } finally { - $lock->release(); - } - }); - } - /** * 非淘系 + 淘系两个来源合并去重(淘系店铺名形如"天猫-xx/淘宝-xx")。 * * 分开各自 DISTINCT 再在 PHP 合并:UNION 会让 MariaDB 把两个上百万行的结果 - * 一起物化去重再排序,单次要两三分钟。这里只在响应结束后或缓存不可用时执行。 + * 一起物化去重再排序,单次要两三分钟。只在定时预热时执行。 * * @return array */ diff --git a/tests/Unit/Deployment/BusinessMirrorScheduleTest.php b/tests/Unit/Deployment/BusinessMirrorScheduleTest.php index 846b73e5..d5133205 100644 --- a/tests/Unit/Deployment/BusinessMirrorScheduleTest.php +++ b/tests/Unit/Deployment/BusinessMirrorScheduleTest.php @@ -36,6 +36,11 @@ class BusinessMirrorScheduleTest extends TestCase $contents, $script, ); + $this->assertStringContainsString( + '45 * * * * root /usr/bin/flock -w 600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan order-data:warm-shop-candidates --quiet', + $contents, + $script, + ); $minuteLines = array_values(array_filter( preg_split('/\R/', $contents) ?: [], static fn (string $line): bool => str_starts_with($line, '* * * * * root'), diff --git a/tests/Unit/EcommerceFinance/OrderDataMirrorRepositoryTest.php b/tests/Unit/EcommerceFinance/OrderDataMirrorRepositoryTest.php index 33b06697..c3ad83f4 100644 --- a/tests/Unit/EcommerceFinance/OrderDataMirrorRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/OrderDataMirrorRepositoryTest.php @@ -147,7 +147,17 @@ class OrderDataMirrorRepositoryTest extends TestCase $names = $this->repository($db)->shopNames(); sort($names); - $this->assertSame(['京东-乙店', '拼多多-甲店'], $names); + // 冷缓存只返回毫秒级的店铺目录,不能为了下拉候选去扫订单宽表。 + $db->table('ops_jst_shop_info')->insert(['shop_id' => 1, 'shop_name' => '抖音-丙店']); + $directory = $this->repository($db)->shopNames(); + sort($directory); + $this->assertSame(['抖音-丙店'], $directory); + + // 完整候选由定时预热写入:店铺目录 ∪ 订单镜像里出现过的店铺。 + $names = $this->repository($db)->refreshShopNamesCache(); + sort($names); + + $this->assertSame(['京东-乙店', '抖音-丙店', '拼多多-甲店'], $names); } public function test_after_sales_includes_taobao_orders_and_their_refunds(): void @@ -193,8 +203,8 @@ class OrderDataMirrorRepositoryTest extends TestCase $this->assertSame(1.0, $taobao['post_return_quantity']); $this->assertArrayNotHasKey('source_branch', $taobao); - // 店铺候选也要包含淘系店铺。 - $names = $this->repository($db)->shopNames(); + // 店铺候选也要包含淘系店铺(完整候选由定时预热写入)。 + $names = $this->repository($db)->refreshShopNamesCache(); $this->assertContains('天猫-卓禾家具旗舰店', $names); }