xiaoai/php_server/app/common/workerman/xhs/handlers/MessageHandler.php

1191 lines
52 KiB
PHP

<?php
namespace app\common\workerman\xhs\handlers;
use Workerman\Connection\TcpConnection;
use app\common\workerman\xhs\BaseMessageHandler;
use app\common\model\sv\SvAccount;
use app\common\model\sv\SvPrivateMessage;
use app\common\model\sv\SvReplyStrategy;
use app\common\model\sv\SvRobot;
use app\common\model\sv\SvAccountContact;
use app\common\model\sv\SvAccountKeyword;
use app\common\model\sv\SvRobotKeyword;
use app\common\model\sv\SvSetting;
use app\api\logic\service\TokenLogService;
use app\common\model\ChatPrompt;
use app\common\enum\user\AccountLogEnum;
use app\common\model\chat\ChatLog;
use app\api\logic\ChatLogic;
use app\common\logic\AccountLogLogic;
use app\common\model\user\User;
use app\common\workerman\xhs\WorkerEnum;
use Workerman\Lib\Timer;
class MessageHandler extends BaseMessageHandler
{
protected string $account;
protected string $accountType;
protected string $friendId;
protected string $friendName;
protected string $deviceCode;
protected array $request;
protected string $taskId;
protected string $appType;
protected int $intervalSeconds = 10;
protected array $replyMessage = [];
protected bool $multipleType = false;
public function handle(TcpConnection $connection, string $uid, array $payload): void
{
$content = !is_array($payload['content']) ? json_decode($payload['content'], true) : $payload['content'];
try {
$this->msgType = WorkerEnum::DESC[$payload['type']] ?? $payload['type'];
$this->uid = $uid;
$this->payload = $payload;
$this->userId = $content['userId'] ?? 0;
$worker = $this->service->getWorker();
$this->setLog(array_keys($worker->uidConnections), 'msg');
$device_uid = $worker->devices[$this->payload['deviceId']]?? '';
if($device_uid == ''){
$this->payload['reply'] = "设备{$this->payload['deviceId']}不在线,无法获取账号信息";
$this->payload['code'] = WorkerEnum::DEVICE_NOT_ONLINE;
$this->sendError($connection, $this->payload);
return;
}
$this->connection = $worker->uidConnections[$device_uid] ?? null;
if($this->connection === null){
$this->payload['reply'] = '设备未连接';
$this->sendResponse($this->uid, $this->payload, $this->payload['reply']);
return;
}
$this->setLog('当前设备对应进程名称:' . $this->connection->name . ' : ' . $this->connection->uid, 'msg');
if($this->msgType == WorkerEnum::RPA_NEW_PRIVATE_MESSAGE){
$this->connection->replyMessage = [];
$this->connection->multipleType = false;
$this->connection->isSendReply = true;
$this->_updatePrivateMessage($content);
}else if($this->msgType == WorkerEnum::WEB_SEND_PRIVATE_MESSAGE){
$this->_sendMessageToDevice($content);
}
} catch (\Exception $e) {
$this->setLog('异常信息'. $e, 'msg');
$this->payload['reply'] = "异常信息:" . $e->getMessage();
$this->payload['code'] = WorkerEnum::MSG_ERROR_CODE;
$this->payload['type'] = 'error';
$this->sendError($this->connection, $this->payload);
$this->sendErrorResponse($content, $this->payload['reply']);
}
}
private function _sendMessageToDevice($content){
try {
$device = $this->payload['deviceId'];
$worker = $this->service->getWorker();
if(!isset($worker->devices[$device])){
$this->payload['reply'] = "设备{$device}不在线,消息发送失败";
$this->payload['code'] = WorkerEnum::DEVICE_NOT_ONLINE;
$this->sendError($this->connection, $this->payload);
$this->setLog($this->payload, 'msg');
}else{
$account = SvAccount::alias('a')
->where('a.device_code', $device)
->join('sv_setting s', 's.account = a.account and s.user_id = a.user_id')
->limit(1)->find();
if(!empty($account)){
$friend = $this->getFriendInfo($account, $content);
$result = SvPrivateMessage::create([
'device_code' => $device,
'account' => $account['account'],
'type' => 3,
'friend_id' => $friend['friend_id'],
'replay_type' => $content['type'] ?? '',
'author_name' => $content['targetRecipient'] ?? '',
'message_content' => $content['content'] ?? '',
'message_timer' => $content['replyTime'] ?? '',
'new_message_count' => 1,
'create_time' => time(),
'is_reply' => 1
]);
$uid = $worker->devices[$device];
if($uid == ''){
$this->payload['reply'] = "设备{$device}不在线,无法获取账号信息";
$this->payload['code'] = WorkerEnum::DEVICE_NOT_ONLINE;
$this->sendError($this->connection, $this->payload);
return;
}
if(!$this->checkDeviceStatus($device)){
$this->payload['reply'] = "设备正在回复消息中, 请稍后再试";
$this->payload['code'] = WorkerEnum::DEVICE_RUNNING_REPLY_MSG;
//$this->sendResponse($this->uid, $this->payload, $this->payload['reply']);
$this->sendError($this->connection, $this->payload);
return;
}
$message = array(
'messageId' => $uid,
'deviceId' => $device,
'type' => WorkerEnum::TO_RPA_SEND_MESSAGE,
'appVersion' => '1.0',
'appType' => 3,
'code' => WorkerEnum::SUCCESS_CODE,
'reply' => [
'type' => $content['type'] ?? 1,
'content' => $content['content']?? '',
'link' => $content['link']?? '',
'targetRecipient' => $content['targetRecipient'] ?? '',
'lastMessageContent' => $content['lastMessageContent'] ?? ''
]);
$this->sendResponse($uid, $message, $message['reply']);
$this->setLog($message, 'msg');
}else{
$sql = SvAccount::alias('a')->where('a.device_code', $device)->join('sv_setting s', 's.account = a.account and s.user_id = a.user_id')->fetchSql(true)->limit(1)->find();
$this->setLog($sql, 'msg');
$this->setLog('请先对账号做基础设置:'. $device, 'msg');
$this->payload['reply'] = "请先对账号做基础设置";
$this->payload['code'] = WorkerEnum::MSG_DEVICE_ACCOUNT_SETTING;
$this->sendError($this->connection, $this->payload);
$this->sendErrorResponse($content, $this->payload['reply']);
}
}
} catch (\Exception $e) {
$this->setLog('_sendMessageToDevice'. $e, 'error');
}
}
private function _updatePrivateMessage($content){
$this->setLog('收到的私信消息:' . $this->payload['deviceId'], 'msg');
$this->setLog($content, 'msg');
try {
$accountNo = $this->service->getRedis()->get("xhs:{$this->payload['deviceId']}:accountNo");
if(empty($accountNo)){
$this->setLog('设备未更新账号信息,请先更新账号信息' , 'msg');
//return;
$accountNo = '';
}
$where = [];
$where[] = ['a.device_code', '=', $this->payload['deviceId']];
if($accountNo != ''){
$where[] = ['a.account', '=', $accountNo];
}
$this->setLog('accountNo:' . $accountNo , 'msg');
$this->setLog($where , 'msg');
$account = SvAccount::alias('a')
->field('*')
->where($where)
->join('sv_setting s', 's.account = a.account and s.user_id = a.user_id')
->order('a.update_time desc')
->limit(1)->find();
$this->setLog($account->toArray(), 'msg');
if(empty($account)){
$sql = SvAccount::alias('a')
->where($where)
->join('sv_setting s', 's.account = a.account and s.user_id = a.user_id')
->fetchSql(true)->limit(1)->find();
$this->setLog($sql, 'msg');
$this->setLog('账号信息不存在:' . $this->payload['deviceId'], 'msg');
$this->payload['reply'] = "请先对账号做基础设置";
$this->payload['code'] = WorkerEnum::MSG_DEVICE_ACCOUNT_SETTING;
$this->sendError($this->connection, $this->payload);
$this->sendErrorResponse($content, $this->payload['reply']);
return;
}
//接收到的消息
$content['type'] = WorkerEnum::WEB_RECEIVE_PRIVATE_MESSAGE_TEXT;
$this->sendToWeb($account, $content);
//回复内容发送给web端
// $payload['reply']['type'] = WorkerEnum::WEB_SEND_PRIVATE_MESSAGE_TEXT;
// $this->sendToWeb($account, $payload['reply']);
// return;
//1 查询私信用户信息是否存在,不存在则创建,并讲私信消息记录
$friend = $this->getFriendInfo($account, $content);
$this->addMessage($account, $friend, $content);
//2 检查账号是否开启了ai,未开启则将消息推送到客户端
if($account->takeover_mode == 1){
//3 开启了ai回复
if((int)$account->open_ai !== 1){
$this->setLog('请先开启ai回复:'. $this->payload['deviceId'],'msg');
$this->payload['reply'] = "请先开启ai回复";
$this->payload['code'] = WorkerEnum::MSG_ACCOUNT_NOT_OPENAI;
$this->sendError($this->connection, $this->payload);;
$this->sendErrorResponse($content, $this->payload['reply']);
$this->updatePrivateMessageStatus($account, $friend);
return;
}
//4 获取ai回复策略数据,未配置策略则提示
$reply = SvReplyStrategy::where('user_id', $account['user_id'])->findOrEmpty();
if($reply->isEmpty()){
$this->setLog('请先设置回复的配置:' . $this->payload['deviceId'], 'msg');
$this->setLog($account, 'msg');
$this->payload['reply'] = "请先设置回复的配置";
$this->payload['code'] = WorkerEnum::MSG_ACCOUNT_NOT_REPLY_STRATEGY;
$this->sendError($this->connection, $this->payload);;
$this->sendErrorResponse($content, $this->payload['reply']);
$this->updatePrivateMessageStatus($account, $friend);
return;
}
$this->setLog('回复策略:','msg');
$this->setLog($reply,'msg');
//5 查询配置的机器人,不存在则提示
$robot = SvRobot::where('id', $account['robot_id'])->findOrEmpty();
if($robot->isEmpty()){
$this->setLog('机器人不存在:' . $this->payload['deviceId'], 'msg');
$this->setLog($account, 'msg');
$this->payload['reply'] = "机器人不存在";
$this->payload['code'] = WorkerEnum::MSG_ACCOUNT_NOT_ROBOT;
$this->sendError($this->connection, $this->payload);;
$this->sendErrorResponse($content, $this->payload['reply']);
$this->updatePrivateMessageStatus($account, $friend);
return;
}
$this->setLog('机器人:','msg');
$this->setLog($robot,'msg');
//6匹配停止策略
// 组装请求参数
$request = [
'uid' => $this->uid,
'user' => $account,
'user_id' => $account['user_id'],
'payload' => $this->payload,
'content' => $content,
'account' => $account['account'],
'account_type' => $account['type'],
'friend_id' => $friend['friend_id'],
'friend_name' => $friend['nickname'],
'friend_remark' => $friend['remark'] ?? '',
'device_code' => $this->payload['deviceId'],
'message' => array_values(array_filter($content['replyContent'])),
'message_id' => $this->payload['messageId'],
'message_type' => 1,
];
$keys = $this->checkCradKeyword($account, $request);
$request['message'] = empty($keys) ? $content['replyContent'] : $keys;
//开启图片回复策略
if($reply->image_enable == 1 && $request['message_type'] == 2){
$this->setLog('图片回复'. $this->payload['deviceId'], 'msg');
$request['message'] = $reply->image_reply;
$this->send($request);
$this->updatePrivateMessageStatus($account, $friend);
return;
}
//step 1. 正则匹配停止AI回复
$stop = $this->regularMatchStopAI($reply, $request);
if($stop){
SvSetting::where('account', $account['account'])->where('user_id', $account['user_id'])->update(['takeover_mode' => 0]);
$this->setLog('已停止ai回复:'. $this->payload['deviceId'], 'msg');
$this->payload['type'] = WorkerEnum::WEB_STOP_AI_TEXT;
$this->payload['reply'] = "已停止ai回复";
$this->payload['code'] = WorkerEnum::MSG_ACCOUNT_NOT_ROBOT;
$this->sendError($this->connection, $this->payload);;
$this->sendErrorResponse($content, $this->payload['reply']);
$this->updatePrivateMessageStatus($account, $friend);
return;
}
//7匹配预设的关键词,匹配到则回复
// $match = $this->regularMatchKeyword($robot, $request);
// if ($match) {
// $this->setLog('正则匹配关键词:'. $this->payload['deviceId'], 'msg');
// return;
// }
SvPrivateMessage::where('user_id', '=', $request['user_id'])
->where('account', $request['account'])
->where('friend_id', $request['friend_id'])
->where('is_reply', 0)
->where('create_time', '<', (time() - 600))->update([
'is_reply' => 1,
'update_time' => time()
]);
if($reply->multiple_type == 0){ //逐条回复
$this->connection->multipleType = true;
$messages = SvPrivateMessage::where('user_id', $request['user_id'])
->where('account', $request['account'])
->where('friend_id', $request['friend_id'])
->where('is_reply', 0)
->where('create_time', 'between', [time() - 600, time()])
->order('create_time asc')
->select()
->toArray();
foreach ($messages as $message){
$request['message'] = $message['message_content'];
$matchAccount = $this->regularAccountKeyword($account, $request);
if ($matchAccount) {
$this->setLog('固定话术匹配:'. $this->payload['deviceId'],'msg');
$this->updatePrivateMessageStatus($account, $friend);
continue;
}
$match = $this->regularMatchKeyword($robot, $request);
if ($match) {
$this->setLog('正则匹配关键词:'. $this->payload['deviceId'], 'msg');
$this->updatePrivateMessageStatus($account, $friend);
continue;
}
$message_logs = array(
'role' => 'user',
'content' => $message['message_content']
);
$this->parseAiPrompt($robot, $request, [$message_logs]);
$this->setLog('已回复消息更改状态:', 'msg');
SvPrivateMessage::where('id', '=', $message['id'])->update([
'is_reply' => 1,
'update_time' => time()
]);
}
$this->setLog('回复消息数组', 'msg');
$this->setLog($this->connection->replyMessage,'msg');
$this->connection->replyMessage = array_values(array_filter($this->connection->replyMessage));
$this->setLog($this->connection->replyMessage,'msg');
if(empty($this->connection->replyMessage)){
$this->connection->replyMessage = array(
'未找到相关回复,请详细说明您的问题,我们会尽快为您解答'
);
}
if(!empty($this->connection->replyMessage)){
$this->setLog('发送消息:','msg');
// 发送消息
$sendData = array(
'device_code' => $this->payload['deviceId'],
'account' => $request['account'],
'content' => $request['content'],
'user' => $request['user'],
'message_list' => $this->connection->replyMessage,
'message_type' => 1,
'friend_id' => $request['friend_id'],
'payload' => $request['payload']
);
$this->send($sendData);
}
}else{
$this->connection->multipleType = false;
$this->setLog('监听消息2分钟:', 'msg');
if(!empty($this->connection->timerId)){
$this->setLog('合并 最后一条监听:', 'msg');
Timer::del($this->connection->timerId);
}
//开启定时.
$this->connection->timerId = Timer::add($this->intervalSeconds, function() use($robot, $account, $request, $reply, $friend){
$this->setLog('2分钟未收到消息,开始推送:', 'msg');
$this->setLog('超过2分钟的消息标识改为1','msg');
$messages = SvPrivateMessage::where('user_id', $request['user_id'])
->where('account', $request['account'])
->where('friend_id', $request['friend_id'])
->where('is_reply', 0)
->limit("{$reply->number_chat_rounds}")
->order('create_time asc')
->select()
->toArray();
$this->setLog('未回复的消息:','msg');
$this->setLog($messages,'msg');
$this->setLog('回复策略:'. $reply->multiple_type,'msg');
if(empty($messages)){
$this->setLog('删除定时器:', 'msg');
Timer::del($this->connection->timerId);
return;
}
if($reply->multiple_type == 1){ //合并回复
$message_logs = array();
foreach ($messages as $message){
array_push($message_logs, array(
'role' => 'user',
'content' => $message['message_content']
));
}
$this->parseAiPrompt($robot, $request, $message_logs);
}else if($reply->multiple_type == 2){ //仅回复最后一条
$lastMessage = $messages[count($messages) - 1]['message_content'];
$request['message'] = $lastMessage;
$matchAccount = $this->regularAccountKeyword($account, $request);
if ($matchAccount) {
$this->setLog('固定话术匹配:'. $this->payload['deviceId'],'msg');
$this->updatePrivateMessageStatus($account, $friend);
return;
}
$match = $this->regularMatchKeyword($robot, $request);
if ($match) {
$this->setLog('正则匹配关键词:'. $this->payload['deviceId'], 'msg');
$this->updatePrivateMessageStatus($account, $friend);
return;
}else{
$message_logs = array(
'role' => 'user',
'content' => $lastMessage
);
$this->parseAiPrompt($robot, $request, [$message_logs]);
}
}
$this->setLog('已回复消息更改状态:', 'msg');
SvPrivateMessage::where('id', 'in', array_column($messages, 'id'))->update([
'is_reply' => 1,
'update_time' => time()
]);
$this->setLog('删除定时器:', 'msg');
Timer::del($this->connection->timerId);
});
}
}else{//未接管,将消息直接推送到客户端
$content['type'] = WorkerEnum::WEB_RECEIVE_PRIVATE_MESSAGE_TEXT;
$this->sendToWeb($account, $content);
$this->setLog('未接管,直接推送', 'msg');
$this->sendErrorResponse($content, 'AI未接管,直接推送');
$this->updatePrivateMessageStatus($account, $friend);
}
}catch (\Exception $e) {
$this->setLog('_updatePrivateMessage回复私信异常'. $e,'msg');
$this->payload['reply'] = "回复私信异常:" .$e->getMessage();
$this->payload['code'] = WorkerEnum::MSG_ERROR_CODE;
$this->sendError($this->connection, $this->payload);
$this->sendErrorResponse($content, $this->payload['reply']);
}
}
private function updatePrivateMessageStatus($account, $friend){
try {
SvPrivateMessage::where('user_id', $account['user_id'])
->where('account', $account['account'])
->where('friend_id', $friend['friend_id'])
->where('is_reply', 0)
->update([
'is_reply' => 1,
'update_time' => time()
]);
}catch (\Exception $e) {
$this->setLog('updatePrivateMessageStatus'. $e,'error');
}
}
private function addMessage($account, $friend, $content){
try {
$content['replyTime'] = $content['replyTime'] == '刚刚' ? date('Y-m-d H:i:s', time()) : $content['replyTime'];
if(is_array($content['replyContent'])){
$content['replyContent'] = array_filter($content['replyContent']);
foreach ($content['replyContent'] as $_message){
$result = SvPrivateMessage::create([
'user_id' => $account['user_id'],
'device_code' => $this->payload['deviceId'],
'account' => $account['account'],
'type' => 3,
'friend_id' => $friend['friend_id'],
'replay_type' => $content['replyObject'] ?? '',
'author_name' => $content['replyName'] ?? '',
'message_content' => $_message ?? '',
'message_timer' => $content['replyTime'] ?? '',
'new_message_count' => 1,
'create_time' => time()
]);
$this->setLog('addMessage消息记录:','msg');
$this->setLog($result,'msg');
}
}else{
$result = SvPrivateMessage::create([
'user_id' => $account['user_id'],
'device_code' => $this->payload['deviceId'],
'account' => $account['account'],
'type' => 3,
'friend_id' => $friend['friend_id'],
'replay_type' => $content['replyObject'] ?? '',
'author_name' => $content['replyName'] ?? '',
'message_content' => $content['replyContent'] ?? '',
'message_timer' => $content['replyTime'] ?? '',
'new_message_count' => 1,
'create_time' => time()
]);
$this->setLog('addMessage消息记录:','msg');
$this->setLog($result,'msg');
}
} catch (\Exception $e) {
$this->setLog('addMessage'. $e,'error');
}
}
private function getFriendInfo($account, $content){
try {
$nickname = $content['targetRecipient'] ?? $content['replyName'];
$friendId = md5($account['user_id'] . $account['account'] . $account['device_code'] . $nickname);
$friend = SvAccountContact::where('account', $account['account'])->where('account_type', $account['type'])->where('friend_id', $friendId)->limit(1)->find();
if(empty($friend)){
$friend = SvAccountContact::create([
'account' => $account['account'],
'account_type' => $account['type'],
'friend_id' => $friendId,
'friend_no' => $friendId,
'nickname' => $nickname,
'source' => 60, //小红书私信
'create_time' => time(),
'open_ai' => 1,
'takeover_mode' => 0
]);
return $friend;
}
$this->setLog('好友信息:','msg');
$this->setLog($friend,'msg');
return $friend;
}catch (\Exception $e) {
$this->setLog('getFriendInfo'. $e,'error');
}
}
/**
* @desc 解析AI提示词
* @param array $request
* @param array $content
* @return void
*/
protected function parseAiPrompt(SvRobot $robot, array $request, array $logs): void
{
try {
$this->setLog('AI回复逻辑:'. $request['device_code'],'msg');
$appType = $request['payload']['appType'] ?? 3;
$this->setLog('appType:'. $appType,'msg');
//检查扣费
$unit = TokenLogService::checkToken($request['user_id'], 'ai_xhs');
$this->setLog('检查扣费unit:'. $unit,'msg');
//获取提示词
$keyword = ChatPrompt::where('prompt_name', '小红书')->value('prompt_text') ?? '';
if (!$keyword) {
$this->setLog('提示词不存在:'. $request['device_code'], 'msg');
$this->payload['reply'] = "提示词不存在";
$this->payload['code'] = WorkerEnum::MSG_CHAT_PROMPT_NOT_FOUND;
$this->sendError($this->connection, $this->payload);
$this->sendErrorResponse($request['content'], $this->payload['reply']);
return;
}
$this->setLog('提示词:','msg');
$this->setLog($keyword,'msg');
$keyword = str_replace(
['企业背景', '角色设定', '用户备注', '用户标签', '咨询', '最近对话记录', '用户发送的内容'],
[$robot->company_background, $robot->description, $request['friend_remark'], "", "", json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), (is_array($request['message']) ? implode("\n", $request['message']) : $request['message'])],
$keyword
);
$task_id = generate_unique_task_id();
// 检查是否挂载知识库
$bind = \app\common\model\knowledge\KnowledgeBind::where('data_id', $robot->id)->where('user_id', $request['user_id'])->where('type', 1)->limit(1)->find();
$knowledge = [];
if (!empty($bind)) {
$knowledge = \app\common\model\knowledge\Knowledge::where('id', $bind['kid'])->limit(1)->find();
if (empty($knowledge)) {
$this->setLog('机器人挂载的知识库不存在:'. $request['device_code'], 'msg');
$this->payload['reply'] = "机器人挂载的知识库不存在";
$this->payload['code'] = WorkerEnum::MSG_ROBOT_NOT_FOUND_KNOWLEDGE;
$this->sendError($this->connection, $this->payload);;
$this->sendErrorResponse($request['content'], $this->payload['reply']);
return;
}
$knowledge['task_id'] = $task_id;
}
$request = [
'user_id' => $request['user_id'],
'user' => $request['user'],
'task_id' => $task_id,
'account' => $request['account'],
'payload' => $request['payload'],
'content' => $request['content'],
'account_type' => $request['account_type'],
'friend_id' => $request['friend_id'],
'friend_remark' => $request['friend_remark'],
'friend_name' => $request['friend_name'],
'device_code' => $request['device_code'],
'message' => $request['message'],
'message_id' => $request['message_id'],
'chat_type' => AccountLogEnum::TOKENS_DEC_AI_WECHAT,
'now' => time(),
'messages' => array_merge([['role' => 'system', 'content' => $keyword]], $logs),
'knowledge' => $knowledge,
];
// 任务数据
$data = [
'account' => $request['account'],
'friend_id' => $request['friend_id'],
'friend_name' => $request['friend_name'],
'device_code' => $request['device_code'],
'task_id' => $request['task_id'],
'user_id' => $request['user_id'],
'request' => $request,
];
$this->setLog('组合任务数据:','msg');
$this->setLog($data,'msg');
// 推送到队列
$this->beforeSend($data);
}catch (\Exception $e) {
$this->setLog('解析AI提示词异常:'. $e,'msg');
$this->payload['reply'] = "AI 回复异常:" . $e->getMessage();
$this->payload['code'] = WorkerEnum::MSG_ERROR_CODE;
$this->sendError($this->connection, $this->payload);
$this->sendErrorResponse($request['content'], $this->payload['reply']);
return;
}
}
private function beforeSend($data){
try {
$this->account = $data['account'];
$this->friendId = $data['friend_id'];
$this->friendName = $data['friend_name'];
$this->deviceCode = $data['device_code'];
$this->userId = $data['user_id'];
$this->request = $data['request'];
$this->taskId = $data['task_id'];
// 检查AI 是否已有回复记录
$log = ChatLog::where('task_id', $this->taskId)->findOrEmpty();
$reply = '对不起,未找到相关内容,请详细说明';
if($log->isEmpty()){
//clogger((json_encode($this->request['knowledge'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)), 'wechat');
if(isset($this->request['knowledge']) && !empty($this->request['knowledge'])){
[$chatStatus, $response] = \app\api\logic\KnowledgeLogic::socketChat([
'message' => is_array($this->request['message']) ? implode("\n" , $this->request['message']) : $this->request['message'],
'indexid' => $this->request['knowledge']['index_id'],
'rerank_min_score' => $this->request['knowledge']['rerank_min_score'] ?? 0.2,
'stream' => false,
'user_id' => $this->userId,
'scene' => 'socket'
]);
if($chatStatus === false){
$this->setLog($this->taskId.'队列请求知识库失败:'.$response, 'msg');
}else{
$response['msg'] = '知识库消息回复结果';
$this->setLog($response, 'msg');
if (isset($response['choices'][0]) && !empty($response['choices'][0])) {
$reply = $response['choices'][0]['message']['content'];
}
}
}else{
// 执行微信AI消息处理
$response = \app\common\service\ToolsService::Sv()->chat($this->request);
$response['msg'] = 'chat ai消息回复结果';
$this->setLog($response, 'msg');
if (isset($response['code']) && $response['code'] == 10000) {
// 处理响应
$reply = $this->handleResponse($response);
} else {
$this->setLog($this->taskId.'队列请求失败'.json_encode($response), 'msg');
}
}
}else{
$reply = $log->reply;
}
// 发送消息
$data = array(
'device_code' => $this->deviceCode,
'account' => $this->account,
'content' => $this->request['content'],
'user' => $this->request['user'],
'message_list' => $reply,
'message_type' => 1,
'friend_id' => $this->friendId,
'payload' => $this->request['payload']
);
if($this->connection->multipleType){
array_push($this->connection->replyMessage, $reply);
}else{
$this->send($data);
}
} catch (\Throwable $e) {
$this->setLog($e, 'msg');
$this->payload['reply'] = "消息发送异常:" . $e->getMessage();
$this->payload['code'] = WorkerEnum::MSG_SEND_MESSAGE_ERROR;
$this->sendError($this->connection, $this->payload);;
$this->sendErrorResponse($this->request, $this->payload['reply']);
}
}
/**
* 处理响应
* @param array $response
* @return string
*/
private function handleResponse(array $response)
{
try {
//检查扣费
$unit = TokenLogService::checkToken($this->userId, 'ai_xhs');
// 获取回复内容
$reply = $response['data']['message'] ?? '';
//计费
$tokens = $response['data']['usage']['total_tokens'] ?? 0;
if (!$reply || $tokens == 0) {
throw new \Exception('获取内容失败');
}
$response = [
'reply' => $reply,
'usage_tokens' => $response['data']['usage'] ?? [],
];
// 保存聊天记录
ChatLogic::saveChatResponseLog($this->request, $response);
//计算消耗tokens
$points = $unit > 0 ? ceil($tokens / $unit) : 0;
//token扣除
User::userTokensChange($this->userId, $points);
$extra = ['总消耗tokens数' => $tokens, '算力单价' => $unit, '实际消耗算力' => $points];
//扣费记录
AccountLogLogic::recordUserTokensLog(true, $this->userId, AccountLogEnum::TOKENS_DEC_AI_XHS, $points, $this->taskId, $extra);
return $reply;
}catch (\Exception $e) {
$this->setLog($e,'msg');
$this->payload['reply'] = $e->getMessage();
$this->payload['code'] = WorkerEnum::MSG_SEND_MESSAGE_ERROR;
}
}
private function checkCradKeyword(SvAccount $account, array $request){
try {
$keywords = array();
// 获取账号配置的固定话术的正关键词
SvAccountKeyword::where('account', $account->account)->select()->each(function ($item) use ($request, &$keywords) {
$types = array_column($item->reply, 'type');
foreach($request['message'] as $_message){
// 模糊匹配
if ($item->match_type == 0) {
if(strpos($item->keyword, $_message) !== false){
if(in_array(5, $types)){
array_push($keywords, $_message);
}
}
} else {
if ((string)$item->keyword === $_message) {
if(in_array(5, $types)){
array_push($keywords, $_message);
}
}
}
}
});
if(!empty($keywords)){
SvPrivateMessage::where('user_id', '=', $request['user_id'])
->where('account', $request['account'])
->where('friend_id', $request['friend_id'])
->where('is_reply', 0)
->where('message_content', 'not in', $keywords)->update([
'is_reply' => 1,
'update_time' => time()
]);
}
return $keywords;
}catch (\Exception $e) {
$this->setLog($e,'msg');
$this->payload['reply'] = "消息发送异常:". $e->getMessage();
$this->payload['code'] = WorkerEnum::MSG_SEND_MESSAGE_ERROR;
$this->sendError($this->connection, $this->payload);;
$this->sendErrorResponse($request['content'], $this->payload['reply']);
return [];
}
}
private function regularAccountKeyword(SvAccount $account, array $request){
$this->setLog('正则匹配固定话术:'. $account->account,'msg');
$match = false;
try {
//print_r( $account->account);
//优先匹配名片
$keywords = array();
\app\common\model\sv\SvAccountKeyword::where('account', $account->account)->select()->each(function ($item) use(&$keywords) {
$keywords[$item['keyword']]['match_type'] = $item['match_type'];
$keywords[$item['keyword']]['keyword'] = $item['keyword'];
$keywords[$item['keyword']]['weight'] = 0;
if(!isset($keywords[$item['keyword']]['reply'])){
$keywords[$item['keyword']]['reply'] = array();
}
foreach ($item['reply'] as $key => $value) {
if($value['type'] == 5){
$keywords[$item['keyword']]['reply'] = [$value];
$keywords[$item['keyword']]['weight'] = 99;
}else{
array_push( $keywords[$item['keyword']]['reply'], $value);
}
}
return $item;
})->toArray();
$keywords = array_values($keywords);
array_multisort(array_column($keywords, 'weight'), SORT_DESC, $keywords);
//print_r($keywords);
foreach ($keywords as $item) {
// 模糊匹配
if ($item['match_type'] == 0) {
if(strpos($item['keyword'], $request['message']) !== false){
$this->parseMessage($request, $item['reply']);
$match = true;
}
} else {
if ((string)$item['keyword'] === $request['message']) {
$this->parseMessage($request, $item['reply']);
$match = true;
}
}
}
return $match;
}catch (\Exception $e) {
$this->setLog($e,'msg');
$this->payload['reply'] = "消息发送异常:". $e->getMessage();
$this->payload['code'] = WorkerEnum::MSG_SEND_MESSAGE_ERROR;
$this->sendError($this->connection, $this->payload);;
$this->sendErrorResponse($request['content'], $this->payload['reply']);
return $match;
}
// 获取账号配置的固定话术的正关键词
// SvAccountKeyword::where('account', $account->account)->select()->each(function ($item) use ($request, &$match) {
// // 模糊匹配
// if ($item->match_type == 0) {
// // if (str_contains($request['message'], $item->keyword)) {
// // $this->parseMessage($request, $item->reply);
// // $match = true;
// // }
// if(strpos($item->keyword, $request['message']) !== false){
// $this->parseMessage($request, $item->reply);
// $match = true;
// }
// } else {
// if ((string)$item->keyword === $request['message']) {
// $this->parseMessage($request, $item->reply);
// $match = true;
// }
// }
// });
// return $match;
}
private function regularMatchKeyword(SvRobot $robot, array $request){
$this->setLog('正则匹配机器人关键词:'. $robot->id,'msg');
$match = false;
try {
// 获取微信机器人设置的正关键词
SvRobotKeyword::where('robot_id', $robot->id)->select()->each(function ($item) use ($request, &$match) {
$this->setLog('匹配类型:'. $item->match_type . " msg: {$request['message']}, key: {$item->keyword} reg: " . str_contains($request['message'], $item->keyword),'msg');
// 模糊匹配
if ((int)$item->match_type === 0) {
// if (str_contains($request['message'], $item->keyword)) {
// $this->parseMessage($request, $item->reply);
// $match = true;
// }
if(strpos($item->keyword, $request['message']) !== false){
$this->parseMessage($request, $item->reply);
$match = true;
}
} else {
if ((string)$item->keyword === $request['message']) {
$this->parseMessage($request, $item->reply);
$match = true;
}
}
});
return $match;
}catch (\Exception $e) {
$this->setLog($e,'msg');
$this->payload['reply'] = "消息发送异常:". $e->getMessage();
$this->payload['code'] = WorkerEnum::MSG_SEND_MESSAGE_ERROR;
$this->sendError($this->connection, $this->payload);;
$this->sendErrorResponse($request['content'], $this->payload['reply']);
}
}
private function regularMatchStopAI(SvReplyStrategy $reply, array $request){
$stop = false;
$keywords = explode(';', $reply->stop_keywords);
// 获取微信机器人设置的正关键词
foreach ($keywords as $keyword) {
if ((string)$keyword === $request['message']) {
$stop = true;
break;
}
}
return $stop;
}
/**
* @desc 解析消息
* @param array $request
* @param array $content
* @return void
*/
protected function parseMessage(array $request, array $content)
{
try {
$msg = array();
$send = true;
foreach ($content as $item) {
//$send = true;
$request['message'] = '';
switch ((int)$item['type']) {
case 0: //文本
// 推送消息
$request['message_type'] = 1;
$request['message'] = str_replace('${remark}', $request['friend_remark'], $item['content']);
break;
case 1: //图片
// 推送消息
$request['message'] = '图片地址';
$request['message_type'] = 2;
break;
case 5: //小红书名片
$request['message_type'] = 5;
$request['message_list'] = $item['name'];
$this->send($request);
$request['message'] = '';
$this->connection->isSendReply = false;
$send = false;
break;
default:
$send = false;
$request['message'] = '';
}
// if ($send) {
// $this->send($request);
// }
if(!empty($request['message'])){
array_push($msg, $request['message']);
if($this->connection->multipleType){
array_push($this->connection->replyMessage, $request['message']);
}
//$send = true;;
}
}
if($send){
$request['message_list'] = $msg;
if($this->connection->multipleType === false){
$this->send($request);
}
}
}catch (\Exception $e) {
$this->setLog($e,'msg');
$this->payload['reply'] = "AI 回复异常:". $e->getMessage();
$this->payload['code'] = WorkerEnum::MSG_ERROR_CODE;
}
}
private function addReplyMessage(array $request){
try {
$content = json_encode($request['message_list'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
SvPrivateMessage::create([
'user_id' => $request['user']['user_id'],
'device_code' => $request['device_code'],
'account' => $request['account'],
'type' => 3,
'friend_id' => $request['friend_id'],
'replay_type' => $request['content']['replyObject'] ?? '',
'author_name' => $request['user']['nickname'],
'message_content' => $content,
'message_timer' => date('Y-m-d H:i:s', time()),
'new_message_count' => 1,
'create_time' => time(),
'is_reply' => 1
]);
}catch (\Exception $e) {
$this->setLog($e,'msg');
}
}
protected function send(array $request)
{
try {
$this->addReplyMessage($request);
$payload = $request['payload'];
if(isset($request['message_type']) && $request['message_type'] == 5){//小红书名片 需要特殊处理
$payload['reply'] = array(
'type' => 4,
'content' => [$request['message_list']],
'link' => '',
'targetRecipient' => $request['content']['replyName'] ?? '',
'lastMessageContent' => $request['content']['replyContent'] ?? ''
);
$payload['type'] = 7;//回复小红书名片
$this->setLog('小红书名片回复', 'msg');
}else{
$payload['reply'] = array(
'type' => $request['message_type'],
'content' => isset($request['message_list']) ? (is_array($request['message_list']) ? $request['message_list'] : [$request['message_list']]): [$request['message']],
'link' => '',
'targetRecipient' => $request['content']['replyName'] ?? '',
'lastMessageContent' => $request['content']['replyContent'] ?? ''
);
$payload['type'] = 6;
}
$this->sendResponse($this->uid, $payload, $payload['reply']);
$payload['reply']['type'] = $request['message_type'] == 5 ? WorkerEnum::WEB_SEND_CARD_TEXT : WorkerEnum::WEB_SEND_PRIVATE_MESSAGE_TEXT;
$this->sendToWeb($request['user'], $payload['reply']);
$this->setLog($payload, 'msg');
}catch (\Exception $e) {
$this->setLog('send'.$e, 'error');
}
}
private function sendErrorResponse($request, $content){
try {
$payload['reply'] = array(
'type' => 1,
'content' => [$content],
'link' => '',
'targetRecipient' => $request['replyName'] ?? ($request['content']['replyName'] ?? ''),
'lastMessageContent' => $request['replyContent'] ?? ( $request['content']['replyContent'] ?? '')
);
$payload['type'] = 6;
$this->setLog('sendErrorResponse', 'msg');
$this->setLog($payload, 'msg');
$this->sendResponse($this->uid, $payload, $payload['reply']);
}catch (\Exception $e) {
$this->setLog('sendErrorResponse'.$e, 'error');
}
}
private function sendToWeb($account, $content){
try {
$userId = $account['user_id'];
$uid = $this->service->getRedis()->get("xhs:user:{$userId}");
if($uid){
$message = array(
'messageId' => $uid,
'type' => $content['type'],
'appType' => 3,
'deviceId' => $account['device_code'],
'appVersion' => $this->payload['appVersion'],
'code' => WorkerEnum::SUCCESS_CODE,
'reply' => $content
);
$this->sendResponse($uid, $message, $message['reply']);
}
}catch (\Exception $e) {
$this->setLog('sendToWeb'.$e, 'error');
}
}
}