From 1566f8fb7c95b5b107618ddcbbd8f3a1bc38a46d Mon Sep 17 00:00:00 2001 From: Ghost <106998207@qq.com> Date: Mon, 12 Jan 2026 09:43:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=A5=E7=BE=A4=E6=AC=A2=E8=BF=8E=E8=AF=AD?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/WebSocketController.php | 26 +- .../controller/WechatChatroomController.php | 5 +- application/command.php | 1 + .../command/WorkbenchGroupWelcomeCommand.php | 42 ++ .../workbench/WorkbenchController.php | 125 ++++- application/cunkebao/model/Workbench.php | 5 + .../cunkebao/model/WorkbenchGroupWelcome.php | 27 ++ .../model/WorkbenchGroupWelcomeItem.php | 51 ++ application/cunkebao/validate/Workbench.php | 13 +- application/job/MessageChatroomListJob.php | 20 +- application/job/MessageFriendsListJob.php | 18 +- application/job/WorkbenchGroupWelcomeJob.php | 441 ++++++++++++++++++ crontab_tasks.md | 3 + 13 files changed, 734 insertions(+), 43 deletions(-) create mode 100644 application/command/WorkbenchGroupWelcomeCommand.php create mode 100644 application/cunkebao/model/WorkbenchGroupWelcome.php create mode 100644 application/cunkebao/model/WorkbenchGroupWelcomeItem.php create mode 100644 application/job/WorkbenchGroupWelcomeJob.php diff --git a/application/api/controller/WebSocketController.php b/application/api/controller/WebSocketController.php index 5c5e325..73d6bf3 100644 --- a/application/api/controller/WebSocketController.php +++ b/application/api/controller/WebSocketController.php @@ -805,11 +805,8 @@ class WebSocketController extends BaseController "wechatChatroomId" => 0, "wechatFriendId" => $dataArray['wechatFriendId'], ]; - // 发送请求 - $this->client->send(json_encode($params)); - // 接收响应 - $response = $this->client->receive(); - $message = json_decode($response, true); + // 发送请求并获取响应 + $message = $this->sendMessage($params); if (!empty($message)) { return json_encode(['code' => 200, 'msg' => '信息发送成功', 'data' => $message]); } @@ -853,12 +850,8 @@ class WebSocketController extends BaseController "wechatChatroomId" => $dataArray['wechatChatroomId'], "wechatFriendId" => 0, ]; - - // 发送请求 - $this->client->send(json_encode($params)); - // 接收响应 - $response = $this->client->receive(); - $message = json_decode($response, true); + // 发送请求并获取响应 + $message = $this->sendMessage($params); if (!empty($message)) { return json_encode(['code' => 200, 'msg' => '信息发送成功', 'data' => $message]); } @@ -904,7 +897,7 @@ class WebSocketController extends BaseController $message = []; try { //消息拼接 msgType(1:文本 3:图片 43:视频 47:动图表情包 49:小程序) - $result = [ + $params = [ "cmdType" => "CmdSendMessage", "content" => $dataArray['content'], "msgSubType" => 0, @@ -914,15 +907,10 @@ class WebSocketController extends BaseController "wechatChatroomId" => $dataArray['wechatChatroomId'], "wechatFriendId" => 0, ]; - - $result = json_encode($result); - $this->client->send($result); - $message = $this->client->receive(); - //关闭WS链接 - $this->client->close(); + // 发送请求并获取响应 + $message = $this->sendMessage($params); //Log::write('WS群消息发送'); //Log::write($message); - $message = json_decode($message, 1); } catch (\Exception $e) { $msg = $e->getMessage(); } diff --git a/application/api/controller/WechatChatroomController.php b/application/api/controller/WechatChatroomController.php index a1a0f9d..3da5ec6 100644 --- a/application/api/controller/WechatChatroomController.php +++ b/application/api/controller/WechatChatroomController.php @@ -5,7 +5,9 @@ namespace app\api\controller; use app\api\model\WechatChatroomModel; use app\api\model\WechatChatroomMemberModel; use app\job\WechatChatroomJob; +use app\job\WorkbenchGroupWelcomeJob; use think\facade\Request; +use think\Queue; class WechatChatroomController extends BaseController { @@ -218,8 +220,9 @@ class WechatChatroomController extends BaseController ])->find(); if ($member) { - $member->savea($data); + $member->save($data); } else { + // 新成员,记录首次出现时间 $data['createTime'] = time(); WechatChatroomMemberModel::create($data); } diff --git a/application/command.php b/application/command.php index 0fa99a6..2bf23db 100644 --- a/application/command.php +++ b/application/command.php @@ -38,6 +38,7 @@ return [ 'workbench:trafficDistribute' => 'app\command\WorkbenchTrafficDistributeCommand', // 工作台流量分发任务 'workbench:groupPush' => 'app\command\WorkbenchGroupPushCommand', // 工作台群推送任务 'workbench:groupCreate' => 'app\command\WorkbenchGroupCreateCommand', // 工作台群创建任务 + 'workbench:groupWelcome' => 'app\command\WorkbenchGroupWelcomeCommand', // 工作台入群欢迎语任务 'workbench:import-contact' => 'app\command\WorkbenchImportContactCommand', // 工作台通讯录导入任务 'kf:notice' => 'app\command\KfNoticeCommand', // 客服端消息通知 diff --git a/application/command/WorkbenchGroupWelcomeCommand.php b/application/command/WorkbenchGroupWelcomeCommand.php new file mode 100644 index 0000000..6eb55c2 --- /dev/null +++ b/application/command/WorkbenchGroupWelcomeCommand.php @@ -0,0 +1,42 @@ +setName('workbench:groupWelcome') + ->setDescription('工作台入群欢迎语任务队列'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln('开始处理工作台入群欢迎语任务...'); + + try { + $job = new WorkbenchGroupWelcomeJob(); + $result = $job->processWelcomeMessage([], 0); + + if ($result) { + $output->writeln('入群欢迎语任务处理完成'); + } else { + $output->writeln('入群欢迎语任务处理失败'); + } + + return $result; + } catch (\Exception $e) { + $errorMsg = '工作台入群欢迎语任务执行失败:' . $e->getMessage(); + Log::error($errorMsg); + $output->writeln($errorMsg); + return false; + } + } +} + + diff --git a/application/cunkebao/controller/workbench/WorkbenchController.php b/application/cunkebao/controller/workbench/WorkbenchController.php index 60bdbef..3bd4169 100644 --- a/application/cunkebao/controller/workbench/WorkbenchController.php +++ b/application/cunkebao/controller/workbench/WorkbenchController.php @@ -12,6 +12,7 @@ use app\cunkebao\model\WorkbenchImportContact; use app\cunkebao\model\WorkbenchMomentsSync; use app\cunkebao\model\WorkbenchGroupPush; use app\cunkebao\model\WorkbenchGroupCreate; +use app\cunkebao\model\WorkbenchGroupWelcome; use app\cunkebao\validate\Workbench as WorkbenchValidate; use think\Controller; use think\Db; @@ -33,6 +34,7 @@ class WorkbenchController extends Controller const TYPE_GROUP_CREATE = 4; // 自动建群 const TYPE_TRAFFIC_DISTRIBUTION = 5; // 流量分发 const TYPE_IMPORT_CONTACT = 6; // 联系人导入 + const TYPE_GROUP_WELCOME = 7; // 入群欢迎语 /** * 创建工作台 @@ -49,7 +51,6 @@ class WorkbenchController extends Controller // 获取请求参数 $param = $this->request->post(); - // 根据业务默认值补全参数 if ( @@ -201,6 +202,30 @@ class WorkbenchController extends Controller $config->createTime = time(); $config->save(); break; + case self::TYPE_GROUP_WELCOME: // 入群欢迎语 + $config = new WorkbenchGroupWelcome; + $config->workbenchId = $workbench->id; + $config->devices = json_encode($param['deviceGroups'] ?? [], JSON_UNESCAPED_UNICODE); + $config->groups = json_encode($param['wechatGroups'] ?? [], JSON_UNESCAPED_UNICODE); + $config->startTime = $param['startTime'] ?? ''; + $config->endTime = $param['endTime'] ?? ''; + $config->interval = isset($param['interval']) ? intval($param['interval']) : 0; + // messages 作为 JSON 存储(如果表中有 messages 字段) + if (isset($param['messages']) && is_array($param['messages'])) { + // 按 order 排序 + usort($param['messages'], function($a, $b) { + $orderA = isset($a['order']) ? intval($a['order']) : 0; + $orderB = isset($b['order']) ? intval($b['order']) : 0; + return $orderA <=> $orderB; + }); + $config->messages = json_encode($param['messages'], JSON_UNESCAPED_UNICODE); + } else { + $config->messages = json_encode([], JSON_UNESCAPED_UNICODE); + } + $config->createTime = time(); + $config->updateTime = time(); + $config->save(); + break; } Db::commit(); @@ -456,6 +481,23 @@ class WorkbenchController extends Controller } unset($item->importContact, $item->import_contact); break; + case self::TYPE_GROUP_WELCOME: + if (!empty($item->groupWelcome)) { + $item->config = $item->groupWelcome; + $item->config->deviceGroups = json_decode($item->config->devices, true); + $item->config->wechatGroups = json_decode($item->config->groups, true); + // 解析 messages JSON 字段 + if (!empty($item->config->messages)) { + $item->config->messages = json_decode($item->config->messages, true); + if (!is_array($item->config->messages)) { + $item->config->messages = []; + } + } else { + $item->config->messages = []; + } + } + unset($item->groupWelcome, $item->group_welcome); + break; } // 添加创建人名称 $item['creatorName'] = $item->user ? $item->user->username : ''; @@ -510,6 +552,9 @@ class WorkbenchController extends Controller 'importContact' => function ($query) { $query->field('workbenchId,devices,pools,num,remarkType,remark,clearContact,startTime,endTime'); }, + 'groupWelcome' => function ($query) { + $query->field('workbenchId,devices,groups,startTime,endTime,interval,messages'); + }, ]; $where = [ @@ -773,6 +818,23 @@ class WorkbenchController extends Controller } unset($workbench->importContact, $workbench->import_contact); break; + case self::TYPE_GROUP_WELCOME: + if (!empty($workbench->groupWelcome)) { + $workbench->config = $workbench->groupWelcome; + $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); + $workbench->config->wechatGroups = json_decode($workbench->config->groups, true); + // 解析 messages JSON 字段 + if (!empty($workbench->config->messages)) { + $workbench->config->messages = json_decode($workbench->config->messages, true); + if (!is_array($workbench->config->messages)) { + $workbench->config->messages = []; + } + } else { + $workbench->config->messages = []; + } + } + unset($workbench->groupWelcome, $workbench->group_welcome); + break; } unset( $workbench->autoLike, @@ -873,13 +935,14 @@ class WorkbenchController extends Controller } + // 获取群(当targetType=1时) - if (!empty($workbench->config->wechatGroups) && isset($workbench->config->targetType) && $workbench->config->targetType == 1) { - $groupList = Db::name('wechat_group')->alias('wg') - ->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId') - ->where('wg.id', 'in', $workbench->config->wechatGroups) - ->order('wg.id', 'desc') - ->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wa.avatar,wa.alias,wg.avatar as groupAvatar') + if (!empty($workbench->config->wechatGroups) && $workbench->type != self::TYPE_GROUP_CREATE) { + $groupList = Db::table('s2_wechat_chatroom')->alias('wc') + ->whereIn('wc.id', $workbench->config->wechatGroups) + ->where('wc.isDeleted', 0) + ->order('wc.id', 'desc') + ->field('wc.id,wc.nickname as groupName,wc.wechatAccountWechatId as ownerWechatId,wc.wechatAccountNickname as nickName,wc.wechatAccountAvatar as avatar,wc.wechatAccountAlias as alias,wc.chatroomAvatar as groupAvatar') ->select(); $workbench->config->wechatGroupsOptions = $groupList; } else { @@ -887,7 +950,7 @@ class WorkbenchController extends Controller } // 获取好友(当targetType=2时) - if (!empty($workbench->config->wechatFriends) && isset($workbench->config->targetType) && $workbench->config->targetType == 2) { + if (!empty($workbench->config->wechatFriends)) { $friendList = Db::table('s2_wechat_friend')->alias('wf') ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') ->where('wf.id', 'in', $workbench->config->wechatFriends) @@ -900,7 +963,7 @@ class WorkbenchController extends Controller } // 获取流量池(当targetType=2时) - if (!empty($workbench->config->trafficPools) && isset($workbench->config->targetType) && $workbench->config->targetType == 2) { + if (!empty($workbench->config->trafficPools)) { $poolList = []; $companyId = $this->request->userInfo['companyId']; @@ -1065,9 +1128,7 @@ class WorkbenchController extends Controller } $workbench->config->wechatGroupsOptions = $wechatGroupsOptions; - } else { - $workbench->config->wechatGroupsOptions = []; - } + } // 获取管理员选项(自动建群) if ($workbench->type == self::TYPE_GROUP_CREATE && !empty($workbench->config->admins)) { @@ -1258,6 +1319,30 @@ class WorkbenchController extends Controller $config->save(); } break; + case self::TYPE_GROUP_WELCOME: // 入群欢迎语 + $config = WorkbenchGroupWelcome::where('workbenchId', $param['id'])->find(); + if ($config) { + $config->devices = json_encode($param['deviceGroups'] ?? [], JSON_UNESCAPED_UNICODE); + $config->groups = json_encode($param['wechatGroups'] ?? [], JSON_UNESCAPED_UNICODE); + $config->startTime = $param['startTime'] ?? ''; + $config->endTime = $param['endTime'] ?? ''; + $config->interval = isset($param['interval']) ? intval($param['interval']) : 0; + // messages 作为 JSON 存储 + if (isset($param['messages']) && is_array($param['messages'])) { + // 按 order 排序 + usort($param['messages'], function($a, $b) { + $orderA = isset($a['order']) ? intval($a['order']) : 0; + $orderB = isset($b['order']) ? intval($b['order']) : 0; + return $orderA <=> $orderB; + }); + $config->messages = json_encode($param['messages'], JSON_UNESCAPED_UNICODE); + } else { + $config->messages = json_encode([], JSON_UNESCAPED_UNICODE); + } + $config->updateTime = time(); + $config->save(); + } + break; } Db::commit(); @@ -1476,6 +1561,22 @@ class WorkbenchController extends Controller $newConfig->save(); } break; + case self::TYPE_GROUP_WELCOME: // 入群欢迎语 + $config = WorkbenchGroupWelcome::where('workbenchId', $id)->find(); + if ($config) { + $newConfig = new WorkbenchGroupWelcome; + $newConfig->workbenchId = $newWorkbench->id; + $newConfig->devices = $config->devices; + $newConfig->groups = $config->groups; + $newConfig->startTime = $config->startTime; + $newConfig->endTime = $config->endTime; + $newConfig->interval = $config->interval; + $newConfig->messages = $config->messages ?? json_encode([], JSON_UNESCAPED_UNICODE); + $newConfig->createTime = time(); + $newConfig->updateTime = time(); + $newConfig->save(); + } + break; } Db::commit(); diff --git a/application/cunkebao/model/Workbench.php b/application/cunkebao/model/Workbench.php index edde8b7..44e5551 100644 --- a/application/cunkebao/model/Workbench.php +++ b/application/cunkebao/model/Workbench.php @@ -67,6 +67,11 @@ class Workbench extends Model return $this->hasOne('WorkbenchImportContact', 'workbenchId', 'id'); } + // 入群欢迎语配置关联 + public function groupWelcome() + { + return $this->hasOne('WorkbenchGroupWelcome', 'workbenchId', 'id'); + } /** * 用户关联 diff --git a/application/cunkebao/model/WorkbenchGroupWelcome.php b/application/cunkebao/model/WorkbenchGroupWelcome.php new file mode 100644 index 0000000..f449432 --- /dev/null +++ b/application/cunkebao/model/WorkbenchGroupWelcome.php @@ -0,0 +1,27 @@ +belongsTo('Workbench', 'workbenchId', 'id'); + } +} + diff --git a/application/cunkebao/model/WorkbenchGroupWelcomeItem.php b/application/cunkebao/model/WorkbenchGroupWelcomeItem.php new file mode 100644 index 0000000..c75da4e --- /dev/null +++ b/application/cunkebao/model/WorkbenchGroupWelcomeItem.php @@ -0,0 +1,51 @@ +belongsTo('Workbench', 'workbenchId', 'id'); + } + + /** + * 获取状态文本 + * @param int $status 状态值 + * @return string + */ + public static function getStatusText($status) + { + $statusMap = [ + self::STATUS_PENDING => '待发送', + self::STATUS_SENDING => '发送中', + self::STATUS_SUCCESS => '发送成功', + self::STATUS_FAILED => '发送失败', + ]; + return $statusMap[$status] ?? '未知'; + } +} + diff --git a/application/cunkebao/validate/Workbench.php b/application/cunkebao/validate/Workbench.php index ffd9fae..0ba19d7 100644 --- a/application/cunkebao/validate/Workbench.php +++ b/application/cunkebao/validate/Workbench.php @@ -13,13 +13,14 @@ class Workbench extends Validate const TYPE_GROUP_CREATE = 4; // 自动建群 const TYPE_TRAFFIC_DISTRIBUTION = 5; // 流量分发 const TYPE_IMPORT_CONTACT = 6; // 流量分发 + const TYPE_GROUP_WELCOME = 7; // 入群欢迎语 /** * 验证规则 */ protected $rule = [ 'name' => 'require|max:100', - 'type' => 'require|in:1,2,3,4,5,6', + 'type' => 'require|in:1,2,3,4,5,6,7', //'autoStart' => 'require|boolean', // 自动点赞特有参数 'interval' => 'requireIf:type,1|number|min:1', @@ -62,8 +63,14 @@ class Workbench extends Validate 'maxPerDay' => 'requireIf:type,5|number|min:1', 'timeType' => 'requireIf:type,5|in:1,2', 'accountGroups' => 'requireIf:type,5|array|min:1', + // 入群欢迎语特有参数 + 'wechatGroups' => 'requireIf:type,7|array|min:1', // 入群欢迎语必须选择群组 + 'interval' => 'requireIf:type,7|number|min:1', // 间隔时间 + 'startTime' => 'requireIf:type,7|dateFormat:H:i', // 开始时间 + 'endTime' => 'requireIf:type,7|dateFormat:H:i', // 结束时间 + 'messages' => 'requireIf:type,7|array|min:1', // 欢迎消息列表 // 通用参数 - 'deviceGroups' => 'requireIf:type,1,2,5|array', + 'deviceGroups' => 'requireIf:type,1,2,5,7|array', 'trafficPools' => 'checkFriendPushPools', ]; @@ -185,6 +192,7 @@ class Workbench extends Validate 'announcementContent', 'enableAiRewrite', 'aiRewritePrompt', 'groupNameTemplate', 'maxGroupsPerDay', 'groupSizeMin', 'groupSizeMax', 'distributeType', 'timeType', 'accountGroups', + 'messages', ], 'update_status' => ['id', 'status'], 'update' => ['name', 'type', 'autoStart', 'deviceGroups', 'targetGroups', @@ -194,6 +202,7 @@ class Workbench extends Validate 'announcementContent', 'enableAiRewrite', 'aiRewritePrompt', 'groupNameTemplate', 'maxGroupsPerDay', 'groupSizeMin', 'groupSizeMax', 'distributeType', 'timeType', 'accountGroups', + 'messages', ] ]; diff --git a/application/job/MessageChatroomListJob.php b/application/job/MessageChatroomListJob.php index e3db929..d66112d 100644 --- a/application/job/MessageChatroomListJob.php +++ b/application/job/MessageChatroomListJob.php @@ -10,6 +10,11 @@ use app\api\controller\MessageController; class MessageChatroomListJob { + /** + * 最大同步页数 + */ + const MAX_SYNC_PAGES = 5; + /** * 队列任务处理 * @param Job $job 队列任务 @@ -81,12 +86,17 @@ class MessageChatroomListJob if ($response['code'] == 200) { $data = $response['data']; - // 判断是否有下一页 - if (!empty($data) && count($data['results']) > 0) { - // 有下一页,将下一页任务添加到队列 + // 判断是否有下一页,且未超过最大同步页数 + if (!empty($data) && count($data) > 0) { $nextPageIndex = $pageIndex + 1; - $this->addNextPageToQueue($nextPageIndex, $pageSize); - Log::info('添加下一页任务到队列,页码:' . $nextPageIndex); + // 检查是否超过最大同步页数 + if ($nextPageIndex < self::MAX_SYNC_PAGES) { + // 有下一页且未超过最大页数,将下一页任务添加到队列 + $this->addNextPageToQueue($nextPageIndex, $pageSize); + Log::info('添加下一页任务到队列,页码:' . $nextPageIndex); + } else { + Log::info('已达到最大同步页数(' . self::MAX_SYNC_PAGES . '),停止添加下一页任务'); + } } return true; diff --git a/application/job/MessageFriendsListJob.php b/application/job/MessageFriendsListJob.php index ee7c309..408bfe0 100644 --- a/application/job/MessageFriendsListJob.php +++ b/application/job/MessageFriendsListJob.php @@ -10,6 +10,11 @@ use app\api\controller\MessageController; class MessageFriendsListJob { + /** + * 最大同步页数 + */ + const MAX_SYNC_PAGES = 5; + /** * 队列任务处理 * @param Job $job 队列任务 @@ -83,12 +88,17 @@ class MessageFriendsListJob if ($response['code'] == 200) { $data = $response['data']; - // 判断是否有下一页 + // 判断是否有下一页,且未超过最大同步页数 if (!empty($data) && count($data) > 0) { - // 有下一页,将下一页任务添加到队列 $nextPageIndex = $pageIndex + 1; - $this->addNextPageToQueue($nextPageIndex, $pageSize); - Log::info('添加下一页任务到队列,页码:' . $nextPageIndex); + // 检查是否超过最大同步页数 + if ($nextPageIndex < self::MAX_SYNC_PAGES) { + // 有下一页且未超过最大页数,将下一页任务添加到队列 + $this->addNextPageToQueue($nextPageIndex, $pageSize); + Log::info('添加下一页任务到队列,页码:' . $nextPageIndex); + } else { + Log::info('已达到最大同步页数(' . self::MAX_SYNC_PAGES . '),停止添加下一页任务'); + } } return true; diff --git a/application/job/WorkbenchGroupWelcomeJob.php b/application/job/WorkbenchGroupWelcomeJob.php new file mode 100644 index 0000000..aa0dfbd --- /dev/null +++ b/application/job/WorkbenchGroupWelcomeJob.php @@ -0,0 +1,441 @@ +processWelcomeMessage($data, $job->attempts())) { + $job->delete(); + } else { + if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) { + Log::error('入群欢迎语任务执行失败,已超过重试次数,数据:' . json_encode($data)); + $job->delete(); + } else { + Log::warning('入群欢迎语任务执行失败,重试次数:' . $job->attempts() . ',数据:' . json_encode($data)); + $job->release(self::RETRY_DELAY); + } + } + } catch (\Exception $e) { + Log::error('入群欢迎语任务异常:' . $e->getMessage()); + if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) { + $job->delete(); + } else { + $job->release(self::RETRY_DELAY); + } + } + } + + /** + * 处理欢迎消息发送 + * @param array $data 任务数据 + * @param int $attempts 重试次数 + * @return bool + */ + public function processWelcomeMessage($data, $attempts) + { + try { + // 查找该群配置的入群欢迎语工作台 + $welcomeConfigs = Db::table('ck_workbench_group_welcome') + ->alias('wgw') + ->join('ck_workbench w', 'w.id = wgw.workbenchId') + ->where('w.status', 1) // 工作台启用 + ->where('w.type', self::WORKBENCH_TYPE_WELCOME) // 入群欢迎语类型 + ->field('wgw.*,w.id as workbenchId') + ->select(); + + if (empty($welcomeConfigs)) { + return true; // 没有配置欢迎语,不算失败 + } + + foreach ($welcomeConfigs as $config) { + // 解析配置中的群组列表 + $wechatGroups = json_decode($config['groups'] ?? '[]', true); + if (!is_array($wechatGroups) || empty($wechatGroups)) { + continue; // 该配置没有配置群组,跳过 + } + + // 遍历该配置中的每个群ID,处理每个群的欢迎语 + foreach ($wechatGroups as $groupItemId) { + // 检查群是否存在 + $chatroomExists = Db::table('s2_wechat_chatroom') + ->where('id', $groupItemId) + ->where('isDeleted', 0) + ->count(); + if (!$chatroomExists) { + Log::warning("群ID {$groupItemId} 不存在或已删除,跳过欢迎语处理"); + continue; + } + + // 处理单个群的欢迎语 + $this->processSingleGroupWelcome($groupItemId, $config); + } + } + + return true; + } catch (\Exception $e) { + Log::error('处理入群欢迎语异常:' . $e->getMessage() . ', 数据:' . json_encode($data)); + return false; + } + } + + /** + * 处理单个群的欢迎语发送 + * @param int $groupId 群ID(s2_wechat_chatroom表的id) + * @param array $config 工作台配置 + * @return void + */ + protected function processSingleGroupWelcome($groupId, $config) + { + // 根据groupId获取群信息 + $chatroom = Db::table('s2_wechat_chatroom') + ->where('id', $groupId) + ->where('isDeleted', 0) + ->field('wechatAccountId,wechatAccountWechatId') + ->find(); + if (empty($chatroom)) { + Log::warning("群ID {$groupId} 不存在或已删除,跳过欢迎语处理"); + return; + } + // 检查时间范围 + if (!$this->isInTimeRange($config['startTime'] ?? '', $config['endTime'] ?? '')) { + return; // 不在工作时间范围内 + } + + // 解析消息列表 + $messages = json_decode($config['messages'] ?? '[]', true); + if (empty($messages) || !is_array($messages)) { + return; // 没有配置消息 + } + + // interval代表整组消息的时间间隔,在此间隔内进群的成员都需要@ + $interval = intval($config['interval'] ?? 0); // 秒 + + // 查找该群最近一次发送欢迎语的时间 + $lastWelcomeTime = Db::table('ck_workbench_group_welcome_item') + ->where('workbenchId', $config['workbenchId']) + ->where('groupid', $groupId) + ->where('status', self::STATUS_SUCCESS) // 发送成功 + ->order('sendTime', 'desc') + ->value('sendTime'); + // 确定时间窗口起点 + if (!empty($lastWelcomeTime)) { + // 如果上次发送时间在interval内,说明还在同一个时间窗口,需要累积新成员 + $windowStartTime = max($lastWelcomeTime, time() - $interval); + } else { + // 第一次发送,从interval前开始 + $windowStartTime = time() - $interval; + } + + // 查询该群在时间窗口内的新成员 + // 通过关联s2_wechat_chatroom表查询,使用groupId + $recentMembers = Db::table('s2_wechat_chatroom_member') + ->alias('wcm') + ->join(['s2_wechat_chatroom' => 'wc'], 'wc.chatroomId = wcm.chatroomId') + ->where('wc.id', $groupId) + ->where('wcm.createTime', '>=', $windowStartTime) + ->field('wcm.wechatId,wcm.nickname,wcm.createTime') + ->select(); + // 入群太久远的成员不要 @,只保留「近期加入」的成员 + $minJoinTime = time() - self::MAX_JOIN_AGE_SECONDS; + $recentMembers = array_values(array_filter($recentMembers, function ($member) use ($minJoinTime) { + $joinTime = intval($member['createTime'] ?? 0); + return $joinTime >= $minJoinTime; + })); + + if (empty($recentMembers)) { + return; + } + // 如果上次发送时间在interval内,检查是否有新成员 + if (!empty($lastWelcomeTime) && $lastWelcomeTime >= (time() - $interval)) { + // 获取上次发送时的成员列表 + $lastWelcomeItem = Db::table('ck_workbench_group_welcome_item') + ->where('workbenchId', $config['workbenchId']) + ->where('groupid', $groupId) + ->where('sendTime', $lastWelcomeTime) + ->field('friendId') + ->find(); + $lastMemberIds = json_decode($lastWelcomeItem['friendId'] ?? '[]', true); + $currentMemberWechatIds = array_column($recentMembers, 'wechatId'); + + // 找出新加入的成员 + $newMemberWechatIds = array_diff($currentMemberWechatIds, $lastMemberIds); + if (empty($newMemberWechatIds)) { + return; // 没有新成员,跳过 + } + + // 只发送给新加入的成员 + $membersToWelcome = []; + foreach ($recentMembers as $member) { + if (in_array($member['wechatId'], $newMemberWechatIds)) { + $membersToWelcome[] = $member; + } + } + } else { + // 不在同一个时间窗口,@所有在时间间隔内的成员 + $membersToWelcome = $recentMembers; + } + + if (empty($membersToWelcome)) { + return; + } + + // 获取设备信息(用于发送消息) + $devices = json_decode($config['devices'] ?? '[]', true); + if (empty($devices) || !is_array($devices)) { + return; + } + + // wechatAccountId 是 s2_wechat_account 表的 id + $wechatAccountId = $chatroom['wechatAccountId'] ?? 0; + $wechatAccountWechatId = $chatroom['wechatAccountWechatId'] ?? ''; + + if (empty($wechatAccountWechatId)) { + Log::warning("群ID {$groupId} 的微信账号ID为空,跳过欢迎语发送"); + return; + } + + // 初始化WebSocket + $username = Env::get('api.username', ''); + $password = Env::get('api.password', ''); + $toAccountId = ''; + if (!empty($username) || !empty($password)) { + $toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); + } + $webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); + + // 按order排序消息 + usort($messages, function($a, $b) { + return (intval($a['order'] ?? 0)) <=> (intval($b['order'] ?? 0)); + }); + + // 发送每条消息 + foreach ($messages as $messageIndex => $message) { + $messageContent = $message['content'] ?? ''; + $sendInterval = intval($message['sendInterval'] ?? 5); // 秒 + $intervalUnit = $message['intervalUnit'] ?? 'seconds'; + + // 转换间隔单位 + $sendInterval = $this->convertIntervalToSeconds($sendInterval, $intervalUnit); + + // 替换 @{好友} 占位符 + $processedContent = $this->replaceFriendPlaceholder($messageContent, $membersToWelcome); + + // 构建@消息格式 + $atContent = $this->buildAtMessage($processedContent, $membersToWelcome); + + // 判断是否有@人:如果有atId,则使用90001,否则使用1 + $hasAtMembers = !empty($atContent['atId']); + $msgType = $hasAtMembers ? self::MSG_TYPE_AT : self::MSG_TYPE_TEXT; + + // 发送消息 + // 注意:wechatChatroomId 使用 groupId(数字类型),不是 chatroomId + $sendResult = $webSocket->sendCommunitys([ + 'content' => json_encode($atContent, JSON_UNESCAPED_UNICODE), + 'msgType' => $msgType, + 'wechatAccountId' => intval($wechatAccountId), + 'wechatChatroomId' => $groupId, // 使用 groupId(数字类型) + ]); + $sendResultData = json_decode($sendResult, true); + $sendSuccess = !empty($sendResultData) && isset($sendResultData['code']) && $sendResultData['code'] == 200; + // 记录发送记录 + $friendIds = array_column($membersToWelcome, 'wechatId'); + $this->saveWelcomeItem([ + 'workbenchId' => $config['workbenchId'], + 'groupId' => $groupId, + 'deviceId' => !empty($devices) ? intval($devices[0]) : 0, + 'wechatAccountId' => $wechatAccountId, + 'friendId' => $friendIds, + 'status' => $sendSuccess ? WorkbenchGroupWelcomeItem::STATUS_SUCCESS : WorkbenchGroupWelcomeItem::STATUS_FAILED, + 'messageIndex' => $messageIndex, + 'messageId' => $message['id'] ?? '', + 'content' => $processedContent, + 'sendTime' => time(), + 'errorMsg' => $sendSuccess ? '' : ($sendResultData['msg'] ?? '发送失败'), + ]); + + // 如果不是最后一条消息,等待间隔时间 + if ($messageIndex < count($messages) - 1) { + sleep($sendInterval); + } + } + + Log::info("入群欢迎语发送成功,工作台ID: {$config['workbenchId']}, 群ID: {$groupId}, 成员数: " . count($membersToWelcome)); + } + + /** + * 替换 @{好友} 占位符为群成员昵称(带@符号) + * @param string $content 原始内容 + * @param array $members 成员列表 + * @return string 替换后的内容 + */ + protected function replaceFriendPlaceholder($content, $members) + { + if (empty($members)) { + return str_replace('@{好友}', '', $content); + } + + // 将所有成员的昵称拼接,每个昵称前添加@符号 + $atNicknames = []; + foreach ($members as $member) { + $nickname = $member['nickname'] ?? ''; + if (!empty($nickname)) { + $atNicknames[] = '@' . $nickname; + } else { + // 如果没有昵称,使用wechatId + $wechatId = $member['wechatId'] ?? ''; + if (!empty($wechatId)) { + $atNicknames[] = '@' . $wechatId; + } + } + } + + $atNicknameStr = implode(' ', $atNicknames); + + // 替换 @{好友} 为 @昵称1 @昵称2 ... + $content = str_replace('@{好友}', $atNicknameStr, $content); + + return $content; + } + + /** + * 构建@消息格式 + * @param string $text 文本内容(已替换@{好友}占位符,已包含@符号) + * @param array $members 成员列表 + * @return array 格式:{"text":"@wong @wong 11111111","atId":"WANGMINGZHENG000,WANGMINGZHENG000"} + */ + protected function buildAtMessage($text, $members) + { + $atIds = []; + + // 收集所有成员的wechatId用于atId + foreach ($members as $member) { + $wechatId = $member['wechatId'] ?? ''; + if (!empty($wechatId)) { + $atIds[] = $wechatId; + } + } + + // 文本中已经包含了@昵称(在replaceFriendPlaceholder中已添加) + // 直接使用处理后的文本 + return [ + 'text' => trim($text), + 'atId' => implode(',', $atIds) + ]; + } + + /** + * 检查是否在工作时间范围内 + * @param string $startTime 开始时间(格式:HH:mm) + * @param string $endTime 结束时间(格式:HH:mm) + * @return bool + */ + protected function isInTimeRange($startTime, $endTime) + { + if (empty($startTime) || empty($endTime)) { + return true; // 如果没有配置时间,默认全天可用 + } + + $currentTime = date('H:i'); + $currentMinutes = $this->timeToMinutes($currentTime); + $startMinutes = $this->timeToMinutes($startTime); + $endMinutes = $this->timeToMinutes($endTime); + + if ($startMinutes <= $endMinutes) { + // 正常情况:09:00 - 21:00 + return $currentMinutes >= $startMinutes && $currentMinutes <= $endMinutes; + } else { + // 跨天情况:21:00 - 09:00 + return $currentMinutes >= $startMinutes || $currentMinutes <= $endMinutes; + } + } + + /** + * 将时间转换为分钟数 + * @param string $time 时间(格式:HH:mm) + * @return int 分钟数 + */ + protected function timeToMinutes($time) + { + $parts = explode(':', $time); + if (count($parts) != 2) { + return 0; + } + return intval($parts[0]) * 60 + intval($parts[1]); + } + + /** + * 转换间隔单位到秒 + * @param int $interval 间隔数值 + * @param string $unit 单位(seconds/minutes/hours) + * @return int 秒数 + */ + protected function convertIntervalToSeconds($interval, $unit) + { + switch ($unit) { + case 'minutes': + return $interval * 60; + case 'hours': + return $interval * 3600; + case 'seconds': + default: + return $interval; + } + } + + /** + * 保存欢迎语发送记录 + * @param array $data 记录数据 + * @return void + */ + protected function saveWelcomeItem($data) + { + try { + $item = new WorkbenchGroupWelcomeItem(); + $item->workbenchId = $data['workbenchId']; + $item->groupId = $data['groupId']; + $item->deviceId = $data['deviceId'] ?? 0; + $item->wechatAccountId = $data['wechatAccountId'] ?? 0; + $item->friendId = json_encode($data['friendId'] ?? [], JSON_UNESCAPED_UNICODE); + $item->status = $data['status'] ?? WorkbenchGroupWelcomeItem::STATUS_PENDING; + $item->messageIndex = $data['messageIndex'] ?? null; + $item->messageId = $data['messageId'] ?? ''; + $item->content = $data['content'] ?? ''; + $item->sendTime = $data['sendTime'] ?? time(); + $item->errorMsg = $data['errorMsg'] ?? ''; + $item->retryCount = 0; + $item->createTime = time(); + $item->updateTime = time(); + $item->save(); + } catch (\Exception $e) { + Log::error('保存入群欢迎语记录失败:' . $e->getMessage()); + } + } +} + diff --git a/crontab_tasks.md b/crontab_tasks.md index 38e61ea..3f1bdba 100644 --- a/crontab_tasks.md +++ b/crontab_tasks.md @@ -167,6 +167,9 @@ crontab -l */5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:groupCreate >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/workbench_groupCreate.log 2>&1 # 工作台通讯录导入 */5 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:import-contact >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/import_contact.log 2>&1 +# 工作台入群欢迎语 +*/1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think workbench:groupWelcome >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/workbench_groupWelcome.log 2>&1 + # 消息提醒 */1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think kf:notice >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/kf_notice.log 2>&1 # 客服评分