一、什么是web消息推送
Web消息通知是指Web系统向用户发送的有关系统更新、重要事件或消息的通知,以便用户及时了解系统的最新动态。在App和网页应用中,消息通知是最常见的信息交换方式之一,用于产品与用户之间的信息同步,如产品更新、信息提醒、互动提醒、新消息通知等,从而为用户提供帮助,使其能够快速获取对应的通知信息。
如图所示就是一个文本消息推送的案例,当某一个登录用户收到新消息时,网页端能够实时更新消息的数量。
web消息推送在csdn官网的表现形式是,当有新消息到来时,消息处出现一个小红点,提示有未读消息。
二、web消息推送的实现方式
web消息推送的实现方式有很多种,包括短轮询、长轮询、SSE、iframe流、websocket、mqtt等。本文主要主要讲解通过websockt的方式实现web消息推送。
三、websocket 实现消息推送
1.服务端
1.引入依赖
<!--websocket依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.websocketConfig配置类
package com.etime.websocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @Description websocket 配置类
* @Date 2024/5/18 20:28
* @Author liukang
**/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverendpointExporter(){
return new ServerEndpointExporter();
}
}
3.实体类
package com.etime.entity;
import lombok.Data;
/**
* @Date 2024/5/24 14:10
* @Author liukang
**/
@Data
public class MessagePo {
private String msg;
private String sendUser;
private String toUser;
}
4.websockt终端类
package com.etime.websocket;
import com.etime.entity.MessagePo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Description 监听websocket地址 /myWs
* @Date 2024/5/18 20:10
* @Author liukang
**/
@ServerEndpoint("/myWs/{userId}")
@Component
@Slf4j
public class WsServerEndPoint {
// ConcurrentHashMap 和hashMap的区别是 ConcurrentHashMap是线程安全的
static Map<String,Session> sessionMap = new ConcurrentHashMap<>();
/**
* websocket建立链接时触发方法
* @author liukang
* @date 20:15 2024/5/18
* @param session 每一个websocket的链接 对于服务端都是一个session
**/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId){
// 建立连接时,将session存入map中 使用用户ID作为sessionMap的key
sessionMap.put(userId,session);
log.info("websocket is open");
log.info(userId);
}
/**
* 收到了客户端消息时触发方法
* @author liukang
* @date 20:20 2024/5/18
* @param text 客户端传来的消息内容
* @return java.lang.String
**/
@OnMessage
public String onMessage(String text){
return null;
}
/**
* 连接关闭时触发方法
* @author liukang
* @date 20:21 2024/5/18
* @param session 每一个websocket的链接 对于服务端都是一个session
**/
@OnClose
public void onClose(Session session, @PathParam("userId") String userId){
log.info(userId);
sessionMap.remove(userId);
log.info(("websocket is close"));
}
/**
* 发送消息
* @author liukang
* @date 15:49 2024/5/24
* @param message
**/
public void sendMsgToUser(MessagePo message) throws IOException {
String toUserId = message.getToUser();
Session session = sessionMap.get(toUserId);
RemoteEndpoint.Basic basicRemote = session.getBasicRemote();
ObjectMapper mapper = new ObjectMapper();
String msgStr = mapper.writeValueAsString(message);
basicRemote.sendText(msgStr);
}
}
关键点:
每个客户端的用户连接websocket服务终端的时候,携带用户唯一id(用户名唯一的话,也可以使用用户名),在建立websocket连接时,将连接保存到sessionMap,其中把用户userId作为sessionMap的key。这样做的目的是,如果要给某个用户发送消息时,根据用户id就能获取到服务终端与该用户的session连接,这样就可以对该用户发送消息了。
5.controller
package com.etime.controller;
import com.etime.entity.MessagePo;
import com.etime.websocket.WsServerEndPoint;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.IOException;
/**
* @Date 2024/5/24 14:08
* @Author liukang
**/
@RestController
public class MessageController {
@Resource
private WsServerEndPoint wsServerEndPoint;// 引入websocket终端
@RequestMapping("/sendMsg")
@CrossOrigin // 解决跨域问题
public void sendMsg(@RequestBody MessagePo message) throws IOException {
// 1.将消息数据插入到数据库--- 此处演示省略这步
// 2.websocket发送通知
wsServerEndPoint.sendMsgToUser(message);
}
}
在MessageController 类中接受前端的发送消息请求;并引入websocket终端类,掉用该类中发送消息的方法进行消息websocket发送。
6.启动类
package com.etime;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Author liukang
* @Date 2022/7/4 11:32
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
2.客户端
1.写一个假的登录页面,模拟登录。
2.打开多个浏览器窗口,登录多个用户。
窗口1:
窗口2:
3.登录成功后连接websocket,关键代码如下
// 连接websocket
let ws = new WebSocket("ws://localhost:8090/myWs/"+this.user.userId)
ws.onopen = function(){
console.log('open')
}
ws.onmessage = function(msg){
// 当前用户收到消息时,前端在这里进行处理,将消息处设置小红点或者数字;具体处理方式根据业务需求来
console.log("接收到的消息:"+msg.data)
console.log('接收到消息后消息处增加小红点进行提示')
}
ws.onclose = function(){
console.log('close')
}
注意点:连接websockt服务终端时,需要携带用户id
4.发送消息关键代码如下
this.axios.post('http://localhost:8090/sendMsg',{
sendUser:this.user.userId,
toUser:this.toUserId,
msg:this.content,
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
}
3.效果演示
admin1用户向admin2用户发送消息,如下图
admin2窗口效果如下
为了方便,这里前端并没有进行具体的实现,只在控制台进行了打印。