Events.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. <?php
  2. /**
  3. * This file is part of workerman.
  4. *
  5. * Licensed under The MIT License
  6. * For full copyright and license information, please see the MIT-LICENSE.txt
  7. * Redistributions of files must retain the above copyright notice.
  8. *
  9. * @author walkor<walkor@workerman.net>
  10. * @copyright walkor<walkor@workerman.net>
  11. * @link http://www.workerman.net/
  12. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  13. */
  14. /**
  15. * 用于检测业务代码死循环或者长时间阻塞等问题
  16. * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
  17. * 然后观察一段时间workerman.log看是否有process_timeout异常
  18. */
  19. //declare(ticks=1);
  20. use \GatewayWorker\Lib\Gateway;
  21. use Workerman\Lib\Timer;
  22. /**
  23. * 主逻辑
  24. * 主要是处理 onConnect onMessage onClose 三个方法
  25. * onConnect 和 onClose 如果不需要可以不用实现并删除
  26. */
  27. class Events
  28. {
  29. /**
  30. * 新建一个类的静态成员,用来保存数据库实例
  31. */
  32. public static $db = null;
  33. public static $global = null;
  34. /**
  35. * 进程启动后初始化数据库连接
  36. */
  37. public static function onWorkerStart($worker)
  38. {
  39. if (empty(self::$global)) {
  40. self::$global = new \GlobalData\Client('127.0.0.1:2207');
  41. // 客服列表
  42. if (is_null(self::$global->kfList)) {
  43. self::$global->kfList = [];
  44. }
  45. // 会员列表[动态的,这里面只是目前未被分配的会员信息]
  46. if (is_null(self::$global->userList)) {
  47. self::$global->userList = [];
  48. }
  49. // 会员以 uid 为key的信息简表,只有在用户退出的时候,才去执行修改
  50. if (is_null(self::$global->uidSimpleList)) {
  51. self::$global->uidSimpleList = [];
  52. }
  53. // 当天的累积接入值
  54. $key = date('Ymd') . 'total_in';
  55. if (is_null(self::$global->$key)) {
  56. self::$global->$key = 0;
  57. $oldKey = date('Ymd', strtotime('-1 day')); // 删除前一天的统计值
  58. unset(self::$global->$oldKey);
  59. unset($oldKey, $key);
  60. }
  61. // 成功接入值
  62. $key = date('Ymd') . 'success_in';
  63. if (is_null(self::$global->$key)) {
  64. self::$global->$key = 0;
  65. $oldKey = date('Ymd', strtotime('-1 day')); // 删除前一天的统计值
  66. unset(self::$global->$oldKey);
  67. unset($oldKey, $key);
  68. }
  69. }
  70. // 定时统计数据
  71. if (0 == $worker->id) {
  72. self::writeLogKfStatus(0, 0, 0);
  73. // 1分钟统计一次实时数据
  74. Timer::add(60 * 1, function () {
  75. self::writeLog(1);
  76. });
  77. // 40分钟写一次当前日期点数的log数据
  78. Timer::add(60 * 40, function () {
  79. self::writeLog(2);
  80. });
  81. //每1分钟发一次本组排队数
  82. Timer::add(60 * 1, function () {
  83. self::lineup();
  84. });
  85. //初始化.....
  86. self::upsystemconfig();
  87. //每5分钟更新一次系统配置文件
  88. Timer::add(60 * 3, function () {
  89. self::upsystemconfig();
  90. });
  91. // 检查对话时效给出.
  92. Timer::add(6, function () {
  93. self::overTime();
  94. });
  95. // 实时监控.
  96. Timer::add(60, function () {
  97. $adminList = self::$global->adminList ?? [];
  98. if ($adminList) {
  99. self::systemMonitoring($adminList);
  100. }
  101. });
  102. self::resetServiceLog();
  103. }
  104. }
  105. /**
  106. * 每分钟定时向客服发送一次排队情况
  107. */
  108. public static function lineup()
  109. {
  110. }
  111. /**
  112. * 当客户端连接时触发
  113. * 如果业务不需此回调可以删除onConnect
  114. *
  115. * @param int $client_id 连接id
  116. */
  117. public static function onConnect($client_id)
  118. {
  119. // 检测是否开启自动应答
  120. $sayHello = self::$db->query('select `word`,`status` from `ws_reply` where `id` = 1');
  121. if (!empty($sayHello) && 1 == $sayHello['0']['status']) {
  122. $hello = [
  123. 'message_type' => 'helloMessage',
  124. 'data' => [
  125. 'name' => '智能助手',
  126. 'time' => date('H:i'),
  127. 'content' => $sayHello['0']['word']
  128. ]
  129. ];
  130. Gateway::sendToClient($client_id, json_encode($hello, 256));
  131. unset($hello);
  132. }
  133. unset($sayHello);
  134. // 检测是否开启广告
  135. $advertisement = self::$db->query('select * from `ws_advertisement` where `advertisement_status` = 1');
  136. if (!empty($advertisement)) {
  137. $chat_message = [
  138. 'message_type' => 'advertisement',
  139. 'data' => $advertisement
  140. ];
  141. Gateway::sendToClient($client_id, json_encode($chat_message, 256));
  142. unset($chat_message);
  143. }
  144. unset($advertisement);
  145. }
  146. /**
  147. * 当客户端发来消息时触发
  148. * @param int $client_id 连接id
  149. * @param mixed $message 具体消息
  150. */
  151. public static function onMessage($client_id, $message)
  152. {
  153. if ($message == '{"type":"ping"}') {
  154. Gateway::sendToCurrentClient('{"type":"pong"}');
  155. return;
  156. } else {
  157. self::DebugOut($message, "OnMessage");
  158. self::DebugOut([self::$global->kfList, self::$global->userList, self::$global->uidSimpleList, self::$global->userToKf, $_SESSION['remotip'] . ':' . $_SESSION['remotport']], 'Msg mem: ');
  159. }
  160. $message = json_decode($message, true);
  161. if (isset($message['type'])) {
  162. switch ($message['type']) {
  163. // 管理员初始化
  164. case 'adminInit':
  165. $token = $message['token'];
  166. self::adminInit($client_id, $token);
  167. break;
  168. // 客服初始化
  169. case 'init':
  170. $data = $message['data'];
  171. self::Kfinit($client_id, $data);
  172. break;
  173. // 顾客初始化
  174. case 'userInit';
  175. $data = $message['data'];
  176. self::userInitEnt($client_id, $data);
  177. break;
  178. //在线客服信息
  179. case 'getkfonlines':
  180. Gateway::sendToCurrentClient(json_encode(self::getkfonlines(), 256));
  181. break;
  182. case 'kfgetuserinfo':
  183. $tmp_id = isset($message['data']['id']) ? $message['data']['id'] : 0;
  184. self::kfgetuserinfo($client_id, intval($tmp_id));
  185. break;
  186. case 'chatMessage':
  187. break;
  188. // 转接
  189. case 'changeGroup':
  190. break;
  191. case 'closeUser':
  192. break;
  193. // 机器人问答.
  194. case 'toRobot':
  195. self::toRobot($client_id, $message);
  196. break;
  197. // 评价.
  198. case 'evaluate':
  199. self::evaluate($client_id, $message);
  200. break;
  201. // 客服关闭会话.
  202. case 'kfCloseUser':
  203. break;
  204. // 客服更改状态.
  205. case 'kfOnline':
  206. break;
  207. case 'changeOtherhKeFu';
  208. break;
  209. // 弹出评价.
  210. case 'getEvaluate';
  211. break;
  212. }
  213. }
  214. }
  215. //得到一个用户详细信息
  216. public static function kfgetuserinfo($clientid, $id)
  217. {
  218. $ret = self::$db->select('*')->from('ws_account')->where('id=:id')->bindValues(['id' => $id])->row();
  219. Gateway::sendToClient($clientid, json_encode(['message_type' => 'userdetailinfo', 'data' => $ret]));
  220. return;
  221. }
  222. //获取在线客服列表
  223. public static function getkfonlines()
  224. {
  225. }
  226. //客户工单内部组转接
  227. public static function changeOtherhKeFu($client_id, $smessage)
  228. {
  229. return;
  230. }
  231. //获取某个用户全部信息
  232. public static function getClientIndo($id)
  233. {
  234. $ret = self::$db->from('ws_accounts')->select("*")->where(['id' => $id])->row();
  235. return $ret;
  236. }
  237. //客服接入sock,及初始化
  238. public static function Kfinit($client_id, $message)
  239. {
  240. // TODO 尝试拉取用户来服务 [二期规划]
  241. }
  242. /**
  243. * 管理员
  244. * @param $client_id 服务ID
  245. * @param $message 数据
  246. */
  247. public static function adminInit($client_id, $token)
  248. {
  249. // 查询token是否存在.
  250. $systemConfigData = self::$db->query("SELECT `id` FROM `ws_admins` where `token`= '$token'");
  251. //print_r(self::$global->adminList);
  252. if ($systemConfigData) {
  253. $adminList = self::$global->adminList;
  254. $adminList[] = $client_id;
  255. self::$global->adminList = $adminList;
  256. self::systemMonitoring([$client_id]);
  257. } else {
  258. Gateway::closeClient($client_id);
  259. }
  260. }
  261. //客服登陆验证
  262. public static function KfloginCheck($client, $messageArray)
  263. {
  264. $uid = isset($messageArray['uid']) ? $messageArray['uid'] : '';
  265. $token = isset($messageArray['token']) ? $messageArray['token'] : '';
  266. if (empty($uid) || empty($token)) {
  267. return false;
  268. }
  269. $expire_time_vali = time() - 60 * 60 * 24;
  270. $kfid = intval(substr($uid, 2));
  271. $ret = self::$db->select('*')->from('ws_users')->where('id=:id and token=:token and expire_time>=:expire_time')->bindValues(array('id' => $kfid, 'token' => $token, 'expire_time' => $expire_time_vali))->row();
  272. if ($ret) {
  273. self::$db->update('ws_users')->cols(array('online_status' => 1, 'online_connectid' => $client))->where('id=' . $kfid)->query();
  274. return $ret;
  275. }
  276. return false;
  277. }
  278. //用户发送邦定用户事件
  279. public static function userInitEnt($client_id, $message)
  280. {
  281. // 尝试分配新会员进入服务
  282. self::userOnlineTask($client_id, $message['group'], $message['uid']);
  283. }
  284. /**
  285. * 当用户断开连接时触发
  286. * @param int $client_id 连接id
  287. *
  288. * tips: 当服务端主动退出的时候,会出现 exit status 9.原因是:服务端主动断开之后,连接的客户端会走这个方法,而短时间内进程
  289. * 需要处理这多的逻辑,又有cas操作,导致进程退出会超时,然后会被内核杀死,从而报出错误 9.实际对真正的业务没有任何的影响。
  290. */
  291. public static function onClose($client_id)
  292. {
  293. $isKefuoff = isset($_SESSION['iskefu']) ? $_SESSION['iskefu'] : 0;
  294. $uid = isset($_SESSION['uid']) ? $_SESSION['uid'] : false;
  295. echo "下线:uid: $uid - cid: $client_id - iskf: $isKefuoff \n";
  296. $adminList = self::$global->adminList ?? [];
  297. $key = array_search($client_id, $adminList);
  298. if (strlen($key)) {
  299. array_splice($adminList, $key, 1);
  300. self::$global->adminList = $adminList;
  301. }
  302. if (empty($uid)) {
  303. return;
  304. }
  305. if ($isKefuoff) {
  306. self::serviceOffline($client_id, $uid);
  307. } else {
  308. self::guestOffline($client_id, $uid);
  309. }
  310. return;
  311. }
  312. //客服下线了
  313. public static function serviceOffline($client_id, $uid)
  314. {
  315. self::writeLogKfStatus($uid, 0);
  316. return;
  317. }
  318. //用户下线了
  319. public static function guestOffline($client_id, $uid)
  320. {
  321. }
  322. /**
  323. * 客服结束会话
  324. *
  325. * tips: 未有$client_id的关闭
  326. */
  327. public static function closeUser($servicelog_id, $userId, $kf_id, $groupId)
  328. {
  329. }
  330. /**
  331. * 客服结束会话
  332. * @param int $client_id 连接id
  333. *
  334. * tips: 当服务端主动退出的时候,会出现 exit status 9.原因是:服务端主动断开之后,连接的客户端会走这个方法,而短时间内进程
  335. * 需要处理这多的逻辑,又有cas操作,导致进程退出会超时,然后会被内核杀死,从而报出错误 9.实际对真正的业务没有任何的影响。
  336. */
  337. public static function serverClose($client_id, $servicelog_id, $userId, $kf_id, $groupId)
  338. {
  339. }
  340. /**
  341. * 有人退出
  342. * @param $group
  343. */
  344. private static function userOfflineTask($group)
  345. {
  346. }
  347. /**
  348. * 有人进入执行分配
  349. * @param $client_id
  350. * @param $group
  351. * @param $uid
  352. */
  353. private static function userOnlineTask($client_id, $group, $uid = 0)
  354. {
  355. }
  356. //今天排序累加
  357. private static function todayqueuelength()
  358. {
  359. $dtype = 'user.queue.day.length';
  360. $today = date("Y-m-d");
  361. $sret = self::$db->select('*')->from('ws_countmidtable')->where('dtype=:dtype and mdate=:mdate')->bindValues(array('dtype' => $dtype, 'mdate' => $today))->row();
  362. if ($sret) {
  363. self::$db->update('ws_countmidtable')->cols(array('dcontent' => intval($sret['dcontent']) + 1))->where('id=' . $sret['id'])->query();
  364. } else {
  365. self::$db->insert('ws_countmidtable')->cols(array(
  366. 'dtype' => $dtype,
  367. 'mdate' => $today,
  368. 'datatype' => 1,
  369. 'dcontent' => 1))->query();
  370. }
  371. }
  372. //客服工单转单
  373. private static function servicetrutoother($type, $owen, $otherkfid, $serverid, $clientuid)
  374. {
  375. $owen = intval(substr($owen, 2));
  376. $otherkfid = intval(substr($otherkfid, 2));
  377. self::$db->insert('ws_serviceturn_log')->cols(array(
  378. 'stype' => $type,
  379. 'uid' => $owen,
  380. 'tuid' => $otherkfid,
  381. 'serverid' => $serverid,
  382. 'guestuid' => $clientuid
  383. ))->query();
  384. }
  385. /**
  386. * 给客服分配会员【均分策略】
  387. * @param $kfList
  388. * @param $userList
  389. * @param $group
  390. * @param $total
  391. */
  392. private static function assignmentTask($kfList, $userList, $group, $total, $uid = 0)
  393. {
  394. }
  395. /**
  396. * 获取最大的服务人数
  397. * @return int
  398. */
  399. private static function getMaxServiceNum()
  400. {
  401. $maxNumber = self::$db->query('select `max_service` from `ws_kf_config` where `id` = 1');
  402. if (!empty($maxNumber)) {
  403. $maxNumber = 5;
  404. } else {
  405. $maxNumber = $maxNumber['0']['max_service'];
  406. }
  407. return $maxNumber;
  408. }
  409. /**
  410. * 将内存中的数据写入统计表
  411. * @param int $flag
  412. */
  413. private static function writeLog($flag = 1)
  414. {
  415. }
  416. /**
  417. * 机器人问答
  418. * @param $client_id 服务ID
  419. * @param $message 数据
  420. */
  421. private static function toRobot($client_id, $message)
  422. {
  423. $groups_id = $message['data']['groups_id'];
  424. $robot_name = $message['data']['robot_name'];
  425. $robotgroups_id = $message['data']['robotgroups_id'];
  426. // 查询问题.
  427. $getRobot = self::$db->query("select `robot_content` from `ws_robot` where `robot_status`= 1 and `groups_id`= '" . $groups_id . "' and `robot_name`= '" . $robot_name . "' and `robotgroups_id`= '" . $robotgroups_id . "'");
  428. $chat_message = [
  429. 'message_type' => 'robotMessage',
  430. //'message_type' => 'chatMessage',
  431. 'data' => [
  432. 'name' => '智能助手',
  433. 'time' => date('H:i'),
  434. 'content' => $getRobot ? $getRobot[0]['robot_content'] : 'error',
  435. ]
  436. ];
  437. Gateway::sendToClient($client_id, json_encode($chat_message, 256));
  438. }
  439. /**
  440. * 评价
  441. * @param $client_id 服务ID
  442. * @param $message 数据
  443. */
  444. private static function evaluate($client_id, $message)
  445. {
  446. // 修改数据库.
  447. $evaluate_id = $message['data']['evaluate_id'];
  448. $result = self::$db->query("UPDATE `ws_service_log` SET `evaluate_id` = '" . $evaluate_id . "' WHERE `client_id`='" . $client_id . "'");
  449. if ($result) {
  450. $chat_message = [
  451. 'message_type' => 'evaluate',
  452. 'data' => [
  453. 'status' => 1,
  454. 'time' => date('H:i'),
  455. ]
  456. ];
  457. } else {
  458. $chat_message = [
  459. 'message_type' => 'evaluate',
  460. 'data' => [
  461. 'status' => 2,
  462. 'time' => date('H:i'),
  463. ]
  464. ];
  465. }
  466. Gateway::sendToClient($client_id, json_encode($chat_message, 256));
  467. }
  468. //获取系统配置
  469. private static function upsystemconfig()
  470. {
  471. $systemConfigData = self::$db->query("SELECT * FROM `ws_systemconfig`");
  472. $arr = [];
  473. if ($systemConfigData) {
  474. foreach ($systemConfigData as $item) {
  475. $arr[$item['systemconfig_enName']] = $item;
  476. }
  477. self::$global->systemconfig = $arr;
  478. }
  479. $group = self::$db->query("SELECT * FROM `ws_groups`");
  480. $arr = [];
  481. if ($group) {
  482. foreach ($group as $val) {
  483. $arr[$val['id']] = $val['name'];
  484. }
  485. self::$global->groupmap = $arr;
  486. }
  487. }
  488. /**
  489. * 超时
  490. * @param $client_id 服务ID
  491. * @param $message 数据
  492. */
  493. private static function overTime()
  494. {
  495. // 查询对话时效设置.
  496. $systemConfigData = self::$db->query("SELECT `systemconfig_data`,`systemconfig_enName`,`systemconfig_content` FROM `ws_systemconfig`");
  497. foreach ($systemConfigData as $k => $v) {
  498. if ($v['systemconfig_enName'] == 'overtime') {
  499. self::$global->overtime = $v;
  500. } elseif ($v['systemconfig_enName'] == 'unoperated') {
  501. self::$global->unoperated = $v;
  502. } elseif ($v['systemconfig_enName'] == 'noResponse') {
  503. self::$global->noResponse = $v;
  504. }
  505. }
  506. // 查询未断开的工单.
  507. $serviceLog = self::$db->query("SELECT `servicelog_id`,`client_id`,`start_time`,`user_id`,`kf_id`,`group_id` FROM `ws_service_log` WHERE `status`='1' OR `status`='3'");
  508. $whereOr = '1=0';
  509. foreach ($serviceLog as $k => $v) {
  510. if ($k == 0) {
  511. $whereOr = "`servicelog_id`=" . $v['servicelog_id'];
  512. } else {
  513. $whereOr .= " OR `servicelog_id`=" . $v['servicelog_id'];
  514. }
  515. }
  516. // 查询最后一次会话.
  517. //$chatLog = self::$db->query("SELECT `servicelog_id`,MAX(`time_line`) FROM `ws_chat_log` WHERE ".$whereOr." group by `servicelog_id`");
  518. $chatLog = self::$db->query("
  519. select * from ws_chat_log as a where time_line=(
  520. select max(b.time_line) from ws_chat_log as b where a.servicelog_id = b.servicelog_id and from_id not like 'KF%' and (" . $whereOr . ") group by servicelog_id
  521. )
  522. ");
  523. $setOvertime = strtotime('-' . (self::$global->overtime['systemconfig_data'] - 60) . ' second');
  524. $overtime = strtotime('-' . (self::$global->overtime['systemconfig_data']) . ' second');
  525. $setUnoperated = strtotime('-' . (self::$global->unoperated['systemconfig_data'] - 60) . ' second');
  526. $unoperated = strtotime('-' . (self::$global->unoperated['systemconfig_data']) . ' second');
  527. $noResponse = strtotime('-' . (self::$global->noResponse['systemconfig_data']) . ' second');
  528. foreach ($serviceLog as $k => $v) {
  529. if (!strlen(array_search($v['servicelog_id'], array_column($chatLog, 'servicelog_id')))) {
  530. if ($v['start_time'] <= $unoperated) {
  531. $servicelog_id = $v['servicelog_id'];
  532. self::$db->query("update `ws_service_log` set `servicelog_close_type` = 1 where `servicelog_id`= '$servicelog_id'");
  533. self::serverClose($v['client_id'], $servicelog_id, $v['user_id'], 'KF' . $v['kf_id'], $v['group_id']);
  534. // 如果小于设定时间前一分钟则给出提示.
  535. } elseif ($v['start_time'] <= $setUnoperated) {
  536. $chat_message = [
  537. 'message_type' => 'overtime',
  538. 'data' => [
  539. 'content' => self::$global->unoperated['systemconfig_content'],
  540. ]
  541. ];
  542. Gateway::sendToClient($v['client_id'], json_encode($chat_message, 256));
  543. }
  544. }
  545. }
  546. // 双方静默超时.
  547. foreach ($chatLog as $k => $v) {
  548. // 如果对话为客服的最后一次对话且时间小于设定时间则结束工单.
  549. if ($v['time_line'] <= $overtime) {
  550. $found_key = array_search($v['servicelog_id'], array_column($serviceLog, 'servicelog_id'));
  551. $servicelog_id = $v['servicelog_id'];
  552. self::$db->query("update `ws_service_log` set `servicelog_close_type` = 2 where `servicelog_id`= '$servicelog_id'");
  553. self::serverClose($serviceLog[$found_key]['client_id'], $servicelog_id, $serviceLog[$found_key]['user_id'], 'KF' . $serviceLog[$found_key]['kf_id'], $serviceLog[$found_key]['group_id']);
  554. // 如果对话为客服的最后一次对话且时间小于设定时间前一分钟则给出提示.
  555. } elseif ($v['time_line'] <= $setOvertime) {
  556. $chat_message = [
  557. 'message_type' => 'overtime',
  558. 'data' => [
  559. 'content' => self::$global->overtime['systemconfig_content'],
  560. ]
  561. ];
  562. $found_key = array_search($v['servicelog_id'], array_column($serviceLog, 'servicelog_id'));
  563. Gateway::sendToClient($serviceLog[$found_key]['client_id'], json_encode($chat_message, 256));
  564. }
  565. }
  566. }
  567. /**
  568. * 系统监控
  569. * @param $message 数据
  570. */
  571. private static function systemMonitoring($adminList)
  572. {
  573. // 查询未结束工单.
  574. $serviceLog = self::$db->query("select ws_service_log.servicelog_id,ws_users.user_name as server_name,ws_service_log.user_name,kf_id,start_time,end_time,ws_service_log.group_id,evaluate_id,intime,ws_service_log.status,alarm_userSensitive,alarm_serverSensitive,alarm_corresponding
  575. from `ws_service_log`
  576. join `ws_alarm` on ws_service_log.servicelog_id=ws_alarm.servicelog_id
  577. join `ws_users` on ws_service_log.kf_id=ws_users.id
  578. WHERE ws_service_log.status='1' OR ws_service_log.status='3'");
  579. // 查询系统设置表.
  580. $systemconfig = self::$db->query("SELECT `systemconfig_data`,`systemconfig_enName` FROM `ws_systemconfig` WHERE `systemconfig_enName`='verifyReturnTime' or `systemconfig_enName`='verifyAllTime'");
  581. $returnTimeKey = array_search('verifyReturnTime', array_column($systemconfig, 'systemconfig_enName'));
  582. // 质检会话响应时长.
  583. $verifyReturnTime = $systemconfig[$returnTimeKey]['systemconfig_data'];
  584. $allTimeKey = array_search('verifyAllTime', array_column($systemconfig, 'systemconfig_enName'));
  585. // 质检会话时长.
  586. $verifyAllTime = $systemconfig[$allTimeKey]['systemconfig_data'];
  587. // 差评次数.
  588. $evaluateCount = 0;
  589. // 未结束工单id.
  590. $servicelog_ids = '';
  591. $overtimeNumber = 0; // 会话超时次数.
  592. $overtimeTime = []; // 会话超时时间.
  593. $userSensitive = 0; // 用户敏感词报警次数.
  594. $serverSensitive = 0; // 客服敏感词报警次数.
  595. $csdNumber = 0; // 响应超时次数.
  596. $csdTime = []; // 响应超时时间.
  597. foreach ($serviceLog as $k => $v) {
  598. // 工单报警总次数.
  599. $allCount = 0;
  600. // 差评次数.
  601. if ($v['evaluate_id'] == 3) {
  602. $evaluateCount++;
  603. $allCount++;
  604. }
  605. $duration = time() - $v['start_time'];
  606. // 会话超时.
  607. if ($duration > $verifyAllTime) {
  608. $overtimeNumber++;
  609. $allCount++;
  610. $overtimeTime[] = $duration;
  611. }
  612. // 敏感词报警.
  613. $userSensitive += $v['alarm_userSensitive'];
  614. $allCount += $v['alarm_userSensitive'];
  615. $serverSensitive += $v['alarm_serverSensitive'];
  616. $allCount += $v['alarm_serverSensitive'];
  617. // 响应超时.
  618. if ($v['alarm_corresponding'] > $verifyReturnTime) {
  619. $csdTime[] = $v['alarm_corresponding'];
  620. $csdNumber++;
  621. $allCount++;
  622. }
  623. $serviceLog[$k]['allCount'] = $allCount;
  624. }
  625. self::DebugOut([$serviceLog, $csdTime, $verifyReturnTime], 'systemMonitoring');
  626. // 查询对话时效设置.
  627. foreach ($adminList as $v) {
  628. $chat_message = [
  629. 'message_type' => 'monitor',
  630. 'data' => [
  631. 'cvtList' => $serviceLog,
  632. 'userSensitive' => $userSensitive,
  633. 'serverSensitive' => $serverSensitive,
  634. 'csdNumber' => $csdNumber,
  635. 'csdTime' => $csdTime,
  636. 'overtimeNumber' => $overtimeNumber,
  637. 'overtimeTime' => $overtimeTime,
  638. 'evaluateCount' => $evaluateCount,
  639. ]
  640. ];
  641. Gateway::sendToClient($v, json_encode($chat_message, 256));
  642. }
  643. }
  644. //客服在线状态写组
  645. private static function writeLogKfStatus($kf, $status, $flag = 1)
  646. {
  647. if ($flag == 1) {
  648. $status = intval($status);
  649. if ($status == 0) {
  650. self::$db->delete('ws_kfonline')->where("uid='$kf'")->query();
  651. } else {
  652. $now = date('Y-m-d H:i;s');
  653. $ip = isset($_SESSION['remotip']) ? $_SESSION['remotip'] : '';
  654. $sql = "insert into ws_kfonline(uid,status,uptime,ip) values('$kf',$status,'$now','$ip') ON DUPLICATE KEY UPDATE status=$status,uptime='$now' ";
  655. self::$db->query($sql);
  656. }
  657. } else {
  658. self::$db->query("delete from ws_kfonline ");
  659. }
  660. }
  661. public static function resetServiceLog($kfid = 0)
  662. {
  663. $t = time() - 24 * 3600;
  664. if ($kfid) {
  665. if ((substr($kfid, 0, 2) == 'KF')) {
  666. $kfid = intval(substr($kfid, 2));
  667. }
  668. $kfid = intval($kfid);
  669. self::$db->query("update ws_service_log set status=2 where kf_id=$kfid and start_time>=$t and status!=2");
  670. } else {
  671. self::$db->query("update ws_service_log set status=2 where start_time>=$t and status!=2");
  672. }
  673. }
  674. public static function onWorkerStop($businessWorker)
  675. {
  676. if ($businessWorker->worker_id == 1) {
  677. self::resetServiceLog();
  678. }
  679. }
  680. //用户下线通知
  681. private static function userCloseNotice($client_id, $cuid, $group)
  682. {
  683. }
  684. //踢掉同一用户的旧用户
  685. private static function tickOlduser($uid)
  686. {
  687. }
  688. private static function DebugOut($msg, $title = '', $type = 'info')
  689. {
  690. $config = self::$global->systemconfig;
  691. if (!isset($config['isdebug']) || empty($config['isdebug']['systemconfig_data'])) {
  692. return;
  693. }
  694. if (!is_string($msg)) {
  695. $msg = json_encode([$msg], 256);
  696. }
  697. $msg = date("Y-m-d H:i:s") . ' - ' . $type . ' - ' . $title . ' - ' . $msg . "\n";
  698. echo $msg;
  699. }
  700. }