feat: 退款当前状态改为分钟级同步 (#899)
This commit is contained in:
parent
40d3ce162a
commit
e8d4e8e345
@ -53,8 +53,14 @@ class Kernel extends ConsoleKernel
|
||||
->withoutOverlapping(60)
|
||||
->runInBackground();
|
||||
|
||||
// 退款待确认的当前状态按分钟增量刷新,确认完成后页面可及时移除。
|
||||
$schedule->command('business-mirror:sync-tables jiansun.dbo.jst_refund_view')
|
||||
->everyMinute()
|
||||
->withoutOverlapping(5)
|
||||
->runInBackground();
|
||||
|
||||
// 退款待确认四张镜像每天 06:00 专属刷新,确保 10:00 前完成;
|
||||
// 不再参与分钟级通用轮转,失败状态由财务驾驶舱展示。
|
||||
// 当前退款状态单独按分钟刷新,失败状态由财务驾驶舱展示。
|
||||
$schedule->command('business-mirror:sync-tables --daily-only --max-batches=0')
|
||||
->dailyAt('06:00')
|
||||
->timezone('Asia/Shanghai')
|
||||
|
||||
@ -325,14 +325,15 @@ return [
|
||||
'full_reconcile_hours' => (int) env('BUSINESS_MIRROR_FULL_RECONCILE_HOURS', 168),
|
||||
'dedicated_datasets' => [
|
||||
'operations_replica.rpt.jst_sku',
|
||||
'jiansun.dbo.jst_refund_view',
|
||||
],
|
||||
// 退款待确认:每天 10 点前由专属批次刷新一次,不再进入分钟轮转。
|
||||
// 四张退款基础镜像每天 10 点前由专属批次刷新一次。
|
||||
// 聚水潭当前退款状态按分钟增量刷新,保证退款确认后页面及时更新。
|
||||
'daily_datasets' => [
|
||||
'operations_replica.rpt.jst_returnAndRefundInfo',
|
||||
'operations_replica.rpt.jst_returnAndRefundItem',
|
||||
'operations_replica.rpt.jst_qmcust_refundlist',
|
||||
'operations_replica.rpt.jst_qmcust_refundlist_item',
|
||||
'jiansun.dbo.jst_refund_view',
|
||||
],
|
||||
],
|
||||
|
||||
@ -491,6 +492,7 @@ return [
|
||||
'keys' => ['as_id'],
|
||||
'watermark' => 'modified',
|
||||
'minimum_datetime' => ['column' => 'as_date', 'value' => '2026-10-01 00:00:00'],
|
||||
'overlap_hours' => 1,
|
||||
'full_reconcile_hours' => 168,
|
||||
'columns' => ['as_id', 'status', 'shop_status', 'confirm_date', 'modified', 'as_date'],
|
||||
'indexes' => [
|
||||
|
||||
@ -200,7 +200,7 @@ mv "$REMOTE_DIR/.deployed-sha.tmp" "$REMOTE_DIR/.deployed-sha"
|
||||
# 普通商品优先高频增量,并在配置的每周低峰窗口自动继续全量校准。
|
||||
cat > /etc/cron.d/caiwu-jiansun-snapshots <<'EOF'
|
||||
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
|
||||
* * * * * 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-tables jiansun.dbo.jst_refund_view --quiet; /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
|
||||
# 店铺候选平时仅读镜像变更;夜间全量核对历史名称。冷页面不扫描订单宽表。
|
||||
@ -230,7 +230,8 @@ CRON_TZ=Asia/Shanghai
|
||||
15 10 * * * root /usr/bin/flock -n /run/caiwu-inventory-warehouse-alert.lock /usr/bin/docker exec -u www-data caiwu_app php artisan inventory:sync-warehouse-mapping-alert --quiet >/dev/null 2>&1
|
||||
# 供应商管理每天北京时间 08:15 完整校准档案、合同、联系人与打款资料。
|
||||
15 8 * * * 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-supplier-management --quiet >/dev/null 2>&1
|
||||
# 退款待确认四张镜像每天 06:00 在独立批容器完整跑完一轮,确保 10:00 前完成。
|
||||
# 退款待确认四张基础镜像每天 06:00 在独立批容器完整跑完一轮,确保 10:00 前完成;
|
||||
# 当前退款状态在上面的分钟任务中优先增量刷新。
|
||||
0 6 * * * root /bin/sh /opt/caiwuxitong/run-finance-batch.sh business-mirror:sync-tables --daily-only --max-batches=0 --quiet >/dev/null 2>&1
|
||||
*/2 * * * * root /usr/bin/flock -n /run/caiwu-esign-polling.lock /usr/bin/docker exec -u www-data caiwu_app php artisan esign:poll-signatures --quiet >/dev/null 2>&1
|
||||
# 财务台账:每天 02:30 同步 AI 库动账并重算流水;10:30 生成前一晚店铺余额快照。
|
||||
|
||||
@ -204,7 +204,7 @@ mv "$REMOTE_DIR/.deployed-sha.tmp" "$REMOTE_DIR/.deployed-sha"
|
||||
# 普通商品优先高频增量,并在配置的每周低峰窗口自动继续全量校准。
|
||||
cat > /etc/cron.d/caiwu-jiansun-snapshots <<'EOF'
|
||||
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
|
||||
* * * * * 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-tables jiansun.dbo.jst_refund_view --quiet; /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
|
||||
# 店铺候选平时仅读镜像变更;夜间全量核对历史名称。冷页面不扫描订单宽表。
|
||||
@ -234,7 +234,8 @@ CRON_TZ=Asia/Shanghai
|
||||
15 10 * * * root /usr/bin/flock -n /run/caiwu-inventory-warehouse-alert.lock /usr/bin/docker exec -u www-data caiwu_app php artisan inventory:sync-warehouse-mapping-alert --quiet >/dev/null 2>&1
|
||||
# 供应商管理每天北京时间 08:15 完整校准档案、合同、联系人与打款资料。
|
||||
15 8 * * * 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-supplier-management --quiet >/dev/null 2>&1
|
||||
# 退款待确认四张镜像每天 06:00 在独立批容器完整跑完一轮,确保 10:00 前完成。
|
||||
# 退款待确认四张基础镜像每天 06:00 在独立批容器完整跑完一轮,确保 10:00 前完成;
|
||||
# 当前退款状态在上面的分钟任务中优先增量刷新。
|
||||
0 6 * * * root /bin/sh /opt/caiwuxitong/run-finance-batch.sh business-mirror:sync-tables --daily-only --max-batches=0 --quiet >/dev/null 2>&1
|
||||
*/2 * * * * root /usr/bin/flock -n /run/caiwu-esign-polling.lock /usr/bin/docker exec -u www-data caiwu_app php artisan esign:poll-signatures --quiet >/dev/null 2>&1
|
||||
# 财务台账:每天 02:30 同步 AI 库动账并重算流水;10:30 生成前一晚店铺余额快照。
|
||||
|
||||
@ -78,7 +78,7 @@
|
||||
|
||||
售后(退货退款)页面及其导出共同读取上述两套售后主单、各自商品明细与店铺镜像,按 `as_date` 筛选。原售后资料优先;退款清单按 `as_id`、或非空的 `(shop_id, outer_as_id)` 排除已存在的业务单。无商品明细的主单仍显示,商品数量、明细金额及成本留空;店铺平台无法唯一确认时标为“待确认”,不推断平台。两个来源的订单关联不得混用。订单号筛选按完整内部订单号、线上订单号或平台售后单号精确查询,避免长文本标识的全表扫描;店铺和商品仍支持包含搜索。分页先限制各来源的候选主单及行标识,再加载当前页明细;导出使用同一筛选及稳定游标。
|
||||
|
||||
“退款待确认”复用同一套售后查询,并用 `dbo.jst_refund_view` 的当前 `as_id => status/shop_status` 覆盖报表副本里的旧状态后固定筛选 `status=WaitConfirm`、`shop_status=SUCCESS`;页面分别显示为“待确认”和“退款成功”。覆盖表只保留 2026-10-01 起的当前状态字段,按 `as_id` 建立索引并每日同步。筛选与导出始终保留该固定条件。
|
||||
“退款待确认”复用同一套售后查询,并用 `dbo.jst_refund_view` 的当前 `as_id => status/shop_status` 覆盖报表副本里的旧状态后固定筛选 `status=WaitConfirm`、`shop_status=SUCCESS`;页面分别显示为“待确认”和“退款成功”。覆盖表只保留 2026-10-01 起的当前状态字段,按 `as_id` 建立索引并按分钟增量同步,使用 1 小时重叠窗口和每周全量校准。筛选与导出始终保留该固定条件。
|
||||
|
||||
`jst_returnAndRefundItem` 没有更新时间,除按 `Id` 追加外,每 168 小时通过现有调度分批执行可续传全量校准,覆盖历史编号变化及删除;全量完成后才清理上一代残留。人工历史校准前须备份相关镜像、目录及同步状态,并持有生产上游同步互斥锁。退款清单两表继续按 `local_updated` 使用重叠增量,不新建重复镜像。
|
||||
|
||||
@ -119,7 +119,7 @@
|
||||
供应商管理保留现有后台轮转刷新,同时每天北京时间 08:15 通过 `business-mirror:sync-supplier-management` 顺序完整校准供应商档案(含易签宝账号)、合同、联系人和打款资料。生产任务使用现有上游同步锁和 `www-data`,有其他同步占用时排队执行;每批沿用现有行数限制,直到四个数据集分别完成,失败记录在原镜像同步状态中。固定任务随正式发布安装,页面继续显示最近同步时间。
|
||||
|
||||
1. 首次全量只能通过 `business-mirror:sync-tables <dataset> --full` 显式启动;失败或中断后使用同一命令继续。
|
||||
2. 日常任务只处理已经完成首次全量的数据集。存在可靠更新时间的表使用 48 小时重叠窗口;没有可靠更新时间的表按稳定主键追加。`rpt.jst_sku` 由普通商品专用任务每分钟优先处理,每轮默认最多 5 批、每批 1000 行,不再参加通用数据集轮转。
|
||||
2. 日常任务只处理已经完成首次全量的数据集。存在可靠更新时间的表默认使用 48 小时重叠窗口;退款当前状态使用 1 小时重叠窗口并每分钟刷新;没有可靠更新时间的表按稳定主键追加。`rpt.jst_sku` 由普通商品专用任务每分钟优先处理,每轮默认最多 5 批、每批 1000 行,不再参加通用数据集轮转。
|
||||
3. 全量校准使用新的同步代次,只有全部完成后才删除镜像中本轮未出现的旧行,因此中途失败不会清空当前可用数据。
|
||||
4. 普通商品在最近完整校准超过 168 小时后,于周日 02:00-08:00 低峰窗口启动全量校准;一旦启动,后续每分钟从稳定游标继续,直至完整结束。首次全量由 Codex 备份后自主执行,定时任务保持既有初始化开关。
|
||||
5. 除专用数据集外,通用任务每轮默认只处理 1 个数据集、每个数据集 2 批,每批 500 行,避免同时压迫两个旧系统。
|
||||
|
||||
@ -411,7 +411,11 @@ class BusinessMirrorSynchronizer
|
||||
return [Carbon::parse((string) $state['window_started_at']), Carbon::parse((string) $state['window_ends_at'])];
|
||||
}
|
||||
|
||||
$overlap = max((int) config('upstream_mirrors.table_sync.overlap_hours', 48), 1);
|
||||
$overlap = max((int) $this->datasets->definitionValue(
|
||||
$dataset->key(),
|
||||
'overlap_hours',
|
||||
config('upstream_mirrors.table_sync.overlap_hours', 48),
|
||||
), 1);
|
||||
$last = Carbon::parse((string) ($state['last_succeeded_at'] ?? now()->subHours($overlap)));
|
||||
|
||||
return [$last->subHours($overlap), now()->addSecond()];
|
||||
|
||||
@ -131,8 +131,8 @@ class RefundPendingSyncStatus
|
||||
'state' => $running ? 'syncing' : 'healthy',
|
||||
'title' => $running ? '退款待确认正在同步' : '退款待确认同步正常',
|
||||
'message' => $running
|
||||
? '每日同步正在执行,完成后会自动更新。'
|
||||
: '每日 06:00 自动同步,10:00 前完成。',
|
||||
? '退款镜像正在同步,完成后会自动更新。'
|
||||
: '基础退款镜像每日 06:00 同步,当前退款状态每分钟刷新。',
|
||||
'last_succeeded_at' => $this->latest($successTimes),
|
||||
'last_attempted_at' => $this->latest($attemptTimes),
|
||||
'issues' => [],
|
||||
|
||||
@ -23,8 +23,15 @@ class BusinessMirrorScheduleTest extends TestCase
|
||||
'operations_replica.rpt.jst_returnAndRefundItem',
|
||||
'operations_replica.rpt.jst_qmcust_refundlist',
|
||||
'operations_replica.rpt.jst_qmcust_refundlist_item',
|
||||
'jiansun.dbo.jst_refund_view',
|
||||
], config('upstream_mirrors.table_sync.daily_datasets'));
|
||||
$this->assertContains(
|
||||
'jiansun.dbo.jst_refund_view',
|
||||
config('upstream_mirrors.table_sync.dedicated_datasets'),
|
||||
);
|
||||
$this->assertSame(
|
||||
1,
|
||||
config('upstream_mirrors.datasets')['jiansun.dbo.jst_refund_view']['overlap_hours'] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
public function test_stale_source_does_not_block_healthy_datasets_and_reports_failure(): void
|
||||
|
||||
@ -59,6 +59,11 @@ class BusinessMirrorScheduleTest extends TestCase
|
||||
$minuteLine = $minuteLines[0] ?? null;
|
||||
$this->assertIsString($minuteLine, $script);
|
||||
$this->assertStringNotContainsString('business-mirror:sync-store-report', $minuteLine, $script);
|
||||
$statusPosition = strpos($minuteLine, 'business-mirror:sync-tables jiansun.dbo.jst_refund_view --quiet');
|
||||
$catalogPosition = strpos($minuteLine, 'business-mirror:sync-product-catalog --quiet');
|
||||
$this->assertNotFalse($statusPosition, $script);
|
||||
$this->assertNotFalse($catalogPosition, $script);
|
||||
$this->assertLessThan($catalogPosition, $statusPosition, $script);
|
||||
$this->assertStringContainsString('docker exec -u www-data caiwu_app', $contents, $script);
|
||||
$this->assertStringContainsString('/run/caiwu-upstream-sync.lock', $contents, $script);
|
||||
$this->assertStringContainsString("CRON_TZ=Asia/Shanghai\n", $contents, $script);
|
||||
|
||||
@ -252,6 +252,31 @@ class BusinessMirrorSynchronizerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_incremental_sync_can_use_a_dataset_specific_overlap_window(): void
|
||||
{
|
||||
config([
|
||||
'upstream_mirrors.datasets.test.dbo.records.overlap_hours' => 1,
|
||||
'app.timezone' => 'Asia/Shanghai',
|
||||
]);
|
||||
\Illuminate\Support\Carbon::setTestNow('2026-08-15 12:30:00');
|
||||
|
||||
try {
|
||||
$this->query->batches = [[
|
||||
['Id' => 1, 'updateTime' => '2026-08-15 10:00:00', 'displayName' => '初始'],
|
||||
]];
|
||||
$this->synchronizer->sync('test.dbo.records', true, 10, 1);
|
||||
|
||||
$this->query->calls = [];
|
||||
$this->query->batches = [[]];
|
||||
$this->synchronizer->sync('test.dbo.records', false, 10, 1);
|
||||
|
||||
$this->assertSame('2026-08-15T11:30:00.000', $this->query->calls[0]['bindings'][0]);
|
||||
$this->assertSame('2026-08-15T12:30:01.000', $this->query->calls[0]['bindings'][1]);
|
||||
} finally {
|
||||
\Illuminate\Support\Carbon::setTestNow();
|
||||
}
|
||||
}
|
||||
|
||||
public function test_key_first_incremental_read_preserves_filters_projection_and_resume_keys(): void
|
||||
{
|
||||
$this->query->batches = [[['Id' => 1, 'updateTime' => '2026-08-14 10:00:00', 'displayName' => '旧值']]];
|
||||
|
||||
@ -64,6 +64,21 @@ class RefundPendingSyncStatusTest extends TestCase
|
||||
$this->assertCount(5, $status['issues']);
|
||||
}
|
||||
|
||||
public function test_healthy_message_describes_realtime_status_refresh(): void
|
||||
{
|
||||
$states = $this->states();
|
||||
foreach ($states as &$state) {
|
||||
$state['last_attempted_at'] = '2026-11-04 10:29:00';
|
||||
$state['last_succeeded_at'] = '2026-11-04 10:29:00';
|
||||
}
|
||||
unset($state);
|
||||
|
||||
$status = $this->service($states)->current(Carbon::parse('2026-11-04 10:30:00'));
|
||||
|
||||
$this->assertSame('healthy', $status['state']);
|
||||
$this->assertSame('基础退款镜像每日 06:00 同步,当前退款状态每分钟刷新。', $status['message']);
|
||||
}
|
||||
|
||||
/** @param array<string,array<string,mixed>> $states */
|
||||
private function service(array $states): RefundPendingSyncStatus
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user