【WebSocket】---多人聊天系統
阿新 • • 發佈:2018-08-14
tom after channel 加載 cal parse ssi 獲得 路徑
多人聊天系統
功能說明:多人聊天系統,主要功能點:
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 = newHashMap<>(); 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配置類
既然寫了兩個攔截器,那麽肯定需要在配置信息裏去配置它們。
@EnableWebSocketMessageBroker public 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
![技術分享圖片](/img/jia.gif)
<!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>index.html
chat.html
![技術分享圖片](/img/jia.gif)
<!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>chat.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】---多人聊天系統