不同上文Spring Boot系列十七 Spring Boot 集成 websocket,使用RabbitMQ做为消息代理,本文我们介绍通过Spring websocket实现向特定的用户发送消息。
本文的内容如下:
1. 首先实现简单的登录功能,这里向特定用户发送消息的必要条件
2. 用户登录系统后,才可以登录websocket,并重写MyPrincipal
3. 实现向特定用户发送消息的功能
4. 测试
TestMQCtl:控制类
提供模拟登录,登录成功后转到websocket页面
/**
* 模拟登录 */
@RequestMapping(value = "loginIn", method = RequestMethod.POST)
public String login(HttpServletRequest request, @RequestParam(required=true) String name, String pwd){
HttpSession httpSession = request.getSession();
// 如果登录成功,则保存到会话中
httpSession.setAttribute("loginName", name);
return "websocket/sendtouser/ws-sendtouser-rabbitmq";
}
/**
* 转到登录页面
*/
@RequestMapping(value = "login", method = RequestMethod.GET)
public String loginPage(){
// 转到登录页面
return "websocket/sendtouser/login";
}
/**
* websocket页面
* @return
*/
@RequestMapping(value="/broadcast-rabbitmq/index")
public String broadcastIndex(){
return "websocket/sendtouser/ws-sendtouser-rabbitmq";
}
login.jsp
简单的form表单,将请求提到loginIn,并转到ws-sendtouser-rabbitmq.jsp页面
ws-sendtouser-rabbitmq.jsp
连接websocket并订阅消息,这个jsp之前的文章已经介绍过了这里不详细描述。页面通过向/ws/icc/websocket启动websocket,然后订阅/user/topic/demo消息
<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
$('#response').html();
}
function connect() {
// websocket的连接地址,此值等于WebSocketMessageBrokerConfigurer中registry.addEndpoint("/ws/icc/websocket").withSockJS()配置的地址
var socket = new SockJS('/ws/icc/websocket'); //1
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
// 客户端订阅消息的目的地址:此值等于BroadcastCtl中@SendTo注解的里配置的值。
stompClient.subscribe(
'/user/topic/demo',
function(respnose){
showResponse(JSON.parse(respnose.body));
}
);
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function showResponse(message) {
var response = $("#response");
response.html(message.name + "
" + response.html());
}
script>
AuthHandshakeInterceptor
AuthHandshakeInterceptor是HandshakeInterceptor 的子类。在websocket握手前判断,判断当前用户是否已经登录。如果未登录,则不允许登录websocket
@Component
public class AuthHandshakeInterceptor implements HandshakeInterceptor {
private static final Logger log = LoggerFactory.getLogger(AuthHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
HttpSession httpSession = getSession(request);
String user = (String)httpSession.getAttribute("loginName");
if(StringUtils.isEmpty(user)){
log.error("未登录系统,禁止登录websocket!");
return false;
}
log.info("login = " + user);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
// 参考 HttpSessionHandshakeInterceptor
private HttpSession getSession(ServerHttpRequest request) {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
return serverRequest.getServletRequest().getSession(false);
}
return null;
}
}
MyPrincipalHandshakeHandler
MyPrincipalHandshakeHandler是DefaultHandshakeHandler 的子类,处理websocket请求,这里我们只重写determineUser方法,生成我们自己的Principal ,这里我们使用loginName标记登录用户,而不是默认值
@Component
public class MyPrincipalHandshakeHandler extends DefaultHandshakeHandler {
private static final Logger log = LoggerFactory.getLogger(MyPrincipalHandshakeHandler.class);
@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map attributes) {
HttpSession httpSession = getSession(request);
String user = (String)httpSession.getAttribute("loginName");
if(StringUtils.isEmpty(user)){
log.error("未登录系统,禁止登录websocket!");
return null;
}
log.info(" MyDefaultHandshakeHandler login = " + user);
return new MyPrincipal(user);
}
private HttpSession getSession(ServerHttpRequest request) {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
return serverRequest.getServletRequest().getSession(false);
}
return null;
}
}
MyPrincipal
定义自己的Principal
public class MyPrincipal implements Principal {
private String loginName;
public MyPrincipal(String loginName){
this.loginName = loginName;
}
@Override
public String getName() {
return loginName;
}
}
配置websocket
在registerStompEndpoints中将我们MyPrincipalHandshakeHandler 和AuthHandshakeInterceptor 配置到服务中
configureMessageBroker方法配置rabbitmq信息,这里略
@Configuration
// 此注解开使用STOMP协议来传输基于消息代理的消息,此时可以在@Controller类中使用@MessageMapping
@EnableWebSocketMessageBroker
public class WebSocketRabbitMQMessageBrokerConfigurer extends AbstractWebSocketMessageBrokerConfigurer {
@Autowired
private MyPrincipalHandshakeHandler myDefaultHandshakeHandler;
@Autowired
private AuthHandshakeInterceptor sessionAuthHandshakeInterceptor;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/icc/websocket")
.addInterceptors(sessionAuthHandshakeInterceptor)
.setHandshakeHandler(myDefaultHandshakeHandler)
.withSockJS();
}
….
}
TestMQCtl:
登录到模拟发送页面:send.jsp
我们使用SimpMessagingTemplate 对象的convertAndSendToUser向指定用户的/topic/demo发送消息
@Autowired
private SimpMessagingTemplate template;
/**
* 发送页面
*/
@RequestMapping(value = "send")
public String sendMq2UserPage(String msg, String userName){
return "websocket/sendtouser/send";
}
/**
* 向执行用户发送请求
*/
@RequestMapping(value = "send2user")
@ResponseBody
public int sendMq2User(String msg, String name){
System.out.println("===========" + msg + "=======" + name);
RequestMessage demoMQ = new RequestMessage();
demoMQ.setName(msg);
template.convertAndSendToUser(name, "/topic/demo", JSON.toJSONString(demoMQ));
return 0;
}
send.jsp
模拟发送页面
<form action="login" method="post">
接收者用户:<input type="text" id="name" name="name" value="<%=session.getAttribute("loginName") %>" />
<p>
消息内容:<input type="text" id="msg" name="msg" />
<p>
<input type="button" id="send" value="发送" />
form>
<script src="/websocket/jquery.js">script>
<script type=text/javascript>
$("#send").click(function(){
$.post("send2user",
{
name: $('#name').val(),
msg: $('#msg').val()
},
function(data, status){
alert("Data: " + data + "\nStatus: " + status);
});
});
script>
登录 http://127.0.0.1:8080/ws/login,使用xiaoming登录,并提交
登录模拟发送页面http://127.0.0.1:8080/ws/send,向xiaoming发送test-msg
此时页面收到信息:
在模拟界面,如果我们向其它用户发送信息,则此界面不会收到信息
打开两个不同的浏览器,分别使用xiaoming1,xiaoming2登录系统,
使用模拟界面向xiaoming1发送消息,则只有xiaoming1收到
使用模拟界面向xiaoming2发送消息,则只有xiaoming2收到
我们已经实现向特定的用户发送消息的功能
所有的详细代码见github代码,请尽量使用tag v0.23,不要使用master,因为master一直在变,不能保证文章中代码和github上的代码一直相同