多人聊天系统
功能说明:多人聊天系统,主要功能点:
1、当你登陆成功后,可以看到所有在线用户(实际开发可以通过redis实现,我这边仅仅用map集合)
2、实现群聊功能,我发送消息,大家都可以看到。
先看案例效果:
这里面有关在线人数有个bug,就是在线用户会被覆盖,lisi登陆的话,zhangsan在线信息就丢来,xiaoxiao登陆,lisi就丢来,这主要原因是因为我放的是普通集合,所以在线用户数据是无法共享
所以只能显示最后显示的用户,如果放到redis就不会有这个问题。
一、案例说明
1、UserChatController
@Controllerpublic class UserChatController { ???@Autowired ???private WebSocketService ws; ???/** ????* 1、登陆时,模拟数据库的用户信息 ????*/ ???//模拟数据库用户的数据 ???public static Map<String, String> userMap = new HashMap<String, String>(); ???static{ ???????userMap.put("zhangsan", "123"); ???????userMap.put("lisi", "456"); ???????userMap.put("wangwu", "789"); ???????userMap.put("zhaoliu", "000"); ???????userMap.put("xiaoxiao", "666"); ???} ???/** ????*2、 模拟用户在线进行页面跳转的时候,判断是否在线 ????* (这个实际开发中肯定存在redis或者session中,这样数据才能共享) ????* 这里只是简单的做个模拟,所以暂且用普通map吧 ????*/ ???public static Map<String, User> onlineUser = new HashMap<>(); ???static{ ???????//key值一般是每个用户的sessionID(这里表示admin用户一开始就在线) ???????onlineUser.put("123",new User("admin","888")); ???} ???????????/** ????*3、 功能描述:用户登录接口 ????*/ ???@RequestMapping(value="login", method=RequestMethod.POST) ???public String userLogin( @RequestParam(value="username", required=true)String username, ????????????@RequestParam(value="pwd",required=true) String pwd, HttpSession session) { ???????//判断是否正确 ???????String password = userMap.get(username); ???????if (pwd.equals(password)) { ???????????User user = new User(username, pwd); ???????????String sessionId = session.getId(); ???????????//用户登陆成功就把该用户放到在线用户中... ???????????onlineUser.put(sessionId, user); ???????????//跳到群聊页面 ???????????return "redirect:/group/chat.html"; ???????} else { ???????????return "redirect:/group/error.html"; ???????} ???????????} ???????/** ????*4、 功能描述:用于定时给客户端推送在线用户 ????*/ ???@Scheduled(fixedRate = 2000) ???public void onlineUser() { ???????ws.sendOnlineUser(onlineUser); ???} ???????/** ????*5、 功能描述 群聊天接口 ????* message 消息体 ????* headerAccessor 消息头访问器,通过这个获取sessionId ????*/ ???@MessageMapping("/group/chat") ???public void topicChat(InMessage message, SimpMessageHeaderAccessor headerAccessor){ ???????//这个sessionId是在HttpHandShakeIntecepter拦截器中放入的 ???????String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString(); ???????//通过sessionID获得在线用户信息 ???????User user = onlineUser.get(sessionId); ???????message.setFrom(user.getUsername()); ???????ws.sendTopicChat(message); ???????????} ???}
2、握手请求的拦截器
/** * WebSocket握手请求的拦截器. 检查握手请求和响应, 对WebSocketHandler传递属性 * 可以通过这个类的方法获取resuest,和response */public class HttpHandShakeIntecepter implements HandshakeInterceptor{ ???//在握手之前执行该方法, 继续握手返回true, 中断握手返回false. 通过attributes参数设置WebSocketSession的属性 ???//这个项目只在WebSocketSession这里存入sessionID ???@Override ???public boolean beforeHandshake(ServerHttpRequest request, ???????????ServerHttpResponse response, WebSocketHandler wsHandler, ???????????Map<String, Object> attributes) throws Exception { ???????System.out.println("【握手拦截器】beforeHandshake"); ???????if(request instanceof ServletServerHttpRequest) { ???????????ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request; ???????????HttpSession session = ?servletRequest.getServletRequest().getSession(); ???????????String sessionId = session.getId(); ???????????System.out.println("【握手拦截器】beforeHandshake sessionId="+sessionId); ???????????//这里将sessionId放入SessionAttributes中, ???????????attributes.put("sessionId", sessionId); ???????} ???????????????return true; ???} ????//在握手之后执行该方法. 无论是否握手成功都指明了响应状态码和相应头(这个项目没有用到该方法) ???@Override ???public void afterHandshake(ServerHttpRequest request, ???????????ServerHttpResponse response, WebSocketHandler wsHandler, ???????????Exception exception) { ???????System.out.println("【握手拦截器】afterHandshake"); ???????????????if(request instanceof ServletServerHttpRequest) { ???????????ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request; ???????????HttpSession session = ?servletRequest.getServletRequest().getSession(); ???????????String sessionId = session.getId(); ???????????System.out.println("【握手拦截器】afterHandshake sessionId="+sessionId); ???????} ???}}
3、频道拦截器
/** ?* 功能描述:频道拦截器 ,类似管道,可以获取消息的一些meta数据 */public class SocketChannelIntecepter extends ChannelInterceptorAdapter{ ???/** ????* 在完成发送之后进行调用,不管是否有异常发生,一般用于资源清理 ????*/ ???@Override ???public void afterSendCompletion(Message<?> message, MessageChannel channel, ???????????boolean sent, Exception ex) { ???????System.out.println("SocketChannelIntecepter->afterSendCompletion"); ???????super.afterSendCompletion(message, channel, sent, ex); ???} ???????/** ????* 在消息被实际发送到频道之前调用 ????*/ ???@Override ???public Message<?> preSend(Message<?> message, MessageChannel channel) { ???????System.out.println("SocketChannelIntecepter->preSend"); ???????????????return super.preSend(message, channel); ???} ???/** ????* 发送消息调用后立即调用 ????*/ ???@Override ???public void postSend(Message<?> message, MessageChannel channel, ???????????boolean sent) { ???????System.out.println("SocketChannelIntecepter->postSend"); ???????????????StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);//消息头访问器 ???????????????if (headerAccessor.getCommand() == null ) return ;// 避免非stomp消息类型,例如心跳检测 ???????????????String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString(); ???????System.out.println("SocketChannelIntecepter -> sessionId = "+sessionId); ???????????????switch (headerAccessor.getCommand()) { ???????case CONNECT: ???????????connect(sessionId); ???????????break; ???????case DISCONNECT: ???????????disconnect(sessionId); ???????????break; ???????case SUBSCRIBE: ???????????????break; ???????????????case UNSUBSCRIBE: ???????????break; ???????default: ???????????break; ???????} ???} ???/** ????* 连接成功 ????*/ ???private void connect(String sessionId){ ???????System.out.println("connect sessionId="+sessionId); ???} ???/** ????* 断开连接 ????*/ ???private void disconnect(String sessionId){ ???????System.out.println("disconnect sessionId="+sessionId); ???????//用户下线操作 ???????UserChatController.onlineUser.remove(sessionId); ???} ???}
4、修改webSocket配置类
既然写了两个拦截器,那么肯定需要在配置信息里去配置它们。
@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer { ???/** ????*配置基站 ????*/ ???public void registerStompEndpoints(StompEndpointRegistry registry) { ???????registry.addEndpoint("/endpoint-websocket").addInterceptors(new HttpHandShakeIntecepter()).setAllowedOrigins("*").withSockJS(); ???} ???/** ????* 配置消息代理(中介) ????* enableSimpleBroker 服务端推送给客户端的路径前缀 ????* setApplicationDestinationPrefixes ?客户端发送数据给服务器端的一个前缀 ????*/ ???@Override ???public void configureMessageBroker(MessageBrokerRegistry registry) { ???????registry.enableSimpleBroker("/topic","/chat"); ???????registry.setApplicationDestinationPrefixes("/app"); ???} ?????@Override ???public void configureClientInboundChannel(ChannelRegistration registration) { ???????registration.interceptors( new SocketChannelIntecepter()); ???} ???@Override ???public void configureClientOutboundChannel(ChannelRegistration registration) { ???????registration.interceptors( new SocketChannelIntecepter()); ???}}
5、app.js
登陆页面和群聊页面就不细聊,贴上代码就好。
index.html
<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><head> ???<title>Hello WebSocket</title> ???<link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet"> ???<link href="/group/main.css" rel="stylesheet"> ???<script src="/webjars/jquery/jquery.min.js"></script></head><body><div id="main-content" class="container"> ???<div class="row"> ???????<div class="col-md-6"> ???????????<form class="form-inline" method=‘post‘ ?action="/login"> ???????????????<div class="form-group"> ???????????????????<input type="text" name="username" class="form-control" placeholder="用户名"> ???????????????????<input type="password" name="pwd" class="form-control" placeholder="密码"> ???????????????????<input type="submit" class="default" value="登录" /> ???????????????</div> ???????????</form> ???????</div> ???</div></div></body></html>
chat.html
<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><head> ???<title>devsq聊天室</title> ???<link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet"> ???<link href="/group/main.css" rel="stylesheet"> ???<script src="/webjars/jquery/jquery.min.js"></script> ???<script src="/webjars/sockjs-client/sockjs.min.js"></script> ???<script src="/webjars/stomp-websocket/stomp.min.js"></script> ???<script src="/group/app.js"></script></head><body><div id="main-content" class="container"> ???<div class="row"> ???????<div class="col-md-6"> ???????????<form class="form-inline"> ???????????????<div class="form-group"> ???????????????????<label for="connect">建立连接通道:</label> ???????????????????<button id="connect" class="btn btn-default" type="submit">Connect</button> ???????????????????<button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect ???????????????????</button> ???????????????</div> ???????????</form> ???????</div> ???????<div class="col-md-6"> ???????????<form class="form-inline"> ???????????????<div class="form-group"> ???????????????????<input type="text" id="content" class="form-control" placeholder="请输入..."> ???????????????</div> ???????????????<button id="send" class="btn btn-default" type="submit">发送</button> ???????????</form> ???????</div> ???</div> ???<div class="row"> ???????<div class="col-md-6"> ???????????<table id="conversation" class="table table-striped"> ???????????????<thead> ???????????????<tr> ???????????????????<th>实时在线用户列表</th> ???????????????</tr> ???????????????</thead> ???????????????<tbody id=‘online‘> ???????????????</tbody> ???????????</table> ???????</div> ????????????????<div class="col-md-6"> ???????????<table id="conversation" class="table table-striped"> ???????????????<thead> ???????????????<tr> ???????????????????<th>聊天记录</th> ???????????????</tr> ???????????????</thead> ???????????????<tbody id=‘record‘> ???????????????</tbody> ???????????</table> ???????</div> ???????????????????</div></div></body></html>
app.js
var stompClient = null;
//一加载就会调用该方法function connect() { ???var socket = new SockJS(‘/endpoint-websocket‘); ???stompClient = Stomp.over(socket); ???stompClient.connect({}, function (frame) { ???????setConnected(true); ???????console.log(‘Connected: ‘ + frame); ???????????????//订阅群聊消息 ???????stompClient.subscribe(‘/topic/chat‘, function (result) { ???????????showContent(JSON.parse(result.body)); ???????}); ???????????????//订阅在线用户消息 ???????stompClient.subscribe(‘/topic/onlineuser‘, function (result) { ???????????showOnlieUser(JSON.parse(result.body)); ???????}); ???????????????????});}//断开连接function disconnect() { ???if (stompClient !== null) { ???????stompClient.disconnect(); ???} ???setConnected(false); ???console.log("Disconnected");}//发送聊天记录function sendContent() { ???stompClient.send("/app/group/chat", {}, JSON.stringify({‘content‘: $("#content").val()})); ???}//显示聊天记录function showContent(body) { ???$("#record").append("<tr><td>" + body.content + "</td> <td>"+new Date(body.time).toLocaleTimeString()+"</td></tr>");}//显示实时在线用户function showOnlieUser(body) { ???$("#online").html("<tr><td>" + body.content + "</td> <td>"+new Date(body.time).toLocaleTimeString()+"</td></tr>");}$(function () { ???????connect();//自动上线 ???????$("form").on(‘submit‘, function (e) { ???????e.preventDefault(); ???}); ????????$( "#disconnect" ).click(function() { disconnect(); }); ???$( "#send" ).click(function() { ???????sendContent(); ????});});
我只是偶尔安静下来,对过去的种种思忖一番。那些曾经的旧时光里即便有过天真愚钝,也不值得谴责。毕竟,往后的日子,还很长。不断鼓励自己,
天一亮,又是崭新的起点,又是未知的征程(上校1)
【WebSocket】---多人聊天系统
原文地址:https://www.cnblogs.com/qdhxhz/p/9471659.html