array_values(array_unique($accounts)), 'index' => 0, 'side' => 0, 'cursor' => [], 'inserted' => 0, 'errors' => [], ]; } public static function step(array $state, callable $fetchPage, callable $archive): array { if ($state['index'] >= count($state['accounts'])) { return $state; } $account = $state['accounts'][$state['index']]; try { $page = $fetchPage($account, $state['cursor']); } catch (\Throwable $e) { // 失败的会话不妨碍其他医生记录落库,下一轮仍从头补拉该会话。 $state['errors'][] = $account . ($state['side'] === 0 ? '(医生侧)' : '(患者侧)') . ':' . $e->getMessage(); return self::nextSide($state); } // 落库异常交由调用者处理,绝不能把未归档的页标记为已同步。 $state['inserted'] += $archive($account, $page['msgList']); if ($page['completed']) { $state = self::nextSide($state); } else { $state['cursor'] = $page['cursor']; } return $state; } private static function nextSide(array $state): array { $state['cursor'] = []; if ($state['side'] === 0) { $state['side'] = 1; } else { $state['side'] = 0; $state['index']++; } return $state; } public static function progress(array $state): array { $checking = array_key_exists('accounts_verified', $state) && !$state['accounts_verified']; $check = $state['account_check'] ?? null; $completed = !$checking && $state['index'] >= count($state['accounts']); return [ 'completed' => $completed, 'phase' => $checking ? 'checking_accounts' : ($completed ? 'completed' : 'syncing'), 'checked_accounts' => $check['offset'] ?? 0, 'candidate_accounts' => $check ? count($check['candidates']) : 0, 'skipped_accounts' => $check ? count($check['missing']) : 0, 'inserted' => $state['inserted'], 'processed_peers' => $state['index'], 'total_peers' => count($state['accounts']), 'errors' => $state['errors'], ]; } }