From 4bfe33a37fea7b54b9717c5c47fc6aca8bafb2ab Mon Sep 17 00:00:00 2001 From: kuaifan Date: Thu, 6 Nov 2025 13:59:10 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=89=93=E5=BC=80?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E4=BA=8B=E4=BB=B6=E6=8E=A5=E5=8F=A3=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9C=BA=E5=99=A8=E4=BA=BAwebhook=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 `open__event` 方法用于处理打开会话事件 - 移除旧的 `open__webhook` 方法 - 更新前端调用逻辑,使用新的事件接口 - 优化 webhook 事件推送逻辑,简化参数传递 --- app/Http/Controllers/Api/DialogController.php | 93 ++-- app/Models/UserBot.php | 98 ++-- app/Models/WebSocketDialog.php | 86 +--- app/Observers/WebSocketDialogUserObserver.php | 12 + app/Tasks/BotReceiveMsgTask.php | 37 +- .../DialogView/template/bot-api.vue | 468 +++++++++++++----- .../pages/manage/components/DialogWrapper.vue | 3 +- resources/assets/js/store/actions.js | 54 +- 8 files changed, 492 insertions(+), 359 deletions(-) diff --git a/app/Http/Controllers/Api/DialogController.php b/app/Http/Controllers/Api/DialogController.php index a34958dd0..96e194b4f 100755 --- a/app/Http/Controllers/Api/DialogController.php +++ b/app/Http/Controllers/Api/DialogController.php @@ -448,6 +448,39 @@ class DialogController extends AbstractController return Base::retSuccess('success', $data); } + /** + * @api {get} api/dialog/open/event 打开会话事件 + * + * @apiDescription 需要token身份 + * @apiVersion 1.0.0 + * @apiGroup dialog + * @apiName open__event + * + * @apiParam {Number} dialog_id 对话ID + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function open__event() + { + $user = User::auth(); + // + $dialog_id = intval(Request::input('dialog_id')); + // + $dialog = WebSocketDialog::checkDialog($dialog_id); + if (empty($dialog)) { + return Base::retError('打开会话失败'); + } + // + Cache::remember("webhook_dialog_open_{$dialog->id}_{$user->userid}", Carbon::now()->addMinute(), function () use ($dialog, $user) { + $dialog->dispatchMemberWebhook(UserBot::WEBHOOK_EVENT_DIALOG_OPEN, $user->userid, $user->userid); + return true; + }); + // + return Base::retSuccess('success'); + } + /** * @api {get} api/dialog/msg/list 获取消息列表 * @@ -1258,9 +1291,6 @@ class DialogController extends AbstractController if ($model_name) { $msgData['model_name'] = $model_name; } - if (User::isBot($user->userid)) { - $msgData['force_webhook'] = true; // 强制使用webhook发送 - } $result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'text', $msgData, $user->userid, false, false, $silence, $key); } } @@ -3688,61 +3718,4 @@ class DialogController extends AbstractController // return Base::retSuccess('重命名成功', $session); } - - /** - * @api {get} api/dialog/open/webhook 打开机器人会话推送 webhook - * - * @apiDescription 需要token身份 - * @apiVersion 1.0.0 - * @apiGroup dialog - * @apiName open__webhook - * - * @apiParam {Number} dialog_id 对话ID - * - * @apiSuccess {Number} ret 返回状态码(1正确、0错误) - * @apiSuccess {String} msg 返回信息(错误描述) - * @apiSuccess {Object} data 返回数据 - */ - public function open__webhook() - { - $user = User::auth(); - // - $dialog_id = intval(Request::input('dialog_id')); - if (empty($dialog_id)) { - return Base::retError('错误的会话'); - } - // - $dialog = WebSocketDialog::checkDialog($dialog_id); - if (empty($dialog)) { - return Base::retError('打开会话失败'); - } - $data = WebSocketDialog::synthesizeData($dialog->id, $user->userid); - if ($data['bot'] == 1) { - $botTarget = User::whereUserid($data['dialog_user']['userid'])->whereBot(1)->first(); - if ($botTarget) { - $userBot = UserBot::whereBotId($botTarget->userid)->first(); - if ($userBot) { - // 每个机器人1分钟只触发一次 webhook - Cache::remember('webhook_dialog_open_' . $botTarget->userid, 60, function () use ($userBot, $dialog, $user) { - $userBot->dispatchWebhook(UserBot::WEBHOOK_EVENT_DIALOG_OPEN, [ - 'dialog_id' => $dialog->id, - 'dialog_type' => $dialog->type, - 'session_id' => $dialog->session_id, - 'dialog_name' => $dialog->getGroupName(), - 'user' => [ - 'userid' => $user->userid, - 'email' => $user->email, - 'nickname' => $user->nickname, - ], - ], 10, [ - 'dialog' => $dialog->id, - 'operator' => $user->userid, - ]); - return true; - }); - } - } - } - return Base::retSuccess('success'); - } } diff --git a/app/Models/UserBot.php b/app/Models/UserBot.php index d3cd78eb8..92c960c13 100644 --- a/app/Models/UserBot.php +++ b/app/Models/UserBot.php @@ -4,7 +4,6 @@ namespace App\Models; use App\Module\Base; use App\Module\Doo; -use App\Module\Extranet; use App\Module\Ihttp; use App\Module\Timer; use App\Tasks\JokeSoupTask; @@ -56,43 +55,6 @@ class UserBot extends AbstractModel 'webhook_events' => 'array', ]; - /** - * 获取可选的 webhook 事件 - * - * @return string[] - */ - public static function webhookEventOptions(): array - { - return [ - self::WEBHOOK_EVENT_MESSAGE, - self::WEBHOOK_EVENT_DIALOG_OPEN, - self::WEBHOOK_EVENT_MEMBER_JOIN, - self::WEBHOOK_EVENT_MEMBER_LEAVE, - ]; - } - - /** - * 标准化 webhook 事件配置 - * - * @param mixed $events - * @return array - */ - public static function normalizeWebhookEvents(mixed $events, bool $useFallback = true): array - { - if (is_string($events)) { - $events = Base::json2array($events); - } - if ($events === null) { - $events = []; - } - if (!is_array($events)) { - $events = [$events]; - } - $events = array_filter(array_map('strval', $events)); - $events = array_values(array_intersect($events, self::webhookEventOptions())); - return $events ?: ($useFallback ? [self::WEBHOOK_EVENT_MESSAGE] : []); - } - /** * 获取 webhook 事件配置 * @@ -140,35 +102,27 @@ class UserBot extends AbstractModel * 发送 webhook * * @param string $event - * @param array $payload + * @param array $data * @param int $timeout - * @param array $context * @return array|null */ - public function dispatchWebhook(string $event, array $payload, int $timeout = 30, array $context = []): ?array + public function dispatchWebhook(string $event, array $data, int $timeout = 30): ?array { if (!$this->shouldDispatchWebhook($event)) { return null; } - $payload = array_merge([ - 'event' => $event, - 'timestamp' => time(), - 'bot_uid' => $this->bot_id, - 'owner_uid' => $this->userid, - ], $payload); - try { - $result = Ihttp::ihttp_post($this->webhook_url, $payload, $timeout); + $data['event'] = $event; + $result = Ihttp::ihttp_post($this->webhook_url, $data, $timeout); $this->increment('webhook_num'); return $result; } catch (Throwable $th) { - info(Base::array2json(array_merge($context, [ - 'bot_userid' => $this->bot_id, - 'event' => $event, + info(Base::array2json([ 'webhook_url' => $this->webhook_url, + 'data' => $data, 'error' => $th->getMessage(), - ]))); + ])); return null; } } @@ -607,4 +561,42 @@ class UserBot extends AbstractModel } return Base::retSuccess("创建成功。", $data); } + + /** + * 获取可选的 webhook 事件 + * + * @return string[] + */ + public static function webhookEventOptions(): array + { + return [ + self::WEBHOOK_EVENT_MESSAGE, + self::WEBHOOK_EVENT_DIALOG_OPEN, + self::WEBHOOK_EVENT_MEMBER_JOIN, + self::WEBHOOK_EVENT_MEMBER_LEAVE, + ]; + } + + /** + * 标准化 webhook 事件配置 + * + * @param mixed $events + * @param bool $useFallback + * @return array + */ + public static function normalizeWebhookEvents(mixed $events, bool $useFallback = true): array + { + if (is_string($events)) { + $events = Base::json2array($events); + } + if ($events === null) { + $events = []; + } + if (!is_array($events)) { + $events = [$events]; + } + $events = array_filter(array_map('strval', $events)); + $events = array_values(array_intersect($events, self::webhookEventOptions())); + return $events ?: ($useFallback ? [self::WEBHOOK_EVENT_MESSAGE] : []); + } } diff --git a/app/Models/WebSocketDialog.php b/app/Models/WebSocketDialog.php index 0b6f3267d..fd5923a26 100644 --- a/app/Models/WebSocketDialog.php +++ b/app/Models/WebSocketDialog.php @@ -461,8 +461,7 @@ class WebSocketDialog extends AbstractModel */ public function joinGroup($userid, $inviter, $important = null) { - $addedUserIds = []; - AbstractModel::transaction(function () use ($important, $inviter, $userid, &$addedUserIds) { + AbstractModel::transaction(function () use ($important, $inviter, $userid) { foreach (is_array($userid) ? $userid : [$userid] as $value) { if ($value > 0) { $updateData = [ @@ -481,7 +480,6 @@ class WebSocketDialog extends AbstractModel ]); }, $isInsert); if ($isInsert) { - $addedUserIds[] = $value; WebSocketDialogMsg::sendMsg(null, $this->id, 'notice', [ 'notice' => User::userid2nickname($value) . " 已加入群组" ], $inviter, true, true); @@ -492,16 +490,6 @@ class WebSocketDialog extends AbstractModel $data = WebSocketDialog::generatePeople($this->id); $data['id'] = $this->id; $this->pushMsg("groupUpdate", $data); - if ($addedUserIds) { - $meta = ['action' => 'join']; - if ($inviter > 0) { - $actor = $this->getUserSnapshots([$inviter]); - if (!empty($actor)) { - $meta['actor'] = $actor[0]; - } - } - $this->dispatchMemberWebhook(UserBot::WEBHOOK_EVENT_MEMBER_JOIN, $addedUserIds, $meta); - } return true; } @@ -515,15 +503,14 @@ class WebSocketDialog extends AbstractModel public function exitGroup($userid, $type = 'exit', $checkDelete = true, $pushMsg = true) { $typeDesc = $type === 'remove' ? '移出' : '退出'; - $removedUserIds = []; - AbstractModel::transaction(function () use ($pushMsg, $checkDelete, $typeDesc, $type, $userid, &$removedUserIds) { + AbstractModel::transaction(function () use ($pushMsg, $checkDelete, $typeDesc, $type, $userid) { $builder = WebSocketDialogUser::whereDialogId($this->id); if (is_array($userid)) { $builder->whereIn('userid', $userid); } else { $builder->whereUserid($userid); } - $builder->chunkById(100, function($list) use ($pushMsg, $checkDelete, $typeDesc, $type, &$removedUserIds) { + $builder->chunkById(100, function($list) use ($pushMsg, $checkDelete, $typeDesc, $type) { /** @var WebSocketDialogUser $item */ foreach ($list as $item) { if ($checkDelete) { @@ -543,8 +530,8 @@ class WebSocketDialog extends AbstractModel } } // + $item->operator_id = User::userid(); $item->delete(); - $removedUserIds[] = $item->userid; // if ($pushMsg) { if ($type === 'remove') { @@ -563,58 +550,17 @@ class WebSocketDialog extends AbstractModel $data = WebSocketDialog::generatePeople($this->id); $data['id'] = $this->id; $this->pushMsg("groupUpdate", $data); - if ($removedUserIds) { - $meta = ['action' => $type]; - $operatorId = User::userid(); - if ($operatorId > 0) { - $actor = $this->getUserSnapshots([$operatorId]); - if (!empty($actor)) { - $meta['actor'] = $actor[0]; - } - } - $this->dispatchMemberWebhook(UserBot::WEBHOOK_EVENT_MEMBER_LEAVE, $removedUserIds, $meta); - } - } - - /** - * 获取用户快照 - * @param array $userIds - * @return array - */ - protected function getUserSnapshots(array $userIds): array - { - $userIds = array_values(array_unique(array_filter($userIds))); - if (empty($userIds)) { - return []; - } - return User::whereIn('userid', $userIds) - ->get(['userid', 'nickname', 'email', 'bot']) - ->map(function (User $user) { - return [ - 'userid' => $user->userid, - 'nickname' => $user->nickname, - 'email' => $user->email, - 'is_bot' => (bool)$user->bot, - ]; - }) - ->values() - ->all(); } /** * 推送成员事件到机器人 webhook * @param string $event - * @param array $memberIds - * @param array $meta + * @param int $memberId + * @param int $operatorId * @return void */ - protected function dispatchMemberWebhook(string $event, array $memberIds, array $meta = []): void + public function dispatchMemberWebhook(string $event, int $memberId, int $operatorId): void { - $memberIds = array_values(array_unique(array_filter($memberIds))); - if (empty($memberIds)) { - return; - } - $botIds = $this->dialogUser()->where('bot', 1)->pluck('userid')->toArray(); if (empty($botIds)) { return; @@ -625,24 +571,20 @@ class WebSocketDialog extends AbstractModel return; } - $members = $this->getUserSnapshots($memberIds); - if (empty($members)) { - return; - } + $member = User::find($memberId, ['userid', 'nickname', 'email', 'bot'])?->toArray(); + $operator = $operatorId === $memberId ? $member : User::find($operatorId, ['userid', 'nickname', 'email', 'bot'])?->toArray(); - $payload = array_merge([ + $payload = [ 'dialog_id' => $this->id, 'dialog_type' => $this->type, 'group_type' => $this->group_type, 'dialog_name' => $this->getGroupName(), - 'members' => $members, - ], array_filter($meta, fn ($value) => $value !== null)); + 'member' => $member, + 'operator' => $operator, + ]; foreach ($userBots as $userBot) { - $userBot->dispatchWebhook($event, $payload, 10, [ - 'dialog' => $this->id, - 'event_members' => $memberIds, - ]); + $userBot->dispatchWebhook($event, $payload, 10); } } diff --git a/app/Observers/WebSocketDialogUserObserver.php b/app/Observers/WebSocketDialogUserObserver.php index 6aab42287..743b2b70c 100644 --- a/app/Observers/WebSocketDialogUserObserver.php +++ b/app/Observers/WebSocketDialogUserObserver.php @@ -3,6 +3,7 @@ namespace App\Observers; use App\Models\Deleted; +use App\Models\UserBot; use App\Models\WebSocketDialogUser; use App\Tasks\ZincSearchSyncTask; use Carbon\Carbon; @@ -31,6 +32,11 @@ class WebSocketDialogUserObserver extends AbstractObserver } Deleted::forget('dialog', $webSocketDialogUser->dialog_id, $webSocketDialogUser->userid); self::taskDeliver(new ZincSearchSyncTask('userSync', $webSocketDialogUser->toArray())); + // + $dialog = $webSocketDialogUser->webSocketDialog; + if ($dialog) { + $dialog->dispatchMemberWebhook(UserBot::WEBHOOK_EVENT_MEMBER_JOIN, $webSocketDialogUser->userid, intval($webSocketDialogUser->inviter)); + } } /** @@ -54,6 +60,12 @@ class WebSocketDialogUserObserver extends AbstractObserver { Deleted::record('dialog', $webSocketDialogUser->dialog_id, $webSocketDialogUser->userid); self::taskDeliver(new ZincSearchSyncTask('deleteUser', $webSocketDialogUser->toArray())); + // + $dialog = $webSocketDialogUser->webSocketDialog; + if ($dialog) { + $operatorId = $webSocketDialogUser->operator_id ?? 0; + $dialog->dispatchMemberWebhook(UserBot::WEBHOOK_EVENT_MEMBER_LEAVE, $webSocketDialogUser->userid, intval($operatorId)); + } } /** diff --git a/app/Tasks/BotReceiveMsgTask.php b/app/Tasks/BotReceiveMsgTask.php index b6193d4b6..cec726633 100644 --- a/app/Tasks/BotReceiveMsgTask.php +++ b/app/Tasks/BotReceiveMsgTask.php @@ -75,11 +75,7 @@ class BotReceiveMsgTask extends AbstractTask $msg->readSuccess($botUser->userid); // 判断消息是否是机器人发送的则不处理,避免循环 - if ((!$msg->user || $msg->user->bot)) { - $msgData = Base::json2array($msg->msg); - if (Base::val($msgData, 'force_webhook') && $msg->webSocketDialog) { - $this->handleWebhookRequest($msgData['text'], null, $msg, $msg->webSocketDialog, $botUser); - } + if (!$msg->user || $msg->user->bot) { return; } @@ -539,9 +535,6 @@ class BotReceiveMsgTask extends AbstractTask return; } } - if (!$userBot && !preg_match("/^https?:\/\//", $webhookUrl)) { - return; - } } catch (\Exception $e) { WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [ 'type' => 'content', @@ -549,8 +542,10 @@ class BotReceiveMsgTask extends AbstractTask ], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务 return; } - // + + // 基本请求数据 $data = [ + 'event' => UserBot::WEBHOOK_EVENT_MESSAGE, 'text' => $sendText, 'reply_text' => $replyText, 'token' => User::generateToken($botUser), @@ -561,8 +556,9 @@ class BotReceiveMsgTask extends AbstractTask 'msg_uid' => $msg->userid, 'mention' => $this->mention ? 1 : 0, 'bot_uid' => $botUser->userid, + 'extras' => Base::array2json($extras), 'version' => Base::getVersion(), - 'extras' => Base::array2json($extras) + 'timestamp' => time(), ]; // 添加用户信息 $userInfo = User::find($msg->userid); @@ -579,19 +575,14 @@ class BotReceiveMsgTask extends AbstractTask $result = null; if ($userBot) { - $result = $userBot->dispatchWebhook(UserBot::WEBHOOK_EVENT_MESSAGE, $data, 30, [ - 'dialog' => $dialog->id, - 'msg' => $msg->id, - ]); + $result = $userBot->dispatchWebhook(UserBot::WEBHOOK_EVENT_MESSAGE, $data); } else { try { $result = Ihttp::ihttp_post($webhookUrl, $data, 30); } catch (\Throwable $th) { info(Base::array2json([ - 'bot_userid' => $botUser->userid, - 'dialog' => $dialog->id, - 'msg' => $msg->id, 'webhook_url' => $webhookUrl, + 'data' => $data, 'error' => $th->getMessage(), ])); } @@ -599,7 +590,7 @@ class BotReceiveMsgTask extends AbstractTask if ($result && isset($result['data'])) { $responseData = Base::json2array($result['data']); - if (($responseData['code'] ?? 0) != 200 && !empty($responseData['message'])) { + if (($responseData['code'] ?? 0) === 200 && !empty($responseData['message'])) { WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', [ 'text' => $responseData['message'] ], $botUser->userid, false, false, true); @@ -722,7 +713,7 @@ class BotReceiveMsgTask extends AbstractTask EOF; } - + // 上下文信息(项目、任务、部门等)+ 操作指令 switch ($dialog->type) { // 用户对话 @@ -757,7 +748,7 @@ class BotReceiveMsgTask extends AbstractTask 项目状态:{$projectStatus} EOF; - + $sections[] = << 如果你判断我想要或需要添加任务,请按照以下格式回复: @@ -787,7 +778,7 @@ class BotReceiveMsgTask extends AbstractTask {$taskContext} EOF; - + $sections[] = << 如果你判断我想要或需要添加子任务,请按照以下格式回复: @@ -822,7 +813,7 @@ class BotReceiveMsgTask extends AbstractTask EOF; break; } - + // 聊天历史 if ($dialog->type === 'group') { $chatHistory = $this->getRecentChatHistory($dialog, 15); @@ -836,7 +827,7 @@ class BotReceiveMsgTask extends AbstractTask } break; } - + // 更新系统提示词 if (!empty($sections)) { $extras['system_message'] = implode("\n\n", $sections); diff --git a/resources/assets/js/pages/manage/components/DialogView/template/bot-api.vue b/resources/assets/js/pages/manage/components/DialogView/template/bot-api.vue index dbbc3d1f2..b2779aa6b 100644 --- a/resources/assets/js/pages/manage/components/DialogView/template/bot-api.vue +++ b/resources/assets/js/pages/manage/components/DialogView/template/bot-api.vue @@ -4,6 +4,7 @@ \ No newline at end of file + diff --git a/resources/assets/js/pages/manage/components/DialogWrapper.vue b/resources/assets/js/pages/manage/components/DialogWrapper.vue index 9e8225cc5..abe39b040 100644 --- a/resources/assets/js/pages/manage/components/DialogWrapper.vue +++ b/resources/assets/js/pages/manage/components/DialogWrapper.vue @@ -1241,8 +1241,7 @@ export default { this.getDialogBase(dialog_id) this.generateUnreadData(old_id) // - this.$store.dispatch('openDialogWebhook', dialog_id) - // + this.$store.dispatch('openDialogEvent', dialog_id) this.$store.dispatch('closeDialog', {id: old_id}) // window.localStorage.removeItem('__cache:vote__') diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js index 54b814789..c742a6aea 100644 --- a/resources/assets/js/store/actions.js +++ b/resources/assets/js/store/actions.js @@ -3514,35 +3514,6 @@ export default { }) }, - /** - * 打开会话(打开机器人会话推送 webhook) - * @param state - * @param dispatch - * @param dialogId - * @returns {Promise} - */ - openDialogWebhook({state, dispatch}, dialogId) { - return new Promise((resolve, reject) => { - const dialog = state.cacheDialogs.find(item => { - if (item.type !== 'user') { - return false - } - return item.id === dialogId - }); - if (dialog && dialog.bot === 1) { - dispatch("call", { - url: 'dialog/open/webhook', - data: { - dialog_id: dialogId, - }, - }).catch(e => { - console.warn(e); - reject(e); - }) - } - }); - }, - /** * 打开会话(通过会员ID打开个人会话) * @param state @@ -3576,6 +3547,31 @@ export default { }); }, + /** + * 打开会话事件 + * @param state + * @param dispatch + * @param dialogId + * @returns {Promise} + */ + openDialogEvent({state, dispatch}, dialogId) { + return new Promise((resolve, reject) => { + if (!dialogId) { + reject({msg: 'Parameter error'}); + return; + } + dispatch("call", { + url: 'dialog/open/event', + data: { + dialog_id: dialogId, + }, + }).catch(e => { + console.warn(e); + reject(e); + }) + }); + }, + /** * 打开会话(客户端新窗口) * @param state