fix(部署): 聚水潭发货数据定时同步失败自动重试并留日志,晚上补拉一次 (#1183)
每晚 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 <noreply@anthropic.com> 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: #1183
This commit is contained in:
parent
d3532bf0da
commit
e12c9c20ec
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 "$@"
|
||||
|
||||
@ -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<string,string|null> $environment */
|
||||
private function runBatch(
|
||||
string $directory,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user