112 lines
4.7 KiB
PHP
112 lines
4.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace app\command;
|
||
|
||
use app\adminapi\logic\qywx\CustomerLogic;
|
||
use think\console\Command;
|
||
use think\console\Input;
|
||
use think\console\Output;
|
||
use think\facade\Log;
|
||
|
||
/**
|
||
* 企业微信客户同步定时任务
|
||
*
|
||
* 使用方法:
|
||
* php think qywx:sync-customer
|
||
* php think qywx:sync-customer --force --today # 仅落库「今日首次加为外部联系人」的客户(app.default_timezone)
|
||
* php think qywx:sync-customer --force --today --today-any-follow # 任一条跟进在今天即落库(旧逻辑,命中多)
|
||
*
|
||
* 说明:企微「客户列表」API 不支持按时间筛选;--today 只减少写库条数。拉取侧默认用 batch/get_by_user 批量详情(project.qywx_sync_use_batch_detail)加速。
|
||
*
|
||
* 配置crontab(每小时执行一次):
|
||
* 0 * * * * cd /path/to/project && php think qywx:sync-customer >> /dev/null 2>&1
|
||
*/
|
||
class QywxSyncCustomer extends Command
|
||
{
|
||
protected function configure()
|
||
{
|
||
$this->setName('qywx:sync-customer')
|
||
->addOption('force', 'f', \think\console\input\Option::VALUE_NONE, '强制同步,忽略自动同步设置')
|
||
->addOption(
|
||
'today',
|
||
't',
|
||
\think\console\input\Option::VALUE_NONE,
|
||
'仅落库「企微首次添加时间」在今日的客户(min(createtime),时区见 app.default_timezone);仍会拉全量列表与详情'
|
||
)
|
||
->addOption(
|
||
'today-any-follow',
|
||
null,
|
||
\think\console\input\Option::VALUE_NONE,
|
||
'需与 --today 同时使用:任一条跟进 createtime 在今日即落库(旧行为,容易大量命中)'
|
||
)
|
||
->setDescription('同步企业微信客户数据');
|
||
}
|
||
|
||
protected function execute(Input $input, Output $output)
|
||
{
|
||
$output->writeln('开始同步企业微信客户...');
|
||
|
||
try {
|
||
$force = $input->getOption('force');
|
||
|
||
if (!$force) {
|
||
// 检查是否开启自动同步
|
||
$settings = CustomerLogic::getSyncSettings();
|
||
if (!($settings['auto_sync'] ?? false)) {
|
||
$output->writeln('自动同步未开启,跳过(使用 --force 强制同步)');
|
||
return 0;
|
||
}
|
||
|
||
// 检查距离上次同步是否超过间隔时间
|
||
$lastSyncTime = $settings['last_sync_time'] ?? 0;
|
||
$interval = $settings['interval'] ?? 3600;
|
||
$now = time();
|
||
|
||
if ($now - $lastSyncTime < $interval) {
|
||
$output->writeln('距离上次同步时间不足,跳过(使用 --force 强制同步)');
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
// 执行同步
|
||
$syncOptions = [];
|
||
if ($input->getOption('today')) {
|
||
[$syncOptions['follow_createtime_from'], $syncOptions['follow_createtime_to']] = CustomerLogic::todayCreatetimeWindowBounds();
|
||
$syncOptions['follow_createtime_mode'] = $input->getOption('today-any-follow') ? 'any' : 'first';
|
||
$tz = (string) config('app.default_timezone', 'Asia/Shanghai');
|
||
$output->writeln(sprintf(
|
||
'今日窗口(%s): %s ~ %s | 模式: %s',
|
||
$tz,
|
||
date('Y-m-d H:i:s', $syncOptions['follow_createtime_from']),
|
||
date('Y-m-d H:i:s', $syncOptions['follow_createtime_to']),
|
||
$syncOptions['follow_createtime_mode'] === 'any' ? '任一条跟进在今天' : '仅首次添加在今天(今日新客)'
|
||
));
|
||
}
|
||
$result = CustomerLogic::syncCustomers($syncOptions);
|
||
if ($result === false) {
|
||
$error = CustomerLogic::getError();
|
||
$output->writeln('同步失败: ' . $error);
|
||
Log::error('企业微信客户同步失败: ' . $error);
|
||
return 1;
|
||
}
|
||
|
||
$output->writeln('同步成功!');
|
||
$output->writeln('同步数量: ' . ($result['sync_count'] ?? 0));
|
||
$output->writeln('新增数量: ' . ($result['new_count'] ?? 0));
|
||
$output->writeln('更新数量: ' . ($result['update_count'] ?? 0));
|
||
$skipped = (int) ($result['skipped_count'] ?? 0);
|
||
if ($skipped > 0) {
|
||
$output->writeln('跳过数量(非指定日期内新建跟进): ' . $skipped);
|
||
}
|
||
|
||
return 0;
|
||
} catch (\Throwable $e) {
|
||
$output->writeln('同步异常: ' . $e->getMessage());
|
||
Log::error('企业微信客户同步异常: ' . $e->getMessage());
|
||
return 1;
|
||
}
|
||
}
|
||
}
|