From e12c9c20ec413cec8f7f48ef9ae98cfcfc1e1c78 Mon Sep 17 00:00:00 2001 From: limeng <1131177064@qq.com> Date: Sun, 15 Nov 2026 14:07:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E9=83=A8=E7=BD=B2):=20=E8=81=9A=E6=B0=B4?= =?UTF-8?q?=E6=BD=AD=E5=8F=91=E8=B4=A7=E6=95=B0=E6=8D=AE=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=A4=B1=E8=B4=A5=E8=87=AA=E5=8A=A8=E9=87=8D?= =?UTF-8?q?=E8=AF=95=E5=B9=B6=E7=95=99=E6=97=A5=E5=BF=97=EF=BC=8C=E6=99=9A?= =?UTF-8?q?=E4=B8=8A=E8=A1=A5=E6=8B=89=E4=B8=80=E6=AC=A1=20(#1183)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 每晚 01:28 只跑一次 jushuitan:sync-shipping --days=3,输出丢到 /dev/null; 副本库偶发超时时整次同步失败且无记录,最近几天大量漏单(如 11-06 只同步到 43%、11-08 68%)。 - run-finance-batch.sh:发货数据同步输出含"同步失败"时自动重试(默认 3 次、间隔 5 分钟), 每次输出写 /opt/caiwuxitong/jst-sync-shipping.log(超 5MB 轮转);只有警告不重试; - cron 增加 21:28 再拉最近 3 天,补齐凌晨商品明细未到齐/超时漏掉的包裹。 注:BusinessMirrorScheduleTest 在 main 上已有 2 个与本改动无关的失败(compose.jiangling.yml 的 config_db 内存参数、app 服务配置断言),本次未改动。 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014nvGZkg27YMEdhry6eiuwf > 开工时标题使用 `WIP: <任务名称>` 并先填写“任务占用与边界”。Codex 完成独立合并前自审后补齐其余内容并移除 `WIP:`。 ## 任务占用与边界 - 任务名称: - 实际开发者: - 任务来源/团队分工: - 目标结果: - 预计修改路径: - 将写入的公共契约(路由、Controller 方法、Repository、表/视图、`source_key`、`scope`、权限名、菜单键或共享组件): - 仅复用、明确不修改的公共契约: - 数据源、表/视图和字段: - 上游 `source_key` / `scope`: - 权限名、菜单键或共享组件: - 明确不修改的范围: ## 开工碰撞检查 - [ ] Codex 已执行 `scripts/codex-task-preflight.ps1` - [ ] 已检查最新 `main` 和所有远程任务分支 - [ ] 已搜索主线既有页面、接口、Repository 和数据查询 - [ ] 已把“将写入”和“仅复用、不修改”的公共契约分开;仅复用项不作为单独阻断条件 - [ ] 未发现实际写入重叠;或已在下方写明相关分支、具体重叠项和协调结论 相关分支、重叠项与协调结论: ## 实际改动 - 完成内容: - Codex 实际修改的文件: - 新增或变更的公共契约: - 与原计划的差异: ## 验证证据 - 正常流程: - 空数据/错误输入/无权限场景: - 受影响旧功能: - 自动测试及结果: - 实际页面、接口或只读业务查询结果: ## 风险检查 - [ ] 不涉及金额、权限、审批、数据库迁移、生产配置或数据修复 - [ ] 如涉及上述高风险内容,已在下方写明影响、回滚和所需授权 - [ ] 未提交 `.env`、密码、Token、私钥、真实财务数据、生产日志或构建产物 - [ ] 前端改动符合 `DESIGN.md`;无前端改动则写“不适用” 高风险影响、回滚和授权状态: ## 合并前复查 - [ ] 已重新拉取远程引用并用实际修改范围再次运行碰撞检查 - [ ] PR 不再是 `WIP:`,所有测试结果已经补齐 - [ ] Codex 已重新读取原始需求和验收标准,并逐文件审核 `origin/main...任务分支` 的完整 diff - [ ] Codex 自审确认无越界修改、重复实现、敏感内容、未处理冲突和未验证结果 - [ ] 合并顺序不会覆盖其他未合并任务 - [ ] 已授权当前任务的 Codex 执行 Squash merge、验证 `main` 并清理任务分支 - [ ] 如涉及生产迁移、数据修复、删除、覆盖配置或部署,已另行取得对应高风险授权 Reviewed-on: https://git.1080p.hk/WuYongJie/CaiWuXiTong/pulls/1183 --- deploy/activate-incremental-release.sh | 2 ++ deploy/activate-release.sh | 2 ++ deploy/run-finance-batch.sh | 24 +++++++++++++ .../Deployment/FinanceBatchIsolationTest.php | 36 +++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/deploy/activate-incremental-release.sh b/deploy/activate-incremental-release.sh index 5af3b369..58018a83 100644 --- a/deploy/activate-incremental-release.sh +++ b/deploy/activate-incremental-release.sh @@ -253,6 +253,8 @@ CRON_TZ=Asia/Shanghai * * * * * root /usr/bin/flock -n /run/caiwu-account-mapping-resync.lock /bin/sh /opt/caiwuxitong/run-finance-batch.sh store-receipt:auto-resync --quiet >/dev/null 2>&1 * * * * * root /usr/bin/flock -n /run/caiwu-account-bills-export.lock /bin/sh /opt/caiwuxitong/run-finance-batch.sh account-bills:exports:run --quiet >/dev/null 2>&1 28 1 * * * root /bin/sh /opt/caiwuxitong/run-finance-batch.sh jushuitan:sync-shipping --days=3 >/dev/null 2>&1 +# 聚水潭发货数据:凌晨部分出库单商品明细未到齐、副本库也可能超时,晚上再补拉最近 3 天。 +28 21 * * * root /bin/sh /opt/caiwuxitong/run-finance-batch.sh jushuitan:sync-shipping --days=3 >/dev/null 2>&1 # 头条放心购(店铺收款账户账单 › 抖音账户)资金流水:每天 09:30 定点把该渠道所用 # 原始账单镜像追平一次,保证上班前当日/前一日流水已进系统,而不是等每分钟轮转慢慢走。 30 9 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-tables operations_replica.rpt.jst_originalBillInfo --max-batches=20 --quiet >/dev/null 2>&1 diff --git a/deploy/activate-release.sh b/deploy/activate-release.sh index a6780b7b..fbdae985 100644 --- a/deploy/activate-release.sh +++ b/deploy/activate-release.sh @@ -257,6 +257,8 @@ CRON_TZ=Asia/Shanghai * * * * * root /usr/bin/flock -n /run/caiwu-account-mapping-resync.lock /bin/sh /opt/caiwuxitong/run-finance-batch.sh store-receipt:auto-resync --quiet >/dev/null 2>&1 * * * * * root /usr/bin/flock -n /run/caiwu-account-bills-export.lock /bin/sh /opt/caiwuxitong/run-finance-batch.sh account-bills:exports:run --quiet >/dev/null 2>&1 28 1 * * * root /bin/sh /opt/caiwuxitong/run-finance-batch.sh jushuitan:sync-shipping --days=3 >/dev/null 2>&1 +# 聚水潭发货数据:凌晨部分出库单商品明细未到齐、副本库也可能超时,晚上再补拉最近 3 天。 +28 21 * * * root /bin/sh /opt/caiwuxitong/run-finance-batch.sh jushuitan:sync-shipping --days=3 >/dev/null 2>&1 # 头条放心购(店铺收款账户账单 › 抖音账户)资金流水:每天 09:30 定点把该渠道所用 # 原始账单镜像追平一次,保证上班前当日/前一日流水已进系统,而不是等每分钟轮转慢慢走。 30 9 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-tables operations_replica.rpt.jst_originalBillInfo --max-batches=20 --quiet >/dev/null 2>&1 diff --git a/deploy/run-finance-batch.sh b/deploy/run-finance-batch.sh index 8da739b5..19b9186d 100644 --- a/deploy/run-finance-batch.sh +++ b/deploy/run-finance-batch.sh @@ -64,4 +64,28 @@ esac RUN_MIGRATIONS=0 export IMAGE_TAG RUN_MIGRATIONS # Mutating jobs hold the upstream lock; read-only exports use their own single-worker lock. +if [ "$1" = 'jushuitan:sync-shipping' ]; then + # 聚水潭副本库偶发超时会让整次发货数据同步失败(当晚最近几天漏单): + # 输出里有"同步失败"时自动重试,每次输出留日志;只有警告(未写"同步失败")不重试。 + sync_log="${CAIWU_JST_SYNC_LOG:-$REMOTE_DIR/jst-sync-shipping.log}" + sync_attempts="${CAIWU_JST_SYNC_ATTEMPTS:-3}" + if [ -f "$sync_log" ] && [ "$(wc -c <"$sync_log")" -gt 5000000 ]; then + mv -f "$sync_log" "$sync_log.1" + fi + sync_out="$(mktemp)" + trap 'rm -f "$sync_out"' EXIT + attempt=1 + while :; do + echo "== $(date '+%Y-%m-%d %H:%M:%S') attempt $attempt/$sync_attempts: $*" >>"$sync_log" + rc=0 + docker compose -f compose.jiangling.yml --profile batch run --rm -T --no-deps batch "$@" >"$sync_out" 2>&1 || rc=$? + tr '\r' '\n' <"$sync_out" | grep -v '已扫描' | tail -n 60 >>"$sync_log" || true + echo "== rc=$rc" >>"$sync_log" + if [ "$rc" -eq 0 ] || ! grep -q '同步失败' "$sync_out" || [ "$attempt" -ge "$sync_attempts" ]; then + exit "$rc" + fi + attempt=$((attempt + 1)) + sleep "${CAIWU_JST_SYNC_RETRY_WAIT:-300}" + done +fi docker compose -f compose.jiangling.yml --profile batch run --rm -T --no-deps batch "$@" diff --git a/tests/Unit/Deployment/FinanceBatchIsolationTest.php b/tests/Unit/Deployment/FinanceBatchIsolationTest.php index 8b3be896..70fcad31 100644 --- a/tests/Unit/Deployment/FinanceBatchIsolationTest.php +++ b/tests/Unit/Deployment/FinanceBatchIsolationTest.php @@ -35,6 +35,42 @@ class FinanceBatchIsolationTest extends TestCase } } + public function test_shipping_sync_retries_after_upstream_failure_and_keeps_a_log(): void + { + $directory = sys_get_temp_dir() . '/caiwu-jst-retry-' . bin2hex(random_bytes(6)); + mkdir($directory); + // 第一次同步输出"同步失败"(副本库超时),第二次成功 + file_put_contents($directory . '/docker', '#!/bin/sh' . "\n" + . 'if [ "$1" = inspect ]; then echo caiwu_akaunting:123456789abc; exit 0; fi' . "\n" + . 'n=$(cat "$CAIWU_REMOTE_DIR/count" 2>/dev/null || echo 0); n=$((n + 1)); echo $n > "$CAIWU_REMOTE_DIR/count"' . "\n" + . 'if [ $n -eq 1 ]; then echo "同步失败:Adaptive Server connection timed out"; exit 1; fi' . "\n" + . 'echo "同步完成"; exit 0' . "\n"); + chmod($directory . '/docker', 0700); + try { + $process = new Process( + ['sh', dirname(__DIR__, 3) . '/deploy/run-finance-batch.sh', 'jushuitan:sync-shipping', '--days=3'], + $directory, + [ + 'PATH' => $directory . ':' . getenv('PATH'), 'CAIWU_REMOTE_DIR' => $directory, + 'CAIWU_UPSTREAM_LOCK' => $directory . '/sync.lock', 'CAIWU_BATCH_LOCK_WAIT' => '0.1', + 'CAIWU_JST_SYNC_RETRY_WAIT' => '0', + ], + ); + $process->run(); + + $this->assertTrue($process->isSuccessful(), $process->getErrorOutput()); + $this->assertSame('2', trim((string) file_get_contents($directory . '/count'))); + $log = (string) file_get_contents($directory . '/jst-sync-shipping.log'); + $this->assertStringContainsString('attempt 1/3', $log); + $this->assertStringContainsString('同步失败', $log); + $this->assertStringContainsString('attempt 2/3', $log); + $this->assertStringContainsString('同步完成', $log); + } finally { + foreach (glob($directory . '/*') as $path) { unlink($path); } + rmdir($directory); + } + } + /** @param array $environment */ private function runBatch( string $directory,