说明:该示例只简单的实现了客服聊天功能。
1、聊天记录没有保存到数据库中,一旦服务重启,消息记录将会没有,如果需要保存到数据库中,可以扩展
2、页面样式用的网上模板,样式可以自己进行修改
3、只能由用户主要发起会话,管理员无法主动进行对话
4、页面之间跳转代码没有包含在里面,请自己书写,在管理员消息列表页中,需要把该咨询的用户ID带到客服回复页面中
5、${websocket_url} 这个为项目的URL地址
效果截图:
客服回复页面(member_admin_chat.html) |
管理员消息列表页(member_admin_chat_list.html) |
用户咨询页面(member_chat.html) |
![]() |
|
|
代码:
页面所需要用到的基础样式、图片,下载链接:https://www.lanzous.com/ias1kcb
pom.xml
<dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>8.0</version> <scope>provided</scope> </dependency>
配置类
WebSocketConfig.java
package com.config;
import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
import java.util.Set;
public class WebSocketConfig implements ServerApplicationConfig {
@Override
public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> endpointClasses) {
return null;
}
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) {
//在这里会把含有@ServerEndpoint注解的类扫描加载进来 ,可以在这里做过滤等操作
return scanned;
}
}
消息DTO类(使用了lombok,这里不在多做说明)
ChatDTO.java
package com.websocket.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* @author 。
*/
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ChatDTO {
/**
* 用户ID
*/
private String userId;
/**
* 用户发送信息
*/
private String message;
/**
* 发送日期
* 消息时间格式(yyyy-MM-dd)
*/
private String createDate;
/**
* 发送时间
* 消息时间格式(yyyy-MM-dd HH:mm:ss)
*/
private String createTime;
}
用户DTO类
ChatUserDTO.java
package com.jeecms.websocket.dto;
import com.jeecms.core.entity.CmsUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* @author 。
*/
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ChatUserDTO {
/**
* 用户id
*/
private String userId;
/**
* 用户名
*/
private String userName;
/**
* 用户图片
*/
private String userImg;
public ChatUserDTO convertUser(CmsUser user){
ChatUserDTO chatUserDTO=new ChatUserDTO();
chatUserDTO.setUserId(user.getId()+"")
.setUserName(user.getUsername())
.setUserImg(user.getUserImg());
return chatUserDTO;
}
}
ChatWebSocket.java
package com.websocket;
import com.service.RedisService;
import com.util.DateFormatUtils;
import com.entity.CmsUser;
import com.manager.CmsUserMng;
import com.enums.ChatTypeEnum;
import com.websocket.Constants;
import com.websocket.dto.ChatDTO;
import com.websocket.dto.ChatUserDTO;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;
/**
* @author 。
*/
@ServerEndpoint(value = "/chat_websocket")
public class ChatWebSocket {
private RedisService redisService;
private CmsUserMng cmsUserMng;
public ChatWebSocket() {
WebApplicationContext webctx = ContextLoader.getCurrentWebApplicationContext();
this.redisService = (RedisService) webctx.getBean("redisService");
this.cmsUserMng = (CmsUserMng) webctx.getBean("cmsUserMng");
}
/**
* 存储用户id
*/
public static Map userMap = new HashMap();
/**
* 聊天记录
*/
public static Map chatRecordMap = new HashMap();
/**
* 管理员列表session
*/
public static Session adminSession;
/**
* 创建
*
* @param session
*/
@OnOpen
public void onOpen(Session session) {
Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
List<String> strs = requestParameterMap.get("msg");
if (strs != null && strs.size() > 0) {
String json = strs.get(0);
//从聊天集中去掉该集合
JSONObject object = JSONObject.fromObject(json);
String userId = object.getString("user_id");
String chatType = object.getString("chat_type");
/*--------------管理员列表-----------------------*/
if (ChatTypeEnum.adminListChatType.getKey().equalsIgnoreCase(chatType)) {
adminSession = session;
List list = getUserList(userMap);
//遍历所有聊天用户集合的id
chat_list_show(adminSession, list);
return;
}
/*--------------管理员聊天框打开-----------------------*/
if (ChatTypeEnum.adminChatType.getKey().equalsIgnoreCase(chatType)) {
//从集合中获取用户对应的数据加入信息列表中
List sessions = (List) userMap.get(userId);
if (sessions == null) {
sessions = new ArrayList();
}
sessions.add(session);
userMap.put(userId, sessions);
}
/*--------------用户聊天框打开-----------------------*/
if (ChatTypeEnum.userChatType.getKey().equalsIgnoreCase(chatType)) {
//判断是否建立聊天通道
List sessions = (List) userMap.get(userId);
if (sessions == null) {
sessions = new ArrayList();
}
sessions.add(session);
userMap.put(userId, sessions);
}
//聊天记录信息存放
List chatRecords = (List) chatRecordMap.get(userId);
if (chatRecords != null) {
chat((List<Session>) userMap.get(userId), chatRecords);
}
}
}
/**
* 发送消息
*
* @param json {userId:‘‘,message:‘‘,create_time:‘‘,create_date:‘‘,chat_type:‘admin_list/admin_chat/user_chat‘}
* admin_list:表示客服列表数据请求
* admin_chat:表示客服回复页面请求
* user_chat表示用户消息页面请求
*
*
* @throws Exception
*/
@OnMessage
public void onMessage(Session session, String json) {
JSONObject object = JSONObject.fromObject(json);
//用户ID
String userId = object.getString("user_id");
//用户发送的信息
String message = object.getString("message");
//请求类型
String chatType = object.getString("chat_type");
/*--------------管理员聊天-----------------------*/
if (ChatTypeEnum.adminChatType.getKey().equalsIgnoreCase(chatType)) {
//把管理员加入用户建立的聊天管道中
//用户聊天
//封装请求参数,时间为当前时间
ChatDTO chatDTO = new ChatDTO();
//userId=0表示是客服的回复
chatDTO.setUserId("0")
.setMessage(message)
.setCreateDate(DateFormatUtils.formatDate(new Date()))
.setCreateTime(DateFormatUtils.formatDateTime(new Date()));
//聊天记录信息存放
List chatRecords = (List) chatRecordMap.get(userId);
if (chatRecords == null) {
chatRecords = new ArrayList();
}
chatRecords.add(JSONObject.fromObject(chatDTO));
chatRecordMap.put(userId, chatRecords);
chat((List<Session>) userMap.get(userId), chatRecords);
}
/*--------------用户聊天-----------------------*/
if (ChatTypeEnum.userChatType.getKey().equalsIgnoreCase(chatType)) {
//封装请求参数,时间为当前时间
ChatDTO chatDTO = new ChatDTO();
chatDTO.setUserId(userId)
.setMessage(message)
.setCreateDate(DateFormatUtils.formatDate(new Date()))
.setCreateTime(DateFormatUtils.formatDateTime(new Date()));
String key = chatDTO.getUserId();
//聊天记录信息存放
List chatRecords = (List) chatRecordMap.get(key);
if (chatRecords == null) {
chatRecords = new ArrayList();
}
chatRecords.add(JSONObject.fromObject(chatDTO));
chatRecordMap.put(key, chatRecords);
chat((List<Session>) userMap.get(key), chatRecords);
if (adminSession != null) {
List list = getUserList(userMap);
//遍历所有聊天用户集合的id
chat_list_show(adminSession, list);
}
}
}
/**
* 关闭
*/
@OnClose
public void onClose(Session session) {
Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
List<String> strs = requestParameterMap.get("msg");
if (strs != null && strs.size() > 0) {
String json = strs.get(0);
JSONObject object = JSONObject.fromObject(json);
String userId = object.getString("user_id");
String chatType = object.getString("chat_type");
/*--------------管理员聊天框关闭-----------------------*/
if (ChatTypeEnum.adminChatType.getKey().equalsIgnoreCase(chatType)) {
}
/*--------------用户聊天框关闭-----------------------*/
if (ChatTypeEnum.userChatType.getKey().equalsIgnoreCase(chatType)) {
}
}
}
/**
* 发生错误
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 消息广播
*
* @param sessions
* @param messages
*/
public void chat(List<Session> sessions, List messages) {
for (Iterator it = sessions.iterator(); it.hasNext(); ) {
Session session = (Session) it.next();
try {
if (session.isOpen()) {
//当当前会话没有被关闭 发送消息
session.getBasicRemote().sendText(JSONArray.fromObject(messages) + "");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 聊天列表显示
*/
public void chat_list_show(Session session, List list) {
try {
if (session.isOpen()) {
//当当前会话没有被关闭 发送消息
session.getBasicRemote().sendText(JSONArray.fromObject(list) + "");
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 通过id获取用户数据
*
* @return
*/
public List getUserList(Map userMap) {
List list = new ArrayList();
for (Object str : userMap.keySet()) {
ChatUserDTO chatUserDTO = new ChatUserDTO();
CmsUser user = cmsUserMng.findById(Integer.valueOf(str + ""));
list.add(chatUserDTO.convertUser(user));
}
return list;
}
}
用户咨询页面
member_chat.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
<title>客服咨询</title>
<link rel="stylesheet" type="text/css" href="/${res}/chat/css/chat.css"/>
<script src="/${res}/js/jquery.1.9.1.js"></script>
<script src="/${res}/chat/js/flexible.js"></script>
</head>
<body>
<header class="header">
<a class="back" href="javascript:history.back()"></a>
<h5 class="tit">客服</h5>
</header>
<div id="message">
</div>
<div id="footer">
<img src="/${res}/chat/images/hua.png" />
<input class="my-input" type="text"/>
<p class="send">发送</p>
</div>
<script>
//聊天
var ws;
var obj={
user_id:‘${user.id}‘,
message:‘‘,
chat_type:"user_chat"
}
var target = "ws:${websocket_url!}/chat_websocket?msg="+encodeURI(JSON.stringify(obj));
var canSend = false;
$(function () {
//处理浏览器兼容性
if (‘WebSocket‘ in window) {
ws = new WebSocket(target);
} else if (‘MozWebSocket‘ in window) {
ws = new MozWebSocket(target);
} else {
alert(‘WebSocket is not supported by this browser.‘);
return;
}
ws.onopen = function () {
};
ws.onmessage = function (event) {
var data = JSON.parse(event.data);
console.log(data)
$(‘#message‘).html("");
for (var i=0;i<data.length;i++){
if (data[i].userId!=‘${user.id}‘){
reply("/${res}/chat/images/touxiangm.png", data[i].message);
} else {
ask("/${res}/chat/images/touxiang.png", data[i].message);
}
}
};
ws.onclose = function (event) {
}
$(‘#footer‘).on(‘keyup‘, ‘input‘, function () {
if ($(this).val().length > 0) {
$(this).next().css(‘background‘, ‘#114F8E‘).prop(‘disabled‘, true);
canSend = true;
} else {
$(this).next().css(‘background‘, ‘#ddd‘).prop(‘disabled‘, false);
canSend = false;
}
})
$(‘#footer .send‘).click(send)
$("#footer .my-input").keydown(function (e) {
if (e.keyCode == 13) {
return send();
}
});
})
/* 对方消息div */
function reply(headSrc, str) {
var html = "<div class=‘reply‘><div class=‘msg‘><img src=" + headSrc + " /><span class=‘name‘>客服</span><p><i class=‘msg_input‘></i>" + str + "</p></div></div>";
return upView(html);
}
/* 自己消息div */
function ask(headSrc, str) {
var html = "<div class=‘ask‘><div class=‘msg‘><img src=" + headSrc + " />" + "<p><i class=‘msg_input‘></i>" + str + "</p></div></div>";
return upView(html);
}
function upView(html) {
var message = $(‘#message‘);
message.append(html);
var h=message.outerHeight() - window.innerHeight;
window.scrollTo(0, document.body.scrollHeight)
return;
}
function send() {
if (canSend) {
var input = $("#footer .my-input");
var val=input.val()
var obj={
user_id:‘${user.id}‘,
message:val,
chat_type:"user_chat"
}
ws.send(JSON.stringify(obj));
//ask("/${res}/chat/images/touxiangm.png", val);
input.val(‘‘);
}
}
function sj() {
return parseInt(Math.random() * 10)
}
</script>
</body>
</html>
管理员消息列表页
member_admin_chat_list.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
<title>客服咨询</title>
<link rel="stylesheet" type="text/css" href="/${res}/chat/css/chat.css"/>
<script src="/${res}/js/jquery.1.9.1.js"></script>
<script src="/${res}/chat/js/flexible.js"></script>
</head>
<body>
<header class="header">
<a class="back" href="javascript:history.back()"></a>
<h5 class="tit">客服</h5>
</header>
<div id="message">
</div>
<div id="footer">
<img src="/${res}/chat/images/hua.png" />
<input class="my-input" type="text"/>
<p class="send">发送</p>
</div>
<script>
//聊天
var ws;
var obj={
user_id:‘${user.id}‘,
message:‘‘,
chat_type:"user_chat"
}
var target = "ws:${websocket_url!}/chat_websocket?msg="+encodeURI(JSON.stringify(obj));
var canSend = false;
$(function () {
//处理浏览器兼容性
if (‘WebSocket‘ in window) {
ws = new WebSocket(target);
} else if (‘MozWebSocket‘ in window) {
ws = new MozWebSocket(target);
} else {
alert(‘WebSocket is not supported by this browser.‘);
return;
}
ws.onopen = function () {
};
ws.onmessage = function (event) {
var data = JSON.parse(event.data);
console.log(data)
$(‘#message‘).html("");
for (var i=0;i<data.length;i++){
if (data[i].userId!=‘${user.id}‘){
reply("/${res}/chat/images/touxiangm.png", data[i].message);
} else {
ask("/${res}/chat/images/touxiang.png", data[i].message);
}
}
};
ws.onclose = function (event) {
}
$(‘#footer‘).on(‘keyup‘, ‘input‘, function () {
if ($(this).val().length > 0) {
$(this).next().css(‘background‘, ‘#114F8E‘).prop(‘disabled‘, true);
canSend = true;
} else {
$(this).next().css(‘background‘, ‘#ddd‘).prop(‘disabled‘, false);
canSend = false;
}
})
$(‘#footer .send‘).click(send)
$("#footer .my-input").keydown(function (e) {
if (e.keyCode == 13) {
return send();
}
});
})
/* 对方消息div */
function reply(headSrc, str) {
var html = "<div class=‘reply‘><div class=‘msg‘><img src=" + headSrc + " /><span class=‘name‘>客服</span><p><i class=‘msg_input‘></i>" + str + "</p></div></div>";
return upView(html);
}
/* 自己消息div */
function ask(headSrc, str) {
var html = "<div class=‘ask‘><div class=‘msg‘><img src=" + headSrc + " />" + "<p><i class=‘msg_input‘></i>" + str + "</p></div></div>";
return upView(html);
}
function upView(html) {
var message = $(‘#message‘);
message.append(html);
var h=message.outerHeight() - window.innerHeight;
window.scrollTo(0, document.body.scrollHeight)
return;
}
function send() {
if (canSend) {
var input = $("#footer .my-input");
var val=input.val()
var obj={
user_id:‘${user.id}‘,
message:val,
chat_type:"user_chat"
}
ws.send(JSON.stringify(obj));
//ask("/${res}/chat/images/touxiangm.png", val);
input.val(‘‘);
}
}
function sj() {
return parseInt(Math.random() * 10)
}
</script>
</body>
</html>
管理员消息回复页面
member_admin_chat.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
<title>客服咨询</title>
<link rel="stylesheet" type="text/css" href="/${res}/chat/css/chat.css"/>
<script src="/${res}/js/jquery.1.9.1.js"></script>
<script src="/${res}/chat/js/flexible.js"></script>
</head>
<body>
<header class="header">
<a class="back" href="javascript:history.back()"></a>
<h5 class="tit">客服</h5>
</header>
<div id="message">
</div>
<div id="footer">
<img src="/${res}/chat/images/hua.png" />
<input class="my-input" type="text"/>
<p class="send">发送</p>
</div>
<script>
//聊天
var ws;
var obj={
user_id:‘${user.id}‘,
message:‘‘,
chat_type:"user_chat"
}
var target = "ws:${websocket_url!}/chat_websocket?msg="+encodeURI(JSON.stringify(obj));
var canSend = false;
$(function () {
//处理浏览器兼容性
if (‘WebSocket‘ in window) {
ws = new WebSocket(target);
} else if (‘MozWebSocket‘ in window) {
ws = new MozWebSocket(target);
} else {
alert(‘WebSocket is not supported by this browser.‘);
return;
}
ws.onopen = function () {
};
ws.onmessage = function (event) {
var data = JSON.parse(event.data);
console.log(data)
$(‘#message‘).html("");
for (var i=0;i<data.length;i++){
if (data[i].userId!=‘${user.id}‘){
reply("/${res}/chat/images/touxiangm.png", data[i].message);
} else {
ask("/${res}/chat/images/touxiang.png", data[i].message);
}
}
};
ws.onclose = function (event) {
}
$(‘#footer‘).on(‘keyup‘, ‘input‘, function () {
if ($(this).val().length > 0) {
$(this).next().css(‘background‘, ‘#114F8E‘).prop(‘disabled‘, true);
canSend = true;
} else {
$(this).next().css(‘background‘, ‘#ddd‘).prop(‘disabled‘, false);
canSend = false;
}
})
$(‘#footer .send‘).click(send)
$("#footer .my-input").keydown(function (e) {
if (e.keyCode == 13) {
return send();
}
});
})
/* 对方消息div */
function reply(headSrc, str) {
var html = "<div class=‘reply‘><div class=‘msg‘><img src=" + headSrc + " /><span class=‘name‘>客服</span><p><i class=‘msg_input‘></i>" + str + "</p></div></div>";
return upView(html);
}
/* 自己消息div */
function ask(headSrc, str) {
var html = "<div class=‘ask‘><div class=‘msg‘><img src=" + headSrc + " />" + "<p><i class=‘msg_input‘></i>" + str + "</p></div></div>";
return upView(html);
}
function upView(html) {
var message = $(‘#message‘);
message.append(html);
var h=message.outerHeight() - window.innerHeight;
window.scrollTo(0, document.body.scrollHeight)
return;
}
function send() {
if (canSend) {
var input = $("#footer .my-input");
var val=input.val()
var obj={
user_id:‘${user.id}‘,
message:val,
chat_type:"user_chat"
}
ws.send(JSON.stringify(obj));
//ask("/${res}/chat/images/touxiangm.png", val);
input.val(‘‘);
}
}
function sj() {
return parseInt(Math.random() * 10)
}
</script>
</body>
</html>
原文:https://www.cnblogs.com/pxblog/p/12596111.html