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.
383 lines
17 KiB
383 lines
17 KiB
<?php
|
|
// +----------------------------------------------------------------------
|
|
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
|
// +----------------------------------------------------------------------
|
|
// | Copyright (c) 2016~2023 https://www.crmeb.com All rights reserved.
|
|
// +----------------------------------------------------------------------
|
|
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
|
// +----------------------------------------------------------------------
|
|
// | Author: CRMEB Team <admin@crmeb.com>
|
|
// +----------------------------------------------------------------------
|
|
|
|
namespace crmeb\services\workerman\chat;
|
|
|
|
use app\services\kefu\LoginServices;
|
|
use app\services\kefu\service\StoreServiceLogServices;
|
|
use app\services\kefu\service\StoreServiceRecordServices;
|
|
use app\services\kefu\service\StoreServiceServices;
|
|
use app\services\order\StoreOrderServices;
|
|
use app\services\product\product\StoreProductServices;
|
|
use app\services\user\UserServices;
|
|
use app\services\wechat\WechatKeyServices;
|
|
use app\services\wechat\WechatReplyServices;
|
|
use app\services\wechat\WechatUserServices;
|
|
use app\services\user\UserAuthServices;
|
|
use crmeb\exceptions\AuthException;
|
|
use crmeb\services\app\WechatService;
|
|
use crmeb\services\workerman\Response;
|
|
use crmeb\utils\Arr;
|
|
use think\facade\Log;
|
|
use Workerman\Connection\TcpConnection;
|
|
|
|
/**
|
|
* Class ChatHandle
|
|
* @package crmeb\services\workerman\chat
|
|
*/
|
|
class ChatHandle
|
|
{
|
|
/**
|
|
* @var ChatService
|
|
*/
|
|
protected $service;
|
|
|
|
/**
|
|
* ChatHandle constructor.
|
|
* @param ChatService $service
|
|
*/
|
|
public function __construct(ChatService &$service)
|
|
{
|
|
$this->service = &$service;
|
|
}
|
|
|
|
/**
|
|
* 客服登录
|
|
* @param TcpConnection $connection
|
|
* @param array $res
|
|
* @param Response $response
|
|
* @return bool|null
|
|
* @throws \Psr\SimpleCache\InvalidArgumentException
|
|
* @throws \think\db\exception\DataNotFoundException
|
|
* @throws \think\db\exception\DbException
|
|
* @throws \think\db\exception\ModelNotFoundException
|
|
*/
|
|
public function kefu_login(TcpConnection &$connection, array $res, Response $response)
|
|
{
|
|
if (!isset($res['data']) || !$token = $res['data']) {
|
|
return $response->close([
|
|
'msg' => '授权失败!'
|
|
]);
|
|
}
|
|
try {
|
|
/** @var LoginServices $services */
|
|
$services = app()->make(LoginServices::class);
|
|
$kefuInfo = $services->parseToken($token);
|
|
} catch (AuthException $e) {
|
|
return $response->close([
|
|
'msg' => $e->getMessage()
|
|
]);
|
|
}
|
|
|
|
$connection->kefuUser = $kefuInfo;
|
|
/** @var UserServices $userService */
|
|
$userService = app()->make(UserServices::class);
|
|
$connection->user = $userService->get($kefuInfo['uid'], ['uid', 'nickname']);
|
|
if (!isset($connection->user->uid)) {
|
|
return $response->close([
|
|
'msg' => '您登录的客服用户不存在'
|
|
]);
|
|
}
|
|
/** @var StoreServiceRecordServices $service */
|
|
$service = app()->make(StoreServiceRecordServices::class);
|
|
$service->updateRecord(['to_uid' => $connection->user->uid], ['online' => 1]);
|
|
/** @var StoreServiceServices $service */
|
|
$service = app()->make(StoreServiceServices::class);
|
|
$service->update(['uid' => $connection->user->uid], ['online' => 1]);
|
|
$this->service->setKefuUser($connection);
|
|
|
|
return $response->success();
|
|
}
|
|
|
|
/**
|
|
* 用户登录
|
|
* @param TcpConnection $connection
|
|
* @param array $res
|
|
* @param Response $response
|
|
* @return bool|null
|
|
*/
|
|
public function login(TcpConnection &$connection, array $res, Response $response)
|
|
{
|
|
if (!isset($res['data']) || !$token = $res['data']) {
|
|
return $response->close([
|
|
'msg' => '授权失败!'
|
|
]);
|
|
}
|
|
|
|
try {
|
|
/** @var UserAuthServices $services */
|
|
$services = app()->make(UserAuthServices::class);
|
|
$authInfo = $services->parseToken($token);
|
|
} catch (AuthException $e) {
|
|
return $response->close([
|
|
'msg' => $e->getMessage()
|
|
]);
|
|
}
|
|
|
|
$connection->user = $authInfo['user'];
|
|
$connection->tokenData = $authInfo['tokenData'];
|
|
$this->service->setUser($connection);
|
|
|
|
/** @var StoreServiceRecordServices $service */
|
|
$service = app()->make(StoreServiceRecordServices::class);
|
|
$service->updateRecord(['to_uid' => $connection->user->uid], ['online' => 1, 'type' => $res['form_type'] ?? 1]);
|
|
$connections = $this->service->kefuUser();
|
|
foreach ($connections as &$conn) {
|
|
if (!isset($conn->onlineUids) || !in_array($connection->user->uid, $conn->onlineUids ?? [])) {
|
|
$response->connection($conn)->send('user_online', ['to_uid' => $connection->user->uid, 'online' => 1]);
|
|
}
|
|
if (!isset($conn->onlineUids)) {
|
|
$conn->onlineUids = [];
|
|
}
|
|
array_push($conn->onlineUids, $connection->user->uid);
|
|
$this->service->setKefuUser($conn, false);
|
|
}
|
|
|
|
return $response->connection($connection)->success();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param TcpConnection $connection
|
|
* @param array $res
|
|
* @param Response $response
|
|
*/
|
|
public function to_chat(TcpConnection &$connection, array $res, Response $response)
|
|
{
|
|
$tourist_uid = $res['data']['tourist_uid'] ?? 0;
|
|
if ($tourist_uid) {
|
|
$connection->isTourist = true;
|
|
$connection->user = (object)['uid' => $tourist_uid];
|
|
$connections = $this->service->user();
|
|
if (!isset($connections[$tourist_uid])) {
|
|
$this->service->setUser($connection);
|
|
}
|
|
}
|
|
$connection->chatToUid = $res['data']['id'] ?? 0;
|
|
if (isset($connection->user)) {
|
|
$uid = $connection->user->uid;
|
|
if ($connection->chatToUid && !isset($connection->isTourist)) {
|
|
/** @var StoreServiceRecordServices $service */
|
|
$service = app()->make(StoreServiceRecordServices::class);
|
|
$service->update(['user_id' => $uid, 'to_uid' => $connection->chatToUid], ['mssage_num' => 0]);
|
|
/** @var StoreServiceLogServices $logServices */
|
|
$logServices = app()->make(StoreServiceLogServices::class);
|
|
$logServices->update(['uid' => $connection->chatToUid, 'to_uid' => $uid], ['type' => 1]);
|
|
}
|
|
$response->send('mssage_num', ['uid' => $connection->chatToUid, 'num' => 0, 'recored' => (object)[]]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 用户向客服发送消息
|
|
* @param TcpConnection $connection
|
|
* @param array $res
|
|
* @param Response $response
|
|
* @return bool|null
|
|
* @throws \think\db\exception\DataNotFoundException
|
|
* @throws \think\db\exception\DbException
|
|
* @throws \think\db\exception\ModelNotFoundException
|
|
*/
|
|
public function chat(TcpConnection &$connection, array $res, Response $response)
|
|
{
|
|
$to_uid = $res['data']['to_uid'] ?? 0;
|
|
$msn_type = $res['data']['type'] ?? 0;
|
|
$msn = $res['data']['msn'] ?? '';
|
|
$formType = $res['form_type'] ?? 0;
|
|
//是否为游客
|
|
$isTourist = $res['data']['is_tourist'] ?? 0;
|
|
$tourist_uid = $res['data']['tourist_uid'] ?? 0;
|
|
$isTourist = $isTourist && $tourist_uid;
|
|
$tourist_avatar = $res['data']['tourist_avatar'] ?? '';
|
|
$uid = $isTourist ? $tourist_uid : $connection->user->uid;
|
|
if (!$to_uid) {
|
|
return $response->send('err_tip', ['msg' => '用户不存在']);
|
|
}
|
|
if ($to_uid == $uid) {
|
|
return $response->send('err_tip', ['msg' => '不能和自己聊天']);
|
|
}
|
|
/** @var StoreServiceLogServices $logServices */
|
|
$logServices = app()->make(StoreServiceLogServices::class);
|
|
if (!in_array($msn_type, $logServices::MSN_TYPE)) {
|
|
return $response->send('err_tip', ['msg' => '格式错误']);
|
|
}
|
|
$msn = trim(strip_tags(str_replace(["\n", "\t", "\r", " "], '', htmlspecialchars_decode($msn))));
|
|
$data = compact('to_uid', 'msn_type', 'msn', 'uid');
|
|
$data['add_time'] = time();
|
|
$data['is_tourist'] = $res['data']['is_tourist'] ?? 0;
|
|
$connections = $this->service->user();
|
|
$online = isset($connections[$to_uid]) && isset($connections[$to_uid]->chatToUid) && $connections[$to_uid]->chatToUid == $uid;
|
|
$data['type'] = $online ? 1 : 0;
|
|
$data = $logServices->save($data);
|
|
$data = $data->toArray();
|
|
$data['_add_time'] = $data['add_time'];
|
|
$data['add_time'] = strtotime($data['add_time']);
|
|
if (!$isTourist) {
|
|
if (isset($this->service->kefuUser()[$data['uid']])) {
|
|
/** @var StoreServiceServices $userService */
|
|
$userService = app()->make(StoreServiceServices::class);
|
|
$_userInfo = $userService->get(['uid' => $data['uid']], ['nickname', 'avatar']);
|
|
} else {
|
|
/** @var UserServices $userService */
|
|
$userService = app()->make(UserServices::class);
|
|
$_userInfo = $userService->getUserInfo($data['uid'], 'nickname,avatar');
|
|
}
|
|
$data['nickname'] = $_userInfo['nickname'];
|
|
$data['avatar'] = $_userInfo['avatar'];
|
|
} else {
|
|
$avatar = sys_config('tourist_avatar');
|
|
$_userInfo['avatar'] = $tourist_avatar ?: Arr::getArrayRandKey(is_array($avatar) ? $avatar : []);
|
|
$_userInfo['nickname'] = '游客' . $uid;
|
|
$data['nickname'] = $_userInfo['nickname'];
|
|
$data['avatar'] = $_userInfo['avatar'];
|
|
}
|
|
|
|
//商品消息类型
|
|
$data['productInfo'] = [];
|
|
if ($msn_type == StoreServiceLogServices::MSN_TYPE_GOODS && $msn) {
|
|
/** @var StoreProductServices $productServices */
|
|
$productServices = app()->make(StoreProductServices::class);
|
|
$productInfo = $productServices->getProductInfo((int)$msn, 'store_name,IFNULL(sales,0) + IFNULL(ficti,0) as sales,image,slider_image,price,vip_price,ot_price,stock,id');
|
|
$data['productInfo'] = $productInfo ? $productInfo->toArray() : [];
|
|
}
|
|
//订单消息类型
|
|
$data['orderInfo'] = [];
|
|
if ($msn_type == StoreServiceLogServices::MSN_TYPE_ORDER && $msn) {
|
|
/** @var StoreOrderServices $orderServices */
|
|
$orderServices = app()->make(StoreOrderServices::class);
|
|
$order = $orderServices->getUserOrderDetail($msn, $uid);
|
|
if ($order) {
|
|
$order = $orderServices->tidyOrder($order->toArray(), true, true);
|
|
$order['add_time_y'] = date('Y-m-d', $order['add_time']);
|
|
$order['add_time_h'] = date('H:i:s', $order['add_time']);
|
|
$data['orderInfo'] = $order;
|
|
}
|
|
}
|
|
//给自己回复消息
|
|
$response->send('chat', $data);
|
|
|
|
//用户向客服发送消息,判断当前客服是否在登录中
|
|
/** @var StoreServiceRecordServices $serviceRecored */
|
|
$serviceRecored = app()->make(StoreServiceRecordServices::class);
|
|
$unMessagesCount = $logServices->getMessageNum(['uid' => $uid, 'to_uid' => $to_uid, 'type' => 0, 'is_tourist' => $isTourist ? 1 : 0]);
|
|
//记录当前用户和他人聊天记录
|
|
$data['recored'] = $serviceRecored->saveRecord($uid, $to_uid, $msn, $formType ?? 0, $msn_type, $unMessagesCount, $isTourist, $data['nickname'], $data['avatar']);
|
|
//是否在线
|
|
if ($online) {
|
|
$response->connection($this->service->user()[$to_uid])->send('reply', $data);
|
|
} else {
|
|
//用户在线,可是没有和当前用户进行聊天,给当前用户发送未读条数
|
|
if (isset($connections[$to_uid])) {
|
|
$data['recored']['nickname'] = $_userInfo['nickname'];
|
|
$data['recored']['avatar'] = $_userInfo['avatar'];
|
|
$response->connection($this->service->user()[$to_uid])->send('mssage_num', [
|
|
'uid' => $uid,
|
|
'num' => $unMessagesCount,
|
|
'recored' => $data['recored']
|
|
]);
|
|
}
|
|
if ($isTourist) {
|
|
return true;
|
|
}
|
|
//用户不在线
|
|
/** @var WechatUserServices $wechatUserServices */
|
|
$wechatUserServices = app()->make(WechatUserServices::class);
|
|
$userInfo = $wechatUserServices->getOne(['uid' => $to_uid, 'user_type' => 'wechat'], 'nickname,subscribe,openid,headimgurl');
|
|
if ($userInfo && $userInfo['subscribe'] && $userInfo['openid']) {
|
|
$description = '您有新的消息,请注意查收!';
|
|
if ($formType) {
|
|
$head = '客服接待消息提醒';
|
|
$url = sys_config('site_url') . '/kefu/mobile_chat?toUid=' . $uid . '&nickname=' . $_userInfo['nickname'];
|
|
} else {
|
|
$head = '客服回复消息提醒';
|
|
$url = sys_config('site_url') . '/pages/extension/customer_list/chat?uid=' . $uid;
|
|
}
|
|
$message = WechatService::newsMessage($head, $description, $url, $_userInfo['avatar']);
|
|
$userInfo = $userInfo->toArray();
|
|
try {
|
|
WechatService::staffService()->message($message)->to($userInfo['openid'])->send();
|
|
} catch (\Exception $e) {
|
|
|
|
Log::error($userInfo['nickname'] . '发送失败' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
if (!isset($this->service->kefuUser()[$uid])) {
|
|
//判断是否有自动回复
|
|
$wechatKeyServices = app()->make(WechatKeyServices::class);
|
|
$replyId = $wechatKeyServices->value(['keys' => $msn, 'key_type' => 1], 'reply_id');
|
|
if(!$replyId) $replyId = $wechatKeyServices->value(['keys_like' => $msn, 'key_type' => 1], 'reply_id');
|
|
if ($replyId) {
|
|
//查询回复内容
|
|
$autoReplyData = app()->make(WechatReplyServices::class)->get($replyId)->toArray();
|
|
$msgData = json_decode($autoReplyData['data'], true);
|
|
$autoReplyMsn = $autoReplyData['type'] == 'text' ? $msgData['content'] : $msgData['src'];
|
|
$autoReply['to_uid'] = $uid;
|
|
$autoReply['msn_type'] = $autoReplyData['type'] == 'text' ? 1 : 3;
|
|
$autoReply['msn'] = $autoReplyMsn;
|
|
$autoReply['uid'] = $to_uid;
|
|
$autoReply['add_time'] = time();
|
|
$autoReply['is_tourist'] = 0;
|
|
$autoReply['type'] = 1;
|
|
$autoReply = $logServices->save($autoReply);
|
|
$autoReply = $autoReply->toArray();
|
|
$autoReply['_add_time'] = $autoReply['add_time'];
|
|
$autoReply['add_time'] = strtotime($autoReply['add_time']);
|
|
$kefuInfo = app()->make(StoreServiceServices::class)->get(['uid' => $to_uid], ['nickname', 'avatar']);
|
|
$replyMessagesCount = $logServices->getMessageNum(['uid' => $to_uid, 'to_uid' => $uid, 'type' => 0, 'is_tourist' => $isTourist ? 1 : 0]);
|
|
$autoReply['recored'] = $serviceRecored->saveRecord($to_uid, $uid, $autoReplyMsn, $formType ?? 0, $autoReplyData['type'] == 'text' ? 1 : 3, $replyMessagesCount, $isTourist, $kefuInfo['nickname'], $kefuInfo['avatar']);
|
|
$response->connection($this->service->user()[$uid])->send('reply', $autoReply);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 上下线
|
|
* @param TcpConnection $connection
|
|
* @param array $res
|
|
* @param Response $response
|
|
*/
|
|
public function online(TcpConnection &$connection, array $res, Response $response)
|
|
{
|
|
$online = $res['data']['online'] ?? 0;
|
|
$connections = $this->service->user();
|
|
if (isset($connection->user->uid)) {
|
|
$uids = $connection->user->uid;
|
|
/** @var StoreServiceServices $service */
|
|
$service = app()->make(StoreServiceServices::class);
|
|
$service->update(['uid' => $uids], ['online' => $online]);
|
|
//广播给正在和自己聊天的用户
|
|
foreach ($connections as $uid => $conn) {
|
|
if ($uid !== $uids && $uids == ($conn->chatToUid ?? 0)) {
|
|
$response->connection($conn)->send('online', ['online' => $online, 'uid' => $uids]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 客服转接
|
|
* @param TcpConnection $connection
|
|
* @param array $res
|
|
* @param Response $response
|
|
*/
|
|
public function transfer(TcpConnection &$connection, array $res, Response $response)
|
|
{
|
|
$data = $res['data'] ?? [];
|
|
$uid = $data['recored']['uid'] ?? 0;
|
|
if ($uid && $this->service->user($uid)) {
|
|
$data['recored']['online'] = 1;
|
|
} else {
|
|
$data['recored']['online'] = 0;
|
|
}
|
|
$response->send('transfer', $data);
|
|
}
|
|
}
|
|
|