Spring Boot中使用WebSocket总结(二):向指定用户发送WebSocket消息并处理对方不在线的情况...

在上一篇文章(www.zifangsky.cn/1355.html)中我介绍了在Spring项目中使用WebSocket的几种实现方式。但是,上篇文章中只介绍了服务端采用广播模式给所有客户端发送消息,然而我们有时需要服务端给指定用户的客户端发送消息(比如:发送Web通知、实时打印用户任务的日志、两个用户点对点聊天等)。

关于服务端如何给指定用户的客户端发送消息,一般可以通过以下三种方案来实现:

  • 方案一:WebSocket使用“Java提供的@ServerEndpoint注解”实现或者使用“Spring低层级API”实现,在建立连接时从HttpSession中获取用户登录后的用户名,然后把“用户名+该WebSocket连接”存储到ConcurrentHashMap。给指定用户发送消息,只需要根据接收者的用户名获取对方已经建立的WebSocket连接,接着给他发送消息即可
  • 方案二:在页面的监听路径前面动态添加当前登录的“用户ID/用户名”,这样给指定用户发送消息,只需要发送广播消息到监听了前面那个路径的客户端即可。
  • 方案三:这种方案类似于方案一。使用Spring的高级API实现WebSocket,然后自定义HandshakeHandler类并重写determineUser方法,其目的是为了在建立连接时使用用户登录后的用户名作为此次WebSocket的凭证,最后我们就可以使用messagingTemplate.convertAndSendToUser方法给指定用户发送消息了。

注:本篇文章的完整源码可以参考:github.com/zifangsky/W…

使用SimpMessagingTemplate发送消息

使用org.springframework.messaging.simp.SimpMessagingTemplate类可以在服务端的任意地方给客户端发送消息。此外,在我们配置Spring支持STOMP后SimpMessagingTemplate类就会被自动装配到Spring的上下文中,因此我们只需要在想要使用的地方使用@Autowired注解注入SimpMessagingTemplate即可使用。

需要说明的是,SimpMessagingTemplate类有两个重要的方法,它们分别是:

  • public void convertAndSend(D destination, Object payload):给监听了路径destination的所有客户端发送消息payload
  • public void convertAndSendToUser(String user, String destination, Object payload):给监听了路径destination的用户user发送消息payload
一个简单示例:
package cn.zifangsky.stompwebsocket.controller;

import cn.zifangsky.stompwebsocket.model.websocket.Greeting;
import cn.zifangsky.stompwebsocket.model.websocket.HelloMessage;
import cn.zifangsky.stompwebsocket.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;

/**
 * 测试{
    @link org.springframework.messaging.simp.SimpMessagingTemplate}类的基本用法
 * @author zifangsky
 * @date 2018/10/10
 * @since 1.0.0
 */
@Controller
@RequestMapping(("/wsTemplate"))
public class MessageTemplateController {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @Autowired
    private SimpUserRegistry userRegistry;

    @Resource(name = "redisServiceImpl")
    private RedisService redisService;

    /**
     * 简单测试SimpMessagingTemplate的用法
     */
    @PostMapping("/greeting")
    @ResponseBody
    public String greeting(@RequestBody Greeting greeting) {
        this.messagingTemplate.convertAndSend("/topic/greeting", new HelloMessage("Hello," + greeting.getName() + "!"));

        return "ok";
    }

}
复制代码

很显然,这里发送的地址是上篇文章中最后那个示例监听的地址,在客户端页面建立连接后,我们使用Postman请求一下上面这个方法,效果如下:

然后我们可以发现页面中也收到消息了:

向指定用户发送WebSocket消息并处理对方不在线的情况

给指定用户发送消息:

  • 如果接收者在线,则直接发送消息;
  • 否则将消息存储到redis,等用户上线后主动拉取未读消息。
(1)自定义HandshakeInterceptor,用于禁止未登录用户连接WebSocket:
package cn.zifangsky.stompwebsocket.interceptor.websocket;

import cn.zifangsky.stompwebsocket.common.Constants;
import cn.zifangsky.stompwebsocket.common.SpringContextUtils;
import cn.zifangsky.stompwebsocket.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.HttpSession;
import java.text.MessageFormat;
import java.util.Map;

/**
 * 自定义{
    @link org.springframework.web.socket.server.HandshakeInterceptor},实现“需要登录才允许连接WebSocket”
 *
 * @author zifangsky
 * @date 2018/10/11
 * @since 1.0.0
 */
@Component
public class AuthHandshakeInterceptor implements HandshakeInterceptor {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        HttpSession session = SpringContextUtils.getSession();
        User loginUser = (User) session.getAttribute(Constants.SESSION_USER);

        if(loginUser != null){
            logger.debug(MessageFormat.format("用户{0}请求建立WebSocket连接", loginUser.getUsername()));
            return true;
        }else{
            logger.error("未登录系统,禁止连接WebSocket");
            return false;
        }

    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

    }

}
复制代码
(2)自定义HandshakeHandler,用于在建立WebSocket的时候使用自定义的Principal:
package cn.zifangsky.stompwebsocket.interceptor.websocket;

import cn.zifangsky.stompwebsocket.common.Constants;
import cn.zifangsky.stompwebsocket.common.SpringContextUtils;
import cn.zifangsky.stompwebsocket.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;

import javax.servlet.http.HttpSession;
import java.security.Principal;
import java.text.MessageFormat;
import java.util.Map;

/**
 * 自定义{
    @lin
  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值