存客宝应用接口初始化

This commit is contained in:
Ghost
2026-01-05 10:25:32 +08:00
parent fa37227954
commit 02797ba50a
414 changed files with 85530 additions and 75 deletions

View File

@@ -0,0 +1,115 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use app\api\controller\AccountController;
class AccountListJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processAccountList($data, $job->attempts())) {
$job->delete();
Log::info('公司账号列表任务执行成功,页码:' . $data['pageIndex']);
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('公司账号列表任务执行失败,已超过重试次数,页码:' . $data['pageIndex']);
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('公司账号列表任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $data['pageIndex']);
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('公司账号列表任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理公司账号列表获取
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processAccountList($data, $attempts)
{
// 获取参数
$pageIndex = isset($data['pageIndex']) ? $data['pageIndex'] : 0;
$pageSize = isset($data['pageSize']) ? $data['pageSize'] : 100;
Log::info('开始获取公司账号列表,页码:' . $pageIndex . ',页大小:' . $pageSize);
// 实例化控制器
$accountController = new AccountController();
// 构建请求参数
$params = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 设置请求信息
$request = request();
$request->withGet($params);
// 调用公司账号列表获取方法
$result = $accountController->getlist(['pageIndex' => $pageIndex,'pageSize' => $pageSize],true);
$response = json_decode($result,true);
// 判断是否成功
if ($response['code'] == 200) {
$data = $response['data'];
// 判断是否有下一页
if (!empty($data) && count($data['results']) > 0) {
// 有下一页,将下一页任务添加到队列
$nextPageIndex = $pageIndex + 1;
$this->addNextPageToQueue($nextPageIndex, $pageSize);
Log::info('添加下一页任务到队列,页码:' . $nextPageIndex);
}
return true;
} else {
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('获取公司账号列表失败:' . $errorMsg);
return false;
}
}
/**
* 添加下一页任务到队列
* @param int $pageIndex 页码
* @param int $pageSize 每页大小
*/
protected function addNextPageToQueue($pageIndex, $pageSize)
{
$data = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 添加到队列,设置任务名为 account_list
Queue::push(self::class, $data, 'account_list');
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace app\job;
use app\command\AllotChatroomCommand;
use think\facade\Log;
use think\facade\Cache;
use think\Queue;
use app\api\controller\AutomaticAssign;
class AllotChatroomJob
{
/**
* 队列执行方法
* @param $job 队列任务
* @param $data 数据
* @return bool
*/
public function fire($job, $data)
{
try {
// 获取数据
$toAccountId = $data['toAccountId'];
$wechatAccountKeyword = $data['wechatAccountKeyword'];
$isDeleted = $data['isDeleted'];
$jobId = isset($data['jobId']) ? $data['jobId'] : '';
$queueLockKey = isset($data['queueLockKey']) ? $data['queueLockKey'] : '';
// 记录日志
Log::info('开始处理微信群聊自动分配任务: ' . json_encode([
'toAccountId' => $toAccountId,
'wechatAccountKeyword' => $wechatAccountKeyword,
'isDeleted' => $isDeleted,
'jobId' => $jobId,
'queueLockKey' => $queueLockKey
]));
// 如果没有提供队列锁键,生成一个
if (empty($queueLockKey)) {
$queueLockKey = "queue_lock:allot_chatroom:{$wechatAccountKeyword}";
}
// 实例化控制器
$automaticAssignController = new AutomaticAssign();
// 调用微信群聊自动分配方法
$result = $automaticAssignController->autoAllotWechatChatroom($toAccountId, $wechatAccountKeyword, $isDeleted);
$response = json_decode($result, true);
// 判断是否成功
if ($response['code'] == 1) {
Log::info("微信群聊自动分配成功: toAccountId={$toAccountId}, wechatAccountKeyword={$wechatAccountKeyword}");
// 释放队列锁
Cache::rm($queueLockKey);
Log::info("任务完成,释放队列锁: {$queueLockKey}");
$job->delete();
return true;
} else {
// API调用出错记录错误
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error("微信群聊自动分配失败: {$errorMsg}");
if ($job->attempts() > 3) {
// 超过重试次数,删除任务并释放队列锁
Cache::rm($queueLockKey);
Log::info("由于错误多次尝试失败,释放队列锁: {$queueLockKey}");
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning("微信群聊自动分配任务执行失败,重试次数: " . $job->attempts());
$job->release(10); // 延迟10秒后重试
}
return false;
}
} catch (\Exception $e) {
// 出现异常,记录错误并释放队列锁
Log::error('微信群聊自动分配任务异常: ' . $e->getMessage());
if (!empty($queueLockKey)) {
Cache::rm($queueLockKey);
Log::info("由于异常释放队列锁: {$queueLockKey}");
}
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(10);
}
return false;
}
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace app\job;
use app\command\AllotFriendCommand;
use think\facade\Log;
use think\facade\Cache;
use think\Queue;
use app\api\controller\AutomaticAssign;
class AllotFriendJob
{
/**
* 队列执行方法
* @param $job 队列任务
* @param $data 数据
* @return bool
*/
public function fire($job, $data)
{
try {
// 获取数据
$toAccountId = $data['toAccountId'];
$wechatAccountKeyword = $data['wechatAccountKeyword'];
$isDeleted = $data['isDeleted'];
$jobId = isset($data['jobId']) ? $data['jobId'] : '';
$queueLockKey = isset($data['queueLockKey']) ? $data['queueLockKey'] : '';
// 记录日志
Log::info('开始处理微信好友自动分配任务: ' . json_encode([
'toAccountId' => $toAccountId,
'wechatAccountKeyword' => $wechatAccountKeyword,
'isDeleted' => $isDeleted,
'jobId' => $jobId,
'queueLockKey' => $queueLockKey
]));
// 如果没有提供队列锁键,生成一个
if (empty($queueLockKey)) {
$queueLockKey = "queue_lock:allot_friends:{$wechatAccountKeyword}";
}
// 实例化控制器
$automaticAssignController = new AutomaticAssign();
// 调用微信好友自动分配方法
$result = $automaticAssignController->autoAllotWechatFriend($toAccountId, $wechatAccountKeyword, $isDeleted);
$response = json_decode($result, true);
// 判断是否成功
if ($response['code'] == 1) {
Log::info("微信好友自动分配成功: toAccountId={$toAccountId}, wechatAccountKeyword={$wechatAccountKeyword}");
// 释放队列锁
Cache::rm($queueLockKey);
Log::info("任务完成,释放队列锁: {$queueLockKey}");
$job->delete();
return true;
} else {
// API调用出错记录错误
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error("微信好友自动分配失败: {$errorMsg}");
if ($job->attempts() > 3) {
// 超过重试次数,删除任务并释放队列锁
Cache::rm($queueLockKey);
Log::info("由于错误多次尝试失败,释放队列锁: {$queueLockKey}");
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning("微信好友自动分配任务执行失败,重试次数: " . $job->attempts());
$job->release(10); // 延迟10秒后重试
}
return false;
}
} catch (\Exception $e) {
// 出现异常,记录错误并释放队列锁
Log::error('微信好友自动分配任务异常: ' . $e->getMessage());
if (!empty($queueLockKey)) {
Cache::rm($queueLockKey);
Log::info("由于异常释放队列锁: {$queueLockKey}");
}
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(10);
}
return false;
}
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use app\api\controller\AllotRuleController;
class AllotRuleListJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processAllotRuleList($data, $job->attempts())) {
$job->delete();
Log::info('分配规则列表任务执行成功');
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('分配规则列表任务执行失败,已超过重试次数');
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('分配规则列表任务执行失败,重试次数:' . $job->attempts());
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('分配规则列表任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理分配规则列表获取
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processAllotRuleList($data, $attempts)
{
Log::info('开始获取分配规则列表');
// 实例化控制器
$allotRuleController = new AllotRuleController();
// 调用分配规则列表获取方法
$result = $allotRuleController->getAllRules([], true);
$response = json_decode($result, true);
// 判断是否成功
if ($response['code'] == 200) {
Log::info('获取分配规则列表成功,共获取 ' . (isset($response['data']) ? count($response['data']) : 0) . ' 条记录');
return true;
} else {
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('获取分配规则列表失败:' . $errorMsg);
return false;
}
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use app\api\controller\AllotRuleController;
class AutoCreateAllotRulesJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processAutoCreateAllotRules($data, $job->attempts())) {
$job->delete();
Log::info('自动创建分配规则任务执行成功,时间:' . date('Y-m-d H:i:s'));
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('自动创建分配规则任务执行失败,已超过重试次数');
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('自动创建分配规则任务执行失败,重试次数:' . $job->attempts());
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('自动创建分配规则任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理自动创建分配规则
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processAutoCreateAllotRules($data, $attempts)
{
Log::info('开始执行自动创建分配规则任务,重试次数:' . $attempts);
// 实例化控制器
$allotRuleController = new AllotRuleController();
// 调用自动创建分配规则方法
$result = $allotRuleController->autoCreateAllotRules([], true);
$response = json_decode($result, true);
// 判断是否成功
if (isset($response['code']) && $response['code'] == 200) {
Log::info('自动创建分配规则成功:' . json_encode($response['data']));
return true;
} else {
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('自动创建分配规则失败:' . $errorMsg);
return false;
}
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use app\api\controller\CallRecordingController;
class CallRecordingListJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processCallRecordingList($data, $job->attempts())) {
$job->delete();
Log::info('通话记录列表任务执行成功,页码:' . $data['pageIndex']);
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('通话记录列表任务执行失败,已超过重试次数,页码:' . $data['pageIndex']);
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('通话记录列表任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $data['pageIndex']);
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('通话记录列表任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理通话记录列表获取
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processCallRecordingList($data, $attempts)
{
// 获取参数
$pageIndex = isset($data['pageIndex']) ? $data['pageIndex'] : 0;
$pageSize = isset($data['pageSize']) ? $data['pageSize'] : 100;
Log::info('开始获取通话记录列表,页码:' . $pageIndex . ',页大小:' . $pageSize);
// 实例化控制器
$callRecordingController = new CallRecordingController();
// 构建请求参数
$params = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize,
'keyword' => '',
'isCallOut' => '',
'secondMin' => 0,
'secondMax' => 99999,
'departmentIds' => '',
'from' => date('Y-m-d 00:00:00', strtotime('-100 days')),
'to' => date('Y-m-d 23:59:59'),
'departmentId' => ''
];
// 调用通话记录列表获取方法
$result = $callRecordingController->getlist($params, true);
$response = json_decode($result, true);
// 判断是否成功
if ($response['code'] == 200) {
$data = $response['data'];
// 判断是否有下一页
if (!empty($data) && isset($data['results']) && count($data['results']) > 0) {
// 有下一页,将下一页任务添加到队列
$nextPageIndex = $pageIndex + 1;
$this->addNextPageToQueue($nextPageIndex, $pageSize);
Log::info('添加下一页任务到队列,页码:' . $nextPageIndex);
}
return true;
} else {
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('获取通话记录列表失败:' . $errorMsg);
return false;
}
}
/**
* 添加下一页任务到队列
* @param int $pageIndex 页码
* @param int $pageSize 每页大小
*/
protected function addNextPageToQueue($pageIndex, $pageSize)
{
$data = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 添加到队列,设置任务名为 call_recording_list
Queue::push(self::class, $data, 'call_recording_list');
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use app\cunkebao\controller\ContentLibraryController;
class ContentCollectJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processContentCollect($data, $job->attempts())) {
$job->delete();
// 去除成功日志,减少日志空间消耗
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('内容采集任务执行失败已超过重试次数内容库ID' . $data['libraryId']);
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('内容采集任务执行失败,重试次数:' . $job->attempts() . '内容库ID' . $data['libraryId']);
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('内容采集任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理内容采集
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processContentCollect($data, $attempts)
{
try {
$controller = new ContentLibraryController();
$result = $controller->collectMoments();
$response = json_decode($result, true);
if ($response['code'] == 200) {
// 记录详细的采集结果
if (!empty($response['data'])) {
foreach ($response['data'] as $result) {
if ($result['status'] == 'success') {
Log::info(sprintf(
'内容库[%s]采集成功: %s',
$result['library_name'],
$result['message']
));
} else {
Log::warning(sprintf(
'内容库[%s]采集失败: %s',
$result['library_name'],
$result['message']
));
}
}
}
return true;
} else {
Log::error('内容采集失败:' . ($response['msg'] ?? '未知错误'));
return false;
}
} catch (\Exception $e) {
Log::error('内容采集处理异常:' . $e->getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use app\api\controller\AccountController;
class DepartmentListJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processDepartmentList($data, $job->attempts())) {
$job->delete();
Log::info('部门列表任务执行成功,页码:' . $data['pageIndex']);
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('部门列表任务执行失败,已超过重试次数,页码:' . $data['pageIndex']);
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('部门列表任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $data['pageIndex']);
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('部门列表任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理部门列表获取
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processDepartmentList($data, $attempts)
{
// 获取参数
$pageIndex = isset($data['pageIndex']) ? $data['pageIndex'] : 0;
$pageSize = isset($data['pageSize']) ? $data['pageSize'] : 100;
Log::info('开始获取部门列表,页码:' . $pageIndex . ',页大小:' . $pageSize);
// 实例化控制器
$accountController = new AccountController();
// 构建请求参数
$params = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 设置请求信息
$request = request();
$request->withGet($params);
// 调用公司账号列表获取方法
$result = $accountController->getDepartmentList(true);
$response = json_decode($result,true);
// 判断是否成功
if ($response['code'] == 200) {
$data = $response['data'];
// 判断是否有下一页
if (!empty($data) && count($data['results']) > 0) {
// 有下一页,将下一页任务添加到队列
$nextPageIndex = $pageIndex + 1;
$this->addNextPageToQueue($nextPageIndex, $pageSize);
Log::info('添加下一页任务到队列,页码:' . $nextPageIndex);
}
return true;
} else {
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('获取部门列表失败:' . $errorMsg);
return false;
}
}
/**
* 添加下一页任务到队列
* @param int $pageIndex 页码
* @param int $pageSize 每页大小
*/
protected function addNextPageToQueue($pageIndex, $pageSize)
{
$data = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 添加到队列,设置任务名为 account_list
Queue::push(self::class, $data, 'department_list');
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace app\job;
use app\command\DeviceListCommand;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use think\facade\Cache;
use app\api\controller\DeviceController;
class DeviceListJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 获取数据
$pageIndex = $data['pageIndex'];
$pageSize = $data['pageSize'];
$isDel = $data['isDel'];
$jobId = isset($data['jobId']) ? $data['jobId'] : '';
$cacheKey = isset($data['cacheKey']) ? $data['cacheKey'] : '';
$queueLockKey = isset($data['queueLockKey']) ? $data['queueLockKey'] : '';
// 记录日志
Log::info('开始处理设备列表任务: ' . json_encode([
'pageIndex' => $pageIndex,
'pageSize' => $pageSize,
'isDel' => $isDel,
'jobId' => $jobId,
'cacheKey' => $cacheKey,
'queueLockKey' => $queueLockKey
]));
// 如果没有提供缓存键根据删除状态和任务ID生成一个
if (empty($cacheKey)) {
$cacheKeyPrefix = "devicePage:" . ($jobId ?: date('YmdHis') . rand(1000, 9999));
$cacheKeySuffix = $isDel === '' ? '' : ":{$isDel}";
$cacheKey = $cacheKeyPrefix . $cacheKeySuffix;
}
// 如果没有提供队列锁键,生成一个
if (empty($queueLockKey)) {
$queueLockKey = "queue_lock:device_list:{$isDel}";
}
// 实例化控制器
$deviceController = new DeviceController();
// 设置请求信息
$request = request();
$request->withGet([
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
]);
// 调用设备列表获取方法传入isDel参数
$result = $deviceController->getlist(['pageIndex' => $pageIndex, 'pageSize' => $pageSize], true, $isDel);
$response = json_decode($result, true);
if ($response['code'] == 200) {
$data = $response['data'];
$dataCount = count($data['results']);
$totalCount = $data['total'];
Log::info("设备列表获取成功,当前页:{$pageIndex},获取数量:{$dataCount},总数量:{$totalCount}");
// 计算是否还有下一页
$hasNextPage = ($pageIndex + 1) * $pageSize < $totalCount;
if ($hasNextPage) {
// 缓存页码信息设置有效期1天
$nextPageIndex = $pageIndex + 1;
Cache::set($cacheKey, $nextPageIndex, 600);
Log::info("更新缓存页码: {$nextPageIndex}, 缓存键: {$cacheKey}");
// 添加下一页任务到队列
$command = new DeviceListCommand();
$command->addToQueue($nextPageIndex, $pageSize, $isDel, $jobId, $cacheKey, $queueLockKey);
Log::info("已添加下一页任务到队列: 页码 {$nextPageIndex}");
} else {
// 处理完所有页面,重置页码并释放队列锁
Cache::set($cacheKey, 0, 600);
Cache::rm($queueLockKey);
Log::info("所有设备列表页面处理完毕重置页码为0释放队列锁: {$queueLockKey}");
}
$job->delete();
return true;
} else {
// API调用出错记录错误并释放队列锁
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error("设备列表获取失败: " . $errorMsg);
if ($job->attempts() > 3) {
// 超过重试次数,删除任务并释放队列锁
Cache::rm($queueLockKey);
Log::info("由于错误释放队列锁: {$queueLockKey}");
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('设备列表任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $pageIndex);
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
} catch (\Exception $e) {
// 出现异常,记录错误并释放队列锁
Log::error('设备列表任务处理异常: ' . $e->getMessage());
if (!empty($queueLockKey)) {
Cache::rm($queueLockKey);
Log::info("由于异常释放队列锁: {$queueLockKey}");
}
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
}
/**
* 获取删除状态的文本描述
* @param string $isDel 删除状态
* @return string 删除状态文本
*/
protected function getDeleteTypeText($isDel)
{
switch ($isDel) {
case '0':
case 0:
return '未删除(unDeleted)';
case '1':
case 1:
return '已删除(deleted)';
case '2':
case 2:
return '已停用(deletedAndStop)';
default:
return '全部';
}
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use think\facade\Cache;
use app\api\controller\FriendTaskController;
class FriendTaskJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processFriendTask($data, $job->attempts())) {
$job->delete();
Log::info('添加好友任务执行成功,页码:' . $data['pageIndex']);
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('添加好友任务执行失败,已超过重试次数,页码:' . $data['pageIndex']);
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('添加好友任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $data['pageIndex']);
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('添加好友任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理添加好友任务获取
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processFriendTask($data, $attempts)
{
// 获取参数
$pageIndex = isset($data['pageIndex']) ? $data['pageIndex'] : 0;
$pageSize = isset($data['pageSize']) ? $data['pageSize'] : 100;
Log::info('开始获取添加好友任务,页码:' . $pageIndex . ',页大小:' . $pageSize);
// 实例化控制器
$friendTaskController = new FriendTaskController();
// 构建请求参数
$params = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 设置请求信息
$request = request();
$request->withGet($params);
// 调用添加好友任务获取方法
$result = $friendTaskController->getlist($pageIndex, $pageSize, true);
$response = json_decode($result, true);
// 判断是否成功
if ($response['code'] == 200) {
$data = $response['data'];
// 判断是否有下一页
if (!empty($data) && count($data['results']) > 0 && $pageIndex < 2) {
// 更新缓存中的页码设置10分钟过期
Cache::set('friendTaskPage', $pageIndex + 1, 600);
Log::info('更新缓存,下一页页码:' . ($pageIndex + 1) . '缓存时间10分钟');
// 有下一页,将下一页任务添加到队列
$nextPageIndex = $pageIndex + 1;
$this->addNextPageToQueue($nextPageIndex, $pageSize);
Log::info('添加下一页任务到队列,页码:' . $nextPageIndex);
} else {
// 没有下一页重置缓存设置10分钟过期
Cache::set('friendTaskPage', 0, 600);
Log::info('获取完成重置缓存缓存时间10分钟');
}
return true;
} else {
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('获取添加好友任务失败:' . $errorMsg);
return false;
}
}
/**
* 添加下一页任务到队列
* @param int $pageIndex 页码
* @param int $pageSize 每页大小
*/
protected function addNextPageToQueue($pageIndex, $pageSize)
{
$data = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 添加到队列,设置任务名为 friend_task
Queue::push(self::class, $data, 'friend_task');
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use think\facade\Cache;
use app\api\controller\WechatChatroomController;
use app\api\model\WechatChatroomModel;
use think\Db;
class GroupFriendsJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processGroupFriendsList($data, $job->attempts())) {
$job->delete();
Log::info('微信群好友列表任务执行成功,页码:' . $data['pageIndex']);
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('微信群好友列表任务执行失败,已超过重试次数,页码:' . $data['pageIndex']);
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('微信群好友列表任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $data['pageIndex']);
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('微信群好友列表任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理微信群好友列表获取
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processGroupFriendsList($data, $attempts)
{
// 获取参数
$pageIndex = isset($data['pageIndex']) ? $data['pageIndex'] : 0;
$pageSize = isset($data['pageSize']) ? $data['pageSize'] : 100;
Log::info('开始获取微信群好友列表,页码:' . $pageIndex . ',页大小:' . $pageSize);
try {
// 从数据库获取未删除的群聊列表
$chatrooms = WechatChatroomModel::where('isDeleted', 0)
->page($pageIndex + 1, $pageSize)
->order('id', 'desc')
->select();
if (empty($chatrooms)) {
Log::info('未找到需要处理的群聊数据,页码:' . $pageIndex);
return true;
}
// 实例化控制器
$wechatChatroomController = new WechatChatroomController();
// 遍历每个群聊,获取其成员
foreach ($chatrooms as $chatroom) {
try {
// 调用获取群成员列表方法
$result = $wechatChatroomController->listChatroomMember($chatroom['id'],$chatroom['chatroomId'],true);
$response = is_string($result) ? json_decode($result, true) : $result;
// 判断是否成功
if (is_array($response) && isset($response['code']) && $response['code'] == 200) {
//Log::info('成功获取群 ' . $chatroom['chatroomId'] . ' 的成员列表');
} else {
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('获取群 ' . $chatroom['chatroomId'] . ' 的成员列表失败:' . $errorMsg);
}
} catch (\Exception $e) {
Log::error('获取群 ' . $chatroom['chatroomId'] . ' 的成员列表异常:' . $e->getMessage());
}
}
//Log::info('群成员获取完成,成功:' . $successCount . ',失败:' . $failCount);
// 计算总数量
$totalCount = WechatChatroomModel::where('isDeleted', 0)->count();
$processedCount = ($pageIndex + 1) * $pageSize;
// 判断是否有下一页
if ($processedCount < $totalCount) {
// 更新缓存中的页码,设置一天过期
Cache::set('groupFriendsPage', $pageIndex + 1, 600);
//Log::info('更新缓存,下一页页码:' . ($pageIndex + 1) . '缓存时间1天');
// 有下一页,将下一页任务添加到队列
$nextPageIndex = $pageIndex + 1;
$this->addNextPageToQueue($nextPageIndex, $pageSize);
Log::info('添加下一页任务到队列,页码:' . $nextPageIndex);
} else {
// 没有下一页,重置缓存,设置一天过期
Cache::set('groupFriendsPage', 0, 600);
Log::info('获取完成重置缓存缓存时间1天');
}
return true;
} catch (\Exception $e) {
Log::error('获取微信群好友列表处理失败:' . $e->getMessage());
return false;
}
}
/**
* 添加下一页任务到队列
* @param int $pageIndex 页码
* @param int $pageSize 每页大小
*/
protected function addNextPageToQueue($pageIndex, $pageSize)
{
$data = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 添加到队列,设置任务名为 group_friends
Queue::push(self::class, $data, 'group_friends');
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use app\api\controller\MessageController;
class MessageChatroomListJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processMessageChatroomList($data, $job->attempts())) {
$job->delete();
Log::info('微信群聊消息列表任务执行成功,页码:' . $data['pageIndex']);
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('微信群聊消息列表任务执行失败,已超过重试次数,页码:' . $data['pageIndex']);
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('微信群聊消息列表任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $data['pageIndex']);
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('微信群聊消息列表任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理微信群聊消息列表获取
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processMessageChatroomList($data, $attempts)
{
// 获取参数
$pageIndex = isset($data['pageIndex']) ? $data['pageIndex'] : 0;
$pageSize = isset($data['pageSize']) ? $data['pageSize'] : 100;
Log::info('开始获取微信群聊消息列表,页码:' . $pageIndex . ',页大小:' . $pageSize);
// 实例化控制器
$messageController = new MessageController();
// 构建请求参数
$params = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 设置请求信息
$request = request();
$request->withGet($params);
// 调用添加好友任务获取方法
$result = $messageController->getChatroomList($pageIndex,$pageSize,true);
$response = json_decode($result,true);
// 判断是否成功
if ($response['code'] == 200) {
$data = $response['data'];
// 判断是否有下一页
if (!empty($data) && count($data['results']) > 0) {
// 有下一页,将下一页任务添加到队列
$nextPageIndex = $pageIndex + 1;
$this->addNextPageToQueue($nextPageIndex, $pageSize);
Log::info('添加下一页任务到队列,页码:' . $nextPageIndex);
}
return true;
} else {
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('获取微信群聊消息列表失败:' . $errorMsg);
return false;
}
}
/**
* 添加下一页任务到队列
* @param int $pageIndex 页码
* @param int $pageSize 每页大小
*/
protected function addNextPageToQueue($pageIndex, $pageSize)
{
$data = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 添加到队列,设置任务名为 message_chatroom_list
Queue::push(self::class, $data, 'message_chatroom_list');
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use app\api\controller\MessageController;
class MessageFriendsListJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processMessageFriendsList($data, $job->attempts())) {
$job->delete();
// 去除成功日志,减少日志空间消耗
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('好友消息列表任务执行失败,已超过重试次数,页码:' . $data['pageIndex']);
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('好友消息列表任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $data['pageIndex']);
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('好友消息列表任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理好友消息列表获取
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processMessageFriendsList($data, $attempts)
{
// 获取参数
$pageIndex = isset($data['pageIndex']) ? $data['pageIndex'] : 0;
$pageSize = isset($data['pageSize']) ? $data['pageSize'] : 100;
// 去除开始日志,减少日志空间消耗
// 实例化控制器
$messageController = new MessageController();
// 构建请求参数
$params = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 设置请求信息
$request = request();
$request->withGet($params);
// 调用添加好友任务获取方法
$result = $messageController->getFriendsList($pageIndex,$pageSize,true);
$response = json_decode($result,true);
// 判断是否成功
if ($response['code'] == 200) {
$data = $response['data'];
// 判断是否有下一页
if (!empty($data) && count($data) > 0) {
// 有下一页,将下一页任务添加到队列
$nextPageIndex = $pageIndex + 1;
$this->addNextPageToQueue($nextPageIndex, $pageSize);
Log::info('添加下一页任务到队列,页码:' . $nextPageIndex);
}
return true;
} else {
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('获取好友消息列表失败:' . $errorMsg);
return false;
}
}
/**
* 添加下一页任务到队列
* @param int $pageIndex 页码
* @param int $pageSize 每页大小
*/
protected function addNextPageToQueue($pageIndex, $pageSize)
{
$data = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 添加到队列,设置任务名为 message_friends_list
Queue::push(self::class, $data, 'message_friends_list');
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use think\Db;
use think\facade\Env;
use app\api\controller\WebSocketController;
class OwnMomentsCollectJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processOwnMomentsCollect($data, $job->attempts())) {
$job->delete();
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('自己朋友圈采集任务执行失败,已超过重试次数');
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('自己朋友圈采集任务执行失败,重试次数:' . $job->attempts());
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('自己朋友圈采集任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理自己朋友圈采集
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processOwnMomentsCollect($data, $attempts)
{
try {
// 获取在线微信账号列表(只获取在线微信)
$onlineWechatAccounts = $this->getOnlineWechatAccounts();
if (empty($onlineWechatAccounts)) {
return true;
}
// 获取API账号配置
$username = Env::get('api.username2', '');
$password = Env::get('api.password2', '');
if (empty($username) || empty($password)) {
Log::error('API账号配置缺失无法执行朋友圈采集');
return false;
}
// 获取账号ID
$toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId');
if (empty($toAccountId)) {
Log::error('未找到API账号对应的账号ID');
return false;
}
$successCount = 0;
$failCount = 0;
// 遍历每个在线微信账号,采集自己的朋友圈
foreach ($onlineWechatAccounts as $account) {
try {
$wechatAccountId = $account['id'];
$wechatId = $account['wechatId'];
// 创建WebSocket控制器实例
$webSocket = new WebSocketController([
'userName' => $username,
'password' => $password,
'accountId' => $toAccountId
]);
// 采集自己的朋友圈wechatFriendId传0或空表示采集自己的朋友圈
$result = $webSocket->getMoments([
'wechatAccountId' => $wechatAccountId,
'wechatFriendId' => 0, // 0表示采集自己的朋友圈
'count' => 10 // 每次采集10条
]);
$resultData = json_decode($result, true);
if (!empty($resultData) && $resultData['code'] == 200) {
$successCount++;
} else {
$failCount++;
Log::warning("微信账号 {$wechatId} 朋友圈采集失败:" . ($resultData['msg'] ?? '未知错误'));
}
// 避免请求过于频繁,每个账号之间稍作延迟
usleep(500000); // 延迟0.5秒
} catch (\Exception $e) {
$failCount++;
Log::error("采集微信账号 {$account['wechatId']} 朋友圈异常:" . $e->getMessage());
continue;
}
}
Log::info("自己朋友圈采集任务完成,成功:{$successCount},失败:{$failCount}");
return true;
} catch (\Exception $e) {
Log::error('自己朋友圈采集处理异常:' . $e->getMessage());
return false;
}
}
/**
* 获取在线微信账号列表
* @return array
*/
protected function getOnlineWechatAccounts()
{
try {
// 查询在线微信账号deviceAlive=1 且 wechatAlive=1
$accounts = Db::table('s2_wechat_account')
->where('deviceAlive', 1)
->where('wechatAlive', 1)
->field('id, wechatId, nickname, alias')
->select();
return $accounts ?: [];
} catch (\Exception $e) {
Log::error('获取在线微信账号列表失败:' . $e->getMessage());
return [];
}
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\facade\Cache;
use think\facade\Config;
use think\Db;
class SyncAllFriendsJob
{
public function fire(Job $job, $data)
{
try {
$wechatId = 'Lytiao1';
$pageIndex = $data['pageIndex'];
$pageSize = $data['pageSize'];
$preFriendId = isset($data['preFriendId']) ? $data['preFriendId'] : '';
$queueLockKey = $data['queueLockKey'];
$jobId = $data['jobId'];
$controller = new \app\api\controller\WechatFriendController();
Log::info('开始同步微信id: ' . $wechatId . ',第' . $pageIndex . '页preFriendId: ' . $preFriendId);
$result = $controller->getlist([
'wechatAccountKeyword' => $wechatId,
'pageIndex' => $pageIndex,
'pageSize' => $pageSize,
'preFriendId' => $preFriendId
], true);
if (is_string($result)) {
$result = json_decode($result, true);
}
if ($result['code'] == 200) {
$friends = $result['data']['list'] ?? $result['data'];
if (is_array($friends) && count($friends) == $pageSize) {
$lastFriendId = $friends[count($friends) - 1]['id'];
// 还有下一页,重新入队
$nextPageIndex = $pageIndex + 1;
\think\Queue::push(self::class, [
'wechatId' => $wechatId,
'pageIndex' => $nextPageIndex,
'pageSize' => $pageSize,
'preFriendId' => $lastFriendId,
'jobId' => $jobId,
'queueLockKey' => $queueLockKey
], $job->getQueue());
Log::info("微信id: {$wechatId} 下一页任务已入队pageIndex: {$nextPageIndex}preFriendId: {$lastFriendId}");
}
}
$job->delete();
Log::info('同步微信id: ' . $wechatId . ' 第' . $pageIndex . '页任务执行成功');
// 释放锁逻辑可在所有账号所有分页都完成后处理
return true;
} catch (\Exception $e) {
Log::error('同步所有好友任务异常:' . $e->getMessage());
if (!empty($data['queueLockKey'])) {
\think\facade\Cache::rm($data['queueLockKey']);
}
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(\think\facade\Config::get('queue.failed_delay', 10));
}
return false;
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace app\job;
use app\chukebao\model\Reply;
use app\chukebao\model\ReplyGroup;
use think\Db;
use think\queue\Job;
class SyncContentJob
{
public function fire(Job $job, $data)
{
$ddd= Db::table('s2_wechat_friend')->where('ownerWechatId','wxid_h7nsh7vxseyn29')->select();
foreach ($ddd as $v) {
$d = Db::table('ck_task_customer')->where('task_id','167')->where('phone',$v['wechatId'])->find();
if (!empty($d) && !in_array($d['status'],[4,5])) {
Db::table('ck_task_customer')->where('id',$d['id'])->update(['status'=>5]);
}
}
exit_data(111);
return true;
/*$ddd= '[{"id":21909,"groupName":"私域运营招聘","sortIndex":240426546,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":8074,"groupName":"存客宝新客户介绍","sortIndex":230,"parentId":0,"replyType":1,"children":[{"id":8081,"groupName":"客户系统上线准备","sortIndex":1,"parentId":8074,"replyType":1,"children":[],"replys":null}],"replys":null},{"id":10441,"groupName":"BOSS直聘","sortIndex":229,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":15111,"groupName":"点了码新客户了解","sortIndex":228,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":12732,"groupName":"测试","sortIndex":219,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":8814,"groupName":"封单话术","sortIndex":176,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":8216,"groupName":"私域合伙人","sortIndex":172,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":6476,"groupName":"新客户了解","sortIndex":119,"parentId":0,"replyType":1,"children":[],"replys":null}]';
$ddd=json_decode($ddd,1);*/
$ddd = ReplyGroup::where('companyId',2778)->where('companyId','<>',21898)->where('isDel',0)->select()->toArray();
$authorization = 'OE7kh6Dsw_0SqqH1FTAPCB2ewCQDhx7VvPw6PrsE_p9tcRKbtlFsZau8kjk2NQ829Yah90KhTh0C_35ek569uRQgM_gC0NtKzfRPDDoqMIUE5mI6AO_hm0dm-xDJqhAFYkXHCdXnJYzQZxWS5dleJCIwtQxgRuIzIbr-_G_5C-7DeLEOSt2vi1oGPleLt00QGQ1WYVYqoHYrbPGMghMQpWIbgk5qNcUCeANlLJ_s7QFC3QzArU95_YiK0HlhU81hZqr8kI_5lmdrRBoR-yNIlyhySLRCmEZYGzOxCiUHL3uFHYZA1VnLBAVbryNj5DElZjMgwA';
// 设置请求头
$headerData = ['client:system'];
$header = setHeader($headerData, $authorization, 'json');
foreach ($ddd as $key => $value) {
$data = [];
// 发送请求获取公司账号列表
$result = requestCurl('https://s2.siyuguanli.com:9991/api/Reply/listReply?groupId='.$value['id'], '', 'GET', $header,'json');
$response = handleApiResponse($result);
foreach ($response as $k => $v) {
$data[] = [
'groupId' => $v['groupId'],
'userId' => $value['userId'],
'title' => $v['title'],
'msgType' => $v['msgType'],
'content' => $v['content'],
'createTime' => strtotime($v['createTime']),
'lastUpdateTime' => strtotime($v['lastUpdateTime']),
'sortIndex' => 50
];
}
$Reply = new Reply();
$Reply->insertAll($data);
}
exit_data(11111);
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace app\job;
use app\command\WechatChatroomCommand;
use think\facade\Log;
use think\facade\Cache;
use think\Queue;
use app\api\controller\WechatChatroomController;
class WechatChatroomJob
{
/**
* 队列执行方法
* @param $data 数据
* @return array|bool
*/
public function fire($job, $data)
{
try {
// 获取数据
$pageIndex = $data['pageIndex'];
$pageSize = $data['pageSize'];
$isDel = $data['isDel'];
$jobId = isset($data['jobId']) ? $data['jobId'] : '';
$cacheKey = isset($data['cacheKey']) ? $data['cacheKey'] : '';
$queueLockKey = isset($data['queueLockKey']) ? $data['queueLockKey'] : '';
// 记录日志
Log::info('开始处理微信聊天室列表任务: ' . json_encode([
'pageIndex' => $pageIndex,
'pageSize' => $pageSize,
'isDel' => $isDel,
'jobId' => $jobId,
'cacheKey' => $cacheKey,
'queueLockKey' => $queueLockKey
]));
// 如果没有提供缓存键根据删除状态和任务ID生成一个
if (empty($cacheKey)) {
$cacheKeyPrefix = "chatroomPage:" . ($jobId ?: date('YmdHis') . rand(1000, 9999));
$cacheKeySuffix = $isDel === '' ? '' : ":{$isDel}";
$cacheKey = $cacheKeyPrefix . $cacheKeySuffix;
}
// 如果没有提供队列锁键,生成一个
if (empty($queueLockKey)) {
$queueLockKey = "queue_lock:wechat_chatroom:{$isDel}";
}
// 调用业务逻辑获取微信聊天室列表
$logic = new WechatChatroomController();
$result = $logic->getlist(['pageIndex' => $pageIndex, 'pageSize' => $pageSize],true, $isDel);
$response = json_decode($result, true);
$data = $response['data'];
// 判断是否有下一页
if (!empty($data) && count($data['results']) > 0 && empty($response['isUpdate'])) {
$dataCount = count($data['results']);
$totalCount = $data['total'];
// 计算是否还有下一页
$hasNextPage = ($pageIndex + 1) * $pageSize < $totalCount;
if ($hasNextPage) {
// 缓存页码信息设置有效期1天
$nextPageIndex = $pageIndex + 1;
Cache::set($cacheKey, $nextPageIndex, 600);
Log::info("更新缓存页码: {$nextPageIndex}, 缓存键: {$cacheKey}");
// 添加下一页任务到队列
$command = new WechatChatroomCommand();
$command->addToQueue($nextPageIndex, $pageSize, $isDel, $jobId, $cacheKey, $queueLockKey);
Log::info("已添加下一页任务到队列: 页码 {$nextPageIndex}");
} else {
// 处理完所有页面,重置页码并释放队列锁
Cache::set($cacheKey, 0, 600);
Cache::rm($queueLockKey);
Log::info("所有微信聊天室列表页面处理完毕重置页码为0释放队列锁: {$queueLockKey}");
}
} else {
// API调用出错记录错误并释放队列锁
Log::error("微信聊天室列表获取失败: " . $response['msg']);
Cache::rm($queueLockKey);
Log::info("由于错误释放队列锁: {$queueLockKey}");
}
$job->delete();
return true;
} catch (\Exception $e) {
// 出现异常,记录错误并释放队列锁
Log::error('微信聊天室列表任务处理异常: ' . $e->getMessage());
if (!empty($queueLockKey)) {
Cache::rm($queueLockKey);
Log::info("由于异常释放队列锁: {$queueLockKey}");
}
$job->delete();
return false;
}
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace app\job;
use app\command\WechatFriendCommand;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use think\facade\Cache;
use app\api\controller\WechatFriendController;
class WechatFriendJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 获取数据
$pageIndex = $data['pageIndex'];
$pageSize = $data['pageSize'];
$preFriendId = $data['preFriendId'];
$isDel = $data['isDel'];
$jobId = isset($data['jobId']) ? $data['jobId'] : '';
$pageIndexCacheKey = isset($data['pageIndexCacheKey']) ? $data['pageIndexCacheKey'] : '';
$preFriendIdCacheKey = isset($data['preFriendIdCacheKey']) ? $data['preFriendIdCacheKey'] : '';
$queueLockKey = isset($data['queueLockKey']) ? $data['queueLockKey'] : '';
// 记录日志
Log::info('开始处理微信好友列表任务: ' . json_encode([
'pageIndex' => $pageIndex,
'pageSize' => $pageSize,
'preFriendId' => $preFriendId,
'isDel' => $isDel,
'jobId' => $jobId,
'pageIndexCacheKey' => $pageIndexCacheKey,
'preFriendIdCacheKey' => $preFriendIdCacheKey,
'queueLockKey' => $queueLockKey
]));
// 如果没有提供缓存键根据删除状态和任务ID生成
if (empty($pageIndexCacheKey)) {
$cacheKeyPrefix = "friendsPage:" . ($jobId ?: date('YmdHis') . rand(1000, 9999));
$cacheKeySuffix = $isDel === '' ? '' : ":{$isDel}";
$pageIndexCacheKey = $cacheKeyPrefix . $cacheKeySuffix;
}
if (empty($preFriendIdCacheKey)) {
$cacheKeyPrefix = "preFriendId:" . ($jobId ?: date('YmdHis') . rand(1000, 9999));
$cacheKeySuffix = $isDel === '' ? '' : ":{$isDel}";
$preFriendIdCacheKey = $cacheKeyPrefix . $cacheKeySuffix;
}
// 如果没有提供队列锁键,生成一个
if (empty($queueLockKey)) {
$queueLockKey = "queue_lock:wechat_friends:{$isDel}";
}
// 实例化控制器
$wechatFriendController = new WechatFriendController();
// 设置请求信息
$request = request();
$request->withGet([
'pageIndex' => $pageIndex,
'pageSize' => $pageSize,
'preFriendId' => $preFriendId
]);
// 调用微信好友列表获取方法传入isDel参数
$result = $wechatFriendController->getlist([
'pageIndex' => $pageIndex,
'pageSize' => $pageSize,
'preFriendId' => $preFriendId,
], true, $isDel);
$response = json_decode($result, true);
// 判断是否成功
if ($response['code'] == 200) {
$data = $response['data'];
// 判断是否有下一页
if (!empty($data) && count($data) > 0 && empty($response['isUpdate'])) {
// 获取最后一条记录的ID
$lastFriendId = $data[count($data)-1]['id'];
// 更新缓存中的页码和最后一个好友ID设置1天过期
$nextPageIndex = $pageIndex + 1;
Cache::set($pageIndexCacheKey, $nextPageIndex, 600);
Cache::set($preFriendIdCacheKey, $lastFriendId, 600);
Log::info("更新缓存,下一页页码:{$nextPageIndex}最后好友ID{$lastFriendId},缓存键: {$pageIndexCacheKey}, {$preFriendIdCacheKey}");
// 有下一页,将下一页任务添加到队列
$command = new WechatFriendCommand();
$command->addToQueue($nextPageIndex, $pageSize, $lastFriendId, $isDel, $jobId, $pageIndexCacheKey, $preFriendIdCacheKey, $queueLockKey);
Log::info("已添加下一页任务到队列: 页码 {$nextPageIndex}");
} else {
// 没有下一页,重置缓存并释放队列锁
Cache::set($pageIndexCacheKey, 0, 600);
Cache::set($preFriendIdCacheKey, '', 600);
Cache::rm($queueLockKey);
Log::info("所有微信好友列表页面处理完毕重置页码为0释放队列锁: {$queueLockKey}");
}
$job->delete();
Log::info('微信好友列表任务执行成功,页码:' . $pageIndex . ',删除状态:' . $this->getDeleteStatusText($isDel));
return true;
} else {
// API调用出错记录错误
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('获取微信好友列表失败:' . $errorMsg);
if ($job->attempts() > 3) {
// 超过重试次数,删除任务并释放队列锁
Cache::rm($queueLockKey);
Log::info("由于错误释放队列锁: {$queueLockKey}");
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('微信好友列表任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $pageIndex);
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
} catch (\Exception $e) {
// 出现异常,记录错误并释放队列锁
Log::error('微信好友列表任务异常:' . $e->getMessage());
if (!empty($queueLockKey)) {
Cache::rm($queueLockKey);
Log::info("由于异常释放队列锁: {$queueLockKey}");
}
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
}
/**
* 获取删除状态的文本描述
* @param string $isDel 删除状态
* @return string 状态文本描述
*/
protected function getDeleteStatusText($isDel)
{
switch ($isDel) {
case '0':
case 0:
return '未删除(false)';
case '1':
case 1:
return '已删除(true)';
default:
return '全部';
}
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use app\api\controller\WechatController;
class WechatListJob
{
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return void
*/
public function fire(Job $job, $data)
{
try {
// 如果任务执行成功后删除任务
if ($this->processWechatList($data, $job->attempts())) {
$job->delete();
Log::info('微信客服列表任务执行成功,页码:' . $data['pageIndex']);
} else {
if ($job->attempts() > 3) {
// 超过重试次数,删除任务
Log::error('微信客服列表任务执行失败,已超过重试次数,页码:' . $data['pageIndex']);
$job->delete();
} else {
// 任务失败,重新放回队列
Log::warning('微信客服列表任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $data['pageIndex']);
$job->release(Config::get('queue.failed_delay', 10));
}
}
} catch (\Exception $e) {
// 出现异常,记录日志
Log::error('微信客服列表任务异常:' . $e->getMessage());
if ($job->attempts() > 3) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
}
}
/**
* 处理微信客服列表获取
* @param array $data 任务数据
* @param int $attempts 重试次数
* @return bool
*/
protected function processWechatList($data, $attempts)
{
// 获取参数
$pageIndex = isset($data['pageIndex']) ? $data['pageIndex'] : 0;
$pageSize = isset($data['pageSize']) ? $data['pageSize'] : 1000;
Log::info('开始获取微信客服列表,页码:' . $pageIndex . ',页大小:' . $pageSize);
// 实例化控制器
$wechatController = new WechatController();
// 构建请求参数
$params = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 设置请求信息
$request = request();
$request->withGet($params);
// 调用设备列表获取方法
$result = $wechatController->getlist($pageIndex,$pageSize,true);
$response = json_decode($result,true);
// 判断是否成功
if ($response['code'] == 200) {
$data = $response['data'];
// 判断是否有下一页
if (!empty($data) && count($data['results']) > 0) {
// 有下一页,将下一页任务添加到队列
$nextPageIndex = $pageIndex + 1;
$this->addNextPageToQueue($nextPageIndex, $pageSize);
Log::info('添加下一页任务到队列,页码:' . $nextPageIndex);
}
return true;
} else {
$errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误';
Log::error('获取微信客服列表失败:' . $errorMsg);
return false;
}
}
/**
* 添加下一页任务到队列
* @param int $pageIndex 页码
* @param int $pageSize 每页大小
*/
protected function addNextPageToQueue($pageIndex, $pageSize)
{
$data = [
'pageIndex' => $pageIndex,
'pageSize' => $pageSize
];
// 添加到队列,设置任务名为 wechat_list
Queue::push(self::class, $data, 'wechat_list');
}
}

View File

@@ -0,0 +1,131 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\facade\Cache;
use think\Db;
use app\command\WechatMomentsCommand;
use app\api\controller\WebSocketController;
use think\facade\Env;
use app\api\controller\AutomaticAssign;
class WechatMomentsJob
{
protected $maxPages = 10; // 最大页数
public function fire(Job $job, $data)
{
$toAccountId = '';
$username = Env::get('api.username2', '');
$password = Env::get('api.password2', '');
if (!empty($username) || !empty($password)) {
$toAccountId = Db::name('users')->where('account',$username)->value('s2_accountId');
}else{
Log::error("没有账号配置");
return;
}
try {
$jobId = $data['jobId'] ?? '';
$queueLockKey = $data['queueLockKey'] ?? '';
Log::info("开始处理朋友圈采集任务任务ID{$jobId}");
// 获取好友列表
$friends = $this->getFriends($data['pageIndex'], $data['pageSize']);
if (empty($friends)) {
Log::info("没有更多好友数据,任务完成");
Cache::rm($queueLockKey);
$job->delete();
return;
}
foreach ($friends as $friend) {
try {
// 执行切换好友命令
$automaticAssign = new AutomaticAssign();
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $toAccountId], true);
//存入缓存
artificialAllotWechatFriend($friend);
// 执行采集朋友圈命令
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
$webSocket->getMoments(['wechatFriendId' => $friend['friendId'], 'wechatAccountId' => $friend['wechatAccountId']]);
// 处理完毕切换回原账号
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
} catch (\Exception $e) {
// 发生异常时也要切换回原账号
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
Log::error("采集好友 {$friend['id']} 的朋友圈失败:" . $e->getMessage());
continue;
}
}
// 判断是否需要继续翻页
if (count($friends) < $data['pageSize']) {
// 如果返回的数据少于页面大小,说明已经没有更多数据了
Log::info("朋友圈采集任务完成,没有更多数据");
Cache::rm($queueLockKey);
$job->delete();
} else {
// 还有更多数据,继续处理下一页
$data['pageIndex']++;
if ($data['pageIndex'] > $this->maxPages) {
Log::info("已达到最大页数限制 {$this->maxPages},任务完成");
Cache::rm($data['pageIndexCacheKey']);
Cache::rm($queueLockKey);
$job->delete();
} else {
// 处理下一页
Cache::set($data['pageIndexCacheKey'], $data['pageIndex']);
// 有下一页,将下一页任务添加到队列
$command = new WechatMomentsCommand();
$command->addToQueue($data['pageIndex'], $data['pageSize'], $jobId, $queueLockKey);
}
}
} catch (\Exception $e) {
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
Log::error("朋友圈采集任务异常:" . $e->getMessage());
Cache::rm($queueLockKey);
$job->delete();
}
}
/**
* 获取账号的好友列表
* @param int $accountId 账号ID
* @return array
*/
private function getFriends($page = 1 ,$pageSize = 100)
{
$list = Db::table('s2_company_account')
->alias('ca')
->join(['s2_wechat_account' => 'wa'], 'ca.id = wa.deviceAccountId')
->join(['s2_wechat_friend' => 'wf'], 'ca.id = wf.accountId AND wf.wechatAccountId = wa.id')
->where([
'ca.status' => 0,
'wf.isDeleted' => 0,
'wa.deviceAlive' => 1,
'wa.wechatAlive' => 1
])
->field([
'ca.id as accountId',
'ca.userName',
'wf.id as friendId',
'wf.wechatId',
'wf.wechatAccountId',
'wa.wechatId as wechatAccountWechatId',
'wa.currentDeviceId as deviceId'
])->group('wf.wechatId')
->order('wf.id DESC')
->page($page, $pageSize)
->select();
return $list;
}
}

View File

@@ -0,0 +1,483 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use think\facade\Cache;
use think\facade\Env;
use app\cunkebao\model\Workbench;
use app\cunkebao\model\WorkbenchAutoLike;
use think\Db;
use app\api\controller\WebSocketController;
use app\api\controller\AutomaticAssign;
use app\api\controller\WechatFriendController;
class WorkbenchAutoLikeJob
{
/**
* 最大重试次数
*/
const MAX_RETRY_ATTEMPTS = 3;
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return bool
*/
public function fire(Job $job, $data)
{
$jobId = $data['jobId'] ?? '';
$queueLockKey = $data['queueLockKey'] ?? '';
try {
$this->logJobStart($jobId, $queueLockKey);
$workbenches = $this->getActiveWorkbenches();
if (empty($workbenches)) {
$this->handleEmptyWorkbenches($job, $queueLockKey);
return true;
}
$this->processWorkbenches($workbenches);
$this->handleJobSuccess($job, $queueLockKey);
return true;
} catch (\Exception $e) {
return $this->handleJobError($e, $job, $queueLockKey);
}
}
/**
* 获取活跃的工作台
* @return \think\Collection
*/
protected function getActiveWorkbenches()
{
return Workbench::where([
['status', '=', 1],
['isDel', '=', 0],
['type', '=', 1] // 只获取自动点赞类型的工作台
])->order('id DESC')->select();
}
/**
* 处理工作台列表
* @param \think\Collection $workbenches
*/
protected function processWorkbenches($workbenches)
{
foreach ($workbenches as $workbench) {
try {
$this->processSingleWorkbench($workbench);
} catch (\Exception $e) {
Log::error("处理工作台 {$workbench->id} 失败: " . $e->getMessage());
}
}
}
/**
* 处理单个工作台
* @param Workbench $workbench
*/
protected function processSingleWorkbench($workbench)
{
$config = WorkbenchAutoLike::where('workbenchId', $workbench->id)->find();
if (!$config) {
Log::error("工作台 {$workbench->id} 配置获取失败");
return;
}
$this->handleAutoLike($workbench, $config);
}
/**
* 处理自动点赞任务
* @param Workbench $workbench
* @param WorkbenchAutoLike $config
*/
protected function handleAutoLike($workbench, $config)
{
if (!$this->validateAutoLikeConfig($workbench, $config)) {
return;
}
// 验证是否在点赞时间范围内
if (!$this->isWithinLikeTimeRange($config)) {
return;
}
// 处理分页获取好友列表
$this->processAllFriends($workbench, $config);
}
/**
* 处理所有好友分页
* @param Workbench $workbench
* @param WorkbenchAutoLike $config
* @param int $page 当前页码
* @param int $pageSize 每页大小
*/
protected function processAllFriends($workbench, $config, $page = 1, $pageSize = 100)
{
$friendList = $this->getFriendList($config, $page, $pageSize);
if (empty($friendList)) {
return;
}
// 直接顺序处理所有好友
foreach ($friendList as $friend) {
// 验证是否达到点赞次数上限
$likeCount = $this->getTodayLikeCount($workbench, $config, $friend['deviceId']);
if ($likeCount >= $config['maxLikes']) {
Log::info("工作台 {$workbench->id} 点赞次数已达上限");
continue;
}
// 验证是否达到好友点赞次数上限
$friendMaxLikes = Db::name('workbench_auto_like_item')
->where('workbenchId', $workbench->id)
->where('wechatFriendId', $friend['friendId'])
->count();
if ($friendMaxLikes < $config['friendMaxLikes']) {
$this->processFriendMoments($workbench, $config, $friend);
}
}
// 如果当前页数据量等于页大小,说明可能还有更多数据,继续处理下一页
if (count($friendList) == $pageSize) {
$this->processAllFriends($workbench, $config, $page + 1, $pageSize);
}
}
/**
* 获取好友列表
* @param WorkbenchAutoLike $config 配置
* @param int $page 页码
* @param int $pageSize 每页大小
* @return array
*/
protected function getFriendList($config, $page = 1, $pageSize = 100)
{
$friends = json_decode($config['friends'], true);
$devices = json_decode($config['devices'], true);
$list = Db::table('s2_company_account')
->alias('ca')
->join(['s2_wechat_account' => 'wa'], 'ca.id = wa.deviceAccountId')
->join(['s2_wechat_friend' => 'wf'], 'wf.wechatAccountId = wa.id')
->join('workbench_auto_like_item wali', 'wali.wechatFriendId = wf.id AND wali.workbenchId = ' . $config['workbenchId'], 'left')
->where([
'ca.status' => 0,
'wf.isDeleted' => 0,
'wa.deviceAlive' => 1,
'wa.wechatAlive' => 1
])
->whereIn('wa.currentDeviceId', $devices)
->field([
'ca.id as accountId',
'ca.userName',
'wf.id as friendId',
'wf.wechatId',
'wf.wechatAccountId',
'wa.wechatId as wechatAccountWechatId',
'wa.currentDeviceId as deviceId',
'COUNT(wali.id) as like_count'
]);
if (!empty($friends) && is_array($friends) && count($friends) > 0) {
$list = $list->whereIn('wf.id', $friends);
}
$list = $list->group('wf.wechatId')
->having('like_count < ' . $config['friendMaxLikes'])
->order('wf.id DESC')
->page($page, $pageSize)
->select();
return $list;
}
/**
* 处理好友朋友圈
* @param Workbench $workbench
* @param WorkbenchAutoLike $config
* @param array $friend
*/
protected function processFriendMoments($workbench, $config, $friend)
{
$toAccountId = '';
$username = Env::get('api.username2', '');
$password = Env::get('api.password2', '');
if (!empty($username) || !empty($password)) {
$toAccountId = Db::name('users')->where('account',$username)->value('s2_accountId');
}
try {
// 执行切换好友命令
$automaticAssign = new AutomaticAssign();
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $toAccountId], true);
//存入缓存
artificialAllotWechatFriend($friend);
// 创建WebSocket链接
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
// 查询未点赞的朋友圈
$moments = $this->getUnlikedMoments($friend['wechatId']);
if (empty($moments) || count($moments) == 0) {
//采集最新朋友圈
$webSocket->getMoments(['wechatFriendId' => $friend['friendId'], 'wechatAccountId' => $friend['wechatAccountId']]);
$moments = $this->getUnlikedMoments($friend['wechatId']);
}
if (empty($moments) || count($moments) == 0) {
// 处理完毕切换回原账号
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
Log::info("好友 {$friend['friendId']} 没有需要点赞的朋友圈");
return;
}
foreach ($moments as $moment) {
// 点赞朋友圈
$this->likeMoment($workbench, $config, $friend, $moment, $webSocket);
if(!empty($config['enableFriendTags']) && !empty($config['friendTags'])){
// 修改好友标签
$labels = $this->getFriendLabels($friend);
$labels[] = $config['friendTags'];
$webSocket->modifyFriendLabel(['wechatFriendId' => $friend['friendId'], 'wechatAccountId' => $friend['wechatAccountId'], 'labels' => $labels]);
//更新用户标签
$friendData = Db::table('s2_wechat_friend')->where('id', $friend['friendId'])->find();
Db::table('s2_wechat_friend')->where('id', $friend['friendId'])->update(['labels' => json_encode($labels,256)]);
}
break;
}
// 处理完毕切换回原账号
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
} catch (\Exception $e) {
// 异常情况下也要确保切换回原账号
$automaticAssign = new AutomaticAssign();
$automaticAssign->allotWechatFriend(['wechatFriendId' => $friend['friendId'], 'toAccountId' => $friend['accountId']], true);
Log::error("处理好友 {$friend['friendId']} 朋友圈失败异常: " . $e->getMessage());
}
}
/**
* 获取未点赞的朋友圈
* @param int $wechatId
* @return \think\Collection
*/
protected function getUnlikedMoments($wechatId)
{
return Db::table('s2_wechat_moments')
->alias('wm')
->join('workbench_auto_like_item wali', 'wali.momentsId = wm.id', 'left')
->where([
['wm.userName', '=', $wechatId],
['wali.id', 'null', null]
])
->where('wm.update_time', '>=', time() - 86400)
->field('wm.id, wm.snsId')
->group('wali.wechatFriendId')
->order('wm.createTime DESC')
->page(1,1)
->select();
}
/**
* 点赞朋友圈
* @param Workbench $workbench
* @param WorkbenchAutoLike $config
* @param array $friend
* @param array $moment
* @param WebSocketController $webSocket
*/
protected function likeMoment($workbench, $config, $friend, $moment, $webSocket)
{
try {
$result = $webSocket->momentInteract([
'snsId' => $moment['snsId'],
'wechatAccountId' => $friend['wechatAccountId'],
]);
$result = json_decode($result, true);
if ($result['code'] == 200) {
$this->recordLike($workbench, $moment, $friend);
// 添加间隔时间
if (!empty($config['interval'])) {
sleep($config['interval']);
}
} else {
Log::error("工作台 {$workbench->id} 点赞失败: " . ($result['msg'] ?? '未知错误'));
}
} catch (\Exception $e) {
Log::error("工作台 {$workbench->id} 点赞异常: " . $e->getMessage());
}
}
/**
* 记录点赞
* @param Workbench $workbench
* @param array $moment
* @param array $friend
*/
protected function recordLike($workbench, $moment, $friend)
{
Db::name('workbench_auto_like_item')->insert([
'workbenchId' => $workbench->id,
'deviceId' => $friend['deviceId'],
'momentsId' => $moment['id'],
'snsId' => $moment['snsId'],
'wechatAccountId' => $friend['wechatAccountId'],
'wechatFriendId' => $friend['friendId'],
'createTime' => time()
]);
Log::info("工作台 {$workbench->id} 点赞成功: {$moment['snsId']}");
}
/**
* 获取好友标签
* @param array $friend
* @return array
*/
protected function getFriendLabels($friend)
{
$wechatFriendController = new WechatFriendController();
$result = $wechatFriendController->getlist([
'friendKeyword' => $friend['wechatId'],
'wechatAccountKeyword' => $friend['wechatAccountWechatId']
], true);
$result = json_decode($result, true);
$labels = [];
if(!empty($result['data'])){
foreach($result['data'] as $item){
$labels = array_merge($labels, $item['labels']);
}
}
return $labels;
}
/**
* 验证自动点赞配置
* @param Workbench $workbench
* @param WorkbenchAutoLike $config
* @return bool
*/
protected function validateAutoLikeConfig($workbench, $config)
{
$requiredFields = ['contentTypes', 'interval', 'maxLikes', 'startTime', 'endTime'];
foreach ($requiredFields as $field) {
if (empty($config[$field])) {
Log::error("工作台 {$workbench->id} 配置字段 {$field} 为空");
return false;
}
}
return true;
}
/**
* 获取今日点赞次数
* @param Workbench $workbench
* @param WorkbenchAutoLike $config
* @return int
*/
protected function getTodayLikeCount($workbench, $config, $deviceId)
{
return Db::name('workbench_auto_like_item')
->where('workbenchId', $workbench->id)
->where('deviceId', $deviceId)
->whereTime('createTime', 'between', [
strtotime(date('Y-m-d') . ' ' . $config['startTime'] . ':00'),
strtotime(date('Y-m-d') . ' ' . $config['endTime'] . ':00')
])
->count();
}
/**
* 检查是否在点赞时间范围内
* @param WorkbenchAutoLike $config
* @return bool
*/
protected function isWithinLikeTimeRange($config)
{
$currentTime = date('H:i');
if ($currentTime < $config['startTime'] || $currentTime > $config['endTime']) {
Log::info("当前时间 {$currentTime} 不在点赞时间范围内 ({$config['startTime']} - {$config['endTime']})");
return false;
}
return true;
}
/**
* 记录任务开始
* @param string $jobId
* @param string $queueLockKey
*/
protected function logJobStart($jobId, $queueLockKey)
{
Log::info('开始处理工作台自动点赞任务: ' . json_encode([
'jobId' => $jobId,
'queueLockKey' => $queueLockKey
]));
}
/**
* 处理任务成功
* @param Job $job
* @param string $queueLockKey
*/
protected function handleJobSuccess($job, $queueLockKey)
{
$job->delete();
Cache::rm($queueLockKey);
Log::info('工作台自动点赞任务执行成功');
}
/**
* 处理任务错误
* @param \Exception $e
* @param Job $job
* @param string $queueLockKey
* @return bool
*/
protected function handleJobError(\Exception $e, $job, $queueLockKey)
{
Log::error('工作台自动点赞任务异常:' . $e->getMessage());
if (!empty($queueLockKey)) {
Cache::rm($queueLockKey);
Log::info("由于异常释放队列锁: {$queueLockKey}");
}
if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
/**
* 处理空工作台情况
* @param Job $job
* @param string $queueLockKey
*/
protected function handleEmptyWorkbenches(Job $job, $queueLockKey)
{
Log::info('没有需要处理的工作台自动点赞任务');
$job->delete();
Cache::rm($queueLockKey);
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace app\job;
use app\api\controller\WebSocketController;
use think\facade\Log;
use think\facade\Env;
use think\Db;
use think\queue\Job;
use think\facade\Cache;
use think\facade\Config;
/**
* 工作台群创建-拉管理员好友任务
* Class WorkbenchGroupCreateAdminFriendJob
* @package app\job
*/
class WorkbenchGroupCreateAdminFriendJob
{
/**
* 最大重试次数
*/
const MAX_RETRY_ATTEMPTS = 3;
/**
* 成员类型常量
*/
const MEMBER_TYPE_ADMIN_FRIEND = 4;
/**
* 状态常量
*/
const STATUS_SUCCESS = 2;
const STATUS_ADMIN_FRIEND_ADDED = 4;
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return bool
*/
public function fire(Job $job, $data)
{
$workbenchId = $data['workbenchId'] ?? 0;
$wechatAccountId = $data['wechatAccountId'] ?? 0;
$groupId = $data['groupId'] ?? 0;
$chatroomId = $data['chatroomId'] ?? '';
$adminFriendIds = $data['adminFriendIds'] ?? [];
$poolUsers = $data['poolUsers'] ?? [];
try {
if (empty($adminFriendIds) || empty($poolUsers)) {
Log::info("管理员好友或流量池用户为空跳过。工作台ID: {$workbenchId}");
$job->delete();
return true;
}
// 获取管理员信息
$adminFriends = Db::table('s2_wechat_friend')
->where('id', 'in', $adminFriendIds)
->column('id,wechatId,ownerWechatId');
if (empty($adminFriends)) {
Log::warning("未找到管理员好友信息。工作台ID: {$workbenchId}");
$job->delete();
return true;
}
// 获取微信账号信息
$wechatAccount = Db::table('s2_wechat_account')->where('id', $wechatAccountId)->find();
if (empty($wechatAccount)) {
Log::error("未找到微信账号。微信账号ID: {$wechatAccountId}");
$job->delete();
return false;
}
// 从流量池用户中查找每个管理员的好友
// 管理员的好友从s2_wechat_friend表中查找ownerWechatId=管理员的wechatId且wechatId在流量池用户中
$allAdminFriendIds = [];
foreach ($adminFriends as $adminFriend) {
$adminWechatId = $adminFriend['wechatId'];
// 从好友表中查找该管理员的好友(在流量池用户中)
$adminFriendsList = Db::table('s2_wechat_friend')
->where('ownerWechatId', $adminWechatId)
->whereIn('wechatId', $poolUsers)
->column('id,wechatId');
if (!empty($adminFriendsList)) {
$allAdminFriendIds = array_merge($allAdminFriendIds, array_keys($adminFriendsList));
}
}
$allAdminFriendIds = array_unique($allAdminFriendIds);
if (empty($allAdminFriendIds)) {
Log::info("未找到管理员的好友跳过拉人。工作台ID: {$workbenchId}");
$job->delete();
return true;
}
// 初始化WebSocket
$toAccountId = '';
$username = Env::get('api.username2', '');
$password = Env::get('api.password2', '');
if (!empty($username) || !empty($password)) {
$toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId');
}
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
// 拉管理员好友进群
$inviteResult = $webSocket->CmdChatroomInvite([
'wechatChatroomId' => $groupId,
'wechatFriendIds' => $allAdminFriendIds
]);
// 记录管理员好友进群
$installData = [];
foreach ($allAdminFriendIds as $friendId) {
$friendInfo = Db::table('s2_wechat_friend')->where('id', $friendId)->find();
$installData[] = [
'workbenchId' => $workbenchId,
'friendId' => $friendId,
'wechatId' => $friendInfo['wechatId'] ?? '',
'groupId' => $groupId,
'wechatAccountId' => $wechatAccountId,
'status' => self::STATUS_ADMIN_FRIEND_ADDED,
'memberType' => self::MEMBER_TYPE_ADMIN_FRIEND,
'retryCount' => 0,
'chatroomId' => $chatroomId,
'createTime' => time(),
];
}
if (!empty($installData)) {
Db::name('workbench_group_create_item')->insertAll($installData);
Log::info("管理员好友已拉入群。工作台ID: {$workbenchId}, 群ID: {$groupId}, 好友数: " . count($installData));
}
$job->delete();
return true;
} catch (\Exception $e) {
Log::error("拉管理员好友任务异常:{$e->getMessage()}");
if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
}
}

View File

@@ -0,0 +1,852 @@
<?php
namespace app\job;
use app\api\controller\WebSocketController;
use app\cunkebao\model\Workbench;
use app\cunkebao\model\WorkbenchGroupCreate;
use app\api\model\WechatFriendModel as WechatFriend;
use app\api\model\WechatMomentsModel as WechatMoments;
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
use think\facade\Log;
use think\facade\Env;
use think\Db;
use think\queue\Job;
use think\facade\Cache;
use think\facade\Config;
use app\api\controller\MomentsController as Moments;
use Workerman\Lib\Timer;
use app\api\controller\WechatController;
use think\Queue;
/**
* 工作台群创建任务
* Class WorkbenchGroupCreateJob
* @package app\job
*/
class WorkbenchGroupCreateJob
{
/**
* 最大重试次数
*/
const MAX_RETRY_ATTEMPTS = 3;
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return bool
*/
public function fire(Job $job, $data)
{
$jobId = $data['jobId'] ?? '';
$queueLockKey = $data['queueLockKey'] ?? '';
try {
$this->logJobStart($jobId, $queueLockKey);
$this->execute();
$this->handleJobSuccess($job, $queueLockKey);
return true;
} catch (\Exception $e) {
return $this->handleJobError($e, $job, $queueLockKey);
}
}
/**
* 成员类型常量
*/
const MEMBER_TYPE_OWNER = 1; // 群主成员
const MEMBER_TYPE_ADMIN = 2; // 管理员
const MEMBER_TYPE_OWNER_FRIEND = 3; // 群主好友
const MEMBER_TYPE_ADMIN_FRIEND = 4; // 管理员好友
/**
* 状态常量
*/
const STATUS_PENDING = 0; // 待创建
const STATUS_CREATING = 1; // 创建中
const STATUS_SUCCESS = 2; // 创建成功
const STATUS_FAILED = 3; // 创建失败
const STATUS_ADMIN_FRIEND_ADDED = 4; // 管理员好友已拉入
/**
* 执行任务
* @throws \Exception
*/
public function execute()
{
try {
// 1. 查询启用了建群功能的数据
$workbenches = Workbench::where(['status' => 0, 'type' => 4, 'isDel' => 0,'id' => 354])->order('id desc')->select();
foreach ($workbenches as $workbench) {
// 获取工作台配置
$config = WorkbenchGroupCreate::where('workbenchId', $workbench->id)->find();
if (!$config) {
continue;
}
// 解析配置
$config['poolGroups'] = json_decode($config['poolGroups'] ?? '[]', true) ?: [];
$config['devices'] = json_decode($config['devices'] ?? '[]', true) ?: [];
$config['wechatGroups'] = json_decode($config['wechatGroups'] ?? '[]', true) ?: [];
$config['admins'] = json_decode($config['admins'] ?? '[]', true) ?: [];
// 检查时间限制
if (!$this->isWithinTimeRange($config)) {
continue;
}
// 检查每日建群数量限制
if (!$this->checkDailyLimit($workbench->id, $config)) {
continue;
}
// 检查是否有正在创建中的群,如果有则跳过(避免重复创建)
$creatingCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->where('status', self::STATUS_CREATING)
->where('groupId', '<>', null) // 有groupId的记录
->group('groupId')
->count();
if ($creatingCount > 0) {
Log::info("工作台ID: {$workbench->id} 有正在创建中的群({$creatingCount}个),跳过本次执行");
continue;
}
if (empty($config['devices'])) {
continue;
}
// 获取群主成员(从设备中获取)
$groupMember = [];
$wechatIds = Db::name('device_wechat_login')
->whereIn('deviceId', $config['devices'])
->where('alive', DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE)
->order('id desc')
->column('wechatId');
if (empty($wechatIds)) {
continue;
}
$groupMember = array_unique($wechatIds);
// 获取群主好友ID映射所有群主的好友
$groupMemberWechatId = [];
$groupMemberId = [];
foreach ($groupMember as $ownerWechatId) {
$friends = Db::table('s2_wechat_friend')
->where('ownerWechatId', $ownerWechatId)
->whereIn('wechatId', $groupMember)
->where('isDeleted', 0)
->field('id,wechatId')
->select();
foreach ($friends as $friend) {
if (!isset($groupMemberWechatId[$friend['id']])) {
$groupMemberWechatId[$friend['id']] = $friend['wechatId'];
$groupMemberId[] = $friend['id'];
}
}
}
// 如果配置了wechatGroups从指定的群组中获取成员
if (!empty($config['wechatGroups'])) {
$this->addGroupMembersFromWechatGroups($config['wechatGroups'], $groupMember, $groupMemberId, $groupMemberWechatId);
}
if (empty($groupMemberId)) {
continue;
}
// 获取流量池用户(如果配置了流量池)
$poolItem = [];
if (!empty($config['poolGroups'])) {
$poolItem = Db::name('traffic_source_package_item')
->whereIn('packageId', $config['poolGroups'])
->where('isDel', 0)
->group('identifier')
->column('identifier');
}
// 如果既没有流量池也没有指定群组,跳过
if (empty($poolItem) && empty($config['wechatGroups'])) {
continue;
}
// 获取已入群的用户(排除已成功入群的)
$groupUser = [];
if (!empty($poolItem)) {
$groupUser = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->where('status', 'in', [self::STATUS_SUCCESS, self::STATUS_ADMIN_FRIEND_ADDED, self::STATUS_CREATING])
->whereIn('wechatId', $poolItem)
->group('wechatId')
->column('wechatId');
}
// 待入群的用户(从流量池中筛选)
$joinUser = !empty($poolItem) ? array_diff($poolItem, $groupUser) : [];
// 如果流量池用户已用完或没有配置流量池但配置了wechatGroups至少创建一次使用群主成员
if (empty($joinUser) && !empty($config['wechatGroups'])) {
// 如果没有流量池用户创建一个空批次让processBatchUsers处理只有群主成员的情况
$joinUser = []; // 空数组,但会继续执行
}
// 如果既没有流量池用户也没有配置wechatGroups跳过
if (empty($joinUser) && empty($config['wechatGroups'])) {
continue;
}
// 计算随机群人数(不包含管理员,只减去群主成员数)
// 群主成员数 = 群主好友ID数量
$minGroupSize = max(2, $config['groupSizeMin']); // 至少2人才能建群
$maxGroupSize = max($minGroupSize, $config['groupSizeMax']);
$groupRandNum = mt_rand($minGroupSize, $maxGroupSize) - count($groupMemberId);
if ($groupRandNum <= 0) {
$groupRandNum = 1; // 至少需要1个成员
}
// 分批处理待入群用户
$addGroupUser = [];
if (!empty($joinUser)) {
$totalRows = count($joinUser);
for ($i = 0; $i < $totalRows; $i += $groupRandNum) {
$batchRows = array_slice($joinUser, $i, $groupRandNum);
if (!empty($batchRows)) {
$addGroupUser[] = $batchRows;
}
}
} else {
// 如果没有流量池用户但配置了wechatGroups创建一个空批次
$addGroupUser[] = [];
}
// 初始化WebSocket
$toAccountId = '';
$username = Env::get('api.username2', '');
$password = Env::get('api.password2', '');
if (!empty($username) || !empty($password)) {
$toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId');
}
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
// 遍历每批用户
foreach ($addGroupUser as $batchUsers) {
$this->processBatchUsers($workbench, $config, $batchUsers, $groupMemberId, $groupMemberWechatId, $groupRandNum, $webSocket);
}
}
} catch (\Exception $e) {
Log::error("工作台建群任务异常: " . $e->getMessage());
throw $e;
}
}
/**
* 处理一批用户
* @param Workbench $workbench 工作台
* @param array $config 配置
* @param array $batchUsers 批次用户微信ID数组来自流量池
* @param array $groupMemberId 群主成员ID数组
* @param array $groupMemberWechatId 群主成员微信ID映射
* @param int $groupRandNum 随机群人数(不包含管理员)
* @param WebSocketController $webSocket WebSocket实例
*/
protected function processBatchUsers($workbench, $config, $batchUsers, $groupMemberId, $groupMemberWechatId, $groupRandNum, $webSocket)
{
// 1. 获取群主微信ID列表用于验证管理员
// 从群主成员的好友记录中提取所有群主的微信IDownerWechatId
$groupOwnerWechatIds = [];
foreach ($groupMemberId as $memberId) {
$member = Db::table('s2_wechat_friend')->where('id', $memberId)->find();
if ($member && !in_array($member['ownerWechatId'], $groupOwnerWechatIds)) {
$groupOwnerWechatIds[] = $member['ownerWechatId'];
}
}
// 如果从好友表获取不到使用群主成员微信ID列表作为备用
if (empty($groupOwnerWechatIds)) {
$groupOwnerWechatIds = array_values(array_unique($groupMemberWechatId));
}
// 2. 验证并获取管理员好友ID管理员必须是群主的好友
$adminFriendIds = [];
$adminWechatIds = [];
if (!empty($config['admins'])) {
$adminFriends = Db::table('s2_wechat_friend')
->where('id', 'in', $config['admins'])
->field('id,wechatId,ownerWechatId')
->select();
foreach ($adminFriends as $adminFriend) {
// 验证:管理员必须是群主的好友
if (in_array($adminFriend['ownerWechatId'], $groupOwnerWechatIds)) {
$adminFriendIds[] = $adminFriend['id'];
$adminWechatIds[$adminFriend['id']] = $adminFriend['wechatId'];
}
}
}
// 3. 从流量池用户中筛选出是群主好友的用户(按微信账号分组)
$ownerFriendIdsByAccount = [];
$wechatIds = [];
// 如果batchUsers为空说明没有流量池用户但可能配置了wechatGroups
// 这种情况下,使用群主成员作为基础,按账号分组
if (empty($batchUsers)) {
// 按账号分组群主成员
foreach ($groupMemberId as $memberId) {
$member = Db::table('s2_wechat_friend')->where('id', $memberId)->find();
if ($member) {
$accountWechatId = $member['ownerWechatId'];
$account = Db::table('s2_wechat_account')
->where('wechatId', $accountWechatId)
->field('id')
->find();
if ($account) {
$wechatAccountId = $account['id'];
if (!isset($ownerFriendIdsByAccount[$wechatAccountId])) {
$ownerFriendIdsByAccount[$wechatAccountId] = [];
}
$ownerFriendIdsByAccount[$wechatAccountId][] = $memberId;
$wechatIds[$memberId] = $groupMemberWechatId[$memberId] ?? '';
}
}
}
} else {
// 获取群主的好友关系(从流量池中筛选)
$ownerFriends = Db::table('s2_wechat_friend')->alias('f')
->join(['s2_wechat_account' => 'a'], 'f.wechatAccountId=a.id')
->whereIn('f.wechatId', $batchUsers)
->whereIn('a.wechatId', $groupOwnerWechatIds)
->where('f.isDeleted', 0)
->field('f.id,f.wechatId,a.id as wechatAccountId')
->select();
if (empty($ownerFriends)) {
Log::warning("未找到群主的好友跳过。工作台ID: {$workbench->id}");
return;
}
// 按微信账号分组群主好友
foreach ($ownerFriends as $friend) {
$wechatAccountId = $friend['wechatAccountId'];
if (!isset($ownerFriendIdsByAccount[$wechatAccountId])) {
$ownerFriendIdsByAccount[$wechatAccountId] = [];
}
$ownerFriendIdsByAccount[$wechatAccountId][] = $friend['id'];
$wechatIds[$friend['id']] = $friend['wechatId'];
}
}
// 如果没有找到任何好友,跳过
if (empty($ownerFriendIdsByAccount)) {
Log::warning("未找到任何群主好友或成员跳过。工作台ID: {$workbench->id}");
return;
}
// 4. 遍历每个微信账号,创建群
foreach ($ownerFriendIdsByAccount as $wechatAccountId => $ownerFriendIds) {
// 4.1 获取当前账号的管理员好友ID
$currentAdminFriendIds = [];
$accountWechatId = Db::table('s2_wechat_account')->where('id', $wechatAccountId)->value('wechatId');
foreach ($adminFriendIds as $adminFriendId) {
$adminFriend = Db::table('s2_wechat_friend')->where('id', $adminFriendId)->find();
if ($adminFriend && $adminFriend['ownerWechatId'] == $accountWechatId) {
$currentAdminFriendIds[] = $adminFriendId;
$wechatIds[$adminFriendId] = $adminWechatIds[$adminFriendId];
}
}
// 4.2 获取当前账号的群主成员ID
$currentGroupMemberIds = [];
foreach ($groupMemberId as $memberId) {
$member = Db::table('s2_wechat_friend')->where('id', $memberId)->find();
if ($member && $member['ownerWechatId'] == $accountWechatId) {
$currentGroupMemberIds[] = $memberId;
if (!isset($wechatIds[$memberId])) {
$wechatIds[$memberId] = $groupMemberWechatId[$memberId] ?? '';
}
}
}
// 4.3 限制群主好友数量(按随机群人数)
// 如果ownerFriendIds只包含群主成员没有流量池用户则不需要限制
$limitedOwnerFriendIds = $ownerFriendIds;
if (count($ownerFriendIds) > $groupRandNum) {
$limitedOwnerFriendIds = array_slice($ownerFriendIds, 0, $groupRandNum);
}
// 4.4 创建群:管理员 + 群主成员 + 群主好友(从流量池筛选)
// 合并时去重,避免重复添加群主成员
$createFriendIds = array_merge($currentAdminFriendIds, $currentGroupMemberIds);
foreach ($limitedOwnerFriendIds as $friendId) {
if (!in_array($friendId, $createFriendIds)) {
$createFriendIds[] = $friendId;
}
}
// 微信建群至少需要2个人
if (count($createFriendIds) < 2) {
Log::warning("建群好友数量不足至少需要2人跳过。工作台ID: {$workbench->id}, 微信账号ID: {$wechatAccountId}, 当前人数: " . count($createFriendIds));
continue;
}
// 4.5 检查当前账号是否有正在创建中的群,如果有则跳过
$creatingGroupCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->where('wechatAccountId', $wechatAccountId)
->where('status', self::STATUS_CREATING)
->where('groupId', '<>', null)
->group('groupId')
->count();
if ($creatingGroupCount > 0) {
Log::info("工作台ID: {$workbench->id}, 微信账号ID: {$wechatAccountId} 有正在创建中的群({$creatingGroupCount}个),跳过本次创建");
continue;
}
// 4.6 生成群名称
$existingGroupCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->where('wechatAccountId', $wechatAccountId)
->where('status', self::STATUS_SUCCESS)
->where('groupId', '<>', null) // 排除groupId为NULL的记录
->group('groupId')
->count();
$chatroomName = $existingGroupCount > 0
? $config['groupNameTemplate'] . ($existingGroupCount + 1) . '群'
: $config['groupNameTemplate'];
// 4.7 调用建群接口
$createTime = time();
$createResult = $webSocket->CmdChatroomCreate([
'chatroomName' => $chatroomName,
'wechatFriendIds' => $createFriendIds,
'wechatAccountId' => $wechatAccountId
]);
$createResultData = json_decode($createResult, true);
// 4.8 解析建群结果获取群ID
// chatroomId: varchar(64) - 微信的群聊ID字符串
// groupId: int(10) - 数据库中的群组ID整数
$chatroomId = null; // 微信群聊ID字符串
$groupId = 0; // 数据库群组ID整数
$tempGroupId = null; // 临时群标识,用于轮询查询
if (!empty($createResultData) && isset($createResultData['code']) && $createResultData['code'] == 200) {
// 尝试从返回数据中获取群ID根据实际API返回格式调整
if (isset($createResultData['data']['chatroomId'])) {
// API返回的是chatroomId字符串
$chatroomId = (string)$createResultData['data']['chatroomId'];
// 通过chatroomId查询数据库获取groupId
$group = Db::name('wechat_group')
->where('chatroomId', $chatroomId)
->where('deleteTime', 0)
->find();
if ($group) {
$groupId = intval($group['id']);
}
} elseif (isset($createResultData['data']['id'])) {
// API返回的是数据库ID整数
$groupId = intval($createResultData['data']['id']);
// 通过groupId查询chatroomId
$group = Db::name('wechat_group')
->where('id', $groupId)
->where('deleteTime', 0)
->find();
if ($group && !empty($group['chatroomId'])) {
$chatroomId = (string)$group['chatroomId'];
}
}
// 如果有临时标识,保存用于轮询
if (isset($createResultData['data']['tempId'])) {
$tempGroupId = $createResultData['data']['tempId'];
}
}
// 4.9 如果建群接口没有立即返回群ID进行同步轮询检查
if ($groupId == 0) {
// 获取账号的微信ID群主微信ID
$accountWechatId = Db::table('s2_wechat_account')
->where('id', $wechatAccountId)
->value('wechatId');
if (!empty($accountWechatId)) {
$pollResult = $this->pollGroupCreation($chatroomName, $accountWechatId, $wechatAccountId, $tempGroupId);
if ($pollResult && is_array($pollResult)) {
$groupId = intval($pollResult['groupId'] ?? 0);
$chatroomId = !empty($pollResult['chatroomId']) ? (string)$pollResult['chatroomId'] : null;
} elseif ($pollResult > 0) {
// 兼容旧返回值只返回groupId
$groupId =0;
$chatroomId = null;
}
}
}
// 4.10 记录创建请求
$installData = [];
foreach ($createFriendIds as $friendId) {
$memberType = in_array($friendId, $currentAdminFriendIds)
? self::MEMBER_TYPE_ADMIN
: (in_array($friendId, $currentGroupMemberIds) ? self::MEMBER_TYPE_OWNER : self::MEMBER_TYPE_OWNER_FRIEND);
$installData[] = [
'workbenchId' => $workbench->id,
'friendId' => $friendId,
'wechatId' => $wechatIds[$friendId] ?? ($groupMemberWechatId[$friendId] ?? ''),
'groupId' => $groupId > 0 ? $groupId : null, // int类型
'wechatAccountId' => $wechatAccountId,
'status' => $groupId > 0 ? self::STATUS_SUCCESS : self::STATUS_FAILED,
'memberType' => $memberType,
'retryCount' => 0,
'chatroomId' => $chatroomId, // varchar类型
'createTime' => $createTime,
];
}
Db::name('workbench_group_create_item')->insertAll($installData);
// 5. 如果群创建成功,拉管理员的好友进群
// 注意拉人接口需要chatroomId字符串而不是groupId整数
if (!empty($chatroomId) && !empty($currentAdminFriendIds)) {
$this->inviteAdminFriends($workbench, $config, $batchUsers, $currentAdminFriendIds, $chatroomId, $groupId, $wechatAccountId, $wechatIds, $createTime, $webSocket);
}
}
}
/**
* 拉管理员的好友进群
* @param Workbench $workbench 工作台
* @param array $config 配置
* @param array $batchUsers 批次用户流量池微信ID数组
* @param array $adminFriendIds 管理员好友ID数组
* @param string $chatroomId 群聊ID字符串用于API调用
* @param int $groupId 数据库群组ID整数
* @param int $wechatAccountId 微信账号ID
* @param array $wechatIds 好友ID到微信ID的映射
* @param int $createTime 创建时间
* @param WebSocketController $webSocket WebSocket实例
*/
protected function inviteAdminFriends($workbench, $config, $batchUsers, $adminFriendIds, $chatroomId, $groupId, $wechatAccountId, $wechatIds, $createTime, $webSocket)
{
// 获取管理员的微信ID列表
$adminWechatIds = [];
foreach ($adminFriendIds as $adminFriendId) {
if (isset($wechatIds[$adminFriendId])) {
$adminWechatIds[] = $wechatIds[$adminFriendId];
}
}
if (empty($adminWechatIds)) {
return;
}
// 从流量池用户中筛选出是管理员好友的用户
$adminFriendsFromPool = Db::table('s2_wechat_friend')->alias('f')
->join(['s2_wechat_account' => 'a'], 'f.wechatAccountId=a.id')
->whereIn('f.wechatId', $batchUsers)
->whereIn('a.wechatId', $adminWechatIds)
->where('a.id', $wechatAccountId)
->where('f.isDeleted', 0)
->field('f.id,f.wechatId')
->select();
if (empty($adminFriendsFromPool)) {
Log::info("未找到管理员的好友跳过拉人。工作台ID: {$workbench->id}, 群ID: {$chatroomId}");
return;
}
// 提取好友ID列表
$adminFriendIdsToInvite = [];
foreach ($adminFriendsFromPool as $friend) {
$adminFriendIdsToInvite[] = $friend['id'];
$wechatIds[$friend['id']] = $friend['wechatId'];
}
// 调用拉人接口使用chatroomId字符串
$inviteResult = $webSocket->CmdChatroomInvite([
'wechatChatroomId' => $chatroomId,
'wechatFriendIds' => $adminFriendIdsToInvite
]);
$inviteResultData = json_decode($inviteResult, true);
$inviteSuccess = !empty($inviteResultData) && isset($inviteResultData['code']) && $inviteResultData['code'] == 200;
// 记录管理员好友拉入状态
$adminFriendData = [];
foreach ($adminFriendIdsToInvite as $friendId) {
$adminFriendData[] = [
'workbenchId' => $workbench->id,
'friendId' => $friendId,
'wechatId' => $wechatIds[$friendId] ?? '',
'groupId' => $groupId > 0 ? $groupId : null, // int类型
'wechatAccountId' => $wechatAccountId,
'status' => $inviteSuccess ? self::STATUS_ADMIN_FRIEND_ADDED : self::STATUS_FAILED,
'memberType' => self::MEMBER_TYPE_ADMIN_FRIEND,
'retryCount' => 0,
'chatroomId' => $chatroomId, // varchar类型
'createTime' => $createTime,
];
}
Db::name('workbench_group_create_item')->insertAll($adminFriendData);
if ($inviteSuccess) {
// 去除成功日志,减少日志空间消耗
} else {
Log::warning("管理员好友拉入失败。工作台ID: {$workbench->id}, 群组ID: {$groupId}, 群聊ID: {$chatroomId}");
}
}
/**
* 轮询检查群是否创建成功
* @param string $chatroomName 群名称
* @param string $ownerWechatId 群主微信ID
* @param int $wechatAccountId 微信账号ID
* @param string|null $tempGroupId 临时群标识(如果有)
* @return array|int 返回数组包含groupId和chatroomId或只返回groupId兼容旧代码如果未找到返回0
*/
protected function pollGroupCreation($chatroomName, $ownerWechatId, $wechatAccountId, $tempGroupId = null)
{
$maxAttempts = 10; // 最多查询10次
$interval = 5; // 每次间隔5秒
// 获取账号IDaccountId和微信账号的微信IDwechatAccountWechatId用于查询s2_wechat_chatroom表
$accountInfo = Db::table('s2_wechat_account')
->where('id', $wechatAccountId)
->field('id,wechatId')
->find();
$accountId = $accountInfo['id'] ?? null;
$wechatAccountWechatId = $accountInfo['wechatId'] ?? null;
if (empty($accountId) && empty($wechatAccountWechatId)) {
Log::warning("无法获取账号ID和微信账号ID跳过轮询。微信账号ID: {$wechatAccountId}");
return 0;
}
// 获取授权信息(用于调用同步接口)
$username = Env::get('api.username2', '');
$password = Env::get('api.password2', '');
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
// 等待5秒第一次立即查询后续等待
if ($attempt > 1) {
sleep($interval);
}
// 1. 先调用接口同步最新的群组信息
try {
$chatroomController = new \app\api\controller\WechatChatroomController();
// 构建同步参数
$syncData = [
'wechatAccountKeyword' => $ownerWechatId, // 通过群主微信ID筛选
'isDeleted' => false,
'pageIndex' => 0,
'pageSize' => 5 // 获取足够多的数据
];
// 调用getlist方法同步数据内部调用isInner=true
$chatroomController->getlist($syncData, true, 0);
} catch (\Exception $e) {
Log::warning("同步群组信息失败: " . $e->getMessage());
// 即使同步失败,也继续查询本地数据
}
// 2. 查询本地表 s2_wechat_chatroom
// 计算5分钟前的时间戳
$fiveMinutesAgo = time() - 300; // 5分钟 = 300秒
$now = time();
// 查询群聊通过群名称、账号ID或微信账号ID和创建时间查询
// 如果accountId不为空优先使用accountId查询如果accountId为空则使用wechatAccountWechatId查询
$chatroom = Db::table('s2_wechat_chatroom')
->where('nickname', $chatroomName)
->where('isDeleted', 0)
->where('createTime', '>=', $fiveMinutesAgo) // 创建时间在5分钟内
->where('createTime', '<=', $now)
->where('wechatAccountWechatId', $wechatAccountWechatId)
->order('createTime', 'desc')
->find();
// 如果找到了群聊返回群ID和chatroomId
if ($chatroom && !empty($chatroom['id'])) {
$chatroomId = !empty($chatroom['chatroomId']) ? (string)$chatroom['chatroomId'] : null;
// 如果有chatroomId尝试查询wechat_group表获取groupId
$groupId = $chatroom['id'];
Log::info("轮询检查群创建成功。群名称: {$chatroomName}, 群聊ID: {$chatroom['id']}, chatroomId: {$chatroomId}, 群组ID: {$groupId}, 尝试次数: {$attempt}");
return [
'groupId' => $groupId > 0 ? $groupId : intval($chatroom['id']), // 如果没有groupId使用chatroom的id
'chatroomId' => $chatroomId ?: (string)$chatroom['id']
];
}
Log::debug("轮询检查群创建中。群名称: {$chatroomName}, 尝试次数: {$attempt}/{$maxAttempts}");
}
// 10次查询后仍未找到返回0表示失败
Log::warning("轮询检查群创建失败,已查询{$maxAttempts}次仍未找到群组。群名称: {$chatroomName}, 群主微信ID: {$ownerWechatId}, 账号ID: {$accountId}");
return 0;
}
/**
* 检查是否在时间范围内
* @param array $config 配置
* @return bool
*/
protected function isWithinTimeRange($config)
{
if (empty($config['startTime']) || empty($config['endTime'])) {
return true; // 如果没有配置时间,则允许执行
}
$today = date('Y-m-d');
$startTimestamp = strtotime($today . ' ' . $config['startTime'] . ':00');
$endTimestamp = strtotime($today . ' ' . $config['endTime'] . ':00');
$currentTime = time();
// 如果开始时间大于当前时间,还未到执行时间
if ($startTimestamp > $currentTime) {
return false;
}
// 如果结束时间小于当前时间,已过执行时间
if ($endTimestamp < $currentTime) {
return false;
}
return true;
}
/**
* 检查每日建群数量限制
* @param int $workbenchId 工作台ID
* @param array $config 配置
* @return bool
*/
protected function checkDailyLimit($workbenchId, $config)
{
if (empty($config['maxGroupsPerDay']) || $config['maxGroupsPerDay'] <= 0) {
return true; // 如果没有配置限制,则允许执行
}
$today = date('Y-m-d');
$startTimestamp = strtotime($today . ' 00:00:00');
$endTimestamp = strtotime($today . ' 23:59:59');
// 查询今日已创建的群数量(状态为成功)
$todayCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('status', self::STATUS_SUCCESS)
->where('groupId', '<>', null) // 排除groupId为NULL的记录
->where('createTime', 'between', [$startTimestamp, $endTimestamp])
->group('groupId')
->count();
return $todayCount < $config['maxGroupsPerDay'];
}
/**
* 从指定的微信群组中获取成员
* @param array $wechatGroups 群组ID数组可能是好友ID或群组ID
* @param array $groupMember 群主成员微信ID数组引用传递
* @param array $groupMemberId 群主成员好友ID数组引用传递
* @param array $groupMemberWechatId 群主成员微信ID映射引用传递
*/
protected function addGroupMembersFromWechatGroups($wechatGroups, &$groupMember, &$groupMemberId, &$groupMemberWechatId)
{
foreach ($wechatGroups as $groupId) {
if (is_numeric($groupId)) {
// 数字ID可能是好友ID查询好友信息
$friend = Db::table('s2_wechat_friend')
->where('id', $groupId)
->where('isDeleted', 0)
->field('id,wechatId,ownerWechatId')
->find();
if ($friend) {
// 添加到群主成员
if (!in_array($friend['ownerWechatId'], $groupMember)) {
$groupMember[] = $friend['ownerWechatId'];
}
if (!isset($groupMemberWechatId[$friend['id']])) {
$groupMemberWechatId[$friend['id']] = $friend['wechatId'];
$groupMemberId[] = $friend['id'];
}
} else {
// 如果不是好友ID可能是群组ID查询群组信息
$group = Db::name('wechat_group')
->where('id', $groupId)
->where('deleteTime', 0)
->field('ownerWechatId')
->find();
if ($group && !in_array($group['ownerWechatId'], $groupMember)) {
$groupMember[] = $group['ownerWechatId'];
}
}
} else {
// 字符串ID手动创建的群组可能是wechatId
if (!in_array($groupId, $groupMember)) {
$groupMember[] = $groupId;
}
}
}
}
/**
* 记录任务开始
* @param string $jobId
* @param string $queueLockKey
*/
protected function logJobStart($jobId, $queueLockKey)
{
// 去除开始日志,减少日志空间消耗
}
/**
* 处理任务成功
* @param Job $job
* @param string $queueLockKey
*/
protected function handleJobSuccess($job, $queueLockKey)
{
$job->delete();
Cache::rm($queueLockKey);
// 去除成功日志,减少日志空间消耗
}
/**
* 处理任务错误
* @param \Exception $e
* @param Job $job
* @param string $queueLockKey
* @return bool
*/
protected function handleJobError(\Exception $e, $job, $queueLockKey)
{
Log::error('工作台消息群发任务异常:' . $e->getMessage());
if (!empty($queueLockKey)) {
Cache::rm($queueLockKey);
Log::info("由于异常释放队列锁: {$queueLockKey}");
}
if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace app\job;
use app\api\controller\WebSocketController;
use think\facade\Log;
use think\facade\Env;
use think\Db;
use think\queue\Job;
use think\facade\Cache;
use think\facade\Config;
/**
* 工作台群创建-拉群主好友任务
* Class WorkbenchGroupCreateOwnerFriendJob
* @package app\job
*/
class WorkbenchGroupCreateOwnerFriendJob
{
/**
* 最大重试次数
*/
const MAX_RETRY_ATTEMPTS = 3;
/**
* 成员类型常量
*/
const MEMBER_TYPE_OWNER_FRIEND = 3;
/**
* 状态常量
*/
const STATUS_SUCCESS = 2;
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return bool
*/
public function fire(Job $job, $data)
{
$workbenchId = $data['workbenchId'] ?? 0;
$wechatAccountId = $data['wechatAccountId'] ?? 0;
$groupId = $data['groupId'] ?? 0;
$chatroomId = $data['chatroomId'] ?? '';
$ownerFriendIds = $data['ownerFriendIds'] ?? [];
$createTime = $data['createTime'] ?? 0;
try {
if (empty($ownerFriendIds) || empty($groupId)) {
Log::info("群主好友或群ID为空跳过。工作台ID: {$workbenchId}");
$job->delete();
return true;
}
// 初始化WebSocket
$toAccountId = '';
$username = Env::get('api.username2', '');
$password = Env::get('api.password2', '');
if (!empty($username) || !empty($password)) {
$toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId');
}
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
// 拉群主好友进群
$inviteResult = $webSocket->CmdChatroomInvite([
'wechatChatroomId' => $groupId,
'wechatFriendIds' => $ownerFriendIds
]);
// 获取好友微信ID映射
$friendWechatIds = Db::table('s2_wechat_friend')
->where('id', 'in', $ownerFriendIds)
->column('id,wechatId');
// 更新群主好友记录状态
Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('wechatAccountId', $wechatAccountId)
->where('status', 1) // 创建中
->where('memberType', self::MEMBER_TYPE_OWNER_FRIEND)
->where('createTime', '>=', $createTime - 10)
->where('createTime', '<=', $createTime + 10)
->update([
'status' => self::STATUS_SUCCESS,
'groupId' => $groupId,
'chatroomId' => $chatroomId,
'verifyTime' => time()
]);
Log::info("群主好友已拉入群。工作台ID: {$workbenchId}, 群ID: {$groupId}, 好友数: " . count($ownerFriendIds));
$job->delete();
return true;
} catch (\Exception $e) {
Log::error("拉群主好友任务异常:{$e->getMessage()}");
if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace app\job;
use app\api\controller\WebSocketController;
use app\cunkebao\model\Workbench;
use app\cunkebao\model\WorkbenchGroupCreate;
use think\facade\Log;
use think\facade\Env;
use think\Db;
use think\queue\Job;
use think\facade\Cache;
use think\facade\Config;
use think\Queue;
/**
* 工作台群创建重试任务
* Class WorkbenchGroupCreateRetryJob
* @package app\job
*/
class WorkbenchGroupCreateRetryJob
{
/**
* 最大重试次数
*/
const MAX_RETRY_ATTEMPTS = 3;
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return bool
*/
public function fire(Job $job, $data)
{
$workbenchId = $data['workbenchId'] ?? 0;
$wechatAccountId = $data['wechatAccountId'] ?? 0;
$createTime = $data['createTime'] ?? 0;
try {
// 获取工作台和配置
$workbench = Workbench::where('id', $workbenchId)->find();
if (!$workbench) {
Log::error("未找到工作台。工作台ID: {$workbenchId}");
$job->delete();
return false;
}
$config = WorkbenchGroupCreate::where('workbenchId', $workbench->id)->find();
if (!$config) {
Log::error("未找到工作台配置。工作台ID: {$workbenchId}");
$job->delete();
return false;
}
// 获取失败记录
$failedItems = Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('wechatAccountId', $wechatAccountId)
->where('createTime', '>=', $createTime - 10)
->where('createTime', '<=', $createTime + 10)
->where('status', 'in', [1, 3]) // 创建中或失败
->select();
if (empty($failedItems)) {
Log::info("未找到需要重试的记录。工作台ID: {$workbenchId}");
$job->delete();
return true;
}
// 解析配置
$config['poolGroups'] = json_decode($config['poolGroups'], true);
$config['devices'] = json_decode($config['devices'], true);
$config['admins'] = json_decode($config['admins'] ?? '[]', true) ?: [];
// 获取群主成员
$groupMember = Db::name('device_wechat_login')->alias('dwl')
->join(['s2_wechat_account' => 'a'], 'dwl.wechatId = a.wechatId')
->whereIn('dwl.deviceId', $config['devices'])
->group('a.id')
->column('a.wechatId');
$groupMemberWechatId = Db::table('s2_wechat_friend')
->where('ownerWechatId', $groupMember[0])
->whereIn('wechatId', $groupMember)
->column('id,wechatId');
$groupMemberId = array_keys($groupMemberWechatId);
// 获取管理员好友ID
$adminFriendIds = [];
if (!empty($config['admins'])) {
$adminFriends = Db::table('s2_wechat_friend')
->where('id', 'in', $config['admins'])
->column('id,wechatId,ownerWechatId');
$accountWechatId = Db::table('s2_wechat_account')->where('id', $wechatAccountId)->value('wechatId');
foreach ($adminFriends as $adminFriend) {
if ($adminFriend['ownerWechatId'] == $accountWechatId) {
$adminFriendIds[] = $adminFriend['id'];
}
}
}
// 初始化WebSocket
$toAccountId = '';
$username = Env::get('api.username2', '');
$password = Env::get('api.password2', '');
if (!empty($username) || !empty($password)) {
$toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId');
}
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
// 重新创建群
$createFriendIds = array_merge($adminFriendIds, $groupMemberId);
if (count($createFriendIds) < 2) {
Log::error("重试建群好友数量不足。工作台ID: {$workbenchId}");
$job->delete();
return false;
}
// 生成群名称
$existingGroupCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('wechatAccountId', $wechatAccountId)
->where('status', 2) // 成功
->group('groupId')
->count();
$chatroomName = $existingGroupCount > 0
? $config['groupNameTemplate'] . ($existingGroupCount + 1) . '群'
: $config['groupNameTemplate'];
// 调用建群接口
$createResult = $webSocket->CmdChatroomCreate([
'chatroomName' => $chatroomName,
'wechatFriendIds' => $createFriendIds,
'wechatAccountId' => $wechatAccountId
]);
// 更新记录状态为创建中
Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('wechatAccountId', $wechatAccountId)
->where('createTime', '>=', $createTime - 10)
->where('createTime', '<=', $createTime + 10)
->update([
'status' => 1, // 创建中
'createTime' => time() // 更新创建时间
]);
// 创建新的轮询验证任务
Queue::later(5, 'app\job\WorkbenchGroupCreateVerifyJob', [
'workbenchId' => $workbenchId,
'wechatAccountId' => $wechatAccountId,
'createTime' => time(),
'adminFriendIds' => $adminFriendIds,
'poolUsers' => [], // 重试时暂时不传poolUsers后续可以优化
], 'default');
Log::info("重试建群任务已创建。工作台ID: {$workbenchId}, 微信账号ID: {$wechatAccountId}");
$job->delete();
return true;
} catch (\Exception $e) {
Log::error("重试建群任务异常:{$e->getMessage()}");
if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
}
}

View File

@@ -0,0 +1,248 @@
<?php
namespace app\job;
use app\api\controller\WechatChatroomController;
use think\facade\Log;
use think\Db;
use think\queue\Job;
use think\facade\Cache;
use think\facade\Config;
use think\Queue;
/**
* 工作台群创建验证任务(轮询群创建状态)
* Class WorkbenchGroupCreateVerifyJob
* @package app\job
*/
class WorkbenchGroupCreateVerifyJob
{
/**
* 最大重试次数
*/
const MAX_RETRY_ATTEMPTS = 15; // 最多轮询15次
/**
* 轮询间隔(秒)
*/
const POLL_INTERVAL = 5;
/**
* 状态常量
*/
const STATUS_CREATING = 1;
const STATUS_SUCCESS = 2;
const STATUS_FAILED = 3;
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return bool
*/
public function fire(Job $job, $data)
{
$workbenchId = $data['workbenchId'] ?? 0;
$wechatAccountId = $data['wechatAccountId'] ?? 0;
$createTime = $data['createTime'] ?? 0;
$adminFriendIds = $data['adminFriendIds'] ?? [];
$poolUsers = $data['poolUsers'] ?? [];
try {
$attempts = $job->attempts();
// 查询待验证的群记录
$groupItems = Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('wechatAccountId', $wechatAccountId)
->where('status', self::STATUS_CREATING)
->where('createTime', '>=', $createTime - 10) // 允许10秒误差
->where('createTime', '<=', $createTime + 10)
->group('wechatAccountId')
->select();
if (empty($groupItems)) {
// 去除信息日志,减少日志空间消耗
$job->delete();
return true;
}
// 获取微信账号信息
$wechatAccount = Db::table('s2_wechat_account')->where('id', $wechatAccountId)->find();
if (empty($wechatAccount)) {
Log::error("未找到微信账号任务失败。微信账号ID: {$wechatAccountId}");
$job->delete();
return false;
}
// 调用接口查询群聊列表
$chatroomController = new WechatChatroomController();
$chatroomList = $chatroomController->getlist([
'wechatAccountKeyword' => $wechatAccount['wechatId'],
'pageIndex' => 0,
'pageSize' => 100
], true);
$chatroomListData = json_decode($chatroomList, true);
if (empty($chatroomListData['data']['results'])) {
// 如果超过最大重试次数,标记为失败并重试创建
if ($attempts >= self::MAX_RETRY_ATTEMPTS) {
$this->handleCreateFailed($workbenchId, $wechatAccountId, $createTime, $job);
return false;
}
// 继续轮询
$job->release(self::POLL_INTERVAL);
return false;
}
// 查找符合条件的群chatroomOwnerAvatar和chatroomOwnerNickname不为空
$successGroup = null;
foreach ($chatroomListData['data']['results'] as $chatroom) {
if (!empty($chatroom['chatroomOwnerAvatar']) && !empty($chatroom['chatroomOwnerNickname'])) {
// 检查创建时间是否匹配允许30秒误差
$chatroomCreateTime = isset($chatroom['createTime']) ? strtotime($chatroom['createTime']) : 0;
if (abs($chatroomCreateTime - $createTime) <= 30) {
$successGroup = $chatroom;
break;
}
}
}
if ($successGroup) {
// 群创建成功,更新记录状态
$groupId = $successGroup['id'] ?? 0;
$chatroomId = $successGroup['chatroomId'] ?? '';
// 更新管理员和群主成员的记录状态
Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('wechatAccountId', $wechatAccountId)
->where('status', self::STATUS_CREATING)
->where('memberType', 'in', [1, 2]) // 群主成员和管理员
->where('createTime', '>=', $createTime - 10)
->where('createTime', '<=', $createTime + 10)
->update([
'status' => self::STATUS_SUCCESS,
'groupId' => $groupId,
'chatroomId' => $chatroomId,
'verifyTime' => time()
]);
// 去除成功日志,减少日志空间消耗
// 3. 拉群主好友进群(在验证成功后执行)
$ownerFriendIds = $data['ownerFriendIds'] ?? [];
if (!empty($ownerFriendIds)) {
Queue::push('app\job\WorkbenchGroupCreateOwnerFriendJob', [
'workbenchId' => $workbenchId,
'wechatAccountId' => $wechatAccountId,
'groupId' => $groupId,
'chatroomId' => $chatroomId,
'ownerFriendIds' => $ownerFriendIds,
'createTime' => $createTime
], 'default');
}
// 5. 创建拉管理员好友的任务(在群主好友拉入后执行)
if (!empty($adminFriendIds) && !empty($poolUsers)) {
Queue::push('app\job\WorkbenchGroupCreateAdminFriendJob', [
'workbenchId' => $workbenchId,
'wechatAccountId' => $wechatAccountId,
'groupId' => $groupId,
'chatroomId' => $chatroomId,
'adminFriendIds' => $adminFriendIds,
'poolUsers' => $poolUsers
], 'default');
}
$job->delete();
return true;
} else {
// 如果超过最大重试次数,标记为失败并重试创建
if ($attempts >= self::MAX_RETRY_ATTEMPTS) {
$this->handleCreateFailed($workbenchId, $wechatAccountId, $createTime, $job);
return false;
}
// 继续轮询
$job->release(self::POLL_INTERVAL);
return false;
}
} catch (\Exception $e) {
Log::error("群创建验证任务异常:{$e->getMessage()}");
if ($job->attempts() >= self::MAX_RETRY_ATTEMPTS) {
$job->delete();
} else {
$job->release(self::POLL_INTERVAL);
}
return false;
}
}
/**
* 处理创建失败的情况(重试创建)
* @param int $workbenchId 工作台ID
* @param int $wechatAccountId 微信账号ID
* @param int $createTime 创建时间
* @param Job $job 队列任务
*/
protected function handleCreateFailed($workbenchId, $wechatAccountId, $createTime, $job)
{
// 更新状态为失败
Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('wechatAccountId', $wechatAccountId)
->where('status', self::STATUS_CREATING)
->where('createTime', '>=', $createTime - 10)
->where('createTime', '<=', $createTime + 10)
->update([
'status' => self::STATUS_FAILED,
'verifyTime' => time()
]);
Log::warning("群创建失败准备重试。工作台ID: {$workbenchId}, 微信账号ID: {$wechatAccountId}");
// 检查重试次数
$failedItems = Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('wechatAccountId', $wechatAccountId)
->where('createTime', '>=', $createTime - 10)
->where('createTime', '<=', $createTime + 10)
->select();
$maxRetryCount = 0;
foreach ($failedItems as $item) {
if ($item['retryCount'] >= 3) {
Log::error("群创建重试次数已达上限放弃重试。工作台ID: {$workbenchId}, 微信账号ID: {$wechatAccountId}");
$job->delete();
return;
}
$maxRetryCount = max($maxRetryCount, $item['retryCount']);
}
// 增加重试次数并重置状态
Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('wechatAccountId', $wechatAccountId)
->where('createTime', '>=', $createTime - 10)
->where('createTime', '<=', $createTime + 10)
->update([
'status' => self::STATUS_CREATING,
'retryCount' => Db::raw('retryCount + 1')
]);
// 重新创建建群任务延迟10秒
Queue::later(10, 'app\job\WorkbenchGroupCreateRetryJob', [
'workbenchId' => $workbenchId,
'wechatAccountId' => $wechatAccountId,
'createTime' => $createTime
], 'default');
$job->delete();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,399 @@
<?php
namespace app\job;
use app\api\controller\DeviceController;
use app\cunkebao\model\Workbench;
use app\cunkebao\model\WorkbenchImportContact;
use think\facade\Log;
use think\facade\Env;
use think\Db;
use think\queue\Job;
use think\facade\Cache;
use think\facade\Config;
/**
* 工作台通讯录导入任务
* Class WorkbenchImportContactJob
* @package app\job
*/
class WorkbenchImportContactJob
{
/**
* 最大重试次数
*/
const MAX_RETRY_ATTEMPTS = 3;
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return bool
*/
public function fire(Job $job, $data)
{
$jobId = $data['jobId'] ?? '';
$queueLockKey = $data['queueLockKey'] ?? '';
try {
$this->logJobStart($jobId, $queueLockKey);
$this->execute();
$this->handleJobSuccess($job, $queueLockKey);
return true;
} catch (\Exception $e) {
return $this->handleJobError($e, $job, $queueLockKey);
}
}
/**
* 执行任务
* @throws \Exception
*/
public function execute()
{
try {
// 获取所有启用的通讯录导入工作台
$workbenches = Workbench::where(['status' => 1, 'type' => 6, 'isDel' => 0])->order('id desc')->select();
foreach ($workbenches as $workbench) {
// 获取工作台配置
$config = WorkbenchImportContact::where('workbenchId', $workbench->id)->find();
if (!$config) {
continue;
}
// 判断是否需要导入
$shouldImport = $this->shouldImport($workbench, $config);
if (!$shouldImport) {
continue;
}
// 获取需要导入的设备列表
$devices = $this->getDeviceList($workbench, $config);
if (empty($devices)) {
continue;
}
// 获取通讯录数据
$contactData = $this->getContactFromDatabase($workbench, $config);
if (empty($contactData)) {
continue;
}
// 执行通讯录导入
$this->importContactToDevices($workbench, $config, $devices, $contactData);
}
} catch (\Exception $e) {
Log::error("通讯录导入任务异常: " . $e->getMessage());
throw $e;
}
}
/**
* 导入通讯录到设备
* @param Workbench $workbench
* @param WorkbenchImportContact $config
* @param array $devices
* @param array $contactData
*/
public function importContactToDevices($workbench, $config, $devices, $contactData)
{
$deviceController = new DeviceController();
// 根据设备数量平分通讯录数据
$deviceCount = count($devices);
if ($deviceCount == 0) {
Log::warning("没有可用设备进行通讯录导入");
return;
}
$contactCount = count($contactData);
if ($contactCount == 0) {
Log::warning("没有通讯录数据需要导入");
return;
}
// 计算每个设备分配的联系人数量
$contactsPerDevice = ceil($contactCount / $deviceCount);
foreach ($devices as $index => $device) {
try {
// 计算当前设备的联系人数据范围
$startIndex = $index * $contactsPerDevice;
$endIndex = min($startIndex + $contactsPerDevice, $contactCount);
// 如果起始索引超出范围,跳过
if ($startIndex >= $contactCount) {
continue;
}
// 获取当前设备的联系人数据片段
$deviceContactData = array_slice($contactData, $startIndex, $endIndex - $startIndex);
if (empty($deviceContactData)) {
continue;
}
// 准备联系人数据
$contactJson = $this->formatContactData($deviceContactData, $config);
// 调用设备控制器的导入联系人方法
$result = $deviceController->importContact([
'deviceId' => $device['deviceId'],
'contactJson' => $contactJson,
'clearContact' => $config['clearContact'] ?? false
], true);
$resultData = json_decode($result, true);
// 记录导入历史
$this->recordImportHistory($workbench, $device, $deviceContactData);
if ($resultData['code'] == 200) {
Log::info("设备 {$device['deviceId']} 通讯录导入成功,导入联系人数量: " . count($deviceContactData));
} else {
Log::error("设备 {$device['deviceId']} 通讯录导入失败: " . ($resultData['msg'] ?? '未知错误'));
}
// 添加延迟,避免频繁请求
if ($config['importInterval'] ?? 0 > 0) {
sleep($config['importInterval']);
}
} catch (\Exception $e) {
Log::error("设备 {$device['deviceId']} 通讯录导入异常: " . $e->getMessage());
}
}
}
/**
* 格式化联系人数据
* @param array $contactData
* @param WorkbenchImportContact $config
* @return array|string
*/
protected function formatContactData($contactData, $config)
{
$remarkType = $config['remarkType'] ?? 0;
$remark = $config['remark'] ?? '';
// 根据remarkType添加备注
$suffix = '';
switch ($remarkType) {
case 0:
// 不添加备注
$suffix = '';
break;
case 1:
// 添加年月日
$suffix = date('Ymd') . '_';
break;
case 2:
// 添加月日
$suffix = date('md') . '_';
break;
case 3:
// 自定义备注
$suffix = $remark . '_';
break;
default:
$suffix = '';
break;
}
// 返回数组格式
$contacts = [];
foreach ($contactData as $contact) {
$name = !empty($contact['name']) ? trim($contact['name']) : trim($contact['phone']);
if (!empty($suffix)) {
$name = $suffix . $name;
}
$contacts[] = [
'name' => $name,
'phone' => trim($contact['phone'])
];
}
return $contacts;
}
/**
* 记录导入历史
* @param Workbench $workbench
* @param array $device
* @param array $contactData
* @param array $result
*/
protected function recordImportHistory($workbench, $device, $contactData)
{
$data = [];
foreach ($contactData as $v){
$data[] = [
'workbenchId' => $workbench->id,
'deviceId' => $device['deviceId'],
'packageId' => !empty($v['packageId']) ? $v['packageId'] : 0,
'poolId' => !empty($v['id']) ? $v['id'] : 0,
'createTime' => time(),
];
}
Db::name('workbench_import_contact_item')->insertAll($data);
}
/**
* 获取设备列表
* @param Workbench $workbench 工作台
* @param WorkbenchImportContact $config 配置
* @return array
*/
protected function getDeviceList($workbench, $config)
{
$deviceIds = json_decode($config['devices'], true);
if (empty($deviceIds)) {
return [];
}
// 从数据库获取设备信息
$devices = Db::table('s2_device')
->whereIn('id', $deviceIds)
->where('isDeleted', 0)
->where('alive', 1) // 只选择在线设备
->field('id as deviceId, imei, nickname')
->select();
return $devices;
}
/**
* 判断是否需要导入
* @param Workbench $workbench 工作台
* @param WorkbenchImportContact $config 配置
* @return bool
*/
protected function shouldImport($workbench, $config)
{
// 检查导入间隔
$today = date('Y-m-d');
$startTimestamp = strtotime($today . ' ' . $config['startTime'] . ':00');
$endTimestamp = strtotime($today . ' ' . $config['endTime'] . ':00');
// 如果不在指定时间范围内,则跳过
if ($startTimestamp > time() || $endTimestamp < time()) {
return false;
}
$maxPerDay = $config['num'];
if ($maxPerDay <= 0) {
return false;
}
// 查询今日已导入次数
$count = Db::name('workbench_import_contact_item')
->where('workbenchId', $workbench->id)
->whereTime('createTime', 'between', [$startTimestamp, $endTimestamp])
->count();
if ($count >= $maxPerDay) {
return false;
}
// 计算导入间隔
$totalSeconds = $endTimestamp - $startTimestamp;
$interval = floor($totalSeconds / $maxPerDay);
$nextImportTime = $startTimestamp + $count * $interval;
if (time() < $nextImportTime) {
return false;
}
return true;
}
/**
* 从数据库读取通讯录
* @param WorkbenchImportContact $config
* @return array
*/
protected function getContactFromDatabase($workbench,$config)
{
$pools = json_decode($config['pools'], true);
$deviceIds = json_decode($config['devices'], true);
if (empty($pools) || empty($deviceIds)) {
return false;
}
$deviceNum = count($deviceIds);
$contactNum = $deviceNum * $config['num'];
if (empty($contactNum)) {
return false;
}
//过滤已删除的数据
$packageIds = Db::name('traffic_source_package')
->where(['isDel' => 0])
->whereIn('id', $pools)
->column('id');
if (empty($packageIds)) {
return false;
}
$data = Db::name('traffic_source_package_item')->alias('tpi')
->join('traffic_pool tp', 'tp.identifier = tpi.identifier')
->join('traffic_source ts', 'ts.identifier = tpi.identifier','left')
->join('workbench_import_contact_item wici', 'wici.poolId = tp.id AND wici.workbenchId = '.$workbench->id,'left')
->where('tp.mobile', '>',0)
->where('wici.id','null')
->whereIn('tpi.packageId',$packageIds)
->field('tp.id,tpi.packageId,tp.mobile as phone,ts.name')
->order('tp.id DESC')
->group('tpi.identifier')
->limit($contactNum)
->select();
return $data;
}
/**
* 记录任务开始
* @param string $jobId
* @param string $queueLockKey
*/
protected function logJobStart($jobId, $queueLockKey)
{
Log::info('开始处理工作台通讯录导入任务: ' . json_encode([
'jobId' => $jobId,
'queueLockKey' => $queueLockKey
]));
}
/**
* 处理任务成功
* @param Job $job
* @param string $queueLockKey
*/
protected function handleJobSuccess($job, $queueLockKey)
{
$job->delete();
Cache::rm($queueLockKey);
Log::info('工作台通讯录导入任务执行成功');
}
/**
* 处理任务错误
* @param \Exception $e
* @param Job $job
* @param string $queueLockKey
* @return bool
*/
protected function handleJobError(\Exception $e, $job, $queueLockKey)
{
Log::error('工作台通讯录导入任务异常:' . $e->getMessage());
if (!empty($queueLockKey)) {
Cache::rm($queueLockKey);
Log::info("由于异常释放队列锁: {$queueLockKey}");
}
if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
}

View File

@@ -0,0 +1,562 @@
<?php
namespace app\job;
use app\api\controller\WebSocketController;
use app\cunkebao\model\Workbench;
use app\cunkebao\model\WorkbenchMomentsSync as WorkbenchMoments;
use app\api\model\WechatFriendModel as WechatFriend;
use app\api\model\WechatMomentsModel as WechatMoments;
use think\facade\Log;
use think\facade\Env;
use think\Db;
use think\queue\Job;
use think\facade\Cache;
use think\facade\Config;
use app\api\controller\MomentsController as Moments;
use app\chukebao\model\KfMoments;
/**
* 工作台朋友圈同步任务
* Class WorkbenchMomentsJob
* @package app\job
*/
class WorkbenchMomentsJob
{
/**
* 内容类型映射
* 0未知 1图片 2链接 3视频 4文本 5小程序 6:图文
*/
const CONTENT_TYPE_MAP = [
0 => 1, // 未知 -> 文本
1 => 2, // 图片 -> 图文
2 => 4, // 链接 -> 链接
3 => 3, // 视频 -> 视频
4 => 1, // 文本 -> 文本
5 => 1, // 小程序 -> 文本
6 => 2, // 图文 -> 图文
];
/**
* 最大重试次数
*/
const MAX_RETRY_ATTEMPTS = 3;
/**
* 队列任务处理
* @param Job $job 队列任务
* @param array $data 任务数据
* @return bool
*/
public function fire(Job $job, $data)
{
$jobId = $data['jobId'] ?? '';
$queueLockKey = $data['queueLockKey'] ?? '';
try {
$this->logJobStart($jobId, $queueLockKey);
$this->execute2();
$this->execute();
$this->handleJobSuccess($job, $queueLockKey);
return true;
} catch (\Exception $e) {
return $this->handleJobError($e, $job, $queueLockKey);
}
}
/**
* 执行任务
* @throws \Exception
*/
public function execute()
{
try {
// 获取所有工作台
$workbenches = Workbench::where(['status' => 1, 'type' => 2, 'isDel' => 0])->order('id desc')->select();
foreach ($workbenches as $workbench) {
// 获取工作台配置
$config = WorkbenchMoments::where('workbenchId', $workbench->id)->find();
if (!$config) {
continue;
}
$startTime = strtotime(date('Y-m-d ' . $config['startTime']));
$endTime = strtotime(date('Y-m-d ' . $config['endTime']));
// 如果时间不符,则跳过
if ($startTime > time() || $endTime < time()) {
continue;
}
// 获取设备
$devices = $this->getDevice($workbench, $config);
if (empty($devices)) {
continue;
}
// 获取内容库
$contentLibrary = $this->getContentLibrary($workbench, $config);
if (empty($contentLibrary)) {
continue;
}
// 处理内容发送
$this->handleContentSend($workbench, $config, $devices, $contentLibrary);
}
} catch (\Exception $e) {
Log::error("朋友圈同步任务异常: " . $e->getMessage());
throw $e;
}
}
public function execute2()
{
try {
// 1) 每日重置
$this->resetDailyCountersIfNeeded();
// 2) 获取发送窗口内的任务
[$nowTs, $kfMoments] = $this->getWindowTasks();
foreach ($kfMoments as $val) {
$companyId = (int)($val['companyId'] ?? 0);
$userId = (int)($val['userId'] ?? 0);
// 2.1) 数据规范化
$sendData = json_decode($val->sendData, true);
$sendData = $this->normalizeSendData($sendData);
// 2.2) 账号额度过滤
$items = $sendData['jobPublishWechatMomentsItems'] ?? [];
if (empty($items)) { continue; }
$allowed = $this->filterAccountsByQuota($companyId, $userId, $items);
if (empty($allowed)) { continue; }
$sendData['jobPublishWechatMomentsItems'] = $allowed;
// 3) 下发
$moments = new Moments();
$res = $moments->addJob($sendData);
$res = json_decode($res, true);
if ($res['code'] == 200){
KfMoments::where(['id' => $val['id']])->update(['isSend' => 1]);
// 4) 统计
$this->incrementSendStats($companyId, $userId, $allowed);
}
}
} catch (\Exception $e) {
Log::error("朋友圈同步任务异常: " . $e->getMessage());
throw $e;
}
}
protected function resetDailyCountersIfNeeded()
{
$now = time();
$todayStart = strtotime(date('Y-m-d 00:00:00'));
if ($now - $todayStart >= 0 && $now - $todayStart <= 600) {
$cacheKey = 'moments_settings_reset_' . date('Ymd');
if (!Cache::has($cacheKey)) {
Db::table('ck_kf_moments_settings')->where('sendNum', '<>', 0)
->update(['sendNum' => 0, 'updateTime' => $now]);
Cache::set($cacheKey, 1, 7200);
}
}
}
protected function getWindowTasks()
{
$nowTs = time();
$windowStart = $nowTs - 300;
$windowEnd = $nowTs + 300;
$kfMoments = KfMoments::where(['isSend' => 0, 'isDel' => 0])
->whereBetween('sendTime', [$windowStart, $windowEnd])
->order('id desc')->select();
return [$nowTs, $kfMoments];
}
protected function normalizeSendData(array $sendData)
{
$endTime = strtotime($sendData['endTime'] ?? '');
if ($endTime <= time() + 1800) {
$endTime = time() + 3600;
$sendData['endTime'] = date('Y-m-d H:i:s', $endTime);
}
switch ($sendData['momentContentType'] ?? 1) {
case 1:
$sendData['link'] = ['image' => ''];
$sendData['picUrlList'] = [];
$sendData['videoUrl'] = '';
break;
case 2:
$sendData['link'] = ['image' => ''];
$sendData['videoUrl'] = '';
break;
case 3:
$sendData['link'] = ['image' => ''];
$sendData['picUrlList'] = [];
break;
case 4:
$sendData['picUrlList'] = [];
$sendData['videoUrl'] = '';
break;
default:
$sendData['link'] = ['image' => ''];
$sendData['picUrlList'] = [];
$sendData['videoUrl'] = '';
break;
}
return $sendData;
}
protected function filterAccountsByQuota(int $companyId, int $userId, array $items)
{
$wechatIds = array_values(array_filter(array_map(function($it){ return (int)($it['wechatAccountId'] ?? 0); }, $items)));
if (empty($wechatIds)) { return []; }
$settings = Db::table('ck_kf_moments_settings')
->where('companyId', $companyId)
->where('userId', $userId)
->whereIn('wechatId', $wechatIds)
->column('id,max,sendNum', 'wechatId');
$allowed = [];
foreach ($items as $it) {
$wid = (int)($it['wechatAccountId'] ?? 0);
if ($wid <= 0) { continue; }
if (isset($settings[$wid])) {
$max = (int)$settings[$wid]['max'];
$sent = (int)$settings[$wid]['sendNum'];
if ($sent < ($max > 0 ? $max : 5)) { $allowed[] = $it; }
} else {
$allowed[] = $it;
}
}
return $allowed;
}
protected function incrementSendStats(int $companyId, int $userId, array $items)
{
try {
$nowTs = time();
foreach ($items as $it) {
$wechatId = (int)($it['wechatAccountId'] ?? 0);
if ($wechatId <= 0) { continue; }
$cond = ['companyId' => $companyId, 'userId' => $userId, 'wechatId' => $wechatId];
$setting = Db::table('ck_kf_moments_settings')->where($cond)->find();
if ($setting) {
Db::table('ck_kf_moments_settings')->where('id', $setting['id'])
->update(['sendNum' => Db::raw('sendNum + 1'), 'updateTime' => $nowTs]);
} else {
Db::table('ck_kf_moments_settings')->insert([
'companyId' => $companyId,
'userId' => $userId,
'wechatId' => $wechatId,
'max' => 5,
'sendNum' => 1,
'createTime' => $nowTs,
'updateTime' => $nowTs,
]);
}
}
} catch (\Throwable $e) {
Log::error('朋友圈发送统计失败: ' . $e->getMessage());
}
}
/**
* 处理内容发送
* @param Workbench $workbench
* @param WorkbenchMoments $config
* @param array $devices
* @param array $contentLibrary
*/
protected function handleContentSend($workbench, $config, $devices, $contentLibrary)
{
// 准备评论数据
$comment = [];
if (!empty($contentLibrary['comment'])) {
$comment[] = $contentLibrary['comment'];
}
// 准备发送数据
$jobPublishWechatMomentsItems = [];
foreach ($devices as $device) {
$jobPublishWechatMomentsItems[] = [
'comments' => $comment,
'labels' => [],
'wechatAccountId' => $device['wechatAccountId']
];
}
// 转换内容类型
$momentContentType = self::CONTENT_TYPE_MAP[$contentLibrary['contentType']] ?? 1;
$sendTime = !empty($contentLibrary['sendTime']) ? $contentLibrary['sendTime'] : time();
// 图片url
if ($momentContentType == 2) {
$picUrlList = json_decode($contentLibrary['resUrls'], true);
} else {
$picUrlList = [];
}
// 视频url
if ($momentContentType == 3) {
$videoUrl = json_decode($contentLibrary['urls'], true);
$videoUrl = $videoUrl[0] ?? '';
} else {
$videoUrl = '';
}
// 链接url
if ($momentContentType == 4) {
$urls = json_decode($contentLibrary['urls'], true);
$url = $urls[0] ?? [];
$link = [
'desc' => $url['desc'] ?? '',
'image' => $url['image'] ?? '',
'url' => $url['url'] ?? ''
];
} else {
$link = ['image' => ''];
}
// 准备发送参数
$data = [
'altList' => '',
'immediately' => false,
'isUseLocation' => false,
'jobPublishWechatMomentsItems' => $jobPublishWechatMomentsItems,
'lat' => 0,
'lng' => 0,
'link' => $link,
'momentContentType' => $momentContentType,
'picUrlList' => $picUrlList,
'poiAddress' => '',
'poiName' => '',
'publicMode' => '',
'text' => !empty($contentLibrary['contentAi']) ? $contentLibrary['contentAi'] : $contentLibrary['content'],
'timingTime' => date('Y-m-d H:i:s', $sendTime),
'beginTime' => date('Y-m-d H:i:s', $sendTime),
'endTime' => date('Y-m-d H:i:s', $sendTime + 3600),
'videoUrl' => $videoUrl,
];
// 发送朋友圈
$moments = new Moments();
$res = $moments->addJob($data);
$res = json_decode($res,true);
if ($res['code'] == 200){
// 记录发送记录
$this->recordSendHistory($workbench, $devices, $contentLibrary);
}
}
/**
* 记录发送历史
* @param Workbench $workbench
* @param array $devices
* @param array $contentLibrary
*/
protected function recordSendHistory($workbench, $devices, $contentLibrary)
{
$now = time();
$data = [];
foreach ($devices as $device) {
$data = [
'workbenchId' => $workbench->id,
'deviceId' => $device['deviceId'],
'contentId' => $contentLibrary['id'],
'wechatAccountId' => $device['wechatAccountId'],
'isLoop' => 0, // 初始状态为未完成循环
'createTime' => $now,
];
Db::name('workbench_moments_sync_item')->insert($data);
}
}
/**
* 获取设备列表
* @param Workbench $workbench 工作台
* @param WorkbenchMoments $config 配置
* @return array|bool
*/
protected function getDevice($workbench, $config)
{
$devices = json_decode($config['devices'], true);
if (empty($devices)) {
return false;
}
$list = Db::name('device')->alias('d')
->join('device_wechat_login dw', 'dw.alive = 1 and dw.deviceId = d.id and dw.companyId = d.companyId')
->join(['s2_wechat_account' => 'wa'], 'wa.wechatId = dw.wechatId')
->where(['d.companyId' => $workbench->companyId, 'd.alive' => 1])
->whereIn('d.id', $devices)
->field('d.id as deviceId, d.memo as deviceName, d.companyId, dw.wechatId, wa.id as wechatAccountId')
->select();
$newList = [];
foreach ($list as $val) {
// 检查发送间隔新逻辑根据startTime、endTime、syncCount动态计算
$today = date('Y-m-d');
$startTimestamp = strtotime($today . ' ' . $config['startTime'] . ':00');
$endTimestamp = strtotime($today . ' ' . $config['endTime'] . ':00');
$totalSeconds = $endTimestamp - $startTimestamp;
if ($totalSeconds <= 0 || empty($config['syncCount'])) {
continue;
}
$interval = floor($totalSeconds / $config['syncCount']);
// 查询今日已同步次数
$count = Db::name('workbench_moments_sync_item')
->where('workbenchId', $workbench->id)
->where('deviceId', $val['deviceId'])
->whereTime('createTime', 'between', [$startTimestamp, $endTimestamp])
->count();
if ($count >= $config['syncCount']) {
continue;
}
// 计算本次同步的最早允许时间
$nextSyncTime = $startTimestamp + $count * $interval;
if (time() < $nextSyncTime) {
continue;
}
$newList[] = $val;
}
return $newList;
}
/**
* 获取内容库
* @param Workbench $workbench 工作台
* @param WorkbenchMoments $config 配置
* @return array|bool
*/
protected function getContentLibrary($workbench, $config)
{
$contentids = json_decode($config['contentLibraries'], true);
// 清洗 contentids去除 null/空字符串,并去重,保持原顺序
if (is_array($contentids)) {
$contentids = array_values(array_unique(array_filter($contentids, function ($v) {
return $v !== null && $v !== '';
})));
} else {
$contentids = [];
}
if (empty($contentids)) {
return false;
}
// 基础查询
$query = Db::name('content_library')->alias('cl')
->join('content_item ci', 'ci.libraryId = cl.id')
->where(['cl.isDel' => 0, 'ci.isDel' => 0])
->whereIn('cl.id', $contentids)
->field([
'ci.id',
'ci.libraryId',
'ci.contentType',
'ci.title',
'ci.content',
'ci.resUrls',
'ci.urls',
'ci.comment',
'ci.sendTime'
]);
// 复制 query
$query2 = clone $query;
$query3 = clone $query;
// 根据accountType处理不同的发送逻辑
if ($config['accountType'] == 1) {
// 可以循环发送
// 1. 优先获取未发送的内容
$unsentContent = $query2->join('workbench_moments_sync_item wmsi', 'wmsi.contentId = ci.id and wmsi.workbenchId = ' . $workbench->id, 'left')
->where('wmsi.id', 'null')
->where('ci.sendTime <= ' . (time() + 60))
->order('ci.sendTime desc, ci.id desc')
->find();
if (!empty($unsentContent)) {
return $unsentContent;
}
// 获取下一个要发送的内容从内容库中查询排除isLoop为0的数据
$isPushIds = Db::name('workbench_moments_sync_item')
->where(['workbenchId' => $workbench->id, 'isLoop' => 0])
->column('contentId');
if (empty($isPushIds)) {
$isPushIds = [0];
}
$sentContent = $query3
->whereNotIn('ci.id', $isPushIds)
->group('ci.id')
->order('ci.id asc')
->find();
// 4. 如果仍然没有内容说明内容库为空将所有记录的isLoop标记为1
if (empty($sentContent)) {
// 将所有该工作台的记录标记为循环完成
Db::name('workbench_moments_sync_item')
->where('workbenchId', $workbench->id)
->where('isLoop', 0)
->update(['isLoop' => 1]);
return false;
}
return $sentContent;
} else {
// 不能循环发送,只获取未发送的内容
$list = $query2->join('workbench_moments_sync_item wmsi', 'wmsi.contentId = ci.id and wmsi.workbenchId = ' . $workbench->id, 'left')
->where('wmsi.id', 'null')
->order('ci.sendTime desc, ci.id desc')
->find();
return $list;
}
}
/**
* 记录任务开始
* @param string $jobId
* @param string $queueLockKey
*/
protected function logJobStart($jobId, $queueLockKey)
{
// 去除开始日志,减少日志空间消耗
}
/**
* 处理任务成功
* @param Job $job
* @param string $queueLockKey
*/
protected function handleJobSuccess($job, $queueLockKey)
{
$job->delete();
Cache::rm($queueLockKey);
// 去除成功日志,减少日志空间消耗
}
/**
* 处理任务错误
* @param \Exception $e
* @param Job $job
* @param string $queueLockKey
* @return bool
*/
protected function handleJobError(\Exception $e, $job, $queueLockKey)
{
Log::error('工作台朋友圈同步任务异常:' . $e->getMessage());
if (!empty($queueLockKey)) {
Cache::rm($queueLockKey);
Log::info("由于异常释放队列锁: {$queueLockKey}");
}
if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
}

View File

@@ -0,0 +1,267 @@
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
use think\Queue;
use think\facade\Config;
use think\facade\Cache;
use app\cunkebao\model\Workbench;
use app\cunkebao\model\WorkbenchTrafficConfig;
use think\Db;
use app\api\controller\AutomaticAssign;
class WorkbenchTrafficDistributeJob
{
const MAX_RETRY_ATTEMPTS = 3;
public function fire(Job $job, $data)
{
$jobId = $data['jobId'] ?? '';
$queueLockKey = $data['queueLockKey'] ?? '';
try {
$this->logJobStart($jobId, $queueLockKey);
$workbenches = $this->getActiveWorkbenches();
if (empty($workbenches)) {
$this->handleEmptyWorkbenches($job, $queueLockKey);
return true;
}
$this->processWorkbenches($workbenches);
$this->handleJobSuccess($job, $queueLockKey);
return true;
} catch (\Exception $e) {
return $this->handleJobError($e, $job, $queueLockKey);
}
}
protected function getActiveWorkbenches()
{
return Workbench::where([
['status', '=', 1],
['isDel', '=', 0],
['type', '=', 5]
])->order('id DESC')->select();
}
protected function processWorkbenches($workbenches)
{
foreach ($workbenches as $workbench) {
try {
$this->processSingleWorkbench($workbench);
} catch (\Exception $e) {
Log::error("处理流量分发工作台 {$workbench->id} 失败: " . $e->getMessage());
}
}
}
protected function processSingleWorkbench($workbench)
{
$page = 1;
$pageSize = 20;
$config = WorkbenchTrafficConfig::where('workbenchId', $workbench->id)->find();
if (!$config) {
Log::error("流量分发工作台 {$workbench->id} 配置获取失败");
return;
}
// 验证是否在流量分发时间范围内
if (!$this->isTimeRange($config) && $config['timeType'] == 2) {
return;
}
// 获取当天未超额的可用账号
if(empty($config['account'])){
Log::error("流量分发工作台 {$workbench->id} 未配置分发的客服");
return;
}
$accountIds = json_decode($config['account'],true);
$todayStart = strtotime(date('Y-m-d 00:00:00'));
$todayEnd = strtotime(date('Y-m-d 23:59:59'));
$accounts = Db::table('s2_company_account')
->alias('a')
->where(['a.departmentId' => $workbench->companyId, 'a.status' => 0])
->whereIn('a.id',$accountIds)
->whereNotLike('a.userName', '%_offline%')
->whereNotLike('a.userName', '%_delete%')
->leftJoin('workbench_traffic_config_item wti', "wti.wechatAccountId = a.id AND wti.workbenchId = {$workbench->id} AND wti.createTime BETWEEN {$todayStart} AND {$todayEnd}")
->field('a.id,a.userName,a.realName,a.nickname,COUNT(wti.id) as todayCount')
->group('a.id')
->having('todayCount <= ' . $config['maxPerDay'])
->select();
$accountNum = count($accounts);
if ($accountNum < 1) {
Log::info("流量分发工作台 {$workbench->id} 可分配账号少于1个");
return;
}
$automaticAssign = new AutomaticAssign();
do {
$friends = $this->getFriendsByLabels($workbench, $config, $page, $pageSize);
if (empty($friends) || count($friends) == 0) {
Log::info("流量分发工作台 {$workbench->id} 没有可分配的好友");
break;
}
$i = 0;
$accountNum = count($accounts);
foreach ($friends as $friend) {
if ($accountNum == 0) {
Log::info("流量分发工作台 {$workbench->id} 所有账号今日分配已满");
break 2;
}
if ($i >= $accountNum) {
$i = 0;
}
$account = $accounts[$i];
// 如果该账号今天分配的记录数加上本次分配的记录数超过最大限制
if (($account['todayCount'] + $pageSize) >= $config['maxPerDay']) {
// 查询该客服账号当天分配记录数
$todayCount = Db::name('workbench_traffic_config_item')
->where('workbenchId', $workbench->id)
->where('wechatAccountId', $account['id'])
->whereBetween('createTime', [$todayStart, $todayEnd])
->count();
if ($todayCount >= $config['maxPerDay']) {
unset($accounts[$i]);
$accounts = array_values($accounts);
$accountNum = count($accounts);
$i++;
continue;
}
}
// 执行切换好友命令
$res = $automaticAssign->allotWechatFriend([
'wechatFriendId' => $friend['id'],
'toAccountId' => $account['id']
], true);
$res = json_decode($res,true);
if ($res['code'] == 200){
Db::table('s2_wechat_friend')
->where('id',$friend['id'])
->update([
'accountId' => $account['id'],
'accountUserName' => $account['userName'],
'accountRealName' => $account['realName'],
'accountNickname' => $account['nickname'],
]);
// 写入分配记录表
Db::name('workbench_traffic_config_item')->insert([
'workbenchId' => $workbench->id,
'deviceId' => $friend['deviceId'],
'wechatFriendId' => $friend['id'],
'wechatAccountId' => $account['id'],
'createTime' => time(),
'exp' => $config['exp'],
'expTime' => time() + 86400 * $config['exp'],
]);
- // 去除成功日志,减少日志空间消耗
}
$i++;
}
break;
$page++;
} while (true);
// 去除完成日志,减少日志空间消耗
}
/**
* 检查是否在流量分发时间范围内
* @param WorkbenchAutoLike $config
* @return bool
*/
protected function isTimeRange($config)
{
$currentTime = date('H:i');
if ($currentTime < $config['startTime'] || $currentTime > $config['endTime']) {
Log::info("当前时间 {$currentTime} 不在流量分发时间范围内 ({$config['startTime']} - {$config['endTime']})");
return false;
}
return true;
}
/**
* 一次性查出所有包含指定标签数组的好友(支持分页)
* @param object $workbench 工作台对象
* @param object $config 配置对象
* @param int $page 页码
* @param int $pageSize 每页数量
* @return array
*/
protected function getFriendsByLabels($workbench, $config, $page = 1, $pageSize = 20)
{
$labels = [];
if (!empty($config['pools'])) {
$labels = is_array($config['pools']) ? $config['pools'] : json_decode($config['pools'], true);
}
$devices = [];
if (!empty($config['devices'])) {
$devices = is_array($config['devices']) ? $config['devices'] : json_decode($config['devices'], true);
}
if (empty($devices)) {
return [];
}
$query = Db::table('s2_wechat_friend')->alias('wf')
->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left')
->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left')
->join('workbench_traffic_config_item wtci', 'wtci.isRecycle = 0 and wtci.wechatFriendId = wf.id AND wtci.workbenchId = ' . $config['workbenchId'], 'left')
->where([
['wf.isDeleted', '=', 0],
['wf.isPassed', '=', 1],
//['sa.departmentId', '=', $workbench->companyId],
['wtci.id', 'null', null]
])
->whereIn('wa.currentDeviceId', $devices)
->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.labels,sa.userName,wa.currentDeviceId as deviceId');
if(!empty($labels)){
$query->where(function ($q) use ($labels) {
foreach ($labels as $label) {
$q->whereOrRaw("JSON_CONTAINS(wf.labels, '\"{$label}\"')");
}
});
}
$list = $query->page($page, $pageSize)->order('wf.id DESC')->select();
return $list;
}
protected function logJobStart($jobId, $queueLockKey)
{
// 去除开始日志,减少日志空间消耗
}
protected function handleJobSuccess($job, $queueLockKey)
{
$job->delete();
Cache::rm($queueLockKey);
// 去除成功日志,减少日志空间消耗
}
protected function handleJobError(\Exception $e, $job, $queueLockKey)
{
Log::error('流量分发任务异常:' . $e->getMessage());
if (!empty($queueLockKey)) {
Cache::rm($queueLockKey);
Log::info("由于异常释放队列锁: {$queueLockKey}");
}
if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) {
$job->delete();
} else {
$job->release(Config::get('queue.failed_delay', 10));
}
return false;
}
protected function handleEmptyWorkbenches(Job $job, $queueLockKey)
{
Log::info('没有需要处理的流量分发任务');
$job->delete();
Cache::rm($queueLockKey);
}
}