This commit is contained in:
Your Name
2026-09-09 12:18:17 +08:00
parent 0cea43b027
commit 40319eea41
76 changed files with 4821 additions and 268 deletions
@@ -261,8 +261,13 @@ class QywxPromotionMemberSchedulerService
return ['pool_id' => $poolId, 'next_member_id' => 0, 'queued' => false, 'blocked' => true];
}
$appliedUserIds = self::linkRangeUserIds($linkId);
$changed = !QywxPromotionMemberRange::same($range['userids'], $appliedUserIds);
$alreadyPending = in_array((int) ($sync['status'] ?? 0), [1, 3], true);
$departmentJson = (string) (Db::name('qywx_promotion_link')->where('id', $linkId)->value('range_department_json') ?? '[]');
$departmentIds = json_decode($departmentJson, true);
$changed = !QywxPromotionMemberRange::same($range['userids'], $appliedUserIds)
|| !empty($departmentIds);
// 活跃或超时的租约、尚未应用的版本都不能仅凭旧快照被重算成“已同步”。
$alreadyPending = in_array((int) ($sync['status'] ?? 0), [1, 2, 3], true)
|| (int) ($sync['desired_version'] ?? 0) > (int) ($sync['applied_version'] ?? 0);
$needsSync = $changed || $alreadyPending;
self::upsertSync($poolId, $linkId, $needsSync, $sync, $now);
@@ -99,33 +99,42 @@ class QywxPromotionRangeSyncService
$response = $this->api->getLink($remoteLinkId);
$remote = QywxCustomerAcquisitionLinkService::normaliseRemoteResponse($response, $remoteLinkId);
$actualUserIds = $remote['range_userids'];
if (!QywxPromotionMemberRange::same($actualUserIds, $desiredUserIds)) {
if (!QywxPromotionMemberRange::same($actualUserIds, $desiredUserIds)
|| $remote['range_department_ids'] !== []) {
throw new RuntimeException('企业微信返回的多人路由成员范围与方案可用医助不一致');
}
$url = $remote['url'];
$snapshot = json_encode($remote['snapshot'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
Db::name('qywx_promotion_link')->where('id', (int) $link['id'])->update([
'wecom_url' => $url,
'remote_status' => 1,
'range_user_json' => json_encode($actualUserIds, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'range_department_json' => json_encode($remote['range_department_ids'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'remote_snapshot' => $snapshot === false ? null : $snapshot,
'last_sync_time' => time(),
'sync_error' => '',
'update_time' => time(),
]);
$freshVersion = (int) (Db::name('qywx_promotion_range_sync')
->where('pool_id', $poolId)->value('desired_version') ?? 0);
Db::name('qywx_promotion_range_sync')
->where('pool_id', $poolId)
->where('lock_token', $token)
->update([
'status' => $freshVersion === $desiredVersion ? 0 : 1,
$confirmed = Db::transaction(function () use (
$poolId, $token, $desiredVersion, $link, $url, $actualUserIds, $remote, $snapshot
): bool {
$fresh = Db::name('qywx_promotion_range_sync')->where('pool_id', $poolId)->lock(true)->find();
if (!$fresh || (int) ($fresh['status'] ?? 0) === 5) {
return false;
}
if ((string) ($fresh['lock_token'] ?? '') !== $token
|| (int) ($fresh['lock_until'] ?? 0) <= time()) {
// 过期工作不能覆盖新工作的确认结果。它可能较晚触达企微,需再推一次最新范围。
QywxPromotionMemberSchedulerService::requestPoolSync($poolId, (int) $fresh['promotion_link_id']);
return false;
}
Db::name('qywx_promotion_link')->where('id', (int) $link['id'])->update([
'wecom_url' => $url,
'remote_status' => 1,
'range_user_json' => json_encode($actualUserIds, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'range_department_json' => json_encode($remote['range_department_ids'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'remote_snapshot' => $snapshot === false ? null : $snapshot,
'last_sync_time' => time(),
'sync_error' => '',
'update_time' => time(),
]);
$isLatest = (int) ($fresh['desired_version'] ?? 0) === $desiredVersion;
Db::name('qywx_promotion_range_sync')->where('pool_id', $poolId)->update([
'status' => $isLatest ? 0 : 1,
'desired_member_id' => 0,
'applied_member_id' => 0,
'applied_version' => $desiredVersion,
'next_retry' => $freshVersion === $desiredVersion ? 0 : time(),
'next_retry' => $isLatest ? 0 : time(),
'attempts' => 0,
'lock_token' => '',
'lock_until' => 0,
@@ -133,13 +142,19 @@ class QywxPromotionRangeSyncService
'update_time' => time(),
]);
return ['status' => 'synced', 'pool_id' => $poolId, 'member_id' => 0];
return $isLatest;
});
return ['status' => $confirmed ? 'synced' : 'pending', 'pool_id' => $poolId, 'member_id' => 0];
} catch (\Throwable $e) {
$attempts = max(1, (int) ($claim['attempts'] ?? 0) + 1);
Db::name('qywx_promotion_range_sync')
->where('pool_id', $poolId)
->where('lock_token', $token)
->update([
Db::transaction(function () use ($poolId, $token, $attempts, $claim, $e): void {
$fresh = Db::name('qywx_promotion_range_sync')->where('pool_id', $poolId)->lock(true)->find();
if (!$fresh || (string) ($fresh['lock_token'] ?? '') !== $token
|| (int) ($fresh['status'] ?? 0) === 5) {
return;
}
Db::name('qywx_promotion_range_sync')->where('pool_id', $poolId)->update([
'status' => 3,
'next_retry' => time() + min(300, 15 * $attempts),
'lock_token' => '',
@@ -147,9 +162,10 @@ class QywxPromotionRangeSyncService
'last_error' => mb_substr($e->getMessage(), 0, 500),
'update_time' => time(),
]);
Db::name('qywx_promotion_link')
->where('id', (int) ($claim['promotion_link_id'] ?? 0))
->update(['sync_error' => mb_substr($e->getMessage(), 0, 500), 'update_time' => time()]);
Db::name('qywx_promotion_link')
->where('id', (int) ($claim['promotion_link_id'] ?? 0))
->update(['sync_error' => mb_substr($e->getMessage(), 0, 500), 'update_time' => time()]);
});
throw $e;
}