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上的代碼一直相同

相關文章
相關標籤/搜索