博客列表 >仿百度商桥项目-DOM操作实现客服与多客户聊天/更换识别终端用户的方式/聊天记录保存到数据库

仿百度商桥项目-DOM操作实现客服与多客户聊天/更换识别终端用户的方式/聊天记录保存到数据库

岂几岂几
岂几岂几原创
2020年07月05日 01:05:111323浏览

仿百度商桥项目-DOM操作实现客服与多客户聊天/更换识别终端用户的方式/聊天记录保存到数据库

学习心得

  • 都是复习, DOM操作, js,jQuery操作等. 基本实现西门老师上课讲的功能内容.

  • cURL的使用, 基础课没教, 在这里卡了蛮久, 需要找时间学习.

1. 实现思路

  • 切换客户聊天记录容器: 使用类似朱老师教的”页签菜单切换”demo.

  • 客服端刷新后, 找回正在沟通的客户: 客户端在创建链接前, 强制先输入手机号. 客服端通过后台管理系统, 用账号登录. 这样就可以用手机号或客服账号做客户连接对象全局数组/客服连接对象全局数组的key. 同时连接对象也设置属性identity, 为其赋值客户手机号或客服账号.

    • 客服刷新页面后, 可以遍历客户连接全局数组, 找到连接属性sendId值为客服账号的连接属性, 将其identity(即手机号)发送消息给客服页面, 客服页面遍历手机号数组, 把这些客户重新添加回客户列表. 然后以客户手机号, 客服账号做查询条件, 使用cURL模拟想laravel发送GET请求, 通过laravel从数据库中查询聊天记录, 并渲染回各个客户的”聊天页签”中(回填聊天记录没做了, 懒…).
  • 提供websocket通信的服务器(也就是warkerman的应用服务器), 使用cURL模拟发送POST请求到laravel, 通过laravel的控制器方法操作数据库, 把聊天记录保存到数据库.

    • 因为是服务器和服务器之间通信, 所以可以不必验证token, 在laravel中的VerifyCsrfToken中间件中, 把定位到保存聊天记录到数据库的控制器方法的路由加入到$except属性数组中, 即, 告诉laravel, 这个路由的post请求就不用验证token了.

2. 效果图

  • 1- 客服端界面

  • 2- 客户端界面

  • 3- 客服小姐姐分配到客户时的客服端界面

  • 4- 客户端收发消息

  • 5-客服端收发消息

  • 6-客服端切换聊天对象
  • 7- 数据库数据

3. 代码清单

  • 1- 使用warkerman搭建websocket通信服务器
  1. <?php
  2. use Workerman\Worker;
  3. require_once __DIR__ . '/workerman/Autoloader.php';
  4. // 加载发送请求的方法
  5. require_once 'save_msg.php';
  6. // 注意:这里与上个例子不同,使用的是websocket协议
  7. $ws_worker = new Worker("websocket://0.0.0.0:2000");
  8. // 启动4个进程对外提供服务
  9. $ws_worker->count = 4;
  10. /* 创建全局连接数组,key=连接的id,val=连接; 因为存在客户可能刷新浏览器页面, 触发websocket给同一个客户再分配一个新的连接id
  11. * 所以, 一般是用客户保存在数据表中的用户id来做key, 每次刷新, 用户id都跟最新分配的连接关联, 就能解决用户刷新的问题了.
  12. * 用户id从哪来? 可以在登录成功后把用户id保存到cookie中.
  13. * 另一种方案: 把$conns链接数组保存到缓存(如: Redis)中, 同样用用户id做key, 序列化/json格式化的连接做value.
  14. */
  15. /*
  16. * 新版本: key改为客户在前端录入的手机号, 使用消息数组中key为from_id的元素存储;
  17. * 客服数组的key改为管理员账号, 同样用from_id存储
  18. */
  19. // 客户连接数组
  20. $customerConns = [];
  21. // 客服连接数组
  22. $servicerConns = [];
  23. // 发送约定格式的消息数组(转换成json)
  24. function sendMessage($conn, $from_id, $type, $msg, $custom_id=null) {
  25. $sendInfo['from_id'] = $from_id;
  26. $sendInfo['type'] = $type;
  27. $sendInfo['msg'] = $msg;
  28. if($custom_id != null) {
  29. $sendInfo['custom_id'] = $custom_id;
  30. }
  31. $conn->send(json_encode($sendInfo));
  32. }
  33. function saveMessage($from, $to, $msg) {
  34. $msgInfo['from'] = $from;
  35. $msgInfo['to'] = $to;
  36. $msgInfo['msg'] = $msg;
  37. $msgInfo['send_time'] = time();
  38. saveMsg('cms.com/admin/servicer/savemsg', $msgInfo);
  39. }
  40. // 当收到客户端发来的数据后返回hello $data给客户端
  41. $ws_worker->onMessage = function($connection, $data)
  42. {
  43. global $customerConns;
  44. global $servicerConns;
  45. // 分辨用户类型, 只能客户和客服之间通信.
  46. // 判断客户端模拟用户登录状态的标识type, 若值为login标识登录成功. 则把用户的类型(客户/客服)
  47. // 以自定义属性的方式设置到$connection(即连接)中
  48. // json->数组
  49. $data = json_decode($data, true);
  50. if($data['type'] == 'login') {
  51. var_dump($connection->id);
  52. // 给连接动态加入group属性,标识[客户]和[客服]
  53. $connection->group = $data['group'];
  54. // 改用客户前端录入的手机号/客服小姐姐的登录账号作为连接的识别码, 不用$connection->id了
  55. $connection->identity = $data['from_id'];
  56. // 模拟"登录"的连接,放入到对应连接数组中
  57. if($data['group'] == 'admin') {// admin标识为[客服]
  58. var_dump('客服小姐姐' . $data['from_id'] . '上线了, 其分配到的链接id为: ' . $connection->id);
  59. // 改用客服小姐姐的登录账号作为key
  60. // $servicerConns[$connection->id] = $connection;
  61. $servicerConns[$data['from_id']] = $connection;
  62. // 遍历客户连接全局数组, 找到sendId为当前客服小姐姐的客户链接, 返回给这个客服小姐姐
  63. $customPhoneNumbers = [];
  64. foreach($customerConns as $phoneNumber => $customerConn) {
  65. if(!empty($customerConn->sendId) && $customerConn->sendId == $connection->identity) {
  66. $customPhoneNumbers[] = $phoneNumber;
  67. }
  68. }
  69. // 找到这位客服小姐姐服务的客户
  70. if(count($customPhoneNumbers) > 0) {
  71. sendMessage($connection, 'system', 'login', '继续为[' . implode(',', $customPhoneNumbers) . ']服务吧', $customPhoneNumbers);
  72. }
  73. } else {// member标识为[客户]
  74. // DEL: 改用用户在前端录入的手机号作为key
  75. // $customerConns[$connection->id] = $connection;
  76. $customerConns[$data['from_id']] = $connection;
  77. // 如果是客户登录, 还需要给他安排一个客服(array_rand()函数随机返回数组元素的key值)
  78. $connection->sendId = array_rand($servicerConns, 1);
  79. // 没有小姐姐可供分配
  80. // if(!is_numeric($connection->sendId)) {
  81. if(empty($connection->sendId)) {
  82. sendMessage($connection, 'system', 'msg', '暂无客服小姐姐在线, 请稍后刷新重试');
  83. var_dump('暂无客服小姐姐');
  84. return;
  85. }
  86. var_dump('给手机号为'.$connection->identity.'的客户(链接id: '.$connection->id.')分配的是'.$connection->sendId.'客服小姐姐(链接id: '.$servicerConns[$connection->sendId]->id.')');
  87. // 通知这位客服, 有新客户进来
  88. $target = $servicerConns[$connection->sendId];
  89. sendMessage($target, 'system', 'login', '有新客户登录, 电话号码为: ' . $connection->identity, $connection->identity);
  90. }
  91. } else if($data['type'] == 'msg') {// 模拟"发送数据"的连接
  92. // 判断发送数据的连接是[客服]还是[客户]
  93. if($connection->group == 'member') {// 客户
  94. // if(!is_numeric($connection->sendId)) {
  95. if(empty($connection->sendId)) {
  96. sendMessage($connection, 'system', 'msg', '为您服务的小姐姐可能已掉线, 请刷新');
  97. var_dump('暂无客服小姐姐');
  98. return;
  99. }
  100. // 获取在客户登录时指定的客服连接
  101. $target = $servicerConns[$connection->sendId];
  102. var_dump('客户' .$connection->identity . '给账号为' . $connection->sendId . '的客服小姐姐发信息');
  103. sendMessage($target, $connection->identity, 'msg', $data['msg']);
  104. // 保存消息到数据库
  105. saveMessage($connection->identity, $target->identity, $data['msg']);
  106. } else if($connection->group == 'admin') {// 客服
  107. // 跟谁说话
  108. $phoneNumber = $data['sendId'];
  109. if(empty($phoneNumber) || !isset($customerConns[$phoneNumber])) {
  110. sendMessage($connection, 'system', 'msg', '该用户不存在, 可能已下线');
  111. return;
  112. }
  113. // 要跟其说话的客户的链接对象
  114. $target = $customerConns[$phoneNumber];
  115. sendMessage($target, $connection->identity, 'msg', $data['msg']);
  116. saveMessage($connection->identity, $target->identity, $data['msg']);
  117. var_dump('客服' .$connection->identity . '给手机号为' . $phoneNumber . '的客户发信息');
  118. }
  119. }
  120. };
  121. // 当有连接断开时
  122. $ws_worker->onClose = function($connection) {
  123. global $servicerConns;
  124. global $customerConns;
  125. if($connection->group == 'admin') {// 客服断开
  126. $unconnId = $connection->identity;//$connection->id;
  127. // 把断开的客服连接移除出客服连接数组
  128. unset($servicerConns[$connection->id]);
  129. var_dump('客服' . $unconnId . '下线了');
  130. // 客服断开, 需要给该客服负责的客户重新分配新客服
  131. foreach($customerConns as $customerConn) {
  132. if($customerConn->sendId == $unconnId) {// 该客服小姐姐负责的客户
  133. $customerConn->sendId = array_rand($servicerConns, 1);
  134. // 没分配到客服小姐姐, 直接结束分配.
  135. if(empty($customerConn->sendId)) {
  136. sendMessage($connection, 'system', 'msg', '坏了, 客服小姐姐的网络开小差了, 请稍后刷新重试吧');
  137. var_dump('当前没有客服小姐姐在线');
  138. continue;
  139. }
  140. var_dump('重新给手机号为'.$customerConn->identity.'的客户(链接id: '.$customerConn->id.')分配的是'.$customerConn->sendId.'客服小姐姐(链接id: '.$servicerConns[$customerConn->sendId]->id.')');
  141. // 新分配的客服小姐姐的连接
  142. $target = $servicerConns[$customerConn->sendId];
  143. // 给新分配到的客服小姐姐发分配消息
  144. sendMessage($target, 'system', 'login', '有新客户登录, id为:' . $customerConn->identity, $customerConn->identity);
  145. }
  146. }
  147. } else {// 客户断开
  148. // 断开的客户连接id
  149. $unconnId = $connection->identity;
  150. var_dump('客户' . $unconnId . '下线了');
  151. // 负责该客户的客服小姐姐id
  152. $servicerId = $connection->sendId;
  153. // 把断开的客户连接移除出客户连接数组
  154. unset($customerConns[$connection->identity]);
  155. if(empty($servicerId)) {// 判断断开的客户是否有客服接待, 没有, 则直接断开客户连接;
  156. return;
  157. }
  158. // 客户断开, 系统给负责该客户的客服发消息, 不必再负责该客户
  159. $target = $servicerConns[$servicerId];
  160. sendMessage($target, 'system', 'logout', '客户' . $unconnId . '跟你说了声拜拜后, 下线了', $unconnId);
  161. }
  162. };
  163. // 运行worker
  164. Worker::runAll();
  • 2- 使用cURL模拟发送保存聊天记录的脚本
  1. <?php
  2. function saveMsg($url, $data) {
  3. // $header = ["Content-Type:application/x-www-form-urlencoded", "token:test", "client:h5"];
  4. $header = ["Content-Type:application/json", "token:test", "client:h5"];
  5. $res = curlPost($url, $data, 5, $header, 'json');
  6. var_dump($res);
  7. return $res;
  8. }
  9. /**
  10. * 传入数组进行HTTP POST请求
  11. */
  12. function curlPost($url, $post_data = array(), $timeout = 5, $header = "", $data_type = "") {
  13. $header = empty($header) ? '' : $header;
  14. //支持json数据数据提交
  15. if($data_type == 'json'){
  16. $post_string = json_encode($post_data);
  17. }elseif($data_type == 'array') {
  18. $post_string = $post_data;
  19. }elseif(is_array($post_data)){
  20. $post_string = http_build_query($post_data, '', '&');
  21. }
  22. var_dump($post_string);
  23. $ch = curl_init(); // 启动一个CURL会话
  24. curl_setopt($ch, CURLOPT_URL, $url); // 要访问的地址
  25. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查 // https请求 不验证证书和hosts
  26. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
  27. // curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
  28. //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
  29. curl_setopt($ch, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
  30. curl_setopt($ch, CURLOPT_POST, true); // 发送一个常规的Post请求
  31. curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string); // Post提交的数据包
  32. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); // 设置超时限制防止死循环
  33. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  34. //curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
  35. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 获取的信息以文件流的形式返回
  36. curl_setopt($ch, CURLOPT_HTTPHEADER, $header); //模拟的header头
  37. $result = curl_exec($ch);
  38. // 打印请求的header信息
  39. //$a = curl_getinfo($ch);
  40. //var_dump($a);
  41. curl_close($ch);
  42. return $result;
  43. }
  • 3- 客户端
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>"百度商桥"客服端</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css" media="all">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
  10. <style>
  11. * {
  12. padding: 0;
  13. margin: 0;
  14. box-sizing: border-box;
  15. }
  16. body {
  17. padding: 10px;
  18. width: 100vw;
  19. height: 100vh;
  20. background-color: #f0f0f0;
  21. display: grid;
  22. gap: 10px;
  23. grid-template-columns: 240px auto;
  24. grid-template-rows: 3fr 2fr;
  25. grid-template-areas:
  26. "customer-list message-list"
  27. "customer-list message-send";
  28. }
  29. .customer-list {
  30. grid-area: customer-list;
  31. background-color: white;
  32. border: 1px solid #ccc;
  33. }
  34. .message-list {
  35. grid-area: message-list;
  36. background-color: white;
  37. overflow-y: auto;
  38. padding: 10px;
  39. display: grid;
  40. grid-template-rows: 50px auto;
  41. }
  42. .message-list > .system-message {
  43. height: 40px;
  44. width: 100%;
  45. background-color: wheat;
  46. border: 1px solid #eaeaea;
  47. margin-bottom: 10px;
  48. border-radius: 5PX;
  49. overflow-y: hidden;
  50. }
  51. .message-list > .private-chat-box {
  52. background-color: #DFECEC;
  53. border-radius: 5px;
  54. }
  55. .show {
  56. display: block;
  57. }
  58. .hide {
  59. display: none;
  60. }
  61. .message-send {
  62. display: grid;
  63. grid-auto-rows: auto 40px;
  64. grid-area: message-send;
  65. background-color: white;
  66. padding: 10px;
  67. }
  68. .message-send > .message-input-area {
  69. padding: 5px;
  70. background-color: #fafafa;
  71. border: 1px solid #ccc;
  72. margin-bottom: 10px;
  73. border-radius: 5px;
  74. outline: none;
  75. overflow-y: auto;
  76. }
  77. .message-send > .message-input-area:hover {
  78. box-shadow: 0px 0px 1px #333;
  79. }
  80. .message-send > .btn-area {
  81. width: 100%;
  82. text-align: right;
  83. }
  84. .msg-box-friend {
  85. padding: 10px;
  86. width: 75%;
  87. margin: 5px auto 0 5px;
  88. background-color: lightblue;
  89. border: 1px solid #ccc;
  90. border-radius: 5px;
  91. /* overflow-x: wordwrap; */
  92. white-space:normal;
  93. }
  94. .msg-box-me {
  95. padding: 10px;
  96. width: 75%;
  97. margin: 5px 5px 0 auto;
  98. background-color: wheat;
  99. border: 1px solid #ccc;
  100. border-radius: 5px;
  101. /* overflow-x: wordwrap; */
  102. white-space:normal;
  103. }
  104. .customer-item {
  105. border: 1px solid #e0e0e0;
  106. border-radius: 5px;
  107. margin: 5px;
  108. padding: 5px 10px;
  109. }
  110. .active {
  111. background-color: skyblue;
  112. }
  113. .bling {
  114. color: red;
  115. background-color: linen;
  116. }
  117. </style>
  118. </head>
  119. <body>
  120. <input type="hidden" name="username" value="{{$username}}">
  121. <!-- 客户列表 -->
  122. <div class="customer-list">
  123. </div>
  124. <!-- 消息记录 -->
  125. <div class="message-list">
  126. <div class="system-message"></div>
  127. <!-- <div class="private-chat-box"></div> -->
  128. </div>
  129. <!-- 发送消息 -->
  130. <div class="message-send">
  131. <div class="message-input-area" contenteditable="true">
  132. </div>
  133. <div class="btn-area">
  134. <span class="layui-btn layui-btn-success" onclick="send()">发送</span>
  135. </div>
  136. </div>
  137. </body>
  138. <script>
  139. layui.use(['layer'], function() {
  140. layer = layui.layer;
  141. $ = layui.jquery;
  142. });
  143. // 假设服务端ip为127.0.0.1
  144. ws = new WebSocket("ws://127.0.0.1:2000");
  145. /* 当客户端连通服务器端的时候 */
  146. ws.onopen = function() {
  147. // 当客户端连通服务端时, 把当前客户端的用户标识(客户/客服)发给服务端
  148. var data = {};
  149. // js对象的属性可以自定义. type: login, 标识用户行为为声明登录;type: msg, 标识用户行为为发送消息。
  150. data.type = 'login';
  151. // 约定admin代表登录的用户是客服
  152. data.group = 'admin';
  153. // 使用登录账号作为客服小姐姐的登录标识(因为链接id在刷新或关闭浏览器后值会改变)
  154. data.from_id = $('input[name="username"]').val();
  155. // 发送JSON格式的数据
  156. ws.send(JSON.stringify(data));
  157. };
  158. ws.onmessage = function(e) {
  159. var data = JSON.parse(e.data);
  160. // alert("收到服务端的消息:" + e.data);
  161. // 系统发来的消息, 表示有新客户接入, 并分配到当前客服小姐姐
  162. if(data.from_id == 'system' && data.type == 'login') {
  163. if(!Array.isArray(data.custom_id)) {
  164. addOldCustomer(data.custom_id);
  165. } else {
  166. data.custom_id.forEach(function(item) {
  167. addOldCustomer(item);
  168. });
  169. }
  170. // 发送客户接入的系统消息
  171. var str = "<span style='margin-right: 20px; color: red;'>系统消息: "+data.msg+"</span>";
  172. $('.system-message').prepend($(str));
  173. } else if(data.from_id == 'system' && data.type == 'logout') {// 客户下线
  174. // 客户列表移除客户
  175. $('.customer-item[data-id="'+data.custom_id+'"]').remove();
  176. // 聊天记录窗口移除聊天容器
  177. $('.private-chat-box[data-id="'+data.custom_id+'"]').remove();
  178. // 系统消息走一波
  179. // 发送客户接入的系统消息
  180. var str = "<span style='margin-right: 20px;'>系统消息: 客户"+data.custom_id+"下线了</span>";
  181. $('.system-message').prepend($(str));
  182. } else if(data.type = 'msg') {// 客户给客服发消息
  183. // 客户列表中的客户项背景色变一下再还原
  184. // 客户列表中的当前客户项
  185. var customerItem = $('.customer-item[data-id="'+data.from_id+'"]');
  186. blingbling(customerItem);
  187. // 获取正在聊天的窗口的data-id值
  188. var chatting_id = $('.private-chat-box').filter('.show').data('id');
  189. // 当前没有正在聊天的窗口, 则显示跟发送消息的客户的聊天窗口
  190. if(chatting_id == undefined) {
  191. // 显示聊天对话框
  192. var chatbox = $('.private-chat-box').filter('[data-id="'+data.from_id+'"]');
  193. // chatbox.removeClass('hide').addClass('show');
  194. // 相当于点击当前发送消息的客户的客户向
  195. var customItem = $('.customer-item[data-id="'+data.from_id+'"]')
  196. talkTo(customerItem[0]);
  197. // 显示客户讲的消息
  198. var str = "<div class='msg-box-friend'>"+data.from_id+"说: "+data.msg+"</div>";
  199. $(str).appendTo(chatbox);
  200. } else if(chatting_id != data.from_id) {// 正在聊天的客户不是当前发送消息的客户
  201. // 把消息插入跟当前客户的聊天窗口
  202. var str = "<div class='msg-box-friend'>"+data.from_id+"说: "+data.msg+"</div>";
  203. $(str).appendTo($('.private-chat-box').filter('[data-id="'+data.from_id+'"]'));
  204. // 在客户列表中当前发送消息的客户项右边加上未读标签数量
  205. // 客户项中的未读消息数量框
  206. var msgCount = customerItem.find('.layui-badge');
  207. // 如果没有, 表示之前没有未读消息, 加上即可
  208. if(msgCount == undefined || msgCount.length < 1) {
  209. $('<span class="layui-badge">1</span>').appendTo(customerItem);
  210. } else {// 有, 未读消息加一
  211. var count = parseInt(msgCount.html()) + 1;
  212. msgCount.html(count);
  213. }
  214. } else if(chatting_id == data.from_id) {// 当前聊天窗口就是当前发送消息的客户
  215. // 逻辑可以直接用chatting_id是undefined的逻辑, 后期考虑合并
  216. // 显示聊天对话框
  217. var chatbox = $('.private-chat-box').filter('[data-id="'+data.from_id+'"]');
  218. chatbox.removeClass('hide').addClass('show');
  219. console.log(chatbox);
  220. // 显示客户讲的消息
  221. var str = "<div class='msg-box-friend'>"+data.from_id+"说: "+data.msg+"</div>";
  222. $(str).appendTo(chatbox);
  223. }
  224. }
  225. };
  226. // 客服刷新网页, 找回正在沟通的客户列表
  227. function addOldCustomer(custom_id) {
  228. // 把新登录的客户加到客户列表区
  229. var customer = $('<div class="customer-item" data-id="'+custom_id+'" onclick="talkTo(this)">客户'+custom_id+'</div>');
  230. customer.appendTo('.customer-list');
  231. // 闪一下
  232. blingbling(customer);
  233. // 在消息记录容器(message-list)中插入跟登录的客户聊天的对话框div
  234. var privateChatBox = '<div class="private-chat-box hide" data-id="'+custom_id+'"></div>';
  235. $(privateChatBox).appendTo('.message-list');
  236. }
  237. // 客户上线/有未读消息/有新消息, 变一下色, 2秒后复原
  238. function blingbling(jqele) {
  239. jqele.addClass('bling');
  240. setTimeout(() => {
  241. jqele.removeClass('bling');
  242. }, 2000);
  243. }
  244. // 点击客户列表区, 选择要聊天的客户对象
  245. function talkTo(ele) {
  246. $(ele).siblings().removeClass('active');
  247. $(ele).addClass('active');
  248. // 显示聊天窗口
  249. var dataId = $(ele).data('id');
  250. $('.private-chat-box').removeClass('show').addClass('hide');
  251. $('.private-chat-box[data-id="'+dataId+'"]').removeClass('hide').addClass('show');
  252. // 移除未读消息
  253. $(ele).find('.layui-badge').remove();
  254. }
  255. function send() {
  256. var str = "<div class='msg-box-me'>我说: "+$('.message-input-area').html()+"</div>";
  257. var data = {};
  258. // 标识此次发送的数据是发送消息
  259. data.type='msg';
  260. // 标识是从客服端发的
  261. data.group = 'admin'
  262. // 获取当前选中的客户
  263. var customer_id = $('div[class*=active]').data('id');
  264. if(isNaN(customer_id)) {
  265. return layer.alert('请先选中一个客户, 再发送消息.');
  266. }
  267. data.sendId = customer_id;
  268. // 要发送的消息
  269. data.msg = $('.message-input-area').html();
  270. $(str).appendTo('.message-list > .private-chat-box.show');
  271. // 转成json字符串发送。
  272. ws.send(JSON.stringify(data));
  273. $('.message-input-area').html('');
  274. }
  275. </script>
  276. </html>
  • 4- 客服端
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>"百度商桥"客户端</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css" media="all">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
  10. <style>
  11. * {
  12. margin: 0;
  13. padding: 0;
  14. box-sizing: border-box;
  15. }
  16. body {
  17. background-color: #fafafa;
  18. }
  19. .im {
  20. width: 400px;
  21. height: 520px;
  22. background-color: wheat;
  23. margin: 40px auto;
  24. padding: 10px;
  25. box-shadow: 0 0 5px #999;
  26. }
  27. .im .history {
  28. background-color: white;
  29. border: 1px solid #aaa;
  30. width: 100%;
  31. height: 290px;
  32. padding: 5px;
  33. overflow-y: auto;
  34. border-radius: 5px;
  35. }
  36. .im .inputing {
  37. background-color: white;
  38. border: 1px solid #aaa;
  39. width: 100%;
  40. height: 150px;
  41. margin-top: 10px;
  42. padding: 5px;
  43. outline: none;
  44. border-radius: 5px;
  45. }
  46. .im .btn-area {
  47. width: 100%;
  48. text-align: right;
  49. margin-top: 10px;
  50. }
  51. .msg-box-friend {
  52. padding: 10px;
  53. width: 75%;
  54. margin: 5px auto 0 5px;
  55. background-color: lightblue;
  56. border: 1px solid #ccc;
  57. border-radius: 5px;
  58. /* overflow-x: wordwrap; */
  59. white-space:normal;
  60. }
  61. .msg-box-me {
  62. padding: 10px;
  63. width: 75%;
  64. margin: 5px 5px 0 auto;
  65. background-color: wheat;
  66. border: 1px solid #ccc;
  67. border-radius: 5px;
  68. /* overflow-x: wordwrap; */
  69. white-space:normal;
  70. }
  71. </style>
  72. </head>
  73. <body>
  74. <input type="hidden" name="phone_number" value="">
  75. <div class="im">
  76. <div class="history">
  77. </div>
  78. <!-- contenteditable="true", 这个div就可编辑了 -->
  79. <div class="inputing" contenteditable="true">
  80. </div>
  81. <div class="btn-area">
  82. <span class="layui-btn layui-btn-success" onclick="send()">发送</span>
  83. </div>
  84. </div>
  85. </body>
  86. <script>
  87. layui.use(['layer'], function() {
  88. layer = layui.layer;
  89. $ = layui.jquery;
  90. // 在layer加载完成后再弹出, 否则无法上下左右居中显示弹出框.
  91. layer.ready(function() {
  92. // 用户填入手机号
  93. getPhoneNumber();
  94. });
  95. });
  96. function getPhoneNumber() {
  97. layer.prompt({
  98. formType: 0,
  99. title: '请输入你的手机号',
  100. area: ['800px', '350px'],
  101. btn: ['确定']
  102. }, function(value, index, elem) {
  103. // 验证是否是有效地手机号码
  104. if(!(/^1[3456789]\d{9}$/.test(value))){
  105. return layer.alert("手机号码有误,请重填");
  106. }
  107. // 保存电话号码到隐藏域
  108. $("input[name='phone_number']").val(value);
  109. // 连接websocket服务器
  110. connect(value);
  111. layer.close(index);
  112. });
  113. }
  114. function connect(value) {
  115. // 假设服务端ip为127.0.0.1
  116. ws = new WebSocket("ws://127.0.0.1:2000");
  117. /* 当客户端连通服务器端的时候 */
  118. ws.onopen = function() {
  119. // 当客户端连通服务端时, 把当前客户端的用户标识(客户/客服)发给服务端
  120. var data = {};
  121. // js对象的属性可以自定义. type: login, 标识用户行为为声明登录;type: msg, 标识用户行为为发送消息。
  122. data.type = 'login';
  123. // 约定admin表示登录的用户是客户
  124. data.group = 'member';
  125. // 识别客户的唯一码从连接id改为用户手机号, 因为刷新后, 连接id就会变.
  126. data.from_id = value;
  127. // 发送JSON格式的数据
  128. ws.send(JSON.stringify(data));
  129. };
  130. // 从服务器端获取到消息时
  131. ws.onmessage = function(e) {
  132. // alert("收到服务端的消息:" + e.data);
  133. var receive = JSON.parse(e.data);
  134. var str = "<div class='msg-box-friend'>"+receive.from_id+"说: "+receive.msg+"</div>";
  135. $(str).appendTo('.history');
  136. };
  137. }
  138. function send() {
  139. // 消息框
  140. var str = "<div class='msg-box-me'>我说: "+$('.inputing').html()+"</div>";
  141. var data = {};
  142. // 标识此次发送的数据是发送消息
  143. data.type='msg';
  144. // 标识是从客服端发的
  145. data.group = 'admin'
  146. // 标识私聊的对象id,0标识群发. DEL_客户连接时由系统随机分配客服小姐姐, 不需要手动指定了
  147. // data.sendId = 0;
  148. // 要发送的消息
  149. data.msg = $('.inputing').html();
  150. // 在消息历史中显示发送的消息
  151. $(str).appendTo('.history');
  152. // 发送JSON格式的数据
  153. ws.send(JSON.stringify(data));
  154. $('.inputing').html('');
  155. }
  156. </script>
  157. </html>
  • 5- 保存消息的控制器方法
  1. public function saveMsg(Request $req) {
  2. $msg = $req->all();
  3. DB::table('msg_list')->insert($msg);
  4. return json_encode(['status' => 0, 'message' => '保存成功']);
  5. }
声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议