在这之前,已经做过一个Spring Boot使用WebSocket的广播式运用:
Spring Boot(五):webSocket组件的入门简单使用
现在,我们再来进一步了解webSocket;
下面我们使用webSocket组件创建一对一的聊天室:
一.首先先创建前端页面,代码如下图所示:
1.login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h2>自定义登录页面</h2>
<form action="/user/login" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</body>
</html>
2.chat.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8" />
<head>
<title>Home</title>
<script src="../js/jquery-2.1.1.min.js"></script>
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
</head>
<body>
<p>
赖赖聊天室
</p>
<form id="wiselyForm">
<textarea rows="4" cols="60" name="text"></textarea>
<input type="submit"/>
</form>
<script th:inline="javascript">
$('#wiselyForm').submit(function(e){
e.preventDefault();
var text = $('#wiselyForm').find('textarea[name="text"]').val();
sendSpittle(text);
});
var sock = new SockJS("/stomp"); //1连接SockJS的endpoint是“stomp”,与后台代码中注册的endpoint要一样。-
var stomp = Stomp.over(sock);//2<!--(2)创建STOMP协议的webSocket客户端。-->
stomp.connect('guest', 'guest', function(frame) {//3<!--(3)连接webSocket的服务端。-->
stomp.subscribe("/user/queue/notifications", handleNotification);////4 通过stompClient.subscribe()订阅服务器的目标是'/queue/notifications'发送过来的地址,
});
function handleNotification(message) {
$('#output').append("<b>Received: " + message.body + "</b><br/>")
}
function sendSpittle(text) {
stomp.send("/chat", {}, text);//3
}
$('#stop').click(function() {sock.close()});
</script>
<div id="output"></div>
</body>
</html>
二.在spring boot中创建spring Security,代码如下图所示:
1.设置主页IndexPageConfig;
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.RedirectView;
/**
* @author AxeLai
* @date 2019-04-29 09:31
*/
@Controller
public class IndexPageConfig {
@RequestMapping("/")
public RedirectView ws() {
return new RedirectView("/pages/chat.html");
}
}
2.SecurityConfig类:
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author AxeLai
* @date 2019-04-30 15:15
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public PasswordEncoder passwordEncoder(){
//当然,如果你有自己的加密方法,这个方法就写自己的加密方法好了
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置需要登入验证
// http.formLogin() // 定义当需要用户登录时候,转到的登录页面。
// .and()
// .authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
// .anyRequest() // 任何请求,登录后可以访问
// .authenticated();
//配置需要登入验证的自定义配置
http.formLogin() // 定义当需要用户登录时候,转到的登录页面。
.loginPage("/login.html") // 设置登录页面
.loginProcessingUrl("/user/login") // 自定义的登录接口
.and()
.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/login.html").permitAll() // 设置所有人都可以访问登录页面
.anyRequest() // 任何请求,登录后可以访问
.authenticated()
.and()
.csrf().disable(); // 关闭csrf防护
//配置不需要登入验证
// http.authorizeRequests()
// .anyRequest()
// .permitAll()
// .and()
// .logout()
// .permitAll();
}
//4
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user1").password(passwordEncoder.encode("123")).roles("USER")//这里两个是分别账号和密码(账号:user1密码:123)
.and()
.withUser("user2").password(passwordEncoder.encode("123")).roles("USER");//这里两个是分别账号和密码(账号:user2密码:123)
}
//5忽略静态资源的拦截
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/static/**");
}
}
3.在原来的WsController类中增加聊天的方法handleChat(),代码如下图所示:
package com.example.demo.controller;
import com.example.demo.model.WiselyMessage;
import com.example.demo.model.WiselyResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import java.security.Principal;
/**
* @author AxeLai
* @date 2019-04-29 09:08
*/
@Controller
public class WsController {
@MessageMapping("/welcome")//1@MessageMapping和@RequestMapping功能类似,用于设置URL映射地址,浏览器向服务器发起请求,需要通过该地址。
@SendTo("/topic/getResponse")//2如果服务器接受到了消息,就会对订阅了@SendTo括号中的地址传送消息
public WiselyResponse say(WiselyMessage message) throws Exception {
return new WiselyResponse("你好, " + message.getName() + "!");
}
@Autowired
private SimpMessagingTemplate messagingTemplate;//1 SimpMessagingTemplate用于向浏览器发送信息。
@MessageMapping("/chat")
public void handleChat(Principal principal, String msg) { //2在spring mvc中,principal包含了登录用户的信息,在这里我们直接用。
if (principal.getName().equals("user1")) {//3这里是一段写死的代码,如果登录的用户是dzz,那就将消息发送给zbb,大家根据自己的需求进行修改。通过convertAndSendToUser()方法进行发送,第一个参数是信息接收的目标,第二个参数是要发送的地址,第三个参数是要发送的信息。
messagingTemplate.convertAndSendToUser("user2",
"/queue/notifications", principal.getName() + "-send:"
+ msg);
} else {
messagingTemplate.convertAndSendToUser("user2",
"/queue/notifications", principal.getName() + "-send:"
+ msg);
}
}
}
4.在原来的WebSocketConfig类中再创建一个节点(endpoint)和代理(MessageBroker),代码如下图所示:
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.StompWebSocketEndpointRegistration;
/**
* Created by AxeLai on 2019/04/29 0011.
*/
@Configuration
@EnableWebSocketMessageBroker//(1),@EnableWebSocketMessageBroker注解用于开启使用STOMP协议来传输基于代理(MessageBroker)的消息,这时候控制器(controller)开始支持@MessageMapping,就像是使用@requestMapping一样。
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp").withSockJS(); //(2),注册一个Stomp的节点(endpoint),并指定使用SockJS协议。
//stomp就是websocket的端点,客户端需要注册这个端点进行链接,withSockJS允许客户端利用sockjs进行浏览器兼容性处理
}
// @Override
// public void configureMessageBroker(MessageBrokerRegistry registry) {
// registry.enableSimpleBroker("/topic");//(3),配置消息代理(MessageBroker)。
// //设置服务器广播消息的基础路径
//// registry.setApplicationDestinationPrefixes("/app");//设置客户端订阅消息的基础路径
// }
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//设置一对一聊天室的基础路径
registry.enableSimpleBroker("/queue","/topic");
}
}
三.效果演示
启动项目,分别在两个浏览器进行访问,一个用账号user1一个用账号user2,密码都是123:
登入并发送消息,结果如下:
另一个账号的界面就可以看到:
最后附上项目结构:
项目还是放在原来的github目录上;