Files
zyt/server/app/command/QywxSyncMsgArchive.php
T
2026-05-11 17:49:38 +08:00

116 lines
4.4 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace app\command;
use app\common\service\wechat\QywxMsgArchiveService;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Log;
/**
* 企业微信会话内容存档同步
*
* 使用方法:
* php think qywx:sync-msg-archive # 只拉消息
* php think qywx:sync-msg-archive --download # 拉完后同时下载 pending 媒体
* php think qywx:sync-msg-archive --only-media # 仅下载已登记的 pending 媒体
*
* crontab 建议:每 30 秒拉一次(用 sleep 模拟半分钟级)
* * * * * * cd /path/to/project && php think qywx:sync-msg-archive --download >> /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('<error>无法创建锁文件: ' . $lockFile . '</error>');
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('<comment>会话存档未启用或 SDK 不可用:' . implode('; ', $pull['errors']) . '</comment>');
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('<comment>error: ' . $err . '</comment>');
}
}
}
if ($onlyMedia || $download) {
$media = QywxMsgArchiveService::downloadPendingMedia($maxMedia);
if (!$media['enabled']) {
$output->writeln('<comment>媒体下载-SDK 未启用</comment>');
} else {
$output->writeln(sprintf(
'媒体下载:ok=%d failed=%d skipped=%d',
$media['ok'],
$media['failed'],
$media['skipped']
));
}
}
return 0;
} catch (\Throwable $e) {
$output->writeln('<error>同步异常: ' . $e->getMessage() . '</error>');
Log::error('qywx:sync-msg-archive 异常: ' . $e->getMessage());
return 1;
} finally {
flock($fp, LOCK_UN);
fclose($fp);
}
}
}