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

概述

不同上文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页面

"loginIn" method="post"> 用户名:type="text" name="name" />

密码:type="password" name="password" />

type="submit" value="submit" />

复制代码

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



复制代码

用户登录系统后,才可以登录websocket,并重写MyPrincipal

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

"login" method="post"> 接收者用户:type="text" id="name" name="name" value="<%=session.getAttribute("loginName") %>" />

消息内容:type="text" id="msg" name="msg" />

type="button" id="send" value="发送" />

复制代码

测试

测试一:

登录 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上的代码一直相同

你可能感兴趣的:(Spring Boot系列20 Spring Websocket实现向指定的用户发送消息)