You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1085 lines
36 KiB
1085 lines
36 KiB
<?php
|
|
|
|
namespace addons\shopro\library\chat;
|
|
|
|
use \GatewayWorker\Lib\Gateway;
|
|
use addons\shopro\model\chat\Connection;
|
|
use addons\shopro\model\chat\CustomerService;
|
|
use addons\shopro\model\chat\Log as ChatLog;
|
|
use addons\shopro\model\chat\User as ChatUser;
|
|
use addons\shopro\library\chat\traits\GetLinkerTrait;
|
|
use addons\shopro\model\chat\Question;
|
|
use addons\shopro\model\User;
|
|
use app\admin\model\Admin;
|
|
|
|
class Online
|
|
{
|
|
use GetLinkerTrait;
|
|
|
|
|
|
public static function getConfig($type) {
|
|
// 初始化 workerman 的时候不能读取数据库,会导致数据库连接异常
|
|
$config = require(ROOT_PATH . 'addons' . DS . 'shopro' . DS . 'library' . DS . 'chat' . DS . 'config.php');
|
|
|
|
$system = $config[$type] ?? [];
|
|
return $system;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 先判断是否有对象存储,将对象存储配置注入到 upload 配置
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function uploadConfigInit() {
|
|
// 获取绑定的 插件 钩子
|
|
$hooks = config('addons.hooks');
|
|
$upload_config_inits = isset($hooks['upload_config_init']) && $hooks['upload_config_init'] ? $hooks['upload_config_init'] : [];
|
|
if ($upload_config_inits) {
|
|
$storage = end($upload_config_inits);
|
|
\think\Hook::add('upload_config_init', 'addons\\' . ucfirst($storage) . '\\' . ucfirst($storage));
|
|
// 注入 storage 配置
|
|
$upload = \app\common\model\Config::upload();
|
|
\think\Hook::listen("upload_config_init", $upload);
|
|
\think\Config::set('upload', array_merge(\think\Config::get('upload'), $upload));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 客服自定义 cdnurl 方法
|
|
* 1、提供默认域名,如果没配置对象存储,拼接当前接口域名(访问域名的直接获取,websocket 的从 websocket SESSION['server']请求头中获取)
|
|
* 2、调用默认 cdnurl 方法
|
|
*
|
|
* @param [type] $val
|
|
* @param [type] $domain
|
|
* @return void
|
|
*/
|
|
public static function cdnurl($val, $domain = null) {
|
|
$domain = $domain ? : self::getDomain();
|
|
|
|
return cdnurl($val, $domain);
|
|
}
|
|
|
|
|
|
/**
|
|
* 获取 websocket 的域名拼接 cdnurl
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function getDomain() {
|
|
// 优先以正常访问方式获取域名
|
|
$domain = request()->domain();
|
|
$host = request()->host();
|
|
if (!$domain || !$host) {
|
|
// 如果不能获取,说明是 websocket 连接,获取当前 server 中的 domain
|
|
$server = $_SESSION['server'];
|
|
$systemConfig = self::getConfig('system');
|
|
$is_ssl = $systemConfig['is_ssl'] ? true : false;
|
|
$domain = ($is_ssl ? 'https://' : 'http://') . $server['SERVER_NAME'];
|
|
}
|
|
|
|
return $domain;
|
|
}
|
|
|
|
|
|
// 主要为了记录当前系统总共有多少种分组
|
|
public static function getGrouponName($type, $data = []) {
|
|
switch($type) {
|
|
case 'online_user' : // 当前在线用户分组
|
|
$group_name = 'online_user';
|
|
break;
|
|
case 'online_waiting' : // 当前在线用户待分配客服分组
|
|
$group_name = 'online_waiting';
|
|
break;
|
|
case 'online_customer_service' : // 当前在线客服数组
|
|
$group_name = 'online_customer_service';
|
|
break;
|
|
case 'customer_service_user': // 当前在线用户所在的客服分组
|
|
$group_name = 'customer_service_user:' . ($data['customer_service_id'] ?? 0);
|
|
break;
|
|
default :
|
|
$group_name = $type;
|
|
break;
|
|
}
|
|
|
|
return $group_name;
|
|
}
|
|
|
|
public static function getUId($id, $type)
|
|
{
|
|
$ids = is_array($id) ? $id : [$id];
|
|
foreach ($ids as &$i) {
|
|
$i = $type . '-' . $i;
|
|
}
|
|
|
|
return is_array($id) ? $ids : $ids[0];
|
|
}
|
|
|
|
|
|
public static function updateChatUser($session_id, $user) {
|
|
$chatUser = ChatUser::where(function ($query) use ($session_id, $user) {
|
|
$query->where('session_id', $session_id)
|
|
->whereOr(function ($query) use ($user) {
|
|
$query->where('user_id', '<>', 0)
|
|
->where('user_id', ($user ? $user['id'] : 0));
|
|
});
|
|
})->find();
|
|
|
|
$defaultAvatar = null;
|
|
$config = \addons\shopro\model\Config::where('name', 'user')->find();
|
|
if ($config) {
|
|
$userConfig = json_decode($config['value'], true);
|
|
$defaultAvatar = $userConfig['avatar'];
|
|
}
|
|
|
|
if (!$chatUser) {
|
|
$chatUser = new ChatUser();
|
|
|
|
$chatUser->session_id = $session_id;
|
|
$chatUser->user_id = $user ? $user['id'] : 0;
|
|
$chatUser->nickname = $user ? $user['nickname'] : ('游客-' . substr($session_id, 0, 5));
|
|
$chatUser->avatar = $user ? $user->getData('avatar') : $defaultAvatar;
|
|
$chatUser->customer_service_id = 0; // 断开连接的时候存入
|
|
$chatUser->lasttime = time();
|
|
|
|
} else {
|
|
if ($user) {
|
|
// 更新用户信息
|
|
$chatUser->user_id = $user['id'] ?? 0;
|
|
$chatUser->nickname = $user['nickname'] ? $user['nickname'] : ('游客-' . substr($session_id, 0, 5));
|
|
$chatUser->avatar = $user['avatar'] ? $user->getData('avatar') : $defaultAvatar;
|
|
}
|
|
|
|
$chatUser->lasttime = time(); // 更新时间
|
|
}
|
|
|
|
$chatUser->save();
|
|
return $chatUser->toArray();
|
|
}
|
|
|
|
|
|
/**
|
|
* 根据 id 获取客服
|
|
*/
|
|
public static function customerServiceById($customer_service_id)
|
|
{
|
|
$customerService = CustomerService::where('id', $customer_service_id)->find();
|
|
|
|
return $customerService;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 检查客服身份
|
|
*
|
|
* @param string $token
|
|
* @param string $customer_service_id
|
|
* @param string $expire_time
|
|
* @return array
|
|
*/
|
|
public static function checkAdmin($token, $customer_service_id, $expire_time) {
|
|
if ($token && $customer_service_id) {
|
|
// 获取客服信息
|
|
$customerService = self::customerServiceById($customer_service_id);
|
|
if (!$customerService) {
|
|
return false;
|
|
}
|
|
$customerService = $customerService->toArray();
|
|
|
|
// 获取 admin
|
|
$admin = Admin::get($customerService['admin_id']);
|
|
if (!$admin) {
|
|
return false;
|
|
}
|
|
$admin = $admin->toArray();
|
|
|
|
$current_token = md5($admin['username'] . $expire_time);
|
|
if (($expire_time + (86400 * 30)) > time() && $current_token == $token) {
|
|
// 验证通过
|
|
return [
|
|
'customerService' => $customerService,
|
|
'admin' => $admin
|
|
];
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 检测并获取用户
|
|
*
|
|
* @param [type] $token
|
|
* @return void
|
|
*/
|
|
public static function checkUser($token) {
|
|
$data = \app\common\library\Token::get($token);
|
|
|
|
if (!$data) {
|
|
return false;
|
|
}
|
|
|
|
$user_id = intval($data['user_id']);
|
|
if ($user_id <= 0) {
|
|
return false;
|
|
}
|
|
|
|
// 这个 user 不能转数组,用到了 $user->getData('avatar') 去拿原始的未拼接 cdnurl 头像地址
|
|
$user = User::where('id', $user_id)->find();
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
|
|
return $user;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 判断客服在线状态, 只能客服用
|
|
*
|
|
* @param [type] $customer_service_id
|
|
* @return void
|
|
*/
|
|
public static function customerServiceStatusById($customer_service_id, $oper_type = 'customer_service', $data = [])
|
|
{
|
|
$current_client_id = $data['client_id'] ?? ''; // oper_type = customer_service 时存在
|
|
|
|
// 获取当前用户的所有session,并且当前连接的session是最新的
|
|
$customerServiceSessions = self::onlineCustomerServiceNewSessionById($customer_service_id, $current_client_id);
|
|
|
|
$status = false;
|
|
foreach ($customerServiceSessions as $key => $customerServiceSession) {
|
|
$customerService = $customerServiceSession['user'] ?? [];
|
|
if ($customerService && in_array($customerService['status'], ['online', 'busy'])) {
|
|
// 只要其中一个在线,即为在线
|
|
$status = true;
|
|
}
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 客服上线
|
|
*
|
|
* @param string $client_id
|
|
* @param object $customerService
|
|
* @return object
|
|
*/
|
|
public static function customerServiceOnline($client_id, $customerService)
|
|
{
|
|
// 更新客服状态
|
|
CustomerService::where('id', $customerService['id'])->update([
|
|
'status' => 'online'
|
|
]);
|
|
|
|
// 重新更新客服信息
|
|
$customerService['status'] = 'online';
|
|
|
|
// 更新客服 session 为 online
|
|
$_SESSION['user']['status'] = 'online';
|
|
|
|
// 加入在线客服组
|
|
Gateway::joinGroup($client_id, Online::getGrouponName('online_customer_service'));
|
|
|
|
// 通知客服连接成功
|
|
Sender::customerServiceInit($client_id, $customerService);
|
|
|
|
// 通知连接的用户,客服上线了
|
|
Sender::customerServiceOnline($customerService);
|
|
|
|
// 通知所有在线客服,更新当前在线客服列表
|
|
Sender::customerServiceOnlineList();
|
|
|
|
return $customerService;
|
|
}
|
|
|
|
|
|
/**
|
|
* 客服忙碌
|
|
*
|
|
* @param string $client_id
|
|
* @param object $customerService
|
|
* @return object
|
|
*/
|
|
public static function customerServiceBusy($client_id, $customerService)
|
|
{
|
|
// 更新客服状态
|
|
CustomerService::where('id', $customerService['id'])->update([
|
|
'status' => 'busy'
|
|
]);
|
|
|
|
// 重新更新客服信息
|
|
$customerService['status'] = 'busy';
|
|
|
|
// 更新客服 session 为 busy
|
|
$_SESSION['user']['status'] = 'busy';
|
|
|
|
return $customerService;
|
|
}
|
|
|
|
|
|
/**
|
|
* 客服下线(这里不更新数据库,并且只更新当前连接为下线,如果当前客服在别的浏览器也有登录,则不受此操作影响)
|
|
*
|
|
* @param string $client_id
|
|
* @param object $customerService
|
|
* @return object
|
|
*/
|
|
public static function customerServiceOffline($client_id = null, $customerService)
|
|
{
|
|
// 重新更新客服信息
|
|
$customerService['status'] = 'offline';
|
|
|
|
// 更新客服 session 为 offline
|
|
$_SESSION['user']['status'] = 'offline';
|
|
|
|
// 移除在线客服
|
|
Gateway::leaveGroup($client_id, Online::getGrouponName('online_customer_service'));
|
|
|
|
// 获取客服在线状态
|
|
$onlineStatus = self::customerServiceStatusById($customerService['id'], 'customer_service', ['client_id' => $client_id]);
|
|
|
|
if (!$onlineStatus) {
|
|
// 绑定该 uid 的所有 session 的客服状态都离线了或者客服真实离线了,通知用户客服下线
|
|
Sender::customerServiceOffline($customerService);
|
|
}
|
|
|
|
// 通知所有客服更新在线的客服列表
|
|
Sender::customerServiceOnlineList();
|
|
|
|
return $customerService;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 客服真实离线
|
|
*
|
|
* @param int $customer_service_id
|
|
* @param array $customerService
|
|
* @return void
|
|
*/
|
|
public static function customerServiceRealOffline($customer_service_id, $customerService) {
|
|
$isUidOnline = Gateway::isUidOnline(online::getUId($customer_service_id, 'customer_service'));
|
|
|
|
if (!$isUidOnline) {
|
|
// 绑定该 uid 的所有 client_id 都离线了,更新客服数据库为离线状态
|
|
CustomerService::where('id', $customerService['id'])->update([
|
|
'status' => 'offline'
|
|
]);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 更新客服最后接入时间
|
|
*
|
|
* @param [type] $customerService
|
|
* @param [type] $oper_type
|
|
* @return void
|
|
*/
|
|
public static function updateCustomerServiceLasttime($customerService, $oper_type)
|
|
{
|
|
// 更新 session 的 lasttime
|
|
if ($oper_type == 'customer_service') {
|
|
// 如果是客服自己操作接入
|
|
$_SESSION['user']['lasttime'] = time();
|
|
} else {
|
|
// 获取客服 client_id;
|
|
$customerServiceClientIds = Gateway::getClientIdByUid(Online::getUId($customerService['id'], 'customer_service'));
|
|
|
|
foreach ($customerServiceClientIds as $customer_service_client_id) {
|
|
$session = Gateway::getSession($customer_service_client_id);
|
|
$customerService = $session['user']; // 客服信息
|
|
$customerService['lasttime'] = time(); // 更新最后接入时间
|
|
|
|
// 更新 session 缓存
|
|
Gateway::updateSession($customer_service_client_id, [
|
|
'user' => $customerService,
|
|
]);
|
|
}
|
|
}
|
|
|
|
// 更新数据库
|
|
CustomerService::where('id', $customerService['id'])->update([
|
|
'lasttime' => time()
|
|
]);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 用户最后一次的链接
|
|
*
|
|
* @param integer $user_id
|
|
* @param string $session_id
|
|
* @return array
|
|
*/
|
|
public static function userLastConnection($user_id = 0, $session_id = '') {
|
|
$lastConnection = Connection::where(function ($query) use ($user_id, $session_id) {
|
|
if ($session_id) {
|
|
$query->where(function ($query) use ($session_id) {
|
|
$query->where('session_id', '<>', '')
|
|
->where('session_id', 'not null')
|
|
->where('session_id', $session_id);
|
|
});
|
|
}
|
|
if ($user_id) {
|
|
$query->where(function ($query) use ($user_id) {
|
|
$query->where('user_id', '<>', 0)
|
|
->where('user_id', 'not null')
|
|
->where('user_id', $user_id);
|
|
});
|
|
}
|
|
})->where('customer_service_id', '<>', 0)->order('id', 'desc')->find();
|
|
|
|
return $lastConnection;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 关闭当前 session_id 的所有连接
|
|
*/
|
|
public static function closeConnectionBySessionId($session_id)
|
|
{
|
|
Connection::where('session_id', $session_id)->where('status', 'in', ['ing', 'waiting'])->update([
|
|
'status' => 'end'
|
|
]);
|
|
}
|
|
|
|
|
|
/**
|
|
* 分配客服
|
|
*
|
|
* @param string $session_id
|
|
* @param string $user_id
|
|
* @return array
|
|
*/
|
|
public static function allocatCustomerService($session_id, $user_id) {
|
|
$config = self::getConfig('basic');
|
|
$last_customer_service = $config['last_customer_service'] ?? 1;
|
|
$allocate = $config['allocate'] ?? 'busy';
|
|
|
|
// 分配的客服
|
|
$currentCustomerService = null;
|
|
|
|
// 使用上次客服
|
|
if ($last_customer_service) {
|
|
// 获取上次连接的信息
|
|
$lastConnection = self::userLastConnection($user_id, $session_id);
|
|
|
|
$lastCustomerService = null;
|
|
$currentCustomerService = null;
|
|
if ($lastConnection) {
|
|
// 获取上次客服信息
|
|
// $lastCustomerService = Online::customerServiceById($lastConnection['customer_service_id']); // 读取数据库,不准确
|
|
$lastCustomerService = Self::onlineCustomerServiceById($lastConnection['customer_service_id']); // 获取socket 连接里面上次客服是否在线
|
|
|
|
// 判断客服是否在线
|
|
if ($lastCustomerService) {
|
|
if ($lastCustomerService['status'] == 'online') {
|
|
$currentCustomerService = $lastCustomerService;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 没有客服,随机分配
|
|
if (!$currentCustomerService) {
|
|
// 在线客服列表
|
|
$onlineCustomerServices = self::onlineCustomerServices();
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (!$currentCustomerService) {
|
|
// 如果都不是 online 状态,默认取第一条
|
|
$currentCustomerService = $onlineCustomerServices[0] ?? null;
|
|
}
|
|
} else if ($allocate == 'turns') {
|
|
// 按照最后接入时间正序排序,这里没有离线的客服
|
|
$onlineCustomerServices = array_column($onlineCustomerServices, null, 'lasttime');
|
|
ksort($onlineCustomerServices);
|
|
|
|
// 取忙碌度最小,并且客服为 正常在线状态
|
|
foreach ($onlineCustomerServices as $customerService) {
|
|
if ($customerService['status'] == 'online') {
|
|
$currentCustomerService = $customerService;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$currentCustomerService) {
|
|
// 如果都不是 online 状态,默认取第一条
|
|
$currentCustomerService = $onlineCustomerServices[0] ?? null;
|
|
}
|
|
} else if ($allocate == 'random') {
|
|
// 随机获取一名客服
|
|
$onlineCustomerServices = array_column($onlineCustomerServices, null, 'id');
|
|
|
|
$customer_service_id = 0;
|
|
if ($onlineCustomerServices) {
|
|
$customer_service_id = array_rand($onlineCustomerServices);
|
|
}
|
|
|
|
$currentCustomerService = $onlineCustomerServices[$customer_service_id] ?? null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $currentCustomerService;
|
|
}
|
|
|
|
|
|
/**
|
|
* 通过 session_id 记录客服信息,并且加入对应的客服组
|
|
*
|
|
* @param string $session_id 用户
|
|
* @param array $customerService 客服信息
|
|
* @return null
|
|
*/
|
|
public static function bindCustomerServiceBySessionId($session_id, $customerService, $oper_type = 'customer_service') {
|
|
$uid = Online::getUId($session_id, 'user');
|
|
$client_ids = Gateway::getClientIdByUid($uid);
|
|
|
|
// 将当前 session_id 绑定的 client_id 都加入当前客服组
|
|
foreach ($client_ids as $client_id) {
|
|
self::bindCustomerService($client_id, $customerService, $oper_type);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 记录客服信息,并且加入对应的客服组
|
|
*
|
|
* @param string $client_id 用户
|
|
* @param array $customerService 客服信息
|
|
* @return null
|
|
*/
|
|
public static function bindCustomerService($client_id, $customerService, $oper_type = 'user') {
|
|
// 当前用户使用 updateSession 会吧之前的内容覆盖掉,并且 getSession 无法获取刚刚设置的 session
|
|
|
|
// 更新用户的客服信息
|
|
if ($oper_type == 'user') {
|
|
// 当前连接者
|
|
$_SESSION['customer_service_id'] = $customerService['id'];
|
|
$_SESSION['customer_service'] = $customerService;
|
|
} else {
|
|
// 其他连接着
|
|
Gateway::updateSession($client_id, [
|
|
'customer_service_id' => $customerService['id'],
|
|
'customer_service' => $customerService
|
|
]);
|
|
}
|
|
|
|
// 更新客服的最后接入用户时间
|
|
self::updateCustomerServiceLasttime($customerService, $oper_type);
|
|
|
|
// 加入对应客服组,统计客服信息 customer_service_user:客服 ID
|
|
Gateway::joinGroup($client_id, Online::getGrouponName('customer_service_user', ['customer_service_id' => $customerService['id']]));
|
|
// 从等待接入组移除
|
|
Gateway::leaveGroup($client_id, Online::getGrouponName('online_waiting'));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 通过 session_id 将用户移除客服组
|
|
*
|
|
* @param string $session_id 用户
|
|
* @param array $customerService 客服信息
|
|
* @return null
|
|
*/
|
|
public static function unBindCustomerServiceBySessionId($session_id, $customerService, $oper_type = 'customer_service')
|
|
{
|
|
$uid = Online::getUId($session_id, 'user');
|
|
$client_ids = Gateway::getClientIdByUid($uid);
|
|
|
|
// 将当前 session_id 绑定的 client_id 都移除当前客服组
|
|
foreach ($client_ids as $client_id) {
|
|
// 这里新客服已经绑定了,不需要移除session 了,所以 remove_session 为 false
|
|
self::unBindCustomerService($client_id, $customerService, false, $oper_type);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 将用户的客服信息移除
|
|
*
|
|
* @param string $client_id 用户
|
|
* @param array $customerService 客服信息
|
|
* @return null
|
|
*/
|
|
public static function unBindCustomerService($client_id, $customerService, $remove_session = false, $oper_type = false)
|
|
{
|
|
if ($remove_session) {
|
|
if ($oper_type == 'user') {
|
|
// 当前连接者
|
|
$_SESSION['customer_service_id'] = 0;
|
|
$_SESSION['customer_service'] = [];
|
|
} else {
|
|
// 其他连接着
|
|
Gateway::updateSession($client_id, [
|
|
'customer_service_id' => 0,
|
|
'customer_service' => []
|
|
]);
|
|
}
|
|
}
|
|
|
|
// 移除对应客服组
|
|
Gateway::leaveGroup($client_id, Online::getGrouponName('customer_service_user', ['customer_service_id' => $customerService['id']]));
|
|
}
|
|
|
|
|
|
// 转接用户
|
|
public static function transferCustomerServiceBySessionId($session_id, $newCustomerService, $oldCustomerService, $oper_type = 'customer_service') {
|
|
// 将老客服的服务记录直接保存
|
|
$chatUser = Self::getChatUserBySessionId($session_id);
|
|
if ($chatUser && $chatUser->user_id) {
|
|
$user = User::get($chatUser->user_id);
|
|
}
|
|
$userData = [
|
|
'user' => $user ?? null,
|
|
'session_id' => $session_id,
|
|
'chat_user' => $chatUser
|
|
];
|
|
|
|
// 创建并结束 connection
|
|
$connection = Online::checkOrCreateConnection($userData, $oldCustomerService, true);
|
|
|
|
// 新客服接入用户
|
|
self::bindCustomerServiceBySessionId($session_id, $newCustomerService, $oper_type);
|
|
|
|
// 将用户从旧的客服组移除
|
|
self::unBindCustomerServiceBySessionId($session_id, $oldCustomerService, $oper_type);
|
|
}
|
|
|
|
|
|
/**
|
|
* 排队的用户,将用户加入 waiting 组
|
|
*
|
|
* @param string $client_id
|
|
* @return null
|
|
*/
|
|
public static function bindWaiting ($client_id) {
|
|
Gateway::joinGroup($client_id, Online::getGrouponName('online_waiting'));
|
|
}
|
|
|
|
|
|
/**
|
|
* 判断是否有链接,如果没有创建新链接(为了避免用户刷新,然后这里重新创建新纪录)
|
|
*/
|
|
public static function checkOrCreateConnection($userData, $customerService, $set_end = false) {
|
|
$user = $userData['user'] ?? null;
|
|
$chatUser = $userData['chat_user'] ?? null;
|
|
$session_id = $userData['session_id'] ?? '';
|
|
|
|
// 正在进行中的连接
|
|
$ingConnection = Connection::where('customer_service_id', $customerService['id'])
|
|
->where('status', 'in', ['ing', 'waiting'])
|
|
->where(function ($query) use ($user, $session_id) {
|
|
$query->where('session_id', $session_id)->whereOr('user_id', ($user->id ?? 0));
|
|
})->find();
|
|
|
|
if (!$ingConnection) {
|
|
// 不存在,创建新的
|
|
$ingConnection = new Connection();
|
|
|
|
$ingConnection->user_id = $user ? $user['id'] : 0;
|
|
$ingConnection->nickname = $chatUser['nickname'];
|
|
$ingConnection->session_id = $session_id;
|
|
$ingConnection->customer_service_id = $customerService ? $customerService['id'] : 0; // 0 没有客服在;
|
|
$ingConnection->starttime = time();
|
|
$ingConnection->endtime = $set_end ? time() : 0;
|
|
$ingConnection->status = $set_end ? 'end' : ($customerService ? 'ing' : 'waiting');
|
|
$ingConnection->createtime = time();
|
|
$ingConnection->updatetime = time();
|
|
$ingConnection->save();
|
|
} else {
|
|
if ($ingConnection->status == 'waiting' && $customerService) {
|
|
// 如果是 waiting, 并且存在客服,修改为 ing
|
|
$ingConnection->customer_service_id = $customerService['id'];
|
|
$ingConnection->status = 'ing';
|
|
}
|
|
|
|
if ($set_end) {
|
|
$ingConnection->status = 'end';
|
|
$ingConnection->endtime = time();
|
|
}
|
|
|
|
$ingConnection->save();
|
|
}
|
|
|
|
return $ingConnection;
|
|
}
|
|
|
|
|
|
/**
|
|
* 用户登录,更新当前 session_id 没有 user_id 的记录
|
|
*/
|
|
public static function sessionUserSave($user, $session_id) {
|
|
// 更新用户连接
|
|
$connection = Connection::where('session_id', $session_id)->where('user_id', 0)->update([
|
|
'user_id' => $user['id'],
|
|
'nickname' => $user['nickname']
|
|
]);
|
|
|
|
// 更新用户消息记录
|
|
$connection = ChatLog::where('session_id', $session_id)->where('user_id', 0)->update([
|
|
'user_id' => $user['id']
|
|
]);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* 通过连接 id 获取连接
|
|
*/
|
|
public static function connectionById($connection_id = 0) {
|
|
$connection = Connection::where('id', $connection_id)->find();
|
|
|
|
return $connection;
|
|
}
|
|
|
|
|
|
/**
|
|
* 通过客服 id 获取当前客服正在服务的客户
|
|
*/
|
|
public static function onlineByCustomerServiceId($customerService) {
|
|
$onlines = Connection::where('status', 'ing')->where('customer_service_id', $customerService['id'])->select();
|
|
|
|
return $onlines;
|
|
}
|
|
|
|
|
|
// 通过客服 id 获取当前客服历史服务过的客户
|
|
public static function historyByCustomerServiceId($customerService, $data)
|
|
{
|
|
$except = $data['except'] ?? [];
|
|
|
|
// 关闭 sql mode 的 ONLY_FULL_GROUP_BY
|
|
$oldModes = closeStrict(['ONLY_FULL_GROUP_BY']);
|
|
|
|
$historieLogs = Connection::with(['chat_user'])
|
|
->where('customer_service_id', $customerService['id'])
|
|
->where('session_id', 'not in', $except)
|
|
->group('session_id')
|
|
->select();
|
|
|
|
// 恢复 sql mode
|
|
recoverStrict($oldModes);
|
|
|
|
$histories = array_column($historieLogs, 'chat_user');
|
|
|
|
return array_values(array_filter($histories));
|
|
}
|
|
|
|
// 获取当前所有正在排队的用户
|
|
public static function waiting()
|
|
{
|
|
$waiting = Connection::where('status', 'waiting')->select();
|
|
|
|
return $waiting;
|
|
}
|
|
|
|
|
|
/**
|
|
* 通过 session_id 删除客户服务记录
|
|
*
|
|
* @param array $customerService
|
|
* @param string $session_id
|
|
* @return void
|
|
*/
|
|
public static function delUserBySessionId($customerService, $session_id) {
|
|
return Connection::where('customer_service_id', $customerService['id'])->where('session_id', $session_id)->delete();
|
|
}
|
|
|
|
|
|
/**
|
|
* 删除所有客户服务记录
|
|
*
|
|
* @param array $customerService
|
|
* @param string $session_id
|
|
* @return void
|
|
*/
|
|
public static function delAllUserBySessionId($customerService)
|
|
{
|
|
return Connection::where('customer_service_id', $customerService['id'])->delete();
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 发送消息
|
|
*
|
|
* @param string $name 要调用的方法名,这里未使用
|
|
* @param string $arguments 给要调用的方法的参数
|
|
* @return object
|
|
*/
|
|
public static function addMessage($name, $arguments) {
|
|
$receive_id = $arguments[0] ?? 0;
|
|
$content = $arguments[1] ?? [];
|
|
$params = $arguments[2] ?? []; // 额外参数
|
|
$sender = $params['sender'] ?? [];
|
|
|
|
// 判断是否传入的发送人
|
|
if ($sender) {
|
|
// 传入了发送人信息
|
|
extract($sender);
|
|
} else {
|
|
// 默认对发 用户 发送给客服, 客服发送给用户
|
|
$sender_identify = $_SESSION['identify'];
|
|
|
|
if ($sender_identify == 'customer_service') {
|
|
// 获取发送信息
|
|
extract(self::getCustomerServiceSenderData($receive_id, $content));
|
|
} else {
|
|
// 用户
|
|
$session_id = $_SESSION['uid'];
|
|
$user_id = $_SESSION['user_id'] ?? 0; // 如果是游客,这里为 0
|
|
$user = $_SESSION['user'];
|
|
$sender_id = $_SESSION['chat_user'] ? $_SESSION['chat_user']['id'] : 0;
|
|
}
|
|
}
|
|
|
|
// 返回 message_type message
|
|
extract(self::getMessageData($content));
|
|
|
|
$chatLog = new ChatLog();
|
|
|
|
$chatLog->session_id = $session_id;
|
|
$chatLog->user_id = $user_id;
|
|
$chatLog->sender_identify = $sender_identify;
|
|
$chatLog->sender_id = $sender_id;
|
|
$chatLog->message_type = $message_type;
|
|
$chatLog->message = $message;
|
|
$chatLog->save();
|
|
|
|
// 加载消息人
|
|
$chatLog->identify = $chatLog->identify;
|
|
|
|
return $chatLog;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 客服给用户发消息是,获取发送参数
|
|
*
|
|
* @param [type] $receive_id
|
|
* @param [type] $content
|
|
* @return array
|
|
*/
|
|
public static function getCustomerServiceSenderData($receive_id, $content) {
|
|
// 客服
|
|
$sender_id = $customer_service_id = $_SESSION['uid'];
|
|
$customerService = $_SESSION['user'];
|
|
$session_id = $receive_id;
|
|
|
|
$user_id = 0;
|
|
$uid = Online::getUId($session_id, 'user');
|
|
if (Gateway::isUidOnline($uid)) {
|
|
// 接受者在线, 通过 uid 获取 client_id, 返回的是一个数组,取第一个,只是为了取对应的 user_id
|
|
$client_ids = Gateway::getClientIdByUid($uid);
|
|
|
|
// 获取数组第一个
|
|
$client_ids = array_values(array_filter($client_ids));
|
|
$client_id = $client_ids[0] ?? 0;
|
|
|
|
$receiveSession = Gateway::getSession($client_id);
|
|
if ($receiveSession && $receiveSession['user_id']) {
|
|
$user_id = $receiveSession['user_id'];
|
|
}
|
|
} else {
|
|
// 通过 chatUser 获取 user_id
|
|
$chatUser = self::getChatUserBySessionId($session_id);
|
|
if ($chatUser) {
|
|
$user_id = $chatUser['user_id'];
|
|
}
|
|
}
|
|
|
|
return compact("session_id", "user_id", "sender_id");
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 获取消息类型
|
|
*
|
|
* @param [type] $content
|
|
* @return array
|
|
*/
|
|
public static function getMessageData ($content) {
|
|
$type = $content['type'] ?? '';
|
|
$msg = $content['msg'] ?? '';
|
|
$data = $content['data'] ?? []; // 特定 type 类型存在,包含 type = message
|
|
$messageData = $data['message'] ?? []; // type=message 存在
|
|
|
|
if (in_array($type, ['init', 'access'])) {
|
|
// 系统消息
|
|
$message_type = 'system';
|
|
$message = $msg;
|
|
} else if ($type == 'message') {
|
|
$message_type = $messageData['message_type'] ?? 'text';
|
|
$message_content = $messageData['message'] ?? '';
|
|
|
|
switch ($message_type) {
|
|
case 'text':
|
|
$message = $message_content;
|
|
break;
|
|
default:
|
|
$message = is_array($message_content) ? json_encode($message_content) : $message_content;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return compact("message_type", "message");
|
|
}
|
|
|
|
/**
|
|
* 将消息标记为已读
|
|
*
|
|
* @param array $linker 用户信息,user_id session_id
|
|
* @param string $select_identify user & customer_service
|
|
* @return void
|
|
*/
|
|
public static function readMessage($linker, $select_identify = 'user')
|
|
{
|
|
$session_id = $linker['session_id'] ?? '';
|
|
$user_id = $linker['user_id'] ?? '';
|
|
$select_identify = camelize($select_identify); // 处理为 驼峰
|
|
|
|
ChatLog::where(function ($query) use ($session_id, $user_id) {
|
|
$query->where(function ($query) use ($session_id) {
|
|
$query->where('session_id', $session_id)
|
|
->where('session_id', 'not null')
|
|
->where('session_id', '<>', '');
|
|
})
|
|
->whereOr(function ($query) use ($user_id) {
|
|
$query->where('user_id', $user_id)
|
|
->where('user_id', '<>', 0);
|
|
});
|
|
})->{$select_identify}()->update([
|
|
'readtime' => time()
|
|
]);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 获取消息列表
|
|
*
|
|
* @param array $linker 要获取的用户
|
|
* @param array $data 参数
|
|
* @return array
|
|
*/
|
|
public static function messageList($linker, $data) {
|
|
$session_id = $linker['session_id'] ?? '';
|
|
$user_id = $linker['user_id'] ?? '';
|
|
|
|
$page = $data['page'] ?? 1;
|
|
$per_page = $data['per_page'] ?? 20;
|
|
$last_id = $data['last_id'] ?? 0;
|
|
|
|
$messageList = ChatLog::where(function ($query) use ($session_id, $user_id) {
|
|
$query->where(function ($query) use ($session_id) {
|
|
$query->where('session_id', $session_id)
|
|
->where('session_id', 'not null')
|
|
->where('session_id', '<>', '');
|
|
})
|
|
->whereOr(function ($query) use ($user_id) {
|
|
$query->where('user_id', $user_id)
|
|
->where('user_id', '<>', 0);
|
|
});
|
|
});
|
|
|
|
// 避免消息重复
|
|
if ($last_id) {
|
|
$messageList = $messageList->where('id', '<=', $last_id);
|
|
}
|
|
|
|
$messageList = $messageList->order('id', 'desc')->paginate($per_page, false, [
|
|
'page' => $page
|
|
]);
|
|
|
|
$messageData = $messageList->items();
|
|
if ($messageData) {
|
|
// 批量处理消息发送人
|
|
$messageData = ChatLog::formatIdentify($messageData);
|
|
$messageList->data = $messageData;
|
|
}
|
|
|
|
return $messageList;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 用户列表增加最后一条聊天记录
|
|
* @param array $chatUsers
|
|
* @param string $select_identify 查询对象,默认客服查用户的,用户查客服的
|
|
* @return array
|
|
*/
|
|
public static function userSetMessage($chatUsers, $select_identify = 'user') {
|
|
foreach ($chatUsers as &$chatUser) {
|
|
$chatUser['last_message'] = ChatLog::where('session_id', $chatUser['session_id'])->order('id', 'desc')->find();
|
|
$chatUser['message_unread_num'] = ChatLog::where('session_id', $chatUser['session_id'])->where('readtime', 'null')->{$select_identify}()->count();
|
|
}
|
|
|
|
return $chatUsers;
|
|
}
|
|
|
|
|
|
public static function userRealOffline($session_id, $customerService) {
|
|
// 判断 uid 是否还在线,如果是刷新浏览器,只会出现很短暂的掉线,这里检测依然还是在线状态
|
|
$isUidOnline = Gateway::isUidOnline(online::getUId($session_id, 'user'));
|
|
if (!$isUidOnline) {
|
|
// 不在线,关闭这个用户所有连接
|
|
self::closeConnectionBySessionId($session_id);
|
|
|
|
// 记录用户本次客服人员,并且更新最后服务时间
|
|
$chatUser = self::getChatUserBySessionId($session_id);
|
|
$chatUser->customer_service_id = $customerService['id'] ?? 0;
|
|
$chatUser->lasttime = time();
|
|
$chatUser->save();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 常见问题
|
|
*
|
|
* @param int $question_id
|
|
* @param array $receives
|
|
* @return void
|
|
*/
|
|
public static function questionReply ($question_id, $receives) {
|
|
$question = Question::get($question_id);
|
|
|
|
if ($question) {
|
|
Sender::questionReply($question, $receives);
|
|
}
|
|
}
|
|
} |