init
- 框架初始化 - 安装插件 - 修复PHP8.4报错
This commit is contained in:
324
addons/shopro/library/chat/Chat.php
Normal file
324
addons/shopro/library/chat/Chat.php
Normal file
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\chat;
|
||||
|
||||
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\traits\BindUId;
|
||||
use PHPSocketIO\SocketIO;
|
||||
use PHPSocketIO\Socket;
|
||||
use PHPSocketIO\Nsp;
|
||||
|
||||
class Chat
|
||||
{
|
||||
/**
|
||||
* session 存储助手
|
||||
*/
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* 绑定 UID 助手
|
||||
*/
|
||||
use BindUId;
|
||||
/**
|
||||
* 绑定数据到 nsp 作为全局数据
|
||||
*/
|
||||
use NspData;
|
||||
/**
|
||||
* 助手方法
|
||||
*/
|
||||
use Helper;
|
||||
|
||||
/**
|
||||
* debug 方式注册事件
|
||||
*/
|
||||
use DebugEvent;
|
||||
|
||||
/**
|
||||
* 当前 phpsocket.io 实例
|
||||
*
|
||||
* @var SocketIO
|
||||
*/
|
||||
public $io;
|
||||
|
||||
/**
|
||||
* 当前连接实例
|
||||
*
|
||||
* @var Socket
|
||||
*/
|
||||
public $socket;
|
||||
|
||||
/**
|
||||
* 当前 namespace 实例
|
||||
*
|
||||
* @var Nsp
|
||||
*/
|
||||
public $nsp;
|
||||
|
||||
/**
|
||||
* 当前发送实例
|
||||
*
|
||||
* @var Sender
|
||||
*/
|
||||
public $sender;
|
||||
|
||||
/**
|
||||
* 当前获取数据
|
||||
*
|
||||
* @var Getter
|
||||
*/
|
||||
public $getter;
|
||||
|
||||
/**
|
||||
* chat 操作类
|
||||
*
|
||||
* @var ChatService
|
||||
*/
|
||||
public $chatService;
|
||||
|
||||
protected $auth = [
|
||||
'user',
|
||||
'admin',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 初始化 chat 系统
|
||||
*
|
||||
* @param SocketIo $io
|
||||
* @param Socket $socket
|
||||
* @param Nsp $nsp
|
||||
*/
|
||||
public function __construct(SocketIo $io, Nsp $nsp, Socket $socket = null)
|
||||
{
|
||||
$this->io = $io;
|
||||
$this->socket = $socket;
|
||||
$this->nsp = $nsp;
|
||||
|
||||
// 初始化获取更改数据实例
|
||||
$this->getter = new Getter($socket, $io, $nsp);
|
||||
// 初始化发送实例
|
||||
$this->sender = new Sender($socket, $io, $nsp, $this->getter);
|
||||
// 初始化 客服公共方法实例
|
||||
$this->chatService = new ChatService($socket, $io, $nsp, $this->getter);
|
||||
}
|
||||
|
||||
public function on()
|
||||
{
|
||||
// on 方法只有在连接的时候走一次
|
||||
$this->register('test', function ($data, $callback) {
|
||||
|
||||
// $class = "\\app\\chat\\library\\provider\\auth\\User";
|
||||
// $provider = new $class($this);
|
||||
|
||||
// $this->socket->removeAllListeners('message');
|
||||
|
||||
// 注册相关身份事件
|
||||
// $provider->customerEvent();
|
||||
|
||||
|
||||
$customer_service_room = $this->getRoomName('customer_service_room', ['room_id' => 'admin']);
|
||||
$customerServices = $this->getter('socket')->getSessionsByRoom($customer_service_room, 'customer_service');
|
||||
|
||||
$this->sender->successSocket($callback, '连接成功', [
|
||||
'msg' => '恭喜鏈接成功',
|
||||
'bind' => $this->nsp->bind ?? [],
|
||||
'nsp_room_ids' => $this->nspData('room_ids'),
|
||||
'customer_service' => $customerServices,
|
||||
'nsp_data' => $this->nsp->nspData ?? [],
|
||||
'rooms' => isset($this->nsp->adapter->rooms) ? $this->nsp->adapter->rooms : [],
|
||||
'current_rooms' => $this->socket->rooms,
|
||||
'session' => $this->session(),
|
||||
'client_id' => $this->socket->id,
|
||||
'session_ids' => $this->nspData('session_ids')
|
||||
]);
|
||||
|
||||
|
||||
$this->sender->successUId('new message', '消息桶送', ['aaa' => 'bbb'], [
|
||||
'id' => $this->session('session_id'),
|
||||
'type' => $this->session('auth'),
|
||||
]);
|
||||
|
||||
// foreach ($clientIds as $client_id) {
|
||||
// $this->sender->successSocket('new message', ['aaa' => 'bbb']);
|
||||
// }
|
||||
|
||||
$this->socket->on('test-child', function ($data, $callback) {
|
||||
echo "子集消息来了";
|
||||
|
||||
$this->session('text:child', 'aaa');
|
||||
$this->sender->successSocket($callback, '连接成功', [
|
||||
'msg' => '子事件夜之星成功了'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// socket 连接初始化,socket.io-client 连接后的第一步
|
||||
$this->register('connection', function ($data, $callback) {
|
||||
// 初始化连接
|
||||
$auth = $data['auth'] ?? '';
|
||||
|
||||
if (!in_array($auth, $this->auth)) {
|
||||
throw new ShoproException('身份错误');
|
||||
}
|
||||
|
||||
// 存储当前 auth 驱动
|
||||
$this->session('auth', $auth);
|
||||
|
||||
// 加入对应身份组
|
||||
$this->socket->join($this->getRoomName('auth', ['auth' => $auth]));
|
||||
|
||||
// 加入在线连接组
|
||||
$this->socket->join('online');
|
||||
|
||||
// 检测并自动登录
|
||||
$result = $this->chatService->authLogin($data);
|
||||
|
||||
// 注册各自身份的事件
|
||||
$this->authEvent($auth);
|
||||
|
||||
// 连接成功,发送给自己
|
||||
$this->sender->authSuccess($callback);
|
||||
});
|
||||
|
||||
// auth 身份登录,管理员或者用户
|
||||
$this->register('login', function ($data, $callback) {
|
||||
// 登录,和系统中的用户或者管理员绑定
|
||||
$result = $this->chatService->authLogin($data);
|
||||
if ($result) {
|
||||
// 登录成功
|
||||
$this->sender->authSuccess($callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 登录失败
|
||||
throw new ShoproException('登录失败');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
$this->register('disconnect', function ($data, $callback) {
|
||||
$customer_service_id = $this->session('customer_service_id');
|
||||
$session_id = $this->session('session_id');
|
||||
$identify = $this->session('identify') ?: '';
|
||||
// $auth = $this->session('auth');
|
||||
// $authUser = $this->session('auth_user');
|
||||
|
||||
// 断开连接,解绑 bind 的身份
|
||||
$this->chatService->disconnectUnbindAll();
|
||||
|
||||
// 如果登录了,并且所有客户端都下线了, 删除相关 auth 的 ids
|
||||
// if ($this->chatService->isLogin() && !$this->getter('socket')->isOnlineAuthBySessionId($session_id, $auth)) {
|
||||
// $this->nspSessionIdDel($session_id, $auth);
|
||||
// }
|
||||
|
||||
// 如果是顾客
|
||||
if ($identify == 'customer') {
|
||||
// 顾客所在房间
|
||||
$room_id = $this->session('room_id');
|
||||
|
||||
// 顾客断开连接
|
||||
if (!$this->getter('socket')->isOnLineCustomerById($session_id)) {
|
||||
// 当前所遇用户端都断开了
|
||||
|
||||
$waiting_room_name = $this->getRoomName('customer_service_room_waiting', ['room_id' => $room_id]);
|
||||
$rooms = $this->socket->rooms;
|
||||
// 判断是否在排队中
|
||||
if (in_array($waiting_room_name, $rooms)) {
|
||||
// 这里顾客的所有客户端都断开了,在排队排名中移除
|
||||
$this->nspWaitingDel($room_id, $session_id);
|
||||
|
||||
// 排队发生变化,通知房间中所有排队用户
|
||||
$this->sender->allWaitingQueue($room_id);
|
||||
|
||||
// 离开排队中房间(将离线的用户从等待中移除)
|
||||
$this->socket->leave($waiting_room_name);
|
||||
// 通知更新排队中列表,把当前下线用户移除
|
||||
$this->sender->waiting();
|
||||
}
|
||||
|
||||
// 通知所有客服,顾客下线
|
||||
$this->sender->customerOffline();
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是客服
|
||||
if ($identify == 'customer_service') {
|
||||
// 客服断开连接
|
||||
if (!$this->getter('socket')->isOnLineCustomerServiceById($customer_service_id)) {
|
||||
// 当前客服的所有客户端都下线了
|
||||
|
||||
// 更新客服状态为离线
|
||||
$this->getter()->updateCustomerServiceStatus($customer_service_id, 'offline');
|
||||
|
||||
// 通知连接的用户(在当前客服服务的房间里面的用户),客服下线了
|
||||
$this->sender->customerServiceOffline();
|
||||
|
||||
// 通知当前房间的在线客服,更新当前在线客服列表
|
||||
$this->sender->customerServiceUpdate();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 注册相关身份事件
|
||||
*
|
||||
* @param string $auth
|
||||
* @return void
|
||||
*/
|
||||
private function authEvent($auth)
|
||||
{
|
||||
// 实例化相关身份事件
|
||||
$class = "\\addons\\shopro\\library\\chat\\provider\\auth\\" . ucfirst($auth);
|
||||
$provider = new $class($this);
|
||||
|
||||
// 注册相关身份事件
|
||||
$provider->on();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 站内信通知
|
||||
*
|
||||
* @param object $http_connection
|
||||
* @param string $uri
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function innerWorker($httpConnection, $uri, $data)
|
||||
{
|
||||
if ($uri == '/notification') {
|
||||
$this->exec($httpConnection, function () use ($data) {
|
||||
$receiver = $data['receiver'] ?? [];
|
||||
$sendData = $data['data'] ?? [];
|
||||
|
||||
$receiver_type = $receiver['type'] ?? 'user';
|
||||
$receiverIds = $receiver['ids'] ?? '';
|
||||
$receiverIds = is_array($receiverIds) ? $receiverIds : explode(',', $receiverIds);
|
||||
|
||||
// 循环给接收者发送消息
|
||||
foreach ($receiverIds as $id) {
|
||||
// 获取接收人的 session_id
|
||||
$session_id = $this->getter()->getSessionIdByAuth($id, $receiver_type);
|
||||
if ($session_id) {
|
||||
$this->sender->successUId('notification', '收到通知', $sendData, [
|
||||
'id' => $session_id, 'type' => $receiver_type
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 这句话必须有,否则会提示超时
|
||||
$httpConnection->send('ok');
|
||||
}
|
||||
}
|
||||
856
addons/shopro/library/chat/ChatService.php
Normal file
856
addons/shopro/library/chat/ChatService.php
Normal file
@@ -0,0 +1,856 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\chat;
|
||||
|
||||
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\NspData;
|
||||
use addons\shopro\library\chat\traits\BindUId;
|
||||
use app\admin\model\shopro\chat\User as ChatUser;
|
||||
use app\admin\model\shopro\Config;
|
||||
use PHPSocketIO\SocketIO;
|
||||
use PHPSocketIO\Socket;
|
||||
use PHPSocketIO\Nsp;
|
||||
use Workerman\Timer;
|
||||
|
||||
/**
|
||||
* ChatService 服务类
|
||||
*/
|
||||
class ChatService
|
||||
{
|
||||
|
||||
/**
|
||||
* session 存储助手
|
||||
*/
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* 绑定 UID 助手
|
||||
*/
|
||||
use BindUId;
|
||||
/**
|
||||
* nsp 对象存储全局数据
|
||||
*/
|
||||
use NspData;
|
||||
|
||||
/**
|
||||
* 助手方法
|
||||
*/
|
||||
use Helper;
|
||||
|
||||
/**
|
||||
* 当前 phpsocket.io 实例
|
||||
*
|
||||
* @var SocketIO
|
||||
*/
|
||||
protected $io;
|
||||
/**
|
||||
* 当前socket 连接
|
||||
*
|
||||
* @var Socket
|
||||
*/
|
||||
protected $socket;
|
||||
/**
|
||||
* 当前 命名空间
|
||||
*
|
||||
* @var Nsp
|
||||
*/
|
||||
public $nsp;
|
||||
|
||||
/**
|
||||
* getter 实例
|
||||
*
|
||||
* @var Getter
|
||||
*/
|
||||
protected $getter;
|
||||
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param Socket $socket
|
||||
* @param SocketIO $io
|
||||
* @param Nsp $nsp
|
||||
* @param Getter $getter
|
||||
*/
|
||||
public function __construct(Socket $socket = null, SocketIO $io, Nsp $nsp, Getter $getter)
|
||||
{
|
||||
$this->socket = $socket;
|
||||
$this->io = $io;
|
||||
$this->nsp = $nsp;
|
||||
|
||||
$this->getter = $getter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过 client_id 将当前 client_id,加入对应 room
|
||||
*
|
||||
* @param string $client_id
|
||||
* @param string $room
|
||||
* @return boolean
|
||||
*/
|
||||
public function joinByClientId($client_id, $room)
|
||||
{
|
||||
// 找到 client_id 对应的 socket 实例
|
||||
$client = $this->nsp->sockets[$client_id];
|
||||
|
||||
$client->join($room);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过 session_id 将 session_id 对应的所有客户端加入 room
|
||||
*
|
||||
* @param string $room
|
||||
* @param string $session_id
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function joinBySessionId($session_id, $type, $room)
|
||||
{
|
||||
// 当前用户 uid 绑定的所有客户端 clientIds
|
||||
$clientIds = $this->getClientIdByUId($session_id, $type);
|
||||
|
||||
// 将当前 session_id 绑定的 client_id 都加入当前客服组
|
||||
foreach ($clientIds as $client_id) {
|
||||
$this->joinByClientId($client_id, $room);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过 client_id 将当前 client_id,离开对应 room
|
||||
*
|
||||
* @param string $client_id
|
||||
* @param string $room
|
||||
* @return boolean
|
||||
*/
|
||||
public function leaveByClientId($client_id, $room)
|
||||
{
|
||||
// 找到 client_id 对应的 socket 实例
|
||||
$client = $this->nsp->sockets[$client_id];
|
||||
|
||||
$client->leave($room);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断 auth 是否登录
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isLogin()
|
||||
{
|
||||
$user = $this->session('auth_user');
|
||||
return $user ? true : false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* auth 登录
|
||||
*
|
||||
* @param array $data 登录参数,token session_id 等
|
||||
* @return boolean
|
||||
*/
|
||||
public function authLogin($data)
|
||||
{
|
||||
if ($this->isLogin()) {
|
||||
return true;
|
||||
}
|
||||
$auth = $this->session('auth');
|
||||
$user = null;
|
||||
$token = $data['token'] ?? ''; // fastadmin token
|
||||
$session_id = $data['session_id'] ?? ''; // session_id 如果没有,则后端生成
|
||||
$session_id = $session_id ?: $this->session('session_id'); // 如果没有,默认取缓存中的
|
||||
|
||||
// 根据 token 获取当前登录的用户
|
||||
if ($token) {
|
||||
$user = $this->getter('db')->getAuthByToken($token, $auth);
|
||||
}
|
||||
|
||||
if (!$user && $session_id) {
|
||||
$chatUser = $this->getter('db')->getChatUserBySessionId($session_id);
|
||||
$auth_id = $chatUser ? $chatUser['auth_id'] : 0;
|
||||
$user = $this->getter('db')->getAuthById($auth_id, $auth);
|
||||
}
|
||||
|
||||
// 初始化连接,需要获取 session_id
|
||||
if (!$session_id) {
|
||||
// 如果没有 session_id
|
||||
if ($user) {
|
||||
// 如果存在 user
|
||||
$chatUser = $this->getter('db')->getChatUserByAuth($user['id'], $auth);
|
||||
$session_id = $chatUser ? $chatUser['session_id'] : '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$session_id) {
|
||||
// 如果依然没有 session_id, 随机生成 session_id
|
||||
$session_id = md5(time() . mt_rand(1000000, 9999999));
|
||||
}
|
||||
|
||||
// 更新顾客用户信息
|
||||
$chatUser = $this->updateChatUser($session_id, $user, $auth);
|
||||
$this->session('chat_user', $chatUser->toArray()); // 转为数组
|
||||
$this->session('session_id', $session_id);
|
||||
$this->session('auth_user', $user ? $user->toArray() : $user);
|
||||
|
||||
// bind auth标示session_id,绑定 client_id
|
||||
$this->bindUId($this->session('session_id'), $auth);
|
||||
|
||||
if ($user) {
|
||||
$this->loginOk();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* auth 登录成功,注册相关事件
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loginOk()
|
||||
{
|
||||
$auth = $this->session('auth');
|
||||
$session_id = $this->session('session_id');
|
||||
|
||||
// 将登陆的 auth 记录下来
|
||||
// $this->nspSessionIdAdd($session_id, $auth);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 更新 chat_user
|
||||
*
|
||||
* @param string $session_id
|
||||
* @param array $auth
|
||||
* @param string $auth
|
||||
* @return array|object
|
||||
*/
|
||||
public function updateChatUser($session_id, $authUser, $auth)
|
||||
{
|
||||
// 这里只根据 session_id 查询,
|
||||
// 当前 session_id 如果已经绑定了 auth 和 auth_id,会被 authUser 覆盖掉,无论是否是同一个 auth 和 auth_id
|
||||
// 不在 根据 authUser 或者 session_id 同时查
|
||||
// 用户匿名使用客服,登录绑定用户之后,又退出登录其他用户,那么这个 session_id 和老的聊天记录直接归新用户所有
|
||||
$chatUser = ChatUser::where('auth', $auth)->where(function ($query) use ($session_id, $authUser) {
|
||||
$query->where('session_id', $session_id);
|
||||
// ->whereOr(function ($query) use ($authUser) {
|
||||
// $query->where('auth_id', '<>', 0)
|
||||
// ->where('auth_id', ($authUser ? $authUser['id'] : 0));
|
||||
// });
|
||||
})->find();
|
||||
|
||||
$defaultUser = Config::getConfigs('basic.user');
|
||||
$defaultAvatar = $defaultUser['avatar'] ?? null;
|
||||
// $defaultNickname = $defaultUser['nickname'] ?? null;
|
||||
|
||||
if (!$chatUser) {
|
||||
$chatUser = new ChatUser();
|
||||
|
||||
$chatUser->session_id = $session_id;
|
||||
$chatUser->auth = $auth;
|
||||
$chatUser->auth_id = $authUser ? $authUser['id'] : 0;
|
||||
$chatUser->nickname = $authUser ? $authUser['nickname'] : ('游客-' . substr($session_id, 0, 5));
|
||||
$chatUser->avatar = $authUser ? $authUser->getData('avatar') : $defaultAvatar;
|
||||
$chatUser->customer_service_id = 0; // 断开连接的时候存入
|
||||
$chatUser->last_time = time();
|
||||
} else {
|
||||
if ($authUser) {
|
||||
// 更新用户信息
|
||||
$chatUser->auth = $auth;
|
||||
$chatUser->auth_id = $authUser['id'] ?? 0;
|
||||
$chatUser->nickname = $authUser['nickname'] ? $authUser['nickname'] : ('游客-' . substr($session_id, 0, 5));
|
||||
$chatUser->avatar = $authUser['avatar'] ? $authUser->getData('avatar') : $defaultAvatar;
|
||||
}
|
||||
|
||||
$chatUser->last_time = time(); // 更新时间
|
||||
}
|
||||
|
||||
$chatUser->save();
|
||||
return $chatUser;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检测并且分配客服
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function checkAndAllocatCustomerService()
|
||||
{
|
||||
$room_id = $this->session('room_id');
|
||||
$session_id = $this->session('session_id');
|
||||
$auth = $this->session('auth');
|
||||
|
||||
$customerService = null;
|
||||
// 获取用户临时存的 客服
|
||||
$customer_service_id = $this->nspGetConnectionCustomerServiceId($room_id, $session_id);
|
||||
if ($customer_service_id) {
|
||||
$customerService = $this->getter('socket')->getCustomerServiceById($room_id, $customer_service_id);
|
||||
}
|
||||
|
||||
if (!$customerService) {
|
||||
// 获取当前顾客有没有其他端正在连接客服,如果有,直接获取该客服
|
||||
$customerService = $this->getter('socket')->getCustomerServiceBySessionId($room_id, $session_id);
|
||||
}
|
||||
|
||||
if (!$customerService) {
|
||||
$chatBasic = $this->getConfig('basic');
|
||||
if ($chatBasic['auto_customer_service']) {
|
||||
// 自动分配客服
|
||||
$chatUser = $this->session('chat_user');
|
||||
$customerService = $this->allocatCustomerService($room_id, $chatUser);
|
||||
}
|
||||
}
|
||||
|
||||
// 分配了客服
|
||||
if ($customerService) {
|
||||
// 将当前 用户与 客服绑定
|
||||
$this->bindCustomerServiceBySessionId($room_id, $session_id, $customerService);
|
||||
} else {
|
||||
// 加入等待组中
|
||||
$room = $this->getRoomName('customer_service_room_waiting', ['room_id' => $room_id]);
|
||||
$this->joinBySessionId($session_id, $auth, $room);
|
||||
|
||||
// 将用户 session_id 加入到等待排名中,自动排重
|
||||
$this->nspWaitingAdd($room_id, $session_id);
|
||||
}
|
||||
|
||||
return $customerService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 分配客服
|
||||
*
|
||||
* @param string $room_id, 客服房间号
|
||||
* @return array
|
||||
*/
|
||||
private function allocatCustomerService($room_id, $chatUser)
|
||||
{
|
||||
$config = $this->getConfig('basic');
|
||||
|
||||
$last_customer_service = $config['last_customer_service'] ?? 1;
|
||||
$allocate = $config['allocate'] ?? 'busy';
|
||||
|
||||
// 分配的客服
|
||||
$currentCustomerService = null;
|
||||
|
||||
// 使用上次客服
|
||||
if ($last_customer_service) {
|
||||
// 获取上次连接的信息
|
||||
$lastServiceLog = $this->getter('db')->getLastServiceLogByChatUser($room_id, $chatUser['id']);
|
||||
|
||||
if ($lastServiceLog) {
|
||||
// 获取上次客服信息
|
||||
$currentCustomerService = $this->getter('socket')->getCustomerServiceById($room_id, $lastServiceLog['customer_service_id']); // 通过连接房间,获取socket 连接里面上次客服,不在线为 null
|
||||
}
|
||||
}
|
||||
|
||||
// 没有客服,随机分配
|
||||
if (!$currentCustomerService) {
|
||||
// 在线客服列表
|
||||
$onlineCustomerServices = $this->getter('socket')->getCustomerServicesByRoomId($room_id);
|
||||
|
||||
if ($onlineCustomerServices) {
|
||||
if ($allocate == 'busy') {
|
||||
// 将客服列表,按照工作繁忙程度正序排序, 这里没有离线的客服
|
||||
$onlineCustomerServices = array_column($onlineCustomerServices, null, 'busy_percent');
|
||||
ksort($onlineCustomerServices);
|
||||
|
||||
// 取忙碌度最小,并且客服为 正常在线状态
|
||||
foreach ($onlineCustomerServices as $customerService) {
|
||||
if ($customerService['status'] == 'online') {
|
||||
$currentCustomerService = $customerService;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if ($allocate == 'turns') {
|
||||
// 按照最后接入时间正序排序,这里没有离线的客服
|
||||
$onlineCustomerServices = array_column($onlineCustomerServices, null, 'last_time');
|
||||
ksort($onlineCustomerServices);
|
||||
|
||||
// 取最后接待最早,并且客服为 正常在线状态
|
||||
foreach ($onlineCustomerServices as $customerService) {
|
||||
if ($customerService['status'] == 'online') {
|
||||
$currentCustomerService = $customerService;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if ($allocate == 'random') {
|
||||
// 随机获取一名客服
|
||||
|
||||
// 挑出来状态为在线的客服
|
||||
$onlineStatus = [];
|
||||
foreach ($onlineCustomerServices as $customerService) {
|
||||
if ($customerService['status'] == 'online') {
|
||||
$onlineStatus[] = $customerService;
|
||||
}
|
||||
}
|
||||
|
||||
$onlineStatus = array_column($onlineStatus, null, 'id');
|
||||
|
||||
$customer_service_id = 0;
|
||||
if ($onlineStatus) {
|
||||
$customer_service_id = array_rand($onlineStatus);
|
||||
}
|
||||
|
||||
$currentCustomerService = $onlineStatus[$customer_service_id] ?? null;
|
||||
}
|
||||
|
||||
if (!$currentCustomerService) {
|
||||
// 如果都不是 online 状态(说明全是 busy),默认取第一条
|
||||
$currentCustomerService = $onlineCustomerServices[0] ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $currentCustomerService;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过 session_id 记录客服信息,并且加入对应的客服组
|
||||
*
|
||||
* @param string $room_id 客服房间号
|
||||
* @param string $session_id 用户
|
||||
* @param array $customerService 客服信息
|
||||
* @return void
|
||||
*/
|
||||
public function bindCustomerServiceBySessionId($room_id, $session_id, $customerService)
|
||||
{
|
||||
// 当前用户 uid 绑定的所有客户端 clientIds
|
||||
$clientIds = $this->getClientIdByUId($session_id, 'customer');
|
||||
|
||||
// 将当前 session_id 绑定的 client_id 都加入当前客服组
|
||||
foreach ($clientIds as $client_id) {
|
||||
self::bindCustomerServiceByClientId($room_id, $client_id, $customerService);
|
||||
}
|
||||
|
||||
// 添加 serviceLog
|
||||
$chatUser = $this->getter('db')->getChatUserBySessionId($session_id);
|
||||
$this->getter('db')->createServiceLog($room_id, $chatUser, $customerService);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 顾客session 记录客服信息,并且加入对应的客服组
|
||||
*
|
||||
* @param string $room_id 客服房间号
|
||||
* @param string $client_id 用户
|
||||
* @param array $customerService 客服信息
|
||||
* @return null
|
||||
*/
|
||||
public function bindCustomerServiceByClientId($room_id, $client_id, $customerService)
|
||||
{
|
||||
// 更新用户的客服信息
|
||||
$this->updateSessionByClientId($client_id, [
|
||||
'customer_service_id' => $customerService['id'],
|
||||
'customer_service' => $customerService
|
||||
]);
|
||||
|
||||
// 更新客服的最后接入用户时间
|
||||
$this->getter()->updateCustomerServiceInfo($customerService['id'], ['last_time' => time()]);
|
||||
|
||||
// 加入对应客服组,统计客服信息,通知用户客服上线等
|
||||
$room = $this->getRoomName('customer_service_room_user', ['room_id' => $room_id, 'customer_service_id' => $customerService['id']]);
|
||||
$this->joinByClientId($client_id, $room);
|
||||
|
||||
// 从等待接入组移除
|
||||
$room = $this->getRoomName('customer_service_room_waiting', ['room_id' => $room_id]);
|
||||
$this->leaveByClientId($client_id, $room);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过 session_id 将用户移除客服组
|
||||
*
|
||||
* @param string $room_id 房间号
|
||||
* @param string $session_id 用户
|
||||
* @param array $customerService 客服信息
|
||||
* @return null
|
||||
*/
|
||||
public function unBindCustomerServiceBySessionId($room_id, $session_id, $customerService)
|
||||
{
|
||||
// 当前用户 uid 绑定的所有客户端 clientIds
|
||||
$clientIds = $this->getClientIdByUId($session_id, 'customer');
|
||||
|
||||
// 将当前 session_id 绑定的 client_id 都加入当前客服组
|
||||
foreach ($clientIds as $client_id) {
|
||||
$this->unBindCustomerService($room_id, $client_id, $customerService);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将用户的客服信息移除
|
||||
*
|
||||
* @param string $room_id 房间号
|
||||
* @param string $client_id 用户
|
||||
* @param array $customerService 客服信息
|
||||
* @return null
|
||||
*/
|
||||
public function unBindCustomerService($room_id, $client_id, $customerService)
|
||||
{
|
||||
// 清空连接的客服的 session
|
||||
$this->updateSessionByClientId($client_id, [
|
||||
'customer_service_id' => 0,
|
||||
'customer_service' => []
|
||||
]);
|
||||
|
||||
// 移除对应客服组
|
||||
$room = $this->getRoomName('customer_service_room_user', ['room_id' => $room_id, 'customer_service_id' => $customerService['id']]);
|
||||
$this->leaveByClientId($client_id, $room);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 客服转接,移除旧的客服,加入新的客服
|
||||
*
|
||||
* @param string $room_id 客服房间号
|
||||
* @param string $session_id 用户
|
||||
* @param array $customerService 客服信息
|
||||
* @param array $newCustomerService 新客服信息
|
||||
* @return void
|
||||
*/
|
||||
public function transferCustomerServiceBySessionId($room_id, $session_id, $customerService, $newCustomerService)
|
||||
{
|
||||
// 获取session_id 的 chatUser
|
||||
$chatUser = $this->getter('db')->getChatUserBySessionId($session_id);
|
||||
// 结束 serviceLog,如果没有,会创建一条记录
|
||||
$this->endService($room_id, $chatUser, $customerService);
|
||||
|
||||
// 解绑老客服
|
||||
$this->unBindCustomerServiceBySessionId($room_id, $session_id, $customerService);
|
||||
|
||||
// 接入新客服
|
||||
$this->bindCustomerServiceBySessionId($room_id, $session_id, $newCustomerService);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 结束服务
|
||||
*
|
||||
* @param string $room_id 客服房间号
|
||||
* @param object $chatUser 顾客信息
|
||||
* @param array $customerService 客服信息
|
||||
* @return void
|
||||
*/
|
||||
public function endService($room_id, $chatUser, $customerService)
|
||||
{
|
||||
// 结束掉服务记录
|
||||
$this->getter('db')->endServiceLog($room_id, $chatUser, $customerService);
|
||||
|
||||
// 记录客户最后服务的客服
|
||||
$chatUser->customer_service_id = $customerService['id'] ?? 0;
|
||||
$chatUser->save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 断开用户的服务
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param string $session_id
|
||||
* @param array $customerService
|
||||
* @return void
|
||||
*/
|
||||
public function breakCustomerServiceBySessionId($room_id, $session_id, $customerService)
|
||||
{
|
||||
// 获取session_id 的 chatUser
|
||||
$chatUser = $this->getter('db')->getChatUserBySessionId($session_id);
|
||||
if ($chatUser) {
|
||||
// 结束 serviceLog,如果没有,会创建一条记录
|
||||
$this->endService($room_id, $chatUser, $customerService);
|
||||
}
|
||||
|
||||
// 解绑老客服
|
||||
$this->unBindCustomerServiceBySessionId($room_id, $session_id, $customerService);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查当前用户的客服身份
|
||||
*
|
||||
* @param string $room_id 房间号
|
||||
* @param string $auth 用户类型
|
||||
* @param integer $auth_id 用户 id
|
||||
* @return boolean|object
|
||||
*/
|
||||
public function getCustomerServicesByAuth($auth_id, $auth)
|
||||
{
|
||||
$customerServices = $this->getter('db')->getCustomerServicesByAuth($auth_id, $auth);
|
||||
|
||||
return $customerServices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查当前用户在当前房间是否是客服身份
|
||||
*
|
||||
* @param string $room_id 房间号
|
||||
* @param string $auth 用户类型
|
||||
* @param integer $auth_id 用户 id
|
||||
* @return boolean|object
|
||||
*/
|
||||
public function checkIsCustomerService($room_id, $auth_id, $auth)
|
||||
{
|
||||
$currentCustomerService = $this->getter('db')->getCustomerServiceByAuthAndRoom($room_id, $auth_id, $auth);
|
||||
|
||||
return $currentCustomerService ? : false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 客服登录
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param integer $auth_id
|
||||
* @param string $auth
|
||||
* @return boolean
|
||||
*/
|
||||
public function customerServiceLogin($room_id, $auth_id, $auth) : bool
|
||||
{
|
||||
if ($customerService = $this->checkIsCustomerService($room_id, $auth_id, $auth)) {
|
||||
// 保存 客服信息
|
||||
$this->session('customer_service_id', $customerService['id']);
|
||||
$this->session('customer_service', $customerService->toArray()); // toArray 减少内存占用
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 客服上线
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param integer $customer_service_id
|
||||
* @return void
|
||||
*/
|
||||
public function customerServiceOnline($room_id, $customer_service_id)
|
||||
{
|
||||
// 当前 socket 绑定 客服 id
|
||||
$this->bindUId($customer_service_id, 'customer_service');
|
||||
|
||||
// 更新客服状态
|
||||
$this->getter()->updateCustomerServiceStatus($customer_service_id, 'online');
|
||||
|
||||
// 只把当前连接加入在线客服组,作为服务对象,多个连接同时登录一个客服,状态相互隔离
|
||||
$this->socket->join($this->getRoomName('identify', ['identify' => 'customer_service']));
|
||||
|
||||
// 只把当前连接加入当前频道的客服组,为后续多商户做准备
|
||||
$this->socket->join($this->getRoomName('customer_service_room', ['room_id' => $room_id]));
|
||||
|
||||
// 保存当前客服身份
|
||||
$this->session('identify', 'customer_service');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 客服离线
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param integer $customer_service_id
|
||||
* @return void
|
||||
*/
|
||||
public function customerServiceOffline($room_id, $customer_service_id)
|
||||
{
|
||||
// 只把当前连接移除在线客服组,多个连接同时登录一个客服,状态相互隔离
|
||||
$this->socket->leave($this->getRoomName('identify', ['identify' => 'customer_service']));
|
||||
|
||||
// 只把当前连接移除当前频道的客服组,为后续多商户做准备
|
||||
$this->socket->leave($this->getRoomName('customer_service_room', ['room_id' => $room_id]));
|
||||
|
||||
// 当前 socket 解绑 客服 id
|
||||
$this->unBindUId($customer_service_id, 'customer_service');
|
||||
|
||||
// 更新客服状态为离线
|
||||
$this->getter()->updateCustomerServiceStatus($customer_service_id, 'offline');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 客服忙碌
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param integer $customer_service_id
|
||||
* @return void
|
||||
*/
|
||||
public function customerServiceBusy($room_id, $customer_service_id)
|
||||
{
|
||||
// 当前 socket 绑定 客服 id
|
||||
$this->bindUId($customer_service_id, 'customer_service');
|
||||
|
||||
// (尝试重新加入,避免用户是从离线切换过来的)只把当前连接加入在线客服组,作为服务对象,多个连接同时登录一个客服,状态相互隔离
|
||||
$this->socket->join($this->getRoomName('identify', ['identify' => 'customer_service']));
|
||||
|
||||
// (尝试重新加入,避免用户是从离线切换过来的)只把当前连接加入当前频道的客服组,为后续多商户做准备
|
||||
$this->socket->join($this->getRoomName('customer_service_room', ['room_id' => $room_id]));
|
||||
|
||||
// 更新客服状态为忙碌
|
||||
$this->getter()->updateCustomerServiceStatus($customer_service_id, 'busy');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 客服退出
|
||||
*
|
||||
* @param [type] $room_id
|
||||
* @param [type] $customer_service_id
|
||||
* @return void
|
||||
*/
|
||||
public function customerServiceLogout($room_id, $customer_service_id)
|
||||
{
|
||||
// 退出房间
|
||||
$this->session('room_id', null);
|
||||
|
||||
// 移除当前客服身份
|
||||
$this->session('identify', null);
|
||||
|
||||
// 移除客服信息
|
||||
$this->session('customer_service_id', null);
|
||||
$this->session('customer_service', null); // toArray 减少内存占用
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 顾客上线
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function customerOnline()
|
||||
{
|
||||
// 用户信息
|
||||
$session_id = $this->session('session_id');
|
||||
$auth = $this->session('auth');
|
||||
|
||||
// 当前 socket 绑定 顾客 id
|
||||
$this->bindUId($session_id, 'customer');
|
||||
// 加入在线顾客组,作为被服务对象
|
||||
$this->socket->join($this->getRoomName('identify', ['identify' => 'customer']));
|
||||
// 保存当前顾客身份
|
||||
$this->session('identify', 'customer');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 顾客下线
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function customerOffline()
|
||||
{
|
||||
// 用户信息
|
||||
$session_id = $this->session('session_id');
|
||||
$customer_service_id = $this->session('customer_service_id');
|
||||
$room_id = $this->session('room_id');
|
||||
$customerService = $this->session('customer_service');
|
||||
$chatUser = $this->session('chat_user');
|
||||
|
||||
// 退出房间
|
||||
$this->session('room_id', null);
|
||||
|
||||
// 当前 socket 绑定 顾客 id
|
||||
$this->unbindUId($session_id, 'customer');
|
||||
|
||||
// 离开在线顾客组,作为被服务对象
|
||||
$this->socket->leave($this->getRoomName('identify', ['identify' => 'customer']));
|
||||
// 移除当前顾客身份
|
||||
$this->session('identify', null);
|
||||
|
||||
// 如果有客服正在服务,移除
|
||||
if ($customer_service_id) {
|
||||
// 离开所在客服的服务对象组
|
||||
$this->socket->leave($this->getRoomName('customer_service_room_user', ['room_id' => $room_id, 'customer_service_id' => $customer_service_id]));
|
||||
|
||||
// 移除客服信息
|
||||
$this->session('customer_service_id', null);
|
||||
$this->session('customer_service', null);
|
||||
|
||||
// 获取session_id 的 chatUser
|
||||
$chatUser = $this->getter('db')->getChatUserBySessionId($session_id);
|
||||
|
||||
if ($this->getter('socket')->isOnLineCustomerById($session_id)) {
|
||||
// 结束 serviceLog,如果没有,会创建一条记录
|
||||
$this->endService($room_id, $chatUser, $customerService);
|
||||
}
|
||||
} else {
|
||||
// 离开等待组
|
||||
$this->socket->leave($this->getRoomName('customer_service_room_waiting', ['room_id' => $room_id]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 断开连接解绑
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disconnectUnbindAll()
|
||||
{
|
||||
// 因为 session 是存在 socket 上的,服务端重启断开连接,或者刷新浏览器 socket 会重新连接,所以存的 session 会全部清空
|
||||
// 服务端重启 会导致 bind 到 io 实例的 bind 的数据丢失,但是如果是前端用户刷新浏览器,discount 事件中就必须要解绑 bind 数据
|
||||
|
||||
$room_id = $this->session('room_id');
|
||||
$session_id = $this->session('session_id');
|
||||
$auth = $this->session('auth');
|
||||
$identify = $this->session('identify') ? : '';
|
||||
$customerService = $this->session('customer_service');
|
||||
|
||||
// 解绑顾客身份
|
||||
if ($identify == 'customer') {
|
||||
$this->unbindUId($session_id, 'customer');
|
||||
|
||||
if ($customerService) {
|
||||
// 连接着客服,将客服信息暂存 nsp 中,防止刷新重新连接
|
||||
$this->nspConnectionAdd($room_id, $session_id, $customerService['id']);
|
||||
|
||||
// 如果有客服,定时判断,如果客服掉线了,关闭
|
||||
Timer::add(10, function () use ($room_id, $session_id, $customerService) {
|
||||
// 十秒之后顾客不在线,说明是真的下线了
|
||||
if (!$this->getter('socket')->isOnLineCustomerById($session_id)) {
|
||||
$chatUser = $this->getter('db')->getChatUserBySessionId($session_id);
|
||||
$this->endService($room_id, $chatUser, $customerService);
|
||||
}
|
||||
}, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
// 解绑客服身份
|
||||
if ($identify == 'customer_service') {
|
||||
$customer_service_id = $this->session('customer_service_id');
|
||||
$this->unbindUId($customer_service_id, 'customer_service');
|
||||
}
|
||||
|
||||
// 将当前的 用户与 client 解绑
|
||||
$this->unbindUId($session_id, $auth);
|
||||
}
|
||||
}
|
||||
301
addons/shopro/library/chat/Getter.php
Normal file
301
addons/shopro/library/chat/Getter.php
Normal file
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\chat;
|
||||
|
||||
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\NspData;
|
||||
use addons\shopro\library\chat\provider\getter\Db;
|
||||
use addons\shopro\library\chat\provider\getter\Socket;
|
||||
use PHPSocketIO\SocketIO;
|
||||
use PHPSocketIO\Socket as PhpSocket;
|
||||
use PHPSocketIO\Nsp;
|
||||
|
||||
/**
|
||||
* 客服 getter 获取各种用户类
|
||||
*/
|
||||
class Getter
|
||||
{
|
||||
|
||||
/**
|
||||
* session 存储助手
|
||||
*/
|
||||
use Session;
|
||||
/**
|
||||
* helper 助手
|
||||
*/
|
||||
use Helper;
|
||||
/**
|
||||
* 在 nsp 上存储数据
|
||||
*/
|
||||
use NspData;
|
||||
|
||||
/**
|
||||
* 当前 phpsocket.io 实例
|
||||
*
|
||||
* @var SocketIo
|
||||
*/
|
||||
protected $io = null;
|
||||
/**
|
||||
* 当前socket 连接
|
||||
*
|
||||
* @var PhpSocket
|
||||
*/
|
||||
protected $socket = null;
|
||||
/**
|
||||
* 当前 命名空间实例
|
||||
*
|
||||
* @var Nsp
|
||||
*/
|
||||
protected $nsp = null;
|
||||
|
||||
/**
|
||||
* 数据驱动
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $driver = null;
|
||||
|
||||
protected $dbProvider;
|
||||
protected $socketProvider;
|
||||
|
||||
public function __construct(PhpSocket $socket = null, SocketIo $io, Nsp $nsp)
|
||||
{
|
||||
$this->socket = $socket;
|
||||
$this->io = $io;
|
||||
$this->nsp = $nsp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置 getter 驱动
|
||||
*
|
||||
* @param string $driver
|
||||
* @return self
|
||||
*/
|
||||
public function driver($driver) {
|
||||
$this->driver = $driver;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过auth id 获取 session_id
|
||||
*
|
||||
* @param string $auth_id
|
||||
* @param string $auth
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSessionIdByAuth($auth_id, $auth)
|
||||
{
|
||||
$chatUser = $this->driver('db')->getChatUserByAuth($auth_id, $auth);
|
||||
return $chatUser->session_id ?? null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 更新客服信息,数据库和session 都更新
|
||||
*
|
||||
* @param integer $id 要更新的 客服id
|
||||
* @param array $data 要更新的数据
|
||||
* @return void
|
||||
*/
|
||||
public function updateCustomerServiceInfo($id, $data)
|
||||
{
|
||||
// 更新当前客服的所有连接,比如 last_time
|
||||
$this->driver('socket')->updateSessionsById($id, 'customer_service', ['customer_service' => $data]);
|
||||
$this->driver('db')->updateCustomerService($id, $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新客服信息,数据库和session 都更新
|
||||
*
|
||||
* @param integer $id 要更新的 客服id
|
||||
* @param string $status 状态 online|offline|busy
|
||||
* @return void
|
||||
*/
|
||||
public function updateCustomerServiceStatus($id, $status)
|
||||
{
|
||||
$data = ['status' => $status];
|
||||
// 只更新当前连接,别的当前客服的连接不更新,比如 status
|
||||
$this->updateSessionByClientId($this->socket->id, ['customer_service' => $data]);
|
||||
|
||||
// 不是 (改为离线,并且还有当前客服的其他链接在线),则修改数据库状态
|
||||
if (!($status == 'offline' && $this->driver('socket')->isOnLineCustomerServiceById($id))) {
|
||||
$this->driver('db')->updateCustomerService($id, $data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 更新客服忙碌杜
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateCustomerServiceBusyPercent()
|
||||
{
|
||||
// 所有客服的房间
|
||||
$roomIds = $this->nspData('room_ids');
|
||||
$roomIds = $roomIds ? : [];
|
||||
|
||||
foreach ($roomIds as $room_id) {
|
||||
// 当前房间在线客服组的所有客户端
|
||||
$customer_service_room = $this->getRoomName('customer_service_room', ['room_id' => $room_id]);
|
||||
$clientIds = $this->driver('socket')->getClientIdsByRoom($customer_service_room);
|
||||
|
||||
foreach ($clientIds as $client_id) {
|
||||
$customerService = $this->getSession($client_id, 'customer_service');
|
||||
|
||||
if ($customerService) {
|
||||
// 客服服务的对应用户的房间名
|
||||
$customer_service_room_user = $this->getRoomName('customer_service_room_user', ['room_id' => $room_id, 'customer_service_id' => $customerService['id']]);
|
||||
$customerService['current_num'] = count($this->driver('socket')->getSessionsByRoom($customer_service_room_user, 'session_id')); // 真实用户数量,不是客户端数量
|
||||
$max_num = $customerService['max_num'] > 0 ? $customerService['max_num'] : 1; // 避免除数为 0
|
||||
$customerService['busy_percent'] = $customerService['current_num'] / $max_num;
|
||||
|
||||
// 只更新当前连接,别的当前客服的连接不更新,比如 status
|
||||
$this->updateSessionByClientId($client_id, ['customer_service' => $customerService]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取客服服务中的用户
|
||||
*
|
||||
* @param integer $room_id 房间号
|
||||
* @param integer $customer_service_id 客服 id
|
||||
* @param array $exceptIds 要排除的ids (正在服务的用户)
|
||||
* @return array
|
||||
*/
|
||||
public function getCustomersIngFormatByCustomerService($room_id, $customer_service_id)
|
||||
{
|
||||
// 获取数据库历史
|
||||
$ings = $this->driver('socket')->getCustomersIngByCustomerService($room_id, $customer_service_id);
|
||||
|
||||
// 格式化数据
|
||||
$ings = $this->chatUsersFormat($room_id, $ings);
|
||||
|
||||
return $ings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取等待中的用户
|
||||
*
|
||||
* @param integer $room_id 房间号
|
||||
* @param integer $customer_service_id 客服 id
|
||||
* @param array $exceptIds 要排除的ids (正在服务的用户)
|
||||
* @return array
|
||||
*/
|
||||
public function getCustomersFormatWaiting($room_id)
|
||||
{
|
||||
// 获取数据库历史
|
||||
$waitings = $this->driver('socket')->getCustomersWaiting($room_id);
|
||||
|
||||
// 格式化数据
|
||||
$waitings = $this->chatUsersFormat($room_id, $waitings);
|
||||
|
||||
return $waitings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取客服服务的历史用户
|
||||
*
|
||||
* @param integer $room_id 房间号
|
||||
* @param integer $customer_service_id 客服 id
|
||||
* @param array $exceptIds 要排除的ids (正在服务的用户)
|
||||
* @return array
|
||||
*/
|
||||
public function getCustomersHistoryFormatByCustomerService($room_id, $customer_service_id, $exceptIds = [])
|
||||
{
|
||||
// 获取数据库历史
|
||||
$histories = $this->driver('db')->getCustomersHistoryByCustomerService($room_id, $customer_service_id, $exceptIds = []);
|
||||
|
||||
// 格式化数据
|
||||
$histories = $this->chatUsersFormat($room_id, $histories, ['select_identify' => 'customer', 'is_customer_service' => true]);
|
||||
|
||||
return $histories;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 设置最后一条消息和未读消息数
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param array $chatUsers
|
||||
* @param string $select_identify 查询对象,默认客服查用户的,用户查客服的
|
||||
* @return array
|
||||
*/
|
||||
public function chatUsersFormat($room_id, $chatUsers, $params = [])
|
||||
{
|
||||
$select_identify = $params['select_identify'] ?? 'customer';
|
||||
$is_customer_service = $params['is_customer_service'] ?? false;
|
||||
$first = $params['first'] ?? false;
|
||||
|
||||
foreach ($chatUsers as &$chatUser) {
|
||||
// 获取在线状态
|
||||
$status = $this->driver('socket')->isUIdOnline($chatUser['session_id'], 'customer');
|
||||
$chatUser['status'] = $status; // 在线状态
|
||||
|
||||
// 最后一条消息,未读消息条数
|
||||
$chatUser['last_message'] = $this->driver('db')->getMessageLastByChatUser($room_id, $chatUser['id']);
|
||||
$chatUser['unread_num'] = $this->driver('db')->getMessageUnReadNumByChatUserAndIndentify($room_id, $chatUser['id'], $select_identify);
|
||||
|
||||
// 当前的客服
|
||||
$chatUser['customer_service'] = null;
|
||||
if ($is_customer_service) { // 需要客户的客服信息,【历史用户中】
|
||||
$chatUser['customer_service'] = $this->driver('socket')->getCustomerServiceByCustomerSessionId($chatUser['session_id']); // 如果在线,并且已经接入客服,当前客服信息
|
||||
}
|
||||
}
|
||||
|
||||
return $first ? current($chatUsers) : $chatUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* auth 实例方法注入
|
||||
*
|
||||
* @return Db|Socket
|
||||
*/
|
||||
public function provider()
|
||||
{
|
||||
$classProt = $this->driver . 'Provider';
|
||||
|
||||
if ($this->$classProt) {
|
||||
return $this->$classProt;
|
||||
}
|
||||
|
||||
$class = "\\addons\\shopro\\library\\chat\\provider\\getter\\" . ucfirst($this->driver);
|
||||
|
||||
if (class_exists($class)) {
|
||||
$this->$classProt = new $class($this, $this->socket, $this->io, $this->nsp);
|
||||
|
||||
return $this->$classProt;
|
||||
}
|
||||
|
||||
throw new ShoproException('getter 驱动不支持');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 静态调用
|
||||
*
|
||||
* @param string $funcname
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($funcname, $arguments)
|
||||
{
|
||||
return $this->provider()->{$funcname}(...$arguments);
|
||||
}
|
||||
}
|
||||
259
addons/shopro/library/chat/Sender.php
Normal file
259
addons/shopro/library/chat/Sender.php
Normal file
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\chat;
|
||||
|
||||
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\traits\sender\SenderFunc;
|
||||
use PHPSocketIO\SocketIO;
|
||||
use PHPSocketIO\Socket;
|
||||
use PHPSocketIO\Nsp;
|
||||
|
||||
/**
|
||||
* 客服 Sender 发送类
|
||||
*/
|
||||
class Sender
|
||||
{
|
||||
|
||||
/**
|
||||
* 绑定 UID 助手
|
||||
*/
|
||||
use BindUId;
|
||||
|
||||
/**
|
||||
* 特定的发送方法
|
||||
*/
|
||||
use SenderFunc;
|
||||
/**
|
||||
* session
|
||||
*/
|
||||
use Session;
|
||||
/**
|
||||
* 帮助方法
|
||||
*/
|
||||
use Helper;
|
||||
|
||||
/**
|
||||
* 当前 socket 实例
|
||||
*
|
||||
* @var Socket
|
||||
*/
|
||||
protected $socket = null;
|
||||
/**
|
||||
* 当前 phpsocket.io 实例
|
||||
*
|
||||
* @var SocketIO
|
||||
*/
|
||||
protected $io = null;
|
||||
|
||||
/**
|
||||
* 当前 phpsocket.io 的 nsp 实例
|
||||
*
|
||||
* @var Nsp
|
||||
*/
|
||||
protected $nsp = null;
|
||||
/**
|
||||
* 当前 获取 实例
|
||||
*
|
||||
* @var Getter
|
||||
*/
|
||||
protected $getter = null;
|
||||
|
||||
|
||||
public function __construct(Socket $socket = null, SocketIo $io, Nsp $nsp, Getter $getter = null)
|
||||
{
|
||||
$this->socket = $socket;
|
||||
$this->io = $io;
|
||||
$this->nsp = $nsp;
|
||||
$this->getter = $getter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 返回成功方法
|
||||
*
|
||||
* @param string $event
|
||||
* @param string $msg
|
||||
* @param array $data
|
||||
* @param Socket|Nsp $data
|
||||
* @return void
|
||||
*/
|
||||
public function success($event, $msg = '', $data = null, $sender = null)
|
||||
{
|
||||
$result = [
|
||||
'code' => 1,
|
||||
'msg' => $msg,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
if ($event instanceof \Closure) {
|
||||
$event($result);
|
||||
} else {
|
||||
$sender && $sender->emit($event, $result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回成功方法
|
||||
*
|
||||
* @param string $event
|
||||
* @param string $msg
|
||||
* @param Socket|Nsp $data
|
||||
* @return void
|
||||
*/
|
||||
public function error($event, $msg = '', $data = null, $sender = null)
|
||||
{
|
||||
$result = [
|
||||
'code' => 0,
|
||||
'msg' => $msg,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
if ($event instanceof \Closure) {
|
||||
$event($result);
|
||||
} else {
|
||||
$sender && $sender->emit($event, $result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 成功发给当前渠道
|
||||
*
|
||||
* @param string $event
|
||||
* @param string $msg
|
||||
* @param Socket|Nsp $data
|
||||
* @return void
|
||||
*/
|
||||
public function successSocket($event, $msg = '', $data = null)
|
||||
{
|
||||
$this->success($event, $msg, $data, $this->socket);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 失败发给当前渠道
|
||||
*
|
||||
* @param string $event
|
||||
* @param string $msg
|
||||
* @param Socket|Nsp $data
|
||||
* @return void
|
||||
*/
|
||||
public function errorSocket($event, $msg = '', $data = null)
|
||||
{
|
||||
$this->error($event, $msg, $data, $this->socket);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过 clients 发送成功事件
|
||||
*
|
||||
* @param string $event
|
||||
* @param string $msg
|
||||
* @param array $data
|
||||
* @param array $clientIds
|
||||
* @return void
|
||||
*/
|
||||
public function successClients($event, $msg = '', $data = null, $clientIds = [])
|
||||
{
|
||||
$clientIds = $clientIds ? (is_array($clientIds) ? $clientIds : [$clientIds]) : [];
|
||||
|
||||
foreach ($clientIds as $client_id) {
|
||||
if (isset($this->nsp->connected[$client_id])) {
|
||||
$sender = $this->nsp->connected[$client_id];
|
||||
$this->success($event, $msg, $data, $sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过 UId 发送成功事件
|
||||
*
|
||||
* @param string $event
|
||||
* @param string $msg
|
||||
* @param array $data
|
||||
* @param array $uIdData
|
||||
* @return void
|
||||
*/
|
||||
public function successUId($event, $msg = '', $data = null, $uIdData = [])
|
||||
{
|
||||
$uIdData = $uIdData ? $uIdData : [];
|
||||
|
||||
if ($uIdData) {
|
||||
$clientIds = $this->getClientIdByUId($uIdData['id'], $uIdData['type']);
|
||||
|
||||
$this->successClients($event, $msg, $data, $clientIds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过 UId 发送成功事件
|
||||
*
|
||||
* @param string $event
|
||||
* @param string $msg
|
||||
* @param array $data
|
||||
* @param string $room
|
||||
* @return void
|
||||
*/
|
||||
public function successRoom($event, $msg = '', $data = null, $room = '')
|
||||
{
|
||||
$room = $room ? $room : '';
|
||||
|
||||
if ($room) {
|
||||
$sender = $this->nsp->to($room);
|
||||
$this->success($event, $msg, $data, $sender);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转发, message 开头保存数据库
|
||||
*/
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
$currentName = $name;
|
||||
|
||||
// 需要存储数据库的消息,先存储数据库,再发送
|
||||
if (strpos($name, 'message') === 0) {
|
||||
$room_id = $this->session('room_id');
|
||||
// 存库
|
||||
$chatRecord = $this->getter('db')->addMessage($room_id, $name, $arguments);
|
||||
|
||||
// 将 message 追加到 content 里面
|
||||
$content = $arguments[2] ?? [];
|
||||
$content['message'] = $chatRecord->toArray();
|
||||
$arguments[2] = $content;
|
||||
|
||||
// 重载方法名
|
||||
$currentName = str_replace('message', 'success', $name);
|
||||
}
|
||||
|
||||
|
||||
$sender_status = true;
|
||||
switch($currentName) {
|
||||
case 'successUId':
|
||||
if (!isset($arguments[3]) || !isset($arguments[3]['id']) || !$arguments[3]['id']) {
|
||||
// 缺少参数 id
|
||||
$sender_status = false;
|
||||
}
|
||||
break;
|
||||
case 'successClients':
|
||||
if (!isset($arguments[3]) || !$arguments[3]) {
|
||||
// 缺少接收的 clientIds
|
||||
$sender_status = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($sender_status) {
|
||||
// 接收者正常
|
||||
return self::$currentName(...$arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
addons/shopro/library/chat/provider/auth/Admin.php
Normal file
130
addons/shopro/library/chat/provider/auth/Admin.php
Normal 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
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
121
addons/shopro/library/chat/provider/auth/User.php
Normal file
121
addons/shopro/library/chat/provider/auth/User.php
Normal 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();
|
||||
|
||||
|
||||
// 用户事件,待补充
|
||||
// ......
|
||||
}
|
||||
|
||||
}
|
||||
164
addons/shopro/library/chat/provider/auth/traits/Customer.php
Normal file
164
addons/shopro/library/chat/provider/auth/traits/Customer.php
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
641
addons/shopro/library/chat/provider/getter/Db.php
Normal file
641
addons/shopro/library/chat/provider/getter/Db.php
Normal 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;
|
||||
}
|
||||
}
|
||||
354
addons/shopro/library/chat/provider/getter/Socket.php
Normal file
354
addons/shopro/library/chat/provider/getter/Socket.php
Normal 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));
|
||||
}
|
||||
}
|
||||
117
addons/shopro/library/chat/traits/BindUId.php
Normal file
117
addons/shopro/library/chat/traits/BindUId.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\chat\traits;
|
||||
|
||||
/**
|
||||
* 绑定 uid
|
||||
*/
|
||||
trait BindUId
|
||||
{
|
||||
|
||||
/**
|
||||
* 绑定类型
|
||||
*/
|
||||
protected $bindType = [
|
||||
'user', // 用户
|
||||
'admin', // 管理员
|
||||
'customer', // 顾客 (用户或者管理员的身份提升)
|
||||
'customer_service' // 客服 (用户或者管理员的身份提升)
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 根据类型获取 uid
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $type 目前有三种, user,admin,customer_service
|
||||
* @return string|array
|
||||
*/
|
||||
public function getUId($id, $type)
|
||||
{
|
||||
$ids = is_array($id) ? $id : [$id];
|
||||
foreach ($ids as &$i) {
|
||||
$i = $type . '-' . $i;
|
||||
}
|
||||
|
||||
return is_array($id) ? $ids : $ids[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 绑定 uid
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public function bindUId($id, $type)
|
||||
{
|
||||
$uId = $this->getUId($id, $type);
|
||||
|
||||
// 当前客户端标示
|
||||
$client_id = $this->socket->id;
|
||||
|
||||
$this->nsp->bind[$uId][$client_id] = $client_id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过 uid 获取当前 uid 绑定的所有clientid
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function getClientIdByUId($id, $type) {
|
||||
$uId = $this->getUId($id, $type);
|
||||
|
||||
return $this->nsp->bind[$uId] ?? [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解绑 uid,将当前客户端与当前 uid 解绑,如果 uid 下没有客户端了,则将该 uid 删除
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function unbindUId($id, $type)
|
||||
{
|
||||
$uId = $this->getUId($id, $type);
|
||||
|
||||
// 当前客户端标示
|
||||
$client_id = $this->socket->id;
|
||||
|
||||
if (isset($this->nsp->bind[$uId][$client_id])) {
|
||||
unset($this->nsp->bind[$uId][$client_id]);
|
||||
|
||||
if (!$this->nsp->bind[$uId]) {
|
||||
unset($this->nsp->bind[$uId]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* UId 是否在线
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $type
|
||||
* @return boolean
|
||||
*/
|
||||
public function isUIdOnline($id, $type)
|
||||
{
|
||||
$uId = $this->getUId($id, $type);
|
||||
|
||||
if (isset($this->nsp->bind[$uId]) && $this->nsp->bind[$uId]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
75
addons/shopro/library/chat/traits/DebugEvent.php
Normal file
75
addons/shopro/library/chat/traits/DebugEvent.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\chat\traits;
|
||||
|
||||
use addons\shopro\exception\ShoproException;
|
||||
|
||||
/**
|
||||
* debug 方式注册事件
|
||||
*/
|
||||
trait DebugEvent
|
||||
{
|
||||
|
||||
/**
|
||||
* 注册事件
|
||||
*/
|
||||
public function register($event, \Closure $cb)
|
||||
{
|
||||
$this->socket->on($event, function ($data, $callback = null) use ($cb) {
|
||||
try {
|
||||
$cb($data, $callback);
|
||||
} catch (\Exception $e) {
|
||||
$this->errorHandler($e, 'register');
|
||||
|
||||
// 将错误报告给前端
|
||||
$this->sender->errorSocket('custom_error', ($this->io->debug || $e instanceof ShoproException) ? $e->getMessage() : 'socket 服务器异常');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 执行代码
|
||||
*
|
||||
* @param object $httpConnection 当前 socket 连接
|
||||
* @param \Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public function exec($httpConnection, \Closure $callback)
|
||||
{
|
||||
try {
|
||||
$callback();
|
||||
} catch (\Exception $e) {
|
||||
$this->errorHandler($e, 'exec');
|
||||
|
||||
// 将错误报告给前端
|
||||
$httpConnection->send(($this->io->debug || $e instanceof ShoproException) ? $e->getMessage() : 'socket 服务器异常');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 判断处理异常
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
private function errorHandler(\Exception $e, $type)
|
||||
{
|
||||
$error = [
|
||||
'line' => $e->getLine(),
|
||||
'file' => $e->getFile(),
|
||||
'error' => $e->getMessage()
|
||||
// 'trace' => $e->getTrace(),
|
||||
];
|
||||
|
||||
if ($this->io->debug) {
|
||||
echo 'websocket:' . $type . ':执行失败,错误信息:' . json_encode($error, JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
format_log_error($e, 'WebSocket', 'WebSocket 执行失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
94
addons/shopro/library/chat/traits/Helper.php
Normal file
94
addons/shopro/library/chat/traits/Helper.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\chat\traits;
|
||||
|
||||
use addons\shopro\exception\ShoproException;
|
||||
use addons\shopro\library\chat\Getter;
|
||||
|
||||
/**
|
||||
* 助手方法,需要全局地方可用
|
||||
*/
|
||||
trait Helper
|
||||
{
|
||||
|
||||
/**
|
||||
* 客服配置
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $chatConfig;
|
||||
|
||||
|
||||
/**
|
||||
* 获取客服配置
|
||||
*
|
||||
* @param string $name 要获取的配置项,不能获取第二级的配置项
|
||||
* @return void
|
||||
*/
|
||||
public function getConfig($name = null)
|
||||
{
|
||||
// 初始化 workerman 的时候不能读取数据库,会导致数据库连接异常
|
||||
if (!$this->chatConfig) {
|
||||
$config_path = ROOT_PATH . 'application' . DS . 'extra' . DS . 'chat.php';
|
||||
if (!file_exists($config_path)) {
|
||||
throw new ShoproException('客服配置文件不存在,请在后台->商城配置->客服配置,修改并保存客服配置');
|
||||
}
|
||||
|
||||
$this->chatConfig = require($config_path);
|
||||
}
|
||||
|
||||
return $name ? ($this->chatConfig[$name] ?? null) : $this->chatConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据类型获取房间完整名字,主要为了记录当前系统总共有多少房间
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function getRoomName($type, $data = [])
|
||||
{
|
||||
switch ($type) {
|
||||
case 'online': // 当前在线客户端,包含所有连接着
|
||||
$group_name = 'online';
|
||||
break;
|
||||
case 'auth': // 当前用户认证分组,包含 $this->auth 的所有身份分组
|
||||
$group_name = 'auth:' . $data['auth'];
|
||||
break;
|
||||
case 'identify': // 当前身份分组,customer 顾客,customer_service 客服
|
||||
$group_name = 'identify:' . $data['identify'];
|
||||
break;
|
||||
case 'customer_service_room': // 当前在线客服数组, 这里的客服的状态都是 在线的,如果手动切换为离线,则会被移除该房间
|
||||
$group_name = 'customer_service_room:' . ($data['room_id'] ?? 'admin');
|
||||
break;
|
||||
case 'customer_service_room_waiting': // 当前在线用户所在的客服分组的等待房间种
|
||||
$group_name = 'customer_service_room_waiting:' . ($data['room_id'] ?? 'admin');
|
||||
break;
|
||||
case 'customer_service_room_user': // 当前在线用户所在的客服分组
|
||||
$group_name = 'customer_service_room_user:' . ($data['room_id'] ?? 'admin') . ':' . ($data['customer_service_id'] ?? 0);
|
||||
break;
|
||||
default:
|
||||
$group_name = $type;
|
||||
break;
|
||||
}
|
||||
|
||||
return $group_name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 getter 实例,类中有 getter 属性才可以用
|
||||
*
|
||||
* @param string $driver
|
||||
* @return Getter
|
||||
*/
|
||||
protected function getter($driver = null)
|
||||
{
|
||||
if ($driver) {
|
||||
return $this->getter->driver($driver);
|
||||
}
|
||||
return $this->getter;
|
||||
}
|
||||
}
|
||||
302
addons/shopro/library/chat/traits/NspData.php
Normal file
302
addons/shopro/library/chat/traits/NspData.php
Normal file
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\chat\traits;
|
||||
|
||||
/**
|
||||
* 在 nsp 连接实例绑定数据
|
||||
*/
|
||||
trait NspData
|
||||
{
|
||||
// data 的格式
|
||||
// 'data' => [
|
||||
// 'room_ids' => ['admin'] // 客服房间数组,目前只有 admin
|
||||
// 'session_ids' => ['user' => [1,2,3], 'admin' => [1,2,3]] // 登录的身份的 ids
|
||||
// 'connections' => [
|
||||
// room_id => [
|
||||
// 'session_id' => customer_service_id
|
||||
// ]
|
||||
// ],
|
||||
// 'waitings' => [
|
||||
// 'room_id' => [排队的用户]
|
||||
// ]
|
||||
// ]
|
||||
|
||||
|
||||
/**
|
||||
* 获取存储的数据,当前 Nsp
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function nspData($name = null, $value = '')
|
||||
{
|
||||
$data = $this->nsp->nspData ?? [];
|
||||
|
||||
if (is_null($name)) {
|
||||
// 获取全部数据
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ('' === $value) {
|
||||
// 获取缓存
|
||||
return 0 === strpos($name, '?') ? isset($data[$name]) : ($data[$name] ?? null);
|
||||
} elseif (is_null($value)) {
|
||||
// 删除缓存
|
||||
unset($data[$name]);
|
||||
$this->nsp->nspData = $data;
|
||||
}
|
||||
|
||||
// 存数据
|
||||
$data[$name] = $value;
|
||||
$this->nsp->nspData = $data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取等待中的排队,如果有房间,返回当前房间的
|
||||
*
|
||||
* @param string $room_id
|
||||
* @return array
|
||||
*/
|
||||
// public function nspGetSessionIds($auth)
|
||||
// {
|
||||
// $sessionIds = $this->nspData('session_ids');
|
||||
// $sessionIds = $sessionIds ?: [];
|
||||
|
||||
// return $auth ? $sessionIds[$auth] ?? [] : $sessionIds;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 保存当前在线的 auth id
|
||||
*
|
||||
* @param integer $session_id
|
||||
* @return void
|
||||
*/
|
||||
// public function nspSessionIdAdd($session_id, $auth)
|
||||
// {
|
||||
// $sessionIds = $this->nspData('session_ids');
|
||||
// $sessionIds = $sessionIds ?: [];
|
||||
|
||||
// // 当前 auth 的 ids
|
||||
// $currentsessionIds = $sessionIds[$auth] ?? [];
|
||||
|
||||
// // 已经存在直接忽略
|
||||
// if (!in_array($session_id, $currentsessionIds)) {
|
||||
// // 追加auth
|
||||
// $currentsessionIds[] = $session_id;
|
||||
|
||||
// $sessionIds[$auth] = $currentsessionIds;
|
||||
// $this->nspData('session_ids', $sessionIds);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 删除一个 auth id (离线了)
|
||||
*
|
||||
* @param integer $session_id
|
||||
* @return void
|
||||
*/
|
||||
// public function nspSessionIdDel($session_id, $auth)
|
||||
// {
|
||||
// $sessionIds = $this->nspData('session_ids');
|
||||
// $sessionIds = $sessionIds ?: [];
|
||||
|
||||
// // 当前 auth 的 ids
|
||||
// $currentsessionIds = $sessionIds[$auth] ?? [];
|
||||
|
||||
// $key = array_search($session_id, $currentsessionIds);
|
||||
// if ($key !== false) {
|
||||
// // 移除
|
||||
// unset($currentsessionIds[$key]);
|
||||
|
||||
// // 重新赋值
|
||||
// $sessionIds[$auth] = array_values($currentsessionIds);
|
||||
|
||||
// $this->nspData('session_ids', $sessionIds);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取顾客的连接客服(这个信息只会存 10s,防止用户刷新,重新分配客服问题)
|
||||
*
|
||||
* @param string $room_id
|
||||
* @return array
|
||||
*/
|
||||
public function nspGetConnectionCustomerServiceId($room_id, $session_id)
|
||||
{
|
||||
$connections = $this->nspData('connections');
|
||||
$connections = $connections ?: [];
|
||||
|
||||
// 当前房间的 waitings
|
||||
$roomConnections = $connections[$room_id] ?? [];
|
||||
|
||||
$data_str = $roomConnections[$session_id] ?? '';
|
||||
if ($data_str) {
|
||||
// 删除 connections
|
||||
unset($roomConnections[$session_id]);
|
||||
$connections[$room_id] = $roomConnections;
|
||||
$this->nspData('connections', $connections);
|
||||
|
||||
// 获取 客服 id
|
||||
$data = explode('-', $data_str);
|
||||
if ($data[0] >= (time() - 300)) {
|
||||
return $data[1] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 记录用户的服务客服
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param string $session_id
|
||||
* @param string $customer_service_id
|
||||
* @return void
|
||||
*/
|
||||
public function nspConnectionAdd($room_id, $session_id, $customer_service_id)
|
||||
{
|
||||
// 所有 waitings
|
||||
$connections = $this->nspData('connections');
|
||||
$connections = $connections ?: [];
|
||||
|
||||
// 当前房间的 waitings
|
||||
$roomConnections = $connections[$room_id] ?? [];
|
||||
|
||||
if (!in_array($session_id, $roomConnections)) {
|
||||
// 将 session_id 和对应的 客服 id 关联
|
||||
$roomConnections[$session_id] = time() . '-' . $customer_service_id;
|
||||
// 重新赋值
|
||||
$connections[$room_id] = $roomConnections;
|
||||
// 保存
|
||||
$this->nspData('connections', $connections);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 记录要创建的 客服 room 房间
|
||||
*
|
||||
* @param string $room_id
|
||||
* @return void
|
||||
*/
|
||||
public function nspRoomIdAdd($room_id)
|
||||
{
|
||||
$roomIds = $this->nspData('room_ids');
|
||||
$roomIds = $roomIds ?: [];
|
||||
|
||||
// 已经存在直接忽略
|
||||
if (!in_array($room_id, $roomIds)) {
|
||||
// 追加房间
|
||||
$roomIds[] = $room_id;
|
||||
$this->nspData('room_ids', $roomIds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取等待中的排队,如果有房间,返回当前房间的
|
||||
*
|
||||
* @param string $room_id
|
||||
* @return array
|
||||
*/
|
||||
public function nspGetWaitings($room_id = null)
|
||||
{
|
||||
$waitings = $this->nspData('waitings');
|
||||
$waitings = $waitings ?: [];
|
||||
|
||||
return $room_id ? $waitings[$room_id] ?? [] : $waitings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 给对应房间的排队中追加顾客,如果已经存在,忽略
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param string $session_id
|
||||
* @return void
|
||||
*/
|
||||
public function nspWaitingAdd($room_id, $session_id)
|
||||
{
|
||||
// 所有 waitings
|
||||
$waitings = $this->nspData('waitings');
|
||||
$waitings = $waitings ? : [];
|
||||
|
||||
// 当前房间的 waitings
|
||||
$roomWaitings = $waitings[$room_id] ?? [];
|
||||
|
||||
if (!in_array($session_id, $roomWaitings)) {
|
||||
// 将 session_id 加入 房间 waitings,如果存在,忽略
|
||||
$roomWaitings[] = $session_id;
|
||||
// 重新赋值
|
||||
$waitings[$room_id] = $roomWaitings;
|
||||
// 保存
|
||||
$this->nspData('waitings', $waitings);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 删除对应房间中排队的顾客,如果不存在忽略
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param string $session_id
|
||||
* @return void
|
||||
*/
|
||||
public function nspWaitingDel($room_id, $session_id)
|
||||
{
|
||||
// 所有 waitings
|
||||
$waitings = $this->nspData('waitings');
|
||||
$waitings = $waitings ?: [];
|
||||
|
||||
// 当前房间的 waitings
|
||||
$roomWaitings = $waitings[$room_id] ?? [];
|
||||
|
||||
$key = array_search($session_id, $roomWaitings);
|
||||
if ($key !== false) {
|
||||
// 移除
|
||||
unset($roomWaitings[$key]);
|
||||
|
||||
// 重新赋值
|
||||
$waitings[$room_id] = array_values($roomWaitings);
|
||||
|
||||
$this->nspData('waitings', $waitings);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取在房间中的排名
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param string $session_id
|
||||
* @return integer
|
||||
*/
|
||||
public function nspWaitingRank($room_id, $session_id)
|
||||
{
|
||||
// 所有 waitings
|
||||
$waitings = $this->nspData('waitings');
|
||||
$waitings = $waitings ?: [];
|
||||
|
||||
// 当前房间的 waitings
|
||||
$roomWaitings = $waitings[$room_id] ?? [];
|
||||
|
||||
// 获取 session_id 的下标,就是当前顾客前面还有多少位 顾客
|
||||
$key = array_search($session_id, $roomWaitings);
|
||||
|
||||
return $key !== false ? $key : 0;
|
||||
}
|
||||
}
|
||||
158
addons/shopro/library/chat/traits/Session.php
Normal file
158
addons/shopro/library/chat/traits/Session.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\chat\traits;
|
||||
|
||||
/**
|
||||
* 客服 session 工具类
|
||||
*/
|
||||
trait Session
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取存储的数据,当前 socket
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function session($name = null, $value = '')
|
||||
{
|
||||
$data = $this->socket->session ?? [];
|
||||
|
||||
if (is_null($name)) {
|
||||
// 获取全部数据
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ('' === $value) {
|
||||
// 获取缓存
|
||||
return 0 === strpos($name, '?') ? isset($data[$name]) : ($data[$name] ?? null);
|
||||
} elseif (is_null($value)) {
|
||||
// 删除缓存
|
||||
unset($data[$name]);
|
||||
$this->socket->session = $data;
|
||||
}
|
||||
|
||||
// 存数据
|
||||
$data[$name] = $value;
|
||||
$this->socket->session = $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过 client_id 获取指定 client_id 的 session 数据
|
||||
*
|
||||
* @param string $client_id 要获取 session 的 client_id
|
||||
* @param string $name 要获取 session 中的特定 name
|
||||
* @return string|array
|
||||
*/
|
||||
public function getSession($client_id, $name = null)
|
||||
{
|
||||
$client = $this->nsp->sockets[$client_id] ?? null;
|
||||
|
||||
$session = is_null($client) ? null : (isset($client->session) && $client->session ? $client->session : []);
|
||||
|
||||
return $name ? ($session[$name] ?? null) : $session;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 调用 getSession,别名
|
||||
*
|
||||
* @param string $client_id 要获取 session 的 client_id
|
||||
* @param string $name 要获取 session 中的特定 name
|
||||
* @return string|array
|
||||
*/
|
||||
public function getSessionByClientId($client_id, $name = null)
|
||||
{
|
||||
return $this->getSession($client_id, $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过 client_ids 获取指定 client_ids 的 session 数据集合
|
||||
*
|
||||
* @param string $client_id 要获取 session 的 client_id
|
||||
* @param string $name 要获取 session 中的特定 name
|
||||
* @return array
|
||||
*/
|
||||
public function getSessionByClientIds($clientIds, $name = null)
|
||||
{
|
||||
$customerServices = [];
|
||||
foreach ($clientIds as $client_id) {
|
||||
$currentCustomerService = $this->getSessionByClientId($client_id, $name);
|
||||
if ($currentCustomerService) {
|
||||
$customerServices[] = $currentCustomerService;
|
||||
}
|
||||
}
|
||||
|
||||
return $customerServices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过 client_id 更新指定 client_id 的 session 数据
|
||||
*
|
||||
* @param string $client_id 要获取 session 的 client_id
|
||||
* @param array $data 要更新 session 中的特定 name
|
||||
* @return boolean
|
||||
*/
|
||||
public function updateSession($client_id, $data = [])
|
||||
{
|
||||
$client = $this->nsp->sockets[$client_id] ?? null;
|
||||
|
||||
if (!$client) {
|
||||
// client 没有,可能下线了,直接返回
|
||||
return false;
|
||||
}
|
||||
|
||||
// session 不存在,初始化
|
||||
if (!isset($client->session)) {
|
||||
$this->nsp->sockets[$client_id]->session = [];
|
||||
}
|
||||
|
||||
// 更新对应的键
|
||||
foreach ($data as $key => $value) {
|
||||
$current = $this->nsp->sockets[$client_id]->session[$key] ?? null;
|
||||
if (is_array($value) && $current) {
|
||||
// 合并数组,比如修改 session 中 customer_service 客服信息 中的 status 在线状态(注意,如果value 为空,则覆盖,如果有值,则合并)
|
||||
$this->nsp->sockets[$client_id]->session[$key] = $value ? array_merge($current, $value) : $value;
|
||||
} else {
|
||||
$this->nsp->sockets[$client_id]->session[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 调用 updateSession,别名
|
||||
*
|
||||
* @param string $client_id 要获取 session 的 client_id
|
||||
* @param array $data 要更新 session 中的特定 name
|
||||
* @return boolean
|
||||
*/
|
||||
public function updateSessionByClientId($client_id, $data = [])
|
||||
{
|
||||
return $this->updateSession($client_id, $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过 client_id 更新指定 client_id 的 session 数据集合
|
||||
*
|
||||
* @param string $client_id 要获取 session 的 client_id
|
||||
* @param string $name 要获取 session 中的特定 name
|
||||
* @return array
|
||||
*/
|
||||
public function updateSessionByClientIds($clientIds, $data = [])
|
||||
{
|
||||
foreach ($clientIds as $client_id) {
|
||||
$this->updateSessionByClientId($client_id, $data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
444
addons/shopro/library/chat/traits/sender/SenderFunc.php
Normal file
444
addons/shopro/library/chat/traits/sender/SenderFunc.php
Normal file
@@ -0,0 +1,444 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\chat\traits\sender;
|
||||
|
||||
use addons\shopro\library\chat\traits\Helper;
|
||||
use addons\shopro\library\chat\traits\Session;
|
||||
use addons\shopro\library\chat\traits\NspData;
|
||||
|
||||
/**
|
||||
* 绑定 uid
|
||||
*/
|
||||
trait SenderFunc
|
||||
{
|
||||
use Session;
|
||||
|
||||
use Helper;
|
||||
|
||||
use NspData;
|
||||
|
||||
/**
|
||||
* 用户自己触发:发送给自己,登录成功
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public function authSuccess($callback)
|
||||
{
|
||||
// 通过回调发送给自己
|
||||
$this->successSocket($callback, '连接成功', [
|
||||
'session_id' => $this->session('session_id'),
|
||||
'chat_user' => $this->session('chat_user') ?: null,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 用户自己触发:通知所有房间中的客服,用户上线了
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function customerOnline()
|
||||
{
|
||||
$session_id = $this->session('session_id');
|
||||
$room_id = $this->session('room_id');
|
||||
$chatUser = $this->session('chat_user');
|
||||
$chatUser = $this->getter()->chatUsersFormat($room_id, [$chatUser], ['first' => true]);
|
||||
|
||||
$this->successRoom('customer_online', '顾客上线', [
|
||||
'session_id' => $session_id,
|
||||
'chat_user' => $chatUser,
|
||||
], $this->getRoomName('customer_service_room', ['room_id' => $room_id]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户自己触发:通知所有房间中的客服,用户下线了
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function customerOffline()
|
||||
{
|
||||
$session_id = $this->session('session_id');
|
||||
$room_id = $this->session('room_id');
|
||||
$chatUser = $this->session('chat_user');
|
||||
$chatUser = $this->getter()->chatUsersFormat($room_id, [$chatUser], ['first' => true]);
|
||||
|
||||
$this->successRoom('customer_offline', '顾客下线', [
|
||||
'session_id' => $session_id,
|
||||
'chat_user' => $chatUser,
|
||||
], $this->getRoomName('customer_service_room', ['room_id' => $room_id]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户自己触发:通知用户的客户端,排队中,通知客服,有新的等待中用户
|
||||
*
|
||||
* @param array $waiting 等待中的顾客
|
||||
* @return void
|
||||
*/
|
||||
public function waiting()
|
||||
{
|
||||
$session_id = $this->session('session_id');
|
||||
$room_id = $this->session('room_id');
|
||||
|
||||
// 通知客服更新 等待中列表,返回整个等待的列表
|
||||
$this->successRoom('customer_waiting', '顾客等待', [
|
||||
'waitings' => $this->getter()->getCustomersFormatWaiting($room_id),
|
||||
], $this->getRoomName('customer_service_room', ['room_id' => $room_id]));
|
||||
|
||||
// 通知用户前面排队人数
|
||||
$this->waitingQueue($room_id, $session_id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户自己触发,服务器定时触发:通知用户的客户端,当前有客服,但是需要排队 (传参是因为别的地方也需要调用)
|
||||
*
|
||||
* @param string $room_id
|
||||
* @param string $session_id
|
||||
* @return void
|
||||
*/
|
||||
public function waitingQueue($room_id, $session_id)
|
||||
{
|
||||
$rank = $this->nspWaitingRank($room_id, $session_id);
|
||||
|
||||
$title = $rank > 0 ? '当前还有 ' . $rank . ' 位顾客,请耐心等待' : '客服马上为您服务,请稍等';
|
||||
|
||||
// 通知用户等待接入
|
||||
$this->successUId('waiting_queue', '排队等待', [
|
||||
'title' => $title,
|
||||
], ['id' => $session_id, 'type' => 'customer']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 当有变动,批量通知排队等待排名
|
||||
*
|
||||
* @param string|null $room_id
|
||||
* @return void
|
||||
*/
|
||||
public function allWaitingQueue($room_id = null)
|
||||
{
|
||||
$waitings = $this->nspGetWaitings($room_id);
|
||||
|
||||
if ($room_id) {
|
||||
// 只处理该房间的
|
||||
if ($this->getter->driver('socket')->hasCustomerServiceByRoomId($room_id)) {
|
||||
foreach ($waitings as $key => $session_id) {
|
||||
$this->waitingQueue($room_id, $session_id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 定时器调用, 处理所有房间的
|
||||
foreach ($waitings as $current_room_id => $roomWaitings) {
|
||||
// 判断是否有客服在线
|
||||
if ($this->getter->driver('socket')->hasCustomerServiceByRoomId($current_room_id)) {
|
||||
foreach ($roomWaitings as $key => $session_id) {
|
||||
$this->waitingQueue($current_room_id, $session_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户自己触发:通知用户的客户端,当前没有客服在线
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function noCustomerService()
|
||||
{
|
||||
$session_id = $this->session('session_id');
|
||||
|
||||
$this->successUId('no_customer_service', '暂无客服在线', [
|
||||
'message' => [
|
||||
'message_type' => 'system',
|
||||
'message' => '当前客服不在线',
|
||||
'createtime' => time()
|
||||
],
|
||||
], ['id' => $session_id, 'type' => 'customer']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 给客服发消息
|
||||
*
|
||||
* @param array $message 消息原始内容
|
||||
* @param array $sender 发送者信息
|
||||
* @param integer $customer_service_id 客服 id
|
||||
* @return void
|
||||
*/
|
||||
public function messageToCustomerService($message, $sender, $customer_service_id)
|
||||
{
|
||||
// 给客服发送消息
|
||||
$this->messageUId('message', '收到消息', [
|
||||
'message' => $message,
|
||||
'sender' => $sender
|
||||
], ['id' => $customer_service_id, 'type' => 'customer_service']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 给顾客发消息
|
||||
*
|
||||
* @param array $message 消息原始内容
|
||||
* @param array $sender 发送者信息
|
||||
* @param integer $session_id 顾客 session_id
|
||||
* @return void
|
||||
*/
|
||||
public function messageToCustomer($message, $sender, $session_id)
|
||||
{
|
||||
// 给用户发送消息
|
||||
$this->messageUId('message', '收到消息', [
|
||||
'message' => $message,
|
||||
'sender' => $sender
|
||||
], ['id' => $session_id, 'type' => 'customer']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 同时通知客服和顾客
|
||||
*
|
||||
* @param array $question 要回答的问题
|
||||
* @param string $session_id 用户表示
|
||||
* @param string $customer_service_id 客服
|
||||
* @return void
|
||||
*/
|
||||
public function messageToBoth($question, $session_id, $customer_service_id)
|
||||
{
|
||||
$customerService = $this->session('customer_service');
|
||||
$chatUser = $this->session('chat_user');
|
||||
|
||||
$message = [
|
||||
'message_type' => 'text',
|
||||
'message' => $question['content'],
|
||||
];
|
||||
$sender = [
|
||||
'sender_identify' => 'customer_service',
|
||||
'customer_service' => $customerService,
|
||||
];
|
||||
// 发给用户
|
||||
$this->messageUId('message', '收到消息', [
|
||||
'message' => $message,
|
||||
'sender' => $sender
|
||||
], ['id' => $session_id, 'type' => 'customer']);
|
||||
|
||||
// 发给客服
|
||||
if ($customer_service_id) {
|
||||
// 有客服,发给客服
|
||||
$message['sender_identify'] = 'customer_service';
|
||||
$message['sender_id'] = $customerService['id'];
|
||||
$message['sender'] = $customerService;
|
||||
$message['createtime'] = time();
|
||||
$sender['session_id'] = $session_id;
|
||||
$this->successUId('message', '收到消息', [
|
||||
'message' => $message,
|
||||
'sender' => $sender
|
||||
], ['id' => $customer_service_id, 'type' => 'customer_service']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通知连接的用户(在当前客服服务的房间里面的用户),客服上线了
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function customerServiceOnline()
|
||||
{
|
||||
$room_id = $this->session('room_id');
|
||||
$customerService = $this->session('customer_service');
|
||||
|
||||
// 给客服发送消息
|
||||
$this->successRoom('customer_service_online', '客服 ' . $customerService['name'] . ' 上线', [
|
||||
'customer_service' => $customerService,
|
||||
], $this->getRoomName('customer_service_room_user', ['room_id' => $room_id, 'customer_service_id' => $customerService['id']]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通知连接的用户(在当前客服服务的房间里面的用户),客服忙碌
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function customerServiceBusy()
|
||||
{
|
||||
$room_id = $this->session('room_id');
|
||||
$customerService = $this->session('customer_service');
|
||||
|
||||
// 给客服发送消息
|
||||
$this->successRoom('customer_service_busy', '客服 ' . $customerService['name'] . ' 忙碌', [
|
||||
'customer_service' => $customerService,
|
||||
], $this->getRoomName('customer_service_room_user', ['room_id' => $room_id, 'customer_service_id' => $customerService['id']]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知连接的用户(在当前客服服务的房间里面的用户),客服下线了
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function customerServiceOffline()
|
||||
{
|
||||
$room_id = $this->session('room_id');
|
||||
$customerService = $this->session('customer_service');
|
||||
|
||||
$this->successRoom('customer_service_offline', '客服 ' . $customerService['name'] . ' 离线', [
|
||||
'customer_service' => $customerService,
|
||||
], $this->getRoomName('customer_service_room_user', ['room_id' => $room_id, 'customer_service_id' => $customerService['id']]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通知当前房间的在线客服,更新当前在线客服列表
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function customerServiceUpdate()
|
||||
{
|
||||
$room_id = $this->session('room_id');
|
||||
|
||||
// 给客服发送消息
|
||||
$customerServices = $this->getter('socket')->getCustomerServicesByRoomId($room_id);
|
||||
$this->successRoom('customer_service_update', '更新客服列表', [
|
||||
'customer_services' => $customerServices,
|
||||
], $this->getRoomName('customer_service_room', ['room_id' => $room_id]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 服务结束,通知顾客客服断开连接
|
||||
*
|
||||
* @param string $session_id
|
||||
* @return void
|
||||
*/
|
||||
public function customerServiceBreak($session_id)
|
||||
{
|
||||
$this->successUId('customer_service_break', '客服断开', [
|
||||
'message' => [
|
||||
'message_type' => 'system',
|
||||
'message' => '服务已结束',
|
||||
'createtime' => time()
|
||||
],
|
||||
], ['id' => $session_id, 'type' => 'customer']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 客服转接
|
||||
*
|
||||
* @param string $room_id 房间号
|
||||
* @param string $session_id 用户 UId
|
||||
* @param array $customerService 老客服
|
||||
* @param array $newCustomerService 新客服
|
||||
* @return void
|
||||
*/
|
||||
public function customerTransfer($room_id, $session_id, $customerService, $newCustomerService)
|
||||
{
|
||||
// 通知用户客服被转接
|
||||
$this->customerAccessedToCustomer($session_id, $customerService, $newCustomerService);
|
||||
|
||||
// 通知所有客服用户被接入
|
||||
$this->customerAccessedToCustomerServices($room_id, $session_id, $newCustomerService);
|
||||
|
||||
// 通知新的客服,新用户接入
|
||||
$this->customerAccessedToCustomerService($room_id, $session_id, $newCustomerService);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 客服接入
|
||||
*
|
||||
* @param string $room_id 房间号
|
||||
* @param string $session_id 用户 UId
|
||||
* @param array $customerService 老客服
|
||||
* @return void
|
||||
*/
|
||||
public function customerAccessed($room_id, $session_id, $customerService)
|
||||
{
|
||||
// 通知用户客服接入
|
||||
$this->customerAccessedToCustomer($session_id, $customerService);
|
||||
|
||||
// 通知所有客服用户被接入
|
||||
$this->customerAccessedToCustomerServices($room_id, $session_id, $customerService);
|
||||
|
||||
// 通知新的客服,新用户接入
|
||||
$this->customerAccessedToCustomerService($room_id, $session_id, $customerService);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通知顾客 客服接入
|
||||
*
|
||||
* @param string $session_id 用户 UId
|
||||
* @param array $customerService 老客服
|
||||
* @param array $newCustomerService 新客服
|
||||
* @return void
|
||||
*/
|
||||
private function customerAccessedToCustomer($session_id, $customerService, $newCustomerService = null)
|
||||
{
|
||||
// 通知当前用户的所有客户端,客服接入
|
||||
$message = '您好,客服 ' . $customerService['name'] . " 为您服务";
|
||||
if ($newCustomerService) {
|
||||
$message = '您好,您的客服已由 ' . $customerService['name'] . " 切换为 " . $newCustomerService['name'];
|
||||
}
|
||||
|
||||
$this->successUId('customer_service_access', '客服接入', [
|
||||
'message' => [
|
||||
'message_type' => 'system',
|
||||
'message' => $message,
|
||||
'createtime' => time()
|
||||
],
|
||||
'customer_service' => $newCustomerService ? : $customerService
|
||||
], ['id' => $session_id, 'type' => 'customer']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通知所有客服,用户被接入
|
||||
*
|
||||
* @param string $room_id 房间号
|
||||
* @param string $session_id 用户 UId
|
||||
* @param array $customerService 老客服
|
||||
* @return void
|
||||
*/
|
||||
private function customerAccessedToCustomerServices($room_id, $session_id, $customerService)
|
||||
{
|
||||
// 通知所有客服,用户被接入
|
||||
$this->successRoom('customer_accessed', '顾客被接入', [
|
||||
'session_id' => $session_id,
|
||||
'chat_user' => $this->getter('db')->getChatUserBySessionId($session_id),
|
||||
'customer_service' => $customerService,
|
||||
], $this->getRoomName('customer_service_room', ['room_id' => $room_id]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通知被接入客服,新用户接入
|
||||
*
|
||||
* @param [type] $session_id
|
||||
* @param [type] $customerService
|
||||
* @return void
|
||||
*/
|
||||
private function customerAccessedToCustomerService($room_id, $session_id, $customerService)
|
||||
{
|
||||
// 获取chatUser
|
||||
$chatUser = $this->getter('db')->getChatUserBySessionId($session_id);
|
||||
// 格式化chatUser
|
||||
$chatUser = $this->getter()->chatUsersFormat($room_id, [$chatUser], ['first' => true]);
|
||||
|
||||
// 通知新的客服,新用户接入
|
||||
$this->successUId('customer_access', '新顾客接入', [
|
||||
'session_id' => $session_id,
|
||||
'chat_user' => $chatUser,
|
||||
], ['id' => $customerService['id'], 'type' => 'customer_service']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user