- 框架初始化
 - 安装插件
 - 修复PHP8.4报错
This commit is contained in:
2025-04-19 17:21:20 +08:00
commit c6a4e1f5f6
5306 changed files with 967782 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
<?php
namespace addons\shopro\library\chat\provider\auth;
use addons\shopro\exception\ShoproException;
use addons\shopro\library\chat\traits\DebugEvent;
use addons\shopro\library\chat\traits\Helper;
use addons\shopro\library\chat\traits\Session;
use addons\shopro\library\chat\traits\NspData;
use addons\shopro\library\chat\provider\auth\traits\CustomerService;
use addons\shopro\library\chat\Chat;
use addons\shopro\library\chat\ChatService;
use addons\shopro\library\chat\Getter;
use addons\shopro\library\chat\Sender;
use PHPSocketIO\SocketIO;
use PHPSocketIO\Socket;
use PHPSocketIO\Nsp;
/**
* 管理员身份
*/
class Admin
{
/**
* debug 方式注册事件
*/
use DebugEvent;
/**
* 助手方法
*/
use Helper;
/**
* session 存储助手
*/
use Session;
/**
* 绑定数据到 nsp
*/
use NspData;
/**
* 客服事件
*/
use CustomerService;
/**
* 当前 Chat 实例
*
* @var Chat
*/
protected $chat;
/**
* 当前 phpsocket.io 实例
*
* @var SocketIO
*/
protected $io;
/**
* 当前socket 连接
*
* @var Socket
*/
protected $socket;
/**
* 当前 命名空间
*
* @var Nsp
*/
public $nsp;
/**
* getter 实例
*
* @var Getter
*/
protected $getter;
/**
* sender 实例
*
* @var Getter
*/
protected $sender;
/**
* getter 实例
*
* @var ChatService
*/
protected $chatService;
public function __construct(Chat $chat)
{
$this->chat = $chat;
$this->io = $chat->io;
$this->socket = $chat->socket;
$this->nsp = $chat->nsp;
$this->getter = $chat->getter;
$this->chatService = $chat->chatService;
$this->sender = $chat->sender;
}
public function on()
{
// 检测当前 auth 是否是客服
$this->register('check_identify', function ($data, $callback) {
// 检查当前管理员登录状态
if (!$this->chatService->isLogin()) {
throw new ShoproException('请先登录管理后台');
}
$admin = $this->session('auth_user');
// 获取当前管理员的客服身份
$customerServices = $this->chatService->getCustomerServicesByAuth($admin['id'], $this->session('auth'));
if (!$customerServices) {
throw new ShoproException(''); // 您还不是客服,后台不提示,留空
}
// 注册客服事件
$this->customerServiceOn();
$this->sender->successSocket($callback, '验证成功', [
'customer_services' => $customerServices
]);
});
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace addons\shopro\library\chat\provider\auth;
use addons\shopro\exception\ShoproException;
use addons\shopro\library\chat\traits\DebugEvent;
use addons\shopro\library\chat\traits\Helper;
use addons\shopro\library\chat\traits\Session;
use addons\shopro\library\chat\traits\NspData;
use addons\shopro\library\chat\provider\auth\traits\Customer;
use addons\shopro\library\chat\Chat;
use addons\shopro\library\chat\Sender;
/**
* 用户
*/
class User
{
/**
* debug 方式注册事件
*/
use DebugEvent;
/**
* session 存储助手
*/
use Session;
/**
* 助手方法
*/
use Helper;
/**
* 顾客事件
*/
use Customer;
/**
* 绑定数据到 nsp
*/
use NspData;
/**
* 当前 Chat 实例
*
* @var Chat
*/
protected $chat;
/**
* 当前 phpsocket.io 实例
*
* @var SocketIO
*/
protected $io;
/**
* 当前socket 连接
*
* @var Socket
*/
protected $socket;
/**
* 当前 命名空间
*
* @var Nsp
*/
public $nsp;
/**
* getter 实例
*
* @var Getter
*/
protected $getter;
/**
* sender 实例
*
* @var Getter
*/
protected $sender;
/**
* getter 实例
*
* @var ChatService
*/
protected $chatService;
/**
* 初始化
*
* @param Chat $chat
*/
public function __construct(Chat $chat)
{
$this->chat = $chat;
$this->io = $chat->io;
$this->socket = $chat->socket;
$this->nsp = $chat->nsp;
$this->getter = $chat->getter;
$this->chatService = $chat->chatService;
$this->sender = $chat->sender;
}
public function on()
{
// 用户相关事件
// 处理用户登录,将用户未登录时候产生的 连接,聊天记录,更新成当前用户
// 更新信息
// 直接注册顾客相关事件
$this->customerOn();
// 用户事件,待补充
// ......
}
}

View File

@@ -0,0 +1,164 @@
<?php
namespace addons\shopro\library\chat\provider\auth\traits;
/**
* 顾客事件
*/
trait Customer
{
public function customerOn()
{
// 顾客连接客服事件
$this->register('customer_login', function ($data, $callback) {
// 加入的客服房间
$room_id = $data['room_id'] ?? 'admin';
$this->session('room_id', $room_id);
// 存储当前房间
$this->nspRoomIdAdd($room_id);
// 顾客上线
$this->chatService->customerOnline();
// 通知所有房间中的客服,用户上线了
$this->sender->customerOnline();
// 注册顾客相关的事件
$this->customerEvent();
// 获取常见问题,提醒给顾客
$questions = $this->getter('db')->getChatQuestions($room_id);
// 通知自己连接成功
$this->sender->successSocket($callback, '顾客初始成功', [
'questions' => $questions
]);
// 分配客服
$this->allocatCustomerService();
});
}
public function customerEvent()
{
// 检测是否登录过
if ($this->socket->listeners('message')) {
// 已经注册过了,避免重复注册
return false;
}
// 发送消息
$this->register('message', function ($data, $callback) {
$message = $data['message'] ?? []; // 发送的消息
$question_id = $data['question_id'] ?? 0; // 猜你想问
// 过滤 message
if ($message['message_type'] == 'text') {
$message['message'] = trim(strip_tags($message['message']));
}
$room_id = $this->session('room_id');
$session_id = $this->session('session_id');
$customer_service_id = $this->session('customer_service_id');
// 给客服发消息
$this->sender->messageToCustomerService($message, [
'sender_identify' => 'customer',
'session_id' => $session_id,
], $customer_service_id);
// 通知自己发送成功
$this->sender->successSocket($callback, '发送成功');
// 检查并获取 question
$question = $this->getter('db')->getChatQuestion($room_id, $question_id);
if ($question) {
// 是猜你想问,自动回答问题
$this->sender->messageToBoth($question, $session_id, $customer_service_id);
}
});
// 获取消息列表
$this->register('messages', function ($data, $callback) {
// 当前房间
$room_id = $this->session('room_id');
$session_id = $this->session('session_id');
// 获取 聊天记录
$messages = $this->getter('db')->getCustomerMessagesBySessionId($room_id, $session_id, 'customer_service', $data);
// 获取消息列表
$this->sender->successSocket($callback, '获取成功', [
'messages' => $messages
]);
});
// 顾客退出
$this->register('customer_logout', function ($data, $callback) {
$session_id = $this->session('session_id');
$room_id = $this->session('room_id');
$chatUser = $this->session('chat_user');
// 顾客下线
$this->chatService->customerOffline();
// 顾客断开连接
if (!$this->getter('socket')->isOnLineCustomerById($session_id)) {
// 当前所遇用户端都断开了
// 这里顾客的所有客户端都断开了,在排队排名中移除
$this->nspWaitingDel($room_id, $session_id);
// 排队发生变化,通知房间中所有排队用户
$this->sender->allWaitingQueue($room_id);
// 通知所有房间中的客服,顾客下线
$this->sender->customerOffline();
}
// 解绑顾客相关的事件,等下次顾客在登录时再重新绑定 【customerEvent 方法绑定的所有事件】
$this->socket->removeAllListeners('message');
$this->socket->removeAllListeners('messages');
$this->socket->removeAllListeners('customer_logout');
$this->sender->successSocket($callback, '顾客退出成功');
});
}
/**
* 分配客服
*
* @return void
*/
private function allocatCustomerService()
{
$room_id = $this->session('room_id');
$session_id = $this->session('session_id');
$auth = $this->session('auth');
// 检测并分配客服
$customerService = $this->chatService->checkAndAllocatCustomerService();
if ($customerService) {
// 将用户 session_id 从等待排名中移除(这个用户的所有客户端都会被接入)
$this->nspWaitingDel($room_id, $session_id);
// 排队发生变化,通知房间中所有排队用户
$this->sender->allWaitingQueue($room_id);
// 顾客被接入,通知所有自己的客户端被接入,通知房间中所有客服用户被接入(等待中移除),通知新客服,新用户接入
$this->sender->customerAccessed($room_id, $session_id, $customerService);
} else {
if ($this->getter('socket')->hasCustomerServiceByRoomId($room_id)) {
// 有客服
$this->sender->waiting();
} else {
// 通知用户没有客服在线
$this->sender->noCustomerService();
}
}
}
}

View File

@@ -0,0 +1,438 @@
<?php
namespace addons\shopro\library\chat\provider\auth\traits;
use addons\shopro\exception\ShoproException;
use addons\shopro\library\chat\traits\DebugEvent;
use addons\shopro\library\chat\traits\Helper;
use addons\shopro\library\chat\traits\Session;
/**
* 客服事件
*/
trait CustomerService
{
/**
* debug 方式注册事件
*/
use DebugEvent;
/**
* 助手方法
*/
use Helper;
/**
* session 存储助手
*/
use Session;
public function customerServiceOn()
{
// 客服登录
$this->register('customer_service_login', function ($data, $callback) {
// 加入的客服房间
$room_id = $data['room_id'] ?? 'admin';
$this->session('room_id', $room_id);
// 存储当前房间
$this->nspRoomIdAdd($room_id);
// 当前管理员信息
$auth = $this->session('auth');
$admin = $this->session('auth_user');
if (!$this->chatService->customerServiceLogin($room_id, $admin['id'], $auth)) {
throw new ShoproException('客服登录失败');
}
// 注册链接上客服事件
$this->customerServiceEvent();
// 客服上线
$this->customerServiceOnline();
// 获取客服常用语
$commonWords = $this->getter('db')->getChatCommonWords($room_id);
// 通知自己登录成功
$this->sender->successSocket($callback, '客服登录成功', [
'customer_service' => $this->session('customer_service'),
'common_words' => $commonWords
]);
});
}
/**
* 客服的所有事件
*
* @return void
*/
public function customerServiceEvent()
{
// 检测是否登录过
if ($this->socket->listeners('customer_service_init')) {
// 已经注册过了,避免重复注册
return false;
}
// 客服登录之后的所有事件
// 客服初始化
$this->register('customer_service_init', function ($data, $callback) {
$room_id = $this->session('room_id');
$customerService = $this->session('customer_service');
// 获取当前客服正在服务的顾客
$chatUsers = $this->getter()->getCustomersIngFormatByCustomerService($room_id, $customerService['id']);
$ingChatUserIds = array_column($chatUsers, 'id');
// 获取等待中的顾客
$waitings = $this->getter()->getCustomersFormatWaiting($room_id);
// 获取客服历史服务过的顾客
$histories = $this->getter()->getCustomersHistoryFormatByCustomerService($room_id, $customerService['id'], $ingChatUserIds);
$this->sender->successSocket($callback, '初始化成功', [
'onlines' => $chatUsers,
'histories' => $histories,
'waitings' => $waitings,
]);
});
// 发送消息
$this->register('message', function ($data, $callback) {
$session_id = $data['session_id'] ?? ''; // 接收者
$message = $data['message'] ?? []; // 发送的消息
$customerService = $this->session('customer_service');
// 给用户发送消息
$this->sender->messageToCustomer($message, [
'sender_identify' => 'customer_service',
'customer_service' => $customerService
], $session_id);
// 通知自己发送成功
$this->sender->successSocket($callback, '发送成功');
});
// 获取消息列表
$this->register('messages', function ($data, $callback) {
// 当前房间
$room_id = $this->session('room_id');
$session_id = $data['session_id']; // 要获取的顾客
// 获取 顾客 聊天记录
$messages = $this->getter('db')->getCustomerMessagesBySessionId($room_id, $session_id, 'customer', $data);
// 获取消息列表
$this->sender->successSocket($callback, '获取成功', [
'messages' => $messages
]);
});
// 接入用户
$this->register('access', function ($data, $callback) {
$session_id = $data['session_id']; // 要接入的顾客
$room_id = $this->session('room_id');
$customerService = $this->session('customer_service');
// 将当前 用户与 客服绑定
$this->chatService->bindCustomerServiceBySessionId($room_id, $session_id, $customerService);
// 将用户 session_id 从等待排名中移除(这个用户的所有客户端都会被接入)
$this->nspWaitingDel($room_id, $session_id);
// 排队发生变化,通知房间中所有排队用户
$this->sender->allWaitingQueue($room_id);
// 顾客被接入,通知所有自己的客户端被接入,通知房间中所有客服用户被接入(等待中移除),通知新客服,新用户接入
$this->sender->customerAccessed($room_id, $session_id, $customerService);
// 获取消息列表
$this->sender->successSocket($callback, '接入成功');
});
// 转接
$this->register('transfer', function ($data, $callback) {
// 要转接的顾客
$session_id = $data['session_id'] ?? 0;
// 要转接的客服 id
$new_customer_service_id = $data['customer_service_id'] ?? 0;
// 当前客服信息
$room_id = $this->session('room_id');
$customerService = $this->session('customer_service');
if (!$new_customer_service_id) {
// 没有传入转接客服 id
throw new ShoproException('请选择要转接的客服');
}
// 不能转接给自己
if ($new_customer_service_id == $customerService['id']) {
// 不能转接给自己
throw new ShoproException('您不能转接给自己');
}
// 获取被转接入的客服, 自动只取客服信息,过滤重复
$newCustomerService = $this->getter('socket')->getCustomerServiceById($room_id, $new_customer_service_id);
if (!$newCustomerService) {
throw new ShoproException('转接的客服不在线');
}
// 转接客户,加入新客服,移除老客服
$this->chatService->transferCustomerServiceBySessionId($room_id, $session_id, $customerService, $newCustomerService);
// 将用户 session_id 从等待排名中移除(这个用户的所有客户端都会被接入)
$this->nspWaitingDel($room_id, $session_id);
// 排队发生变化,通知房间中所有排队用户
$this->sender->allWaitingQueue($room_id);
// 顾客被接入,通知所有自己的客户端被接入,通知房间中所有客服用户被接入(等待中移除),通知新客服,新用户接入
$this->sender->customerTransfer($room_id, $session_id, $customerService, $newCustomerService);
// 通知老客服,转接成功
$this->sender->successSocket($callback, '转接成功');
});
// 断开连接中的顾客
$this->register('break_customer', function ($data, $callback) {
// 当前客服信息
$room_id = $this->session('room_id');
$customerService = $this->session('customer_service');
// 要断开的顾客
$session_id = $data['session_id'];
// 结束并断开客服
$this->chatService->breakCustomerServiceBySessionId($room_id, $session_id, $customerService);
// 服务结束,通知顾客客服断开连接
$this->sender->customerServiceBreak($session_id);
$this->sender->successSocket($callback, '服务结束成功');
});
// 删除历史中的顾客
$this->register('del_customer', function ($data, $callback) {
// 当前客服信息
$room_id = $this->session('room_id');
$customerService = $this->session('customer_service');
// 要删除的顾客
$session_id = $data['session_id'];
$is_del_record = $data['is_del_record'] ?? false; // 是否删除聊天记录
$chatUser = $this->getter('db')->getChatUserBySessionId($session_id);
if (!$chatUser) {
throw new ShoproException('删除失败');
}
$this->getter('db')->delCustomerByCustomerService($room_id, $chatUser['id'], $customerService, $is_del_record);
$this->sender->successSocket($callback, '删除成功');
});
$this->register('del_customer_all', function ($data, $callback) {
// 当前客服信息
$room_id = $this->session('room_id');
$customerService = $this->session('customer_service');
$is_del_record = $data['is_del_record'] ?? false; // 是否删除聊天记录
$this->getter('db')->delCustomerAllByCustomerService($room_id, $customerService, $is_del_record);
$this->sender->successSocket($callback, '删除成功');
});
// 客服上线
$this->register('customer_service_online', function ($data, $callback) {
// 客服操作自己在线状态触发
// 客服上线
$this->customerServiceOnline();
// 通知自己上线成功
$this->sender->successSocket($callback, '当前状态已切换为在线', [
'customer_service' => $this->session('customer_service')
]);
});
// 客服离线
$this->register('customer_service_offline', function ($data, $callback) {
// 客服操作自己为离线状态触发
// 客服离线
$this->customerServiceOffline();
// 通知自己离线成功
$this->sender->successSocket($callback, '当前状态已切换为离线', [
'customer_service' => $this->session('customer_service')
]);
});
// 客服忙碌
$this->register('customer_service_busy', function ($data, $callback) {
// 客服操作自己在线状态触发
// 客服忙碌
$this->customerServiceBusy();
// 通知自己离线成功
$this->sender->successSocket($callback, '当前状态已切换为忙碌', [
'customer_service' => $this->session('customer_service')
]);
});
// 客服登录
$this->register('customer_service_logout', function ($data, $callback) {
$this->customerServiceLogout();
// 解绑客服相关的事件,等下次客服再登录时再重新绑定
$this->socket->removeAllListeners('customer_service_init');
$this->socket->removeAllListeners('message');
$this->socket->removeAllListeners('messages');
$this->socket->removeAllListeners('access');
$this->socket->removeAllListeners('transfer');
$this->socket->removeAllListeners('break_customer');
$this->socket->removeAllListeners('del_customer');
$this->socket->removeAllListeners('del_customer_all');
$this->socket->removeAllListeners('customer_service_online');
$this->socket->removeAllListeners('customer_service_offline');
$this->socket->removeAllListeners('customer_service_busy');
$this->socket->removeAllListeners('customer_service_logout');
$this->sender->successSocket($callback, '退出成功');
});
}
/**
* 客服上线,并通知连接的用户,和房间中的其他客服
*
* @return void
*/
private function customerServiceOnline()
{
// 房间号
$room_id = $this->session('room_id');
// 客服信息
$customerService = $this->session('customer_service');
// 记录客服切换之前的在线状态
$isOnLineCustomerService = $this->getter('socket')->isOnLineCustomerServiceById($customerService['id']);
// 客服上线,更新客服状态,加入客服组
$this->chatService->customerServiceOnline($room_id, $customerService['id']);
// if (!$isOnLineCustomerService) {
// (这里不判断,都通知,重复通知不影响)如果之前是离线状态
// 通知连接的用户(在当前客服服务的房间里面的用户),客服上线了
$this->sender->customerServiceOnline();
// 通知当前房间的在线客服,更新当前在线客服列表
$this->sender->customerServiceUpdate();
// }
}
/**
* 客服离线,并通知连接的用户,和房间中的其他客服
*
* @return void
*/
private function customerServiceOffline()
{
// 房间号
$room_id = $this->session('room_id');
// 客服信息
$customerService = $this->session('customer_service');
// 客服下线,更新客服状态,解绑 client_id退出客服组
$this->chatService->customerServiceOffline($room_id, $customerService['id']);
if (!$this->getter('socket')->isOnLineCustomerServiceById($customerService['id'])) {
// 当前客服的所有客户端都下线了
// 通知连接的用户(在当前客服服务的房间里面的用户),客服下线了
$this->sender->customerServiceOffline();
// 通知当前房间的在线客服,更新当前在线客服列表
$this->sender->customerServiceUpdate();
}
}
/**
* 客服忙碌,如果之前客服是离线 通知连接的用户,和房间中的其他客服,上线了
*
* @return void
*/
private function customerServiceBusy()
{
// 房间号
$room_id = $this->session('room_id');
// 客服信息
$customerService = $this->session('customer_service');
// 记录客服切换之前的在线状态
$isOnLineCustomerService = $this->getter('socket')->isOnLineCustomerServiceById($customerService['id']);
// 客服忙碌,更新客服状态,判断并加入客服组
$this->chatService->customerServiceBusy($room_id, $customerService['id']);
// if (!$isOnLineCustomerService) {
// (这里不判断,都通知,重复通知不影响)如果之前是离线状态
// 通知连接的用户(在当前客服服务的房间里面的用户),客服上线了
$this->sender->customerServiceBusy();
// 通知当前房间的在线客服,更新当前在线客服列表
$this->sender->customerServiceUpdate();
// }
}
/**
* 客服退出登录
*
* @return void
*/
public function customerServiceLogout()
{
// 房间号
$room_id = $this->session('room_id');
// 客服信息
$customerService = $this->session('customer_service');
$customer_service_id = $this->session('customer_service_id');
// 客服先离线
$this->chatService->customerServiceOffline($room_id, $customer_service_id);
if (!$this->getter('socket')->isOnLineCustomerServiceById($customerService['id'])) {
// 当前客服的所有客户端都下线了
// 通知连接的用户(在当前客服服务的房间里面的用户),客服下线了
$this->sender->customerServiceOffline();
// 通知当前房间的在线客服,更新当前在线客服列表
$this->sender->customerServiceUpdate();
}
// 客服退出房间,清除客服 session 信息
$this->chatService->customerServiceLogout($room_id, $customer_service_id);
}
}

View File

@@ -0,0 +1,641 @@
<?php
namespace addons\shopro\library\chat\provider\getter;
use think\helper\Str;
use addons\shopro\exception\ShoproException;
use addons\shopro\library\chat\traits\DebugEvent;
use addons\shopro\library\chat\Getter;
use addons\shopro\controller\traits\UnifiedToken;
use app\admin\model\shopro\chat\User as ChatUser;
use app\admin\model\shopro\chat\ServiceLog as ChatServiceLog;
use app\admin\model\shopro\chat\Record as ChatRecord;
use app\admin\model\shopro\chat\CustomerService as ChatCustomerService;
use app\admin\model\shopro\chat\CustomerServiceUser as ChatCustomerServiceUser;
use app\admin\model\shopro\chat\Question as ChatQuestion;
use app\admin\model\shopro\chat\CommonWord as ChatCommonWord;
/**
* 从数据库获取
*/
class Db
{
/**
* token 验证助手
*/
use UnifiedToken;
/**
* getter 实例
*
* @var Getter
*/
protected $getter;
/**
* 实例化的模型数组
*
* @var array
*/
protected $models = [];
public function __construct(Getter $getter)
{
$this->getter = $getter;
}
/**
* 获取动态模型实例
*
* @param string $type 动态类型
* @param string $model 模型名称
* @param boolean $is_instance 是否实例化
* @return string|object 返回模型
*/
public function getModel($type, $model, $is_instance = true)
{
$key = $type . '_' . $model . '_' . strval($is_instance);
if (isset($this->models[$key])) {
return $this->models[$key];
}
switch($type) {
case 'auth' :
if ($model == 'user') {
$class = '\\app\\admin\\model\\shopro\\user\\' . ucfirst($model);
} else {
$class = '\\app\\admin\\model\\shopro\\' . ucfirst($model);
}
break;
}
if ($is_instance) {
$class = new $class;
}
$this->models[$key] = $class;
return $class;
}
/**
* 通过token 获取用户 id
*
* @param string $token
* @param string $auth 认证类型
* @return mixed
*/
public function getAuthIdFromToken($token, $auth)
{
// 获取加密数据
$content = $this->getUnifiedContent($token);
// 判断并返回用户 id
if ($content && strpos($content, $auth) !== false) {
return str_replace($auth . ':', '', $content);
}
return false;
}
/**
* 通过 token 获取 用户信息
*
* @param string $token
* @param string $auth 认证类型
* @return null|object
*/
public function getAuthByToken($token, $auth)
{
// 获取用户 id
$user_id = $this->getAuthIdFromToken($token, $auth);
// 获取用户
return $this->getAuthById($user_id, $auth);
}
/**
* 通过 id 获取用户
*
* @param int $id
* @param string $auth
* @return null|object
*/
public function getAuthById($id, $auth)
{
return $this->getModel('auth', $auth)->where('id', $id)->find();
}
/**
* 通过 session_id 获取chat 用户
*
* @param string $session_id
* @param string $auth 认证类型
* @return null|object
*/
public function getChatUserBySessionId($session_id)
{
$chatUser = ChatUser::where('session_id', $session_id)->order('id asc')->find();
return $chatUser;
}
/**
* 通过 auth 获取 library 用户信息
*
* @param integer $auth_id
* @param string $auth
* @return void
*/
public function getChatUserByAuth($auth_id, $auth)
{
$chatUser = ChatUser::where('auth', $auth)->where('auth_id', $auth_id)->order('id asc')->find();
return $chatUser;
}
/**
* 获取最后一次被服务记录
*
* @param string $room_id
* @param integer $chat_user_id
* @return object|null
*/
public function getLastServiceLogByChatUser($room_id, $chat_user_id)
{
return ChatServiceLog::where('room_id', $room_id)
->where('chat_user_id', $chat_user_id)
->where('customer_service_id', '<>', 0)
->order('id', 'desc')->find();
}
/**
* 获取郑子昂服务中的记录
*
* @param string $room_id
* @param integer $chat_user_id
* @return object
*/
public function getServiceLogIngByChatUser($room_id, $chat_user_id, $customer_service_id = 0)
{
return ChatServiceLog::where('room_id', $room_id)
->where('chat_user_id', $chat_user_id)
->where(function ($query) use ($customer_service_id) {
$query->where(function($query) use ($customer_service_id) {
$query->where('status', 'waiting')->where('customer_service_id', 0);
})->whereOr(function ($query) use ($customer_service_id) {
$query->where('status', 'ing')->where('customer_service_id', $customer_service_id);
});
})
->order('id', 'desc')->find();
}
/**
* 添加服务记录
*
* @param string $room_id
* @param array $chatUser
* @param array $customerService
* @param string $status
* @return void
*/
public function addServiceLog($room_id, $chatUser, $customerService, $status)
{
$chatServiceLog = new ChatServiceLog();
$chatServiceLog->chat_user_id = $chatUser ? $chatUser['id'] : 0;
$chatServiceLog->customer_service_id = $customerService ? $customerService['id'] : 0;
$chatServiceLog->room_id = $room_id;
$chatServiceLog->starttime = time();
$chatServiceLog->endtime = $status == 'end' ? time() : null;
$chatServiceLog->status = $status;
$chatServiceLog->save();
return $chatServiceLog;
}
/**
* 创建正在进行中的 服务
*
* @param [type] $room_id
* @param [type] $chatUser
* @param [type] $customerService
* @return void
*/
public function createServiceLog($room_id, $chatUser, $customerService)
{
// 正在进行中的连接
$serviceLog = $this->getServiceLogIngByChatUser($room_id, $chatUser['id'], $customerService['id']);
if (!$serviceLog) {
// 不存在,创建新的
$serviceLog = $this->addServiceLog($room_id, $chatUser, $customerService, 'ing');
}
return $serviceLog;
}
/**
* 结束 服务
*
* @param string $room_id 房间号
* @param array $chatUser 顾客
* @param array $customerService 客服
* @return object
*/
public function endServiceLog($room_id, $chatUser, $customerService)
{
// 正在进行中的连接
$serviceLog = $this->getServiceLogIngByChatUser($room_id, $chatUser['id'], $customerService['id']);
if (!$serviceLog) {
// 不存在,创建新的
$serviceLog = $this->addServiceLog($room_id, $chatUser, $customerService, 'end');
} else {
$serviceLog->customer_service_id = $customerService ? $customerService['id'] : 0;
$serviceLog->endtime = time();
$serviceLog->status = 'end';
$serviceLog->save();
}
return $serviceLog;
}
/**
* 通过用户获取到用户绑定的在指定房间的客服信息(判断 auth 在指定房间是否是客服,是了才能登录客服)
*
* @param string $room_id 房间号
* @param string $auth 用户类型
* @param integer $auth_id 用户 id
* @return array|null
*/
public function getCustomerServiceByAuthAndRoom($room_id, $auth_id, $auth)
{
// 获取当前 auth 绑定的所有客服的 id
$customerServiceIds = ChatCustomerServiceUser::where('auth', $auth)->where('auth_id', $auth_id)->column('customer_service_id');
// 通过上一步的 客服id 配合 房间获取第一条客服(只能有一条)
$customerService = ChatCustomerService::where('room_id', $room_id)->where('id', 'in', $customerServiceIds)->find();
return $customerService;
}
/**
* 通过用户获取到用户绑定的所有客服信息(暂不使用,当独立登录是,让用户选择客服身份)
*
* @param string $auth 用户类型
* @param integer $auth_id 用户 id
* @return array
*/
public function getCustomerServicesByAuth($auth_id, $auth, $first = false)
{
// 获取当前 auth 绑定的所有客服的 id
$customerServiceIds = ChatCustomerServiceUser::where('auth', $auth)->where('auth_id', $auth_id)->column('customer_service_id');
// 通过上一步的 客服id 配合 房间获取第一条客服(只能有一条)
$customerServices = ChatCustomerService::where('id', 'in', $customerServiceIds)->order('id', 'asc')->select();
return $first ? ($customerServices[0] ?? null) : $customerServices;
}
/**
* 获取客服服务的历史用户
*
* @param string $room_id 房间号
* @param integer $customer_service_id 客服 id
* @param array $exceptIds 要排除的ids (正在服务的用户)
* @return array
*/
public function getCustomersHistoryByCustomerService($room_id, $customer_service_id, $exceptIds = [])
{
// $logs = ChatServiceLog::with('chat_user')
// ->where('room_id', $room_id)
// ->field('chat_user_id')
// ->whereNotIn('chat_user_id', $exceptIds)
// ->where('customer_service_id', $customer_service_id)
// ->group('chat_user_id')
// ->order('id', 'desc')
// ->select();
// $logs = collection($logs)->toArray();
// $chatUsers = array_column($logs, 'chat_user');
// 替代上面的方法,上面方法 group by 在 mysql 严格模式必须要关闭 ONLY_FULL_GROUP_BY
$chatUsers = [];
$logs = ChatServiceLog::with('chat_user')
->field('id,chat_user_id')
->where('room_id', $room_id)
->whereNotIn('chat_user_id', $exceptIds)
->where('customer_service_id', $customer_service_id)
->chunk(100, function ($currentLogs) use (&$chatUsers) {
foreach ($currentLogs as $log) {
$chatUser = $log->chat_user;
$currentIds = array_column($chatUsers, 'id');
if ($chatUser && !in_array($chatUser->id, $currentIds)) {
$chatUsers[] = $chatUser;
}
if (count($chatUsers) >= 20) {
break;
}
}
if (count($chatUsers) >= 20) {
return false;
}
}, 'id', 'desc'); // 如果 id 重复,有坑 (date < 2020-03-28)
return $chatUsers;
}
/**
* 通过 session_id 获取 顾客
*
* @param string $session_id
* @return object
*/
public function getCustomerBySessionId($session_id)
{
return ChatUser::where('session_id', $session_id)->find();
}
/**
* 删除客服的一个顾客的所有服务记录
*
* @param string $room_id
* @param integer $chat_user_id
* @param array $customerService
* @param boolean $is_del_record
* @return void
*/
public static function delCustomerByCustomerService($room_id, $chat_user_id, $customerService, $is_del_record = false)
{
ChatServiceLog::where('room_id', $room_id)
->where('customer_service_id', $customerService['id'])
->where('chat_user_id', $chat_user_id)->delete();
if ($is_del_record) {
self::delCustomerRecordById($room_id, $chat_user_id);
}
}
/**
* 删除客服的一个顾客的所有服务记录
*
* @param string $room_id
* @param array $customerService
* @param boolean $is_del_record
* @return void
*/
public static function delCustomerAllByCustomerService($room_id, $customerService, $is_del_record = false)
{
if ($is_del_record) {
$chatUserIds = ChatServiceLog::where('room_id', $room_id)
->where('customer_service_id', $customerService['id'])->column('chat_user_id');
$chatUserIds = array_values(array_unique($chatUserIds));
foreach ($chatUserIds as $chat_user_id) {
self::delCustomerRecordById($room_id, $chat_user_id);
}
}
ChatServiceLog::where('room_id', $room_id)
->where('customer_service_id', $customerService['id'])->delete();
}
/**
* 删除客户聊天记录
*
* @param string $room_id
* @param int $chat_user_id
* @return void
*/
public static function delCustomerRecordById($room_id, $chat_user_id)
{
ChatRecord::where('room_id', $room_id)->where('chat_user_id', $chat_user_id)->delete();
}
/**
* 获取顾客的聊天记录
*
* @param string $room_id
* @param string $session_id
* @param $select_identify
* @return array
*/
public function getCustomerMessagesBySessionId($room_id, $session_id, $select_identify, $data)
{
$selectIdentify = Str::camel($select_identify);
$customer = $this->getCustomerBySessionId($session_id);
$chat_user_id = $customer ? $customer['id'] : 0;
// 将消息标记为已读
ChatRecord::where('room_id', $room_id)->where('chat_user_id', $chat_user_id)->{$selectIdentify}()->update([
'read_time' => time()
]);
$page = $data['page'] ?? 1;
$list_rows = $data['list_rows'] ?? 20;
$last_id = $data['last_id'] ?? 0;
$messageList = ChatRecord::where('room_id', $room_id)->where('chat_user_id', $chat_user_id);
// 避免消息重复
if ($last_id) {
$messageList = $messageList->where('id', '<=', $last_id);
}
$messageList = $messageList->order('id', 'desc')->paginate([
'page' => $page,
'list_rows' => $list_rows
]);
$messageList = $this->getMessageSender($messageList);
return $messageList;
}
/**
* 获取用户的最后一条消息(当前房间的)
*
* @param string $room_id
* @param integer $chat_user_id
* @return object
*/
public function getMessageLastByChatUser($room_id, $chat_user_id)
{
return ChatRecord::where('room_id', $room_id)->where('chat_user_id', $chat_user_id)->order('id', 'desc')->find();
}
/**
* 根据身份获取未读消息条数(当前房间的)
*
* @param string $room_id
* @param integer $chat_user_id
* @param string $select_identify
* @return integer
*/
public function getMessageUnReadNumByChatUserAndIndentify($room_id, $chat_user_id, $select_identify)
{
$selectIdentify = Str::camel($select_identify);
return ChatRecord::where('room_id', $room_id)->where('chat_user_id', $chat_user_id)->where('read_time', 'null')->{$selectIdentify}()->count();
}
/**
* 更新客服信息
*
* @param integer $id
* @param array $data
* @return void
*/
public function updateCustomerService($id, $data)
{
$customerService = ChatCustomerService::where('id', $id)->update($data);
}
/**
* 添加消息记录
*
* @param string $room_id
* @param string $name
* @param array $arguments
* @return object
*/
public function addMessage($room_id, $name, $arguments)
{
$content = $arguments[2] ?? []; // 额外参数
$message = $content['message'];
$sender = $content['sender'];
$sender_identify = $sender['sender_identify'] ?? 'customer';
$receive = $arguments[3] ?? [];
if ($sender_identify == 'customer') {
$session_id = $sender['session_id'];
$chatUser = $this->getChatUserBySessionId($session_id);
$sender_id = $chatUser['id'] ?? 0;
$chat_user_id = $sender_id;
} else {
$customerService = $sender['customer_service'];
$sender_id = $customerService['id'] ?? 0;
$session_id = $receive['id'];
$chatUser = $this->getChatUserBySessionId($session_id);
$chat_user_id = $chatUser['id'] ?? 0;
}
$chatRecord = new ChatRecord();
$chatRecord->chat_user_id = $chat_user_id;
$chatRecord->room_id = $room_id;
$chatRecord->sender_identify = $sender_identify;
$chatRecord->sender_id = $sender_id;
$chatRecord->message_type = $message['message_type'] ?? 'text';
$chatRecord->message = $message['message'] ?? '';
$chatRecord->createtime = time();
$chatRecord->updatetime = time();
$chatRecord->save();
// 加载消息人
$chatRecord = $this->getMessageSender([$chatRecord])[0];
return $chatRecord;
}
/**
* 获取猜你想问
*
* @param string $room_id 房间
* @return object
*/
public function getChatQuestions($room_id)
{
$chatQuestions = ChatQuestion::roomId($room_id)->order(['weigh' => 'desc', 'id' => 'desc'])->select();
return $chatQuestions;
}
/**
* 根据 id 获取猜你想问
*
* @param string $room_id 房间
* @return object
*/
public function getChatQuestion($room_id, $question_id)
{
if ($question_id) {
$chatQuestion = ChatQuestion::roomId($room_id)->find($question_id);
return $chatQuestion;
}
return null;
}
/**
* 获取客服常用语
*
* @param string $room_id 房间
* @return object
*/
public function getChatCommonWords($room_id)
{
$chatCommonWords = ChatCommonWord::normal()->roomId($room_id)->order(['weigh' => 'desc', 'id' => 'desc'])->select();
return $chatCommonWords;
}
/**
* 获取消息的 发送人
*
* @param array|object $messageList
* @return array|object
*/
private function getMessageSender($messageList)
{
$morphs = [
'customer' => \app\admin\model\shopro\chat\User::class,
'customer_service' => \app\admin\model\shopro\chat\CustomerService::class
];
$messageList = morph_to($messageList, $morphs, ['sender_identify', 'sender_id']);
return $messageList;
}
}

View File

@@ -0,0 +1,354 @@
<?php
namespace addons\shopro\library\chat\provider\getter;
use addons\shopro\exception\ShoproException;
use addons\shopro\library\chat\traits\Helper;
use addons\shopro\library\chat\traits\Session;
use addons\shopro\library\chat\traits\BindUId;
use addons\shopro\library\chat\Getter;
use PHPSocketIO\SocketIO;
use PHPSocketIO\Socket as PhpSocket;
use PHPSocketIO\Nsp;
/**
* 从 socket 连接中获取
*/
class Socket
{
/**
* session 存储助手
*/
use Session;
/**
* BindUid 助手
*/
use BindUId;
/**
* 助手方法
*/
use Helper;
/**
* getter 实例
*
* @var Getter
*/
protected $getter;
/**
* 当前 phpsocket.io 实例
*
* @var SocketIo
*/
protected $io = null;
/**
* 当前socket 连接
*
* @var PhpSocket
*/
protected $socket = null;
/**
* 当前 命名空间实例
*
* @var Nsp
*/
protected $nsp = null;
public function __construct(Getter $getter, PhpSocket $socket = null, SocketIo $io, Nsp $nsp)
{
$this->getter = $getter;
$this->socket = $socket;
$this->io = $io;
$this->nsp = $nsp;
}
/**
* 通过房间获取房间中所有连接的 clientIds
*
* @param string $room 房间名称
* @return void
*/
public function getClientIdsByRoom($room) {
// 获取到的数组 键是 id 值是 boolean true
$clientIds = $this->nsp->adapter->rooms[$room] ?? [];
// 取出 clientIds
$clientIds = array_keys($clientIds);
return $clientIds;
}
/**
* 根据房间获取所有房间中的 authUser
*
* @param string $room_id 房间号
* @param string $is_unique 默认过滤重复的数据
* @return array
*/
public function getAuthsByAuth($auth, $is_unique = true)
{
// 要接入的客服所在的房间,默认 admin 房间,z这里的客服的状态都是 在线的,如果手动切换为离线,则会被移除该房间
$room = $this->getRoomName('auth', ['auth' => $auth]);
$sessions = $this->getSessionsByRoom($room, null, $is_unique);
$authUsers = [];
foreach ($sessions as $session) {
if (isset($session['auth_user']) && $session['auth_user']) {
$authUsers[$session['session_id']] = $session['auth_user'];
}
}
return $authUsers;
}
/**
* 通过id 获取指定客服,并且还必须在对应的客服房间中
*
* @param string $id 客服 id
* @param string $room_id 客服房间号
* @return array|null
*/
public function getCustomerServiceById($room_id, $id)
{
// 要接入的客服所在的房间,默认 admin 房间
$room = $this->getRoomName('customer_service_room', ['room_id' => $room_id]);
// 房间中的所有客服的 clientids
$roomClientIds = $this->getClientIdsByRoom($room);
// 当前客服 uid 绑定的所有客户端 clientIds手动后台离线的已经被解绑了这里都是状态为在线的
$currentClientIds = $this->getClientIdByUId($id, 'customer_service');
if ($clientIds = array_intersect($currentClientIds, $roomClientIds)) {
// 客服在线
return $this->getSession(current($clientIds), 'customer_service');
}
return null;
}
/**
* 通过 客服 id 判断客服是否在线,这里不管客服所在房间,一个客服只能属于一个房间
*
* @param string $id 客服 id
* @return boolean
*/
public function isOnLineCustomerServiceById($id)
{
return $this->isUIdOnline($id, 'customer_service');
}
/**
* 根据房间获取所有房间中的客服
*
* @param string $room_id 客服房间号
* @param string $is_unique 默认过滤重复的数据
* @return array
*/
public function getCustomerServicesByRoomId($room_id, $is_unique = true)
{
// 要接入的客服所在的房间,默认 admin 房间,z这里的客服的状态都是 在线的,如果手动切换为离线,则会被移除该房间
$room = $this->getRoomName('customer_service_room', ['room_id' => $room_id]);
$customerServices = $this->getSessionsByRoom($room, 'customer_service', $is_unique);
return $customerServices;
}
/**
* 获取指定 session_id 在对应房间的所有客户端的服务客服的信息
*
* @param [type] $room_id
* @param [type] $session_id
* @return void
*/
public function getCustomerServiceBySessionId($room_id, $session_id)
{
$sessions = $this->getSessionsById($session_id, 'customer');
$customerService = null;
foreach ($sessions as $session) {
if (isset($session['room_id']) && $session['room_id'] == $room_id
&& isset($session['customer_service']) && $session['customer_service']) {
$currentCustomerService = $session['customer_service'];
if ($this->isOnLineCustomerServiceById($currentCustomerService['id'])) {
// 如果客服在线
$customerService = $currentCustomerService;
break;
}
}
}
return $customerService;
}
/**
* 判断当前房间中是否有客服在线
*
* @param string $room_id 客服房间号
* @return boolean
*/
public function hasCustomerServiceByRoomId($room_id)
{
// 获取房间中所有客服的 session,只要有,就说明有客服在线,手动切换状态的会被移除 在线客服房间
$allCustomerServices = $this->getCustomerServicesByRoomId($room_id, false);
if ($allCustomerServices) {
return true;
}
return false;
}
/**
* 判断并通过 顾客 获取顾客的客服
*/
public function getCustomerServiceByCustomerSessionId($session_id)
{
$customerServices = $this->getSessionsById($session_id, 'customer', 'customer_service');
return current($customerServices) ?? null;
}
/**
* (获取客服正在服务的顾客)根据客服获取客服房间中所有的被服务用户
*
* @param string $room_id 客服房间号
* @param integer $customer_service_id 客服 id
* @param string $is_unique 默认过滤重复的数据
* @return array
*/
public function getCustomersIngByCustomerService($room_id, $customer_service_id, $is_unique = true)
{
// 要接入的客服所在的房间,默认 admin 房间,z这里的客服的状态都是 在线的,如果手动切换为离线,则会被移除该房间
$room = $this->getRoomName('customer_service_room_user', ['room_id' => $room_id, 'customer_service_id' => $customer_service_id]);
$customers = $this->getSessionsByRoom($room, 'chat_user', $is_unique);
return $customers;
}
/**
* (获取等待被接入的用户)根据房间号获取被服务对象
*
* @param string $room_id 客服房间号
* @param integer $customer_service_id 客服 id
* @param string $is_unique 默认过滤重复的数据
* @return array
*/
public function getCustomersWaiting($room_id, $is_unique = true)
{
// 要接入的客服所在的房间,默认 admin 房间,z这里的客服的状态都是 在线的,如果手动切换为离线,则会被移除该房间
$room = $this->getRoomName('customer_service_room_waiting', ['room_id' => $room_id]);
$customers = $this->getSessionsByRoom($room, 'chat_user', $is_unique);
return $customers;
}
/**
* 通过 session_id 判断顾客是否在线
*
* @param string $id 客服 id
* @return boolean
*/
public function isOnLineCustomerById($session_id)
{
return $this->isUIdOnline($session_id, 'customer');
}
/**
* 通过 session_id 判断 auth 是否在线
*
* @param string $id 客服 id
* @return boolean
*/
public function isOnlineAuthBySessionId($session_id, $auth)
{
return $this->isUIdOnline($session_id, $auth);
}
/**
* 通过 id 获取这个人在 type 下的所有客户端 session 数据
*
* @param string $id 要获取的用户的 id
* @param string $type bind 类型user,admin,customer_service 等
* @param string $name 要获取session 中的特定值,默认全部数据
* @return array
*/
public function getSessionsById($id, $type, $name = null)
{
$currentClientIds = $this->getClientIdByUId($id, $type);
$sessionDatas = $this->getSessionByClientIds($currentClientIds, $name);
return $sessionDatas;
}
/**
* 通过 id 更新 id 绑定的所有客户端的 session
*
* @param integer $id
* @param string $type
* @param array $data
* @return void
*/
public function updateSessionsById($id, $type, $data = [])
{
// 当前id 在当前类型下绑定的所有客户端
$currentClientIds = $this->getClientIdByUId($id, $type);
$this->updateSessionByClientIds($currentClientIds, $data);
}
/**
* 获取房间中的所有客户端的 session
*
* @param string $room 房间真实名称
* @param string $name 要取用的 session 中的键名,默认全部取出
* @param string $is_unique 默认根据绑定的Uid 过滤重复的 session
* @return array
*/
public function getSessionsByRoom($room, $name = null, $is_unique = true) {
// 房间中的所有客服的 clientids
$roomClientIds = $this->getClientIdsByRoom($room);
$sessionDatas = $this->getSessionByClientIds($roomClientIds); // 要过滤重复,没办法直接获取指定数据
// 处理数据
$newDatas = [];
foreach ($sessionDatas as $sessionData) {
if ($is_unique) {
// 过滤重复
$newDatas[$sessionData['session_id']] = $name ? $sessionData[$name] : $sessionData;
} else {
// 全部数据
$newDatas[] = $name ? $sessionData[$name] : $sessionData;
}
}
return array_values(array_filter($newDatas));
}
}