本文共 5754 字,大约阅读时间需要 19 分钟。
在线聊天室 由发表在
每天大家都在使用QQ等即时聊天工具,今天我们就使用以及websocket技术在网页端简单的实现一个在线聊天的功能。
在线聊天室使需要使用到的技术或者框架包括:
因此,我们首先在pom.xml
中添加上述依赖:
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-websocket
我们首先来做一个简单的登录功能,主要目的是维护一个用户列表,这里我们不考虑用户验证或者登出等问题,因此,我们只需要一个登录的地址:
@Controllerpublic class LoginController { @Autowired private SimpMessagingTemplate messagingTemplate; @Autowired private ParticipantRepository participantRepository; private static final String LOGIN = "/app/chat.login"; private static final String LOGOUT = "/app/chat.logout"; @RequestMapping(value = "login", method=RequestMethod.POST) public String login(HttpServletRequest httpRequest, User user) throws ServletException{ user.setTime(new Date()); httpRequest.getSession().setAttribute("user", user); messagingTemplate.convertAndSend(LOGIN, user); if(participantRepository.getActiveSessions(). containsKey(httpRequest.getSession().getId())){ messagingTemplate.convertAndSend(LOGOUT, participantRepository.getActiveSessions(). get(httpRequest.getSession().getId())); } participantRepository.add(httpRequest.getSession().getId(), user); return "redirect:/chat"; }}
需要注意的是每个登录用户在登录的时候都需要往websocket的/app/chat.login
地址发送登录的信息,如果以前登录过,则往发送/app/chat.logout
一个登出信息,以便客户端更新用户列表。
我们通过@EnableWebSocketMessageBroker
标注打开websocket服务,并通过继承AbstractWebSocketMessageBrokerConfigurer
对websocket进行配置,这里我们进行两个配置:
@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.setApplicationDestinationPrefixes("/app"); }}
通过registry.addEndpoint("/ws").withSockJS();
使得客户端可以通过/ws
地址与后台建立websocket连接。设置config.setApplicationDestinationPrefixes("/app");
将/app
开头的地址标识为应用地址。此时,用户可以通过/ws
地址与服务器建立websocket连接,并通过websocket进行通信。
这里我们为添加两个方法:
首先是聊天页面,当用户访问/chat
地址时,如果未登录,则抛出AccessDeniedException错误,我们的错误处理会将用户重定向到登录页面,如果用户已登录,则显示聊天页面开始聊天。
其次我们通过websocket的/chat.participants
频道提供了在线用户列表,当用户订阅/app/chat.participants
时,将会获得当前在线用户的列表。
@Controllerpublic class ChatController { @Autowired ParticipantRepository participantRepository; @RequestMapping(value="/chat", method=RequestMethod.GET) public String chatPage(HttpServletRequest request, Model model) throws AccessDeniedException{ if(request.getSession().getAttribute("user") == null){ throw new AccessDeniedException("login please"); } User user = (User)request.getSession().getAttribute("user"); model.addAttribute("username", user.getUsername()); return "chat"; } @SubscribeMapping("/chat.participants") public CollectionretrieveParticipants() { return participantRepository.getActiveSessions().values(); }}
系统前端主要基于angular来实现,因此我们对基本websocket操作进行了封装,包括连接、订阅、发送消息。如果有兴趣,可以参考/src/main/resources/static/js/services.js
。我们的主要逻辑都在/src/main/resources/static/js/controllers.js
中,因此我们主要讲解一下这个文件。
首先我们来看看initStompClient
方法,该方法与服务器建立了websocket连接,同时订阅了我们所需要用到的几个频道:
/app/chat.participants
该频道由服务器提供服务,订阅后,服务器会返回当前的用户列表/topic/chat.login
当有用户登录,服务器会往该频道发送一个信息,客户端接收信息后将该用户添加进在线用户列表/app/chat.logout
当有用户登出,服务器会会往该频道发送一个信息,客户端接收信息后将该用户移出在线用户列表/app/chat.typing
当用户在进行输入时,会往该频道广播一个信息,客户端接收消息后在前端显示该用户正在编辑/app/chat.message
用户发送消息时,会往该频道广播一条消息,客户端接收该消息,并展示该消息
var initStompClient = function() { chatSocket.init('/ws'); chatSocket.connect(function(frame) { chatSocket.subscribe("/app/chat.participants", function(message) { $scope.participants = JSON.parse(message.body); }); chatSocket.subscribe("/app/chat.login", function(message) { $scope.participants.unshift({ username: JSON.parse(message.body).username, typing : false}); }); chatSocket.subscribe("/app/chat.logout", function(message) { var username = JSON.parse(message.body).username; for(var index in $scope.participants) { if($scope.participants[index].username == username) { $scope.participants.splice(index, 1); } } }); chatSocket.subscribe("/app/chat.typing", function(message) { var parsed = JSON.parse(message.body); if(parsed.username == $scope.username) return; for(var index in $scope.participants) { var participant = $scope.participants[index]; if(participant.username == parsed.username) { $scope.participants[index].typing = parsed.typing; } } }); chatSocket.subscribe("/app/chat.message", function(message) { $scope.messages.unshift(JSON.parse(message.body)); }); }, function(error) { toaster.pop('error', 'Error', 'Connection error ' + error); });};
广播消息其实很简单,只需要向某个固定频道发送消息,这样所有订阅了该频道的用户都能接收到相应的信息,具体代码如下:
$scope.sendMessage = function() { chatSocket.send( "/app/chat.message", {}, JSON.stringify({ message: $scope.newMessage, username: $scope.username })); $scope.newMessage = '';};
更多文章请访问