fix(部署): 聚水潭发货数据定时同步失败自动重试并留日志,晚上补拉一次 #1183

Merged
limeng merged 1 commits from fix/jst-shipping-sync-retry-1115140201 into main 2026-11-15 14:07:09 +08:00
4 changed files with 64 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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 "$@"

View File

@ -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,