ChatHandle.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2023 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace crmeb\services\workerman\chat;
  12. use app\services\kefu\LoginServices;
  13. use app\services\kefu\service\StoreServiceLogServices;
  14. use app\services\kefu\service\StoreServiceRecordServices;
  15. use app\services\kefu\service\StoreServiceServices;
  16. use app\services\order\StoreOrderServices;
  17. use app\services\product\product\StoreProductServices;
  18. use app\services\user\UserServices;
  19. use app\services\wechat\WechatKeyServices;
  20. use app\services\wechat\WechatReplyServices;
  21. use app\services\wechat\WechatUserServices;
  22. use app\services\user\UserAuthServices;
  23. use crmeb\exceptions\AuthException;
  24. use crmeb\services\app\WechatService;
  25. use crmeb\services\workerman\Response;
  26. use crmeb\utils\Arr;
  27. use think\facade\Log;
  28. use Workerman\Connection\TcpConnection;
  29. /**
  30. * Class ChatHandle
  31. * @package crmeb\services\workerman\chat
  32. */
  33. class ChatHandle
  34. {
  35. /**
  36. * @var ChatService
  37. */
  38. protected $service;
  39. /**
  40. * ChatHandle constructor.
  41. * @param ChatService $service
  42. */
  43. public function __construct(ChatService &$service)
  44. {
  45. $this->service = &$service;
  46. }
  47. /**
  48. * 客服登录
  49. * @param TcpConnection $connection
  50. * @param array $res
  51. * @param Response $response
  52. * @return bool|null
  53. * @throws \Psr\SimpleCache\InvalidArgumentException
  54. * @throws \think\db\exception\DataNotFoundException
  55. * @throws \think\db\exception\DbException
  56. * @throws \think\db\exception\ModelNotFoundException
  57. */
  58. public function kefu_login(TcpConnection &$connection, array $res, Response $response)
  59. {
  60. if (!isset($res['data']) || !$token = $res['data']) {
  61. return $response->close([
  62. 'msg' => '授权失败!'
  63. ]);
  64. }
  65. try {
  66. /** @var LoginServices $services */
  67. $services = app()->make(LoginServices::class);
  68. $kefuInfo = $services->parseToken($token);
  69. } catch (AuthException $e) {
  70. return $response->close([
  71. 'msg' => $e->getMessage()
  72. ]);
  73. }
  74. $connection->kefuUser = $kefuInfo;
  75. /** @var UserServices $userService */
  76. $userService = app()->make(UserServices::class);
  77. $connection->user = $userService->get($kefuInfo['uid'], ['uid', 'nickname']);
  78. if (!isset($connection->user->uid)) {
  79. return $response->close([
  80. 'msg' => '您登录的客服用户不存在'
  81. ]);
  82. }
  83. /** @var StoreServiceRecordServices $service */
  84. $service = app()->make(StoreServiceRecordServices::class);
  85. $service->updateRecord(['to_uid' => $connection->user->uid], ['online' => 1]);
  86. /** @var StoreServiceServices $service */
  87. $service = app()->make(StoreServiceServices::class);
  88. $service->update(['uid' => $connection->user->uid], ['online' => 1]);
  89. $this->service->setKefuUser($connection);
  90. return $response->success();
  91. }
  92. /**
  93. * 用户登录
  94. * @param TcpConnection $connection
  95. * @param array $res
  96. * @param Response $response
  97. * @return bool|null
  98. */
  99. public function login(TcpConnection &$connection, array $res, Response $response)
  100. {
  101. if (!isset($res['data']) || !$token = $res['data']) {
  102. return $response->close([
  103. 'msg' => '授权失败!'
  104. ]);
  105. }
  106. try {
  107. /** @var UserAuthServices $services */
  108. $services = app()->make(UserAuthServices::class);
  109. $authInfo = $services->parseToken($token);
  110. } catch (AuthException $e) {
  111. return $response->close([
  112. 'msg' => $e->getMessage()
  113. ]);
  114. }
  115. $connection->user = $authInfo['user'];
  116. $connection->tokenData = $authInfo['tokenData'];
  117. $this->service->setUser($connection);
  118. /** @var StoreServiceRecordServices $service */
  119. $service = app()->make(StoreServiceRecordServices::class);
  120. $service->updateRecord(['to_uid' => $connection->user->uid], ['online' => 1, 'type' => $res['form_type'] ?? 1]);
  121. $connections = $this->service->kefuUser();
  122. foreach ($connections as &$conn) {
  123. if (!isset($conn->onlineUids) || !in_array($connection->user->uid, $conn->onlineUids ?? [])) {
  124. $response->connection($conn)->send('user_online', ['to_uid' => $connection->user->uid, 'online' => 1]);
  125. }
  126. if (!isset($conn->onlineUids)) {
  127. $conn->onlineUids = [];
  128. }
  129. array_push($conn->onlineUids, $connection->user->uid);
  130. $this->service->setKefuUser($conn, false);
  131. }
  132. return $response->connection($connection)->success();
  133. }
  134. /**
  135. *
  136. * @param TcpConnection $connection
  137. * @param array $res
  138. * @param Response $response
  139. */
  140. public function to_chat(TcpConnection &$connection, array $res, Response $response)
  141. {
  142. $tourist_uid = $res['data']['tourist_uid'] ?? 0;
  143. if ($tourist_uid) {
  144. $connection->isTourist = true;
  145. $connection->user = (object)['uid' => $tourist_uid];
  146. $connections = $this->service->user();
  147. if (!isset($connections[$tourist_uid])) {
  148. $this->service->setUser($connection);
  149. }
  150. }
  151. $connection->chatToUid = $res['data']['id'] ?? 0;
  152. if (isset($connection->user)) {
  153. $uid = $connection->user->uid;
  154. if ($connection->chatToUid && !isset($connection->isTourist)) {
  155. /** @var StoreServiceRecordServices $service */
  156. $service = app()->make(StoreServiceRecordServices::class);
  157. $service->update(['user_id' => $uid, 'to_uid' => $connection->chatToUid], ['mssage_num' => 0]);
  158. /** @var StoreServiceLogServices $logServices */
  159. $logServices = app()->make(StoreServiceLogServices::class);
  160. $logServices->update(['uid' => $connection->chatToUid, 'to_uid' => $uid], ['type' => 1]);
  161. }
  162. $response->send('mssage_num', ['uid' => $connection->chatToUid, 'num' => 0, 'recored' => (object)[]]);
  163. }
  164. }
  165. /**
  166. * 用户向客服发送消息
  167. * @param TcpConnection $connection
  168. * @param array $res
  169. * @param Response $response
  170. * @return bool|null
  171. * @throws \think\db\exception\DataNotFoundException
  172. * @throws \think\db\exception\DbException
  173. * @throws \think\db\exception\ModelNotFoundException
  174. */
  175. public function chat(TcpConnection &$connection, array $res, Response $response)
  176. {
  177. $to_uid = $res['data']['to_uid'] ?? 0;
  178. $msn_type = $res['data']['type'] ?? 0;
  179. $msn = $res['data']['msn'] ?? '';
  180. $formType = $res['form_type'] ?? 0;
  181. //是否为游客
  182. $isTourist = $res['data']['is_tourist'] ?? 0;
  183. $tourist_uid = $res['data']['tourist_uid'] ?? 0;
  184. $isTourist = $isTourist && $tourist_uid;
  185. $tourist_avatar = $res['data']['tourist_avatar'] ?? '';
  186. $uid = $isTourist ? $tourist_uid : $connection->user->uid;
  187. if (!$to_uid) {
  188. return $response->send('err_tip', ['msg' => '用户不存在']);
  189. }
  190. if ($to_uid == $uid) {
  191. return $response->send('err_tip', ['msg' => '不能和自己聊天']);
  192. }
  193. /** @var StoreServiceLogServices $logServices */
  194. $logServices = app()->make(StoreServiceLogServices::class);
  195. if (!in_array($msn_type, $logServices::MSN_TYPE)) {
  196. return $response->send('err_tip', ['msg' => '格式错误']);
  197. }
  198. $msn = trim(strip_tags(str_replace(["\n", "\t", "\r", "&nbsp;"], '', htmlspecialchars_decode($msn))));
  199. $data = compact('to_uid', 'msn_type', 'msn', 'uid');
  200. $data['add_time'] = time();
  201. $data['is_tourist'] = $res['data']['is_tourist'] ?? 0;
  202. $connections = $this->service->user();
  203. $online = isset($connections[$to_uid]) && isset($connections[$to_uid]->chatToUid) && $connections[$to_uid]->chatToUid == $uid;
  204. $data['type'] = $online ? 1 : 0;
  205. $data = $logServices->save($data);
  206. $data = $data->toArray();
  207. $data['_add_time'] = $data['add_time'];
  208. $data['add_time'] = strtotime($data['add_time']);
  209. if (!$isTourist) {
  210. if (isset($this->service->kefuUser()[$data['uid']])) {
  211. /** @var StoreServiceServices $userService */
  212. $userService = app()->make(StoreServiceServices::class);
  213. $_userInfo = $userService->get(['uid' => $data['uid']], ['nickname', 'avatar']);
  214. } else {
  215. /** @var UserServices $userService */
  216. $userService = app()->make(UserServices::class);
  217. $_userInfo = $userService->getUserInfo($data['uid'], 'nickname,avatar');
  218. }
  219. $data['nickname'] = $_userInfo['nickname'];
  220. $data['avatar'] = $_userInfo['avatar'];
  221. } else {
  222. $avatar = sys_config('tourist_avatar');
  223. $_userInfo['avatar'] = $tourist_avatar ?: Arr::getArrayRandKey(is_array($avatar) ? $avatar : []);
  224. $_userInfo['nickname'] = '游客' . $uid;
  225. $data['nickname'] = $_userInfo['nickname'];
  226. $data['avatar'] = $_userInfo['avatar'];
  227. }
  228. //商品消息类型
  229. $data['productInfo'] = [];
  230. if ($msn_type == StoreServiceLogServices::MSN_TYPE_GOODS && $msn) {
  231. /** @var StoreProductServices $productServices */
  232. $productServices = app()->make(StoreProductServices::class);
  233. $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');
  234. $data['productInfo'] = $productInfo ? $productInfo->toArray() : [];
  235. }
  236. //订单消息类型
  237. $data['orderInfo'] = [];
  238. if ($msn_type == StoreServiceLogServices::MSN_TYPE_ORDER && $msn) {
  239. /** @var StoreOrderServices $orderServices */
  240. $orderServices = app()->make(StoreOrderServices::class);
  241. $order = $orderServices->getUserOrderDetail($msn, $uid);
  242. if ($order) {
  243. $order = $orderServices->tidyOrder($order->toArray(), true, true);
  244. $order['add_time_y'] = date('Y-m-d', $order['add_time']);
  245. $order['add_time_h'] = date('H:i:s', $order['add_time']);
  246. $data['orderInfo'] = $order;
  247. }
  248. }
  249. //给自己回复消息
  250. $response->send('chat', $data);
  251. //用户向客服发送消息,判断当前客服是否在登录中
  252. /** @var StoreServiceRecordServices $serviceRecored */
  253. $serviceRecored = app()->make(StoreServiceRecordServices::class);
  254. $unMessagesCount = $logServices->getMessageNum(['uid' => $uid, 'to_uid' => $to_uid, 'type' => 0, 'is_tourist' => $isTourist ? 1 : 0]);
  255. //记录当前用户和他人聊天记录
  256. $data['recored'] = $serviceRecored->saveRecord($uid, $to_uid, $msn, $formType ?? 0, $msn_type, $unMessagesCount, $isTourist, $data['nickname'], $data['avatar']);
  257. //是否在线
  258. if ($online) {
  259. $response->connection($this->service->user()[$to_uid])->send('reply', $data);
  260. } else {
  261. //用户在线,可是没有和当前用户进行聊天,给当前用户发送未读条数
  262. if (isset($connections[$to_uid])) {
  263. $data['recored']['nickname'] = $_userInfo['nickname'];
  264. $data['recored']['avatar'] = $_userInfo['avatar'];
  265. $response->connection($this->service->user()[$to_uid])->send('mssage_num', [
  266. 'uid' => $uid,
  267. 'num' => $unMessagesCount,
  268. 'recored' => $data['recored']
  269. ]);
  270. }
  271. if ($isTourist) {
  272. return true;
  273. }
  274. //用户不在线
  275. /** @var WechatUserServices $wechatUserServices */
  276. $wechatUserServices = app()->make(WechatUserServices::class);
  277. $userInfo = $wechatUserServices->getOne(['uid' => $to_uid, 'user_type' => 'wechat'], 'nickname,subscribe,openid,headimgurl');
  278. if ($userInfo && $userInfo['subscribe'] && $userInfo['openid']) {
  279. $description = '您有新的消息,请注意查收!';
  280. if ($formType) {
  281. $head = '客服接待消息提醒';
  282. $url = sys_config('site_url') . '/kefu/mobile_chat?toUid=' . $uid . '&nickname=' . $_userInfo['nickname'];
  283. } else {
  284. $head = '客服回复消息提醒';
  285. $url = sys_config('site_url') . '/pages/extension/customer_list/chat?uid=' . $uid;
  286. }
  287. $message = WechatService::newsMessage($head, $description, $url, $_userInfo['avatar']);
  288. $userInfo = $userInfo->toArray();
  289. try {
  290. WechatService::staffService()->message($message)->to($userInfo['openid'])->send();
  291. } catch (\Exception $e) {
  292. Log::error($userInfo['nickname'] . '发送失败' . $e->getMessage());
  293. }
  294. }
  295. }
  296. if (!isset($this->service->kefuUser()[$uid])) {
  297. //判断是否有自动回复
  298. $wechatKeyServices = app()->make(WechatKeyServices::class);
  299. $replyId = $wechatKeyServices->value(['keys' => $msn, 'key_type' => 1], 'reply_id');
  300. if(!$replyId) $replyId = $wechatKeyServices->value(['keys_like' => $msn, 'key_type' => 1], 'reply_id');
  301. if ($replyId) {
  302. //查询回复内容
  303. $autoReplyData = app()->make(WechatReplyServices::class)->get($replyId)->toArray();
  304. $msgData = json_decode($autoReplyData['data'], true);
  305. $autoReplyMsn = $autoReplyData['type'] == 'text' ? $msgData['content'] : $msgData['src'];
  306. $autoReply['to_uid'] = $uid;
  307. $autoReply['msn_type'] = $autoReplyData['type'] == 'text' ? 1 : 3;
  308. $autoReply['msn'] = $autoReplyMsn;
  309. $autoReply['uid'] = $to_uid;
  310. $autoReply['add_time'] = time();
  311. $autoReply['is_tourist'] = 0;
  312. $autoReply['type'] = 1;
  313. $autoReply = $logServices->save($autoReply);
  314. $autoReply = $autoReply->toArray();
  315. $autoReply['_add_time'] = $autoReply['add_time'];
  316. $autoReply['add_time'] = strtotime($autoReply['add_time']);
  317. $kefuInfo = app()->make(StoreServiceServices::class)->get(['uid' => $to_uid], ['nickname', 'avatar']);
  318. $replyMessagesCount = $logServices->getMessageNum(['uid' => $to_uid, 'to_uid' => $uid, 'type' => 0, 'is_tourist' => $isTourist ? 1 : 0]);
  319. $autoReply['recored'] = $serviceRecored->saveRecord($to_uid, $uid, $autoReplyMsn, $formType ?? 0, $autoReplyData['type'] == 'text' ? 1 : 3, $replyMessagesCount, $isTourist, $kefuInfo['nickname'], $kefuInfo['avatar']);
  320. $response->connection($this->service->user()[$uid])->send('reply', $autoReply);
  321. }
  322. }
  323. }
  324. /**
  325. * 上下线
  326. * @param TcpConnection $connection
  327. * @param array $res
  328. * @param Response $response
  329. */
  330. public function online(TcpConnection &$connection, array $res, Response $response)
  331. {
  332. $online = $res['data']['online'] ?? 0;
  333. $connections = $this->service->user();
  334. if (isset($connection->user->uid)) {
  335. $uids = $connection->user->uid;
  336. /** @var StoreServiceServices $service */
  337. $service = app()->make(StoreServiceServices::class);
  338. $service->update(['uid' => $uids], ['online' => $online]);
  339. //广播给正在和自己聊天的用户
  340. foreach ($connections as $uid => $conn) {
  341. if ($uid !== $uids && $uids == ($conn->chatToUid ?? 0)) {
  342. $response->connection($conn)->send('online', ['online' => $online, 'uid' => $uids]);
  343. }
  344. }
  345. }
  346. }
  347. /**
  348. * 客服转接
  349. * @param TcpConnection $connection
  350. * @param array $res
  351. * @param Response $response
  352. */
  353. public function transfer(TcpConnection &$connection, array $res, Response $response)
  354. {
  355. $data = $res['data'] ?? [];
  356. $uid = $data['recored']['uid'] ?? 0;
  357. if ($uid && $this->service->user($uid)) {
  358. $data['recored']['online'] = 1;
  359. } else {
  360. $data['recored']['online'] = 0;
  361. }
  362. $response->send('transfer', $data);
  363. }
  364. }