> /dev/null 2>&1 * * * * * * cd /path/to/project && sleep 30 && php think qywx:sync-msg-archive --download >> /dev/null 2>&1 * * 并发控制:通过文件锁,同一时刻只会跑一个,多开直接退出。 */ class QywxSyncMsgArchive extends Command { protected function configure() { $this->setName('qywx:sync-msg-archive') ->addOption('download', 'd', Option::VALUE_NONE, '拉完消息后顺便下载 pending 媒体') ->addOption('only-media', null, Option::VALUE_NONE, '仅下载已登记的 pending 媒体') ->addOption('max-batches', 'b', Option::VALUE_REQUIRED, '单次运行最多拉多少批 (默认 20)', 20) ->addOption('max-media', 'm', Option::VALUE_REQUIRED, '单次最多下载多少条媒体 (默认 200)', 200) ->setDescription('拉取企业微信会话内容存档'); } protected function execute(Input $input, Output $output) { $lockFile = runtime_path() . 'lock' . DIRECTORY_SEPARATOR . 'qywx_msg_archive.lock'; $lockDir = dirname($lockFile); if (!is_dir($lockDir)) { @mkdir($lockDir, 0755, true); } $fp = @fopen($lockFile, 'c'); if ($fp === false) { $output->writeln('无法创建锁文件: ' . $lockFile . ''); return 1; } if (!flock($fp, LOCK_EX | LOCK_NB)) { fclose($fp); $output->writeln('另一个会话存档同步进程正在运行,跳过本次。'); return 0; } try { $onlyMedia = (bool) $input->getOption('only-media'); $download = (bool) $input->getOption('download'); $maxBatches = max(1, (int) $input->getOption('max-batches')); $maxMedia = max(1, (int) $input->getOption('max-media')); if (!$onlyMedia) { $pull = QywxMsgArchiveService::pullLoop($maxBatches); if (!$pull['enabled']) { $output->writeln('会话存档未启用或 SDK 不可用:' . implode('; ', $pull['errors']) . ''); return 0; } $output->writeln(sprintf( '消息拉取:batches=%d pulled=%d inserted=%d updated=%d media_pending=%d last_seq=%d', $pull['batches'], $pull['pulled'], $pull['inserted'], $pull['updated'], $pull['media_pending'], $pull['last_seq'] )); if (!empty($pull['errors'])) { foreach ($pull['errors'] as $err) { Log::warning('qywx:sync-msg-archive pull error: ' . $err); $output->writeln('error: ' . $err . ''); } } } if ($onlyMedia || $download) { $media = QywxMsgArchiveService::downloadPendingMedia($maxMedia); if (!$media['enabled']) { $output->writeln('媒体下载-SDK 未启用'); } else { $output->writeln(sprintf( '媒体下载:ok=%d failed=%d skipped=%d', $media['ok'], $media['failed'], $media['skipped'] )); } } return 0; } catch (\Throwable $e) { $output->writeln('同步异常: ' . $e->getMessage() . ''); Log::error('qywx:sync-msg-archive 异常: ' . $e->getMessage()); return 1; } finally { flock($fp, LOCK_UN); fclose($fp); } } }