Spring Boot系列20 Spring Websocket实现向指定的用户发送消息

概述

不一样上文Spring Boot系列十七 Spring Boot 集成 websocket,使用RabbitMQ作为消息代理,本文咱们介绍经过Spring websocket实现向特定的用户发送消息。 本文的内容以下: 1. 首先实现简单的登陆功能,这里向特定用户发送消息的必要条件 2. 用户登陆系统后,才能够登陆websocket,并重写MyPrincipal 3. 实现向特定用户发送消息的功能 4. 测试javascript

首先实现简单的登陆功能,这是向特定用户发送消息的必要条件

TestMQCtl:控制类 提供模拟登陆,登陆成功后转到websocket页面html

/**
     * 模拟登陆     */
    @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页面java

<form action="loginIn" method="post">
    用户名:<input type="text" name="name" />
    <p>
        密码:<input type="password" name="password" />
    <p>
        <input type="submit" value="submit" />
</form>
复制代码

ws-sendtouser-rabbitmq.jsp 链接websocket并订阅消息,这个jsp以前的文章已经介绍过了这里不详细描述。页面经过向/ws/icc/websocket启动websocket,而后订阅/user/topic/demo消息jquery

<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 + "<br\>" + response.html());
    }
</script>

复制代码

用户登陆系统后,才能够登陆websocket,并重写MyPrincipal

AuthHandshakeInterceptor AuthHandshakeInterceptor是HandshakeInterceptor 的子类。在websocket握手前判断,判断当前用户是否已经登陆。若是未登陆,则不容许登陆websocketgit

@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<String, Object> 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标记登陆用户,而不是默认值github

@Component
public class MyPrincipalHandshakeHandler extends DefaultHandshakeHandler {
    private static final Logger log = LoggerFactory.getLogger(MyPrincipalHandshakeHandler.class);

    @Override
    protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> 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 定义本身的Principalweb

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信息,这里略spring

@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 模拟发送页面bash

<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登陆,并提交

点击链接,若是链接变灰色,则登陆websocket成功

登陆模拟发送页面http://127.0.0.1:8080/ws/send,向xiaoming发送test-msg

此时页面收到信息:

在模拟界面,若是咱们向其它用户发送信息,则此界面不会收到信息

测试二:

打开两个不一样的浏览器,分别使用xiaoming1,xiaoming2登陆系统, 使用模拟界面向xiaoming1发送消息,则只有xiaoming1收到 使用模拟界面向xiaoming2发送消息,则只有xiaoming2收到

###结论: 咱们已经实现向特定的用户发送消息的功能

代码

全部的详细代码见github代码,请尽可能使用tag v0.23,不要使用master,由于master一直在变,不能保证文章中代码和github上的代码一直相同

相关文章
相关标签/搜索