1.springboot整合websocket的依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.webSocket的作用
WebSocket是基于TCP协议的,它是全双工通信的,服务端可以向客户端发送信息,客户端同样可以向服务器发送指令,常用于聊天应用中。
3.Springboot加载websocket
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
/**
* 注入ServerEndpointExporter,这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
/**
* 一个http请求,先走filter,到达servlet后才进行拦截器的处理,
* 如果我们把cors放在filter里,就可以优先于权限拦截器执行。
* @return
*/
@Bean
public FilterRegistrationBean filter(){
CorsConfiguration config=new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedMethod("*");
config.addAllowedHeader("*");
//When allowCredentials is true, allowedOrigins cannot contain the special value "*“since that cannot be set on the “Access-Control-Allow-Origin” response header.
// To allow credentials to a set of origins, list them explicitly or consider using"allowedOriginPatterns” instead.
config.addAllowedOriginPattern("*");
UrlBasedCorsConfigurationSource configSource=new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**",config);
FilterRegistrationBean registrationBean=new FilterRegistrationBean(new CorsFilter(configSource));
registrationBean.addUrlPatterns("/*");
//优先级,越低越优先
registrationBean.setOrder(1);
return registrationBean;
}
@Override
protected void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
.allowedMethods("GET","POST","PUT","DELETE","OPTIONS")
// 是否允许证书(cookies)
.allowCredentials(true)
//设置时间有效
.maxAge(3600);
}
}
4.websocket核心代码
4.1.server类
package com.zyp.websocket;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author syl
* @description TODO
* @since 2022/3/26
*/
//通过注解ServerEndpoint设置WebSocket连接点的服务地址
@ServerEndpoint(value = "/websocket/{userId}")
/**
*@Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean,此处必须要有
*/
@Component
public class WebSocketServer {
private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
/**
* 在线人数
*/
private static AtomicInteger count=new AtomicInteger(0);
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 使用map对象,便于根据userId来获取对应的WebSocket,或者放redis里面
*/
private static Map<String,WebSocketServer> webSocketServerMap=new ConcurrentHashMap<>();
/**
* 用户id
*/
private String userId="";
/**
* 强制下线指定用户
* @param userId
* @return
*/
public static String offline(String userId) {
if(webSocketServerMap.containsKey(userId)){
webSocketServerMap.remove(userId);
//用户-1
count.getAndDecrement();
return "强制下线用户"+userId+"成功";
}else{
return "当前用户"+userId+"不在线";
}
}
/**
* 连接建立成功调用的方法
* 注意此处是PathParam不是PathVariable
* @param userId
*/
@OnOpen
public void onOpen(@PathParam(value = "userId") String userId,Session session){
this.userId=userId;
this.session=session;
//判断当前用户是否在线
if(webSocketServerMap.containsKey(userId)){
webSocketServerMap.remove(userId);
webSocketServerMap.put(userId,this);
}else {
webSocketServerMap.put(userId, this);
//数量+1
count.getAndIncrement();
}
log.info("websocket新连接:{},当前在线人数为:{}",userId,getOnline());
}
/**
* 该方法在断开连接后调用
*/
@OnClose
public void OnClose(){
//用户在线才断开连接
if(webSocketServerMap.containsKey(this.userId)){
webSocketServerMap.remove(this.userId);
//数量-1
count.getAndDecrement();
log.info("websocket连接关闭:{},当前在线人数为:{}",this.userId,getOnline());
}
}
/**
* 该方法在连接异常调用
*/
@OnError
public void OnError(Throwable throwable){
log.info("websocket连接关闭:{},错误原因为:{}",this.userId,throwable.getMessage());
webSocketServerMap.remove(this.userId);
//数量-1
count.getAndDecrement();
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void OnMessage(String message,Session session){
log.info("收到来自窗口"+userId+"的信息:"+message);
if(StringUtils.isNotBlank(message)){
JSONArray list= JSONArray.parseArray(message);
for (int i = 0; i < list.size(); i++) {
try {
//解析发送的报文
JSONObject object = list.getJSONObject(i);
String toUserId=object.getString("toUserId");
String contentText=object.getString("contentText");
object.put("fromUserId",this.userId);
//传送给对应用户的websocket
if(StringUtils.isNotBlank(toUserId)&&StringUtils.isNotBlank(contentText)){
WebSocketServer webSocketServer = webSocketServerMap.get(toUserId);
//需要进行转换,userId
if(webSocketServer!=null){
// webSocketServer.sendMessage(JSON.toJSONString(ApiReturnUtil.success(object)));
//此处可以放置相关业务代码,例如存储到数据库
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
/**
* 实现服务器主动发送信息
* @param message
* @param message
*/
public void sendMessage(String message){
//异步发送
this.session.getAsyncRemote().sendText(message);
}
/**
* 向指定的用户发送信息
* @param message
* @param message
*/
public static void sendInfo(String userId,String message){
if(webSocketServerMap.containsKey(userId)){
webSocketServerMap.get(userId).sendMessage(message);
log.info("发送给用户:{}的信息:{}成功",userId,message);
}else {
log.info("用户:{}不在线",userId);
}
}
/**
* 群发信息
* @param message
* @param userIds
*/
public static void batchSendInfo(String message, List<String> userIds){
if(CollectionUtils.isEmpty(userIds)){
webSocketServerMap.keySet().forEach(userId->sendInfo(userId,message));
}else {
userIds.forEach(userId->sendInfo(userId, message));
}
}
/**
* 获取在线人数
* @return
*/
public static int getOnline(){
return count.intValue();
}
/**
* 获取在线用户
* @return
*/
public static Set<String> getUser(){
return webSocketServerMap.keySet();
}
}
4.2.控制层
package com.zyp.controller;
import com.google.common.collect.Lists;
import com.zyp.common.NoLogin;
import com.zyp.websocket.WebSocketServer;
import com.zyp.util.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.Set;
/**
* @author syl
* @description 测试websocket的使用
* @since 2022/3/26
*/
@RestController
@RequestMapping(value = "websocket/")
@Api(tags = "websocket测试")
public class WebSocketController {
@ApiOperation("发送信息")
@GetMapping("pushMessage/{userId}")
@NoLogin
public Result pushMessage(@PathVariable String userId, @RequestParam String message){
if(StringUtils.equals(userId, "all")){
WebSocketServer.batchSendInfo(message, Lists.newArrayList());
}else{
WebSocketServer.batchSendInfo(message, Arrays.asList(userId.split(",")));
}
return Result.ok("发送成功");
}
@ApiOperation("获取在线的")
@GetMapping("getOnline")
@NoLogin
public Result getOnline(){
//在线人数
int count = WebSocketServer.getOnline();
Set<String> users = WebSocketServer.getUser();
return Result.ok().put("count",count).put("userList",users);
}
@ApiOperation("强制下线指定用户")
@GetMapping("offline/{userId}")
@NoLogin
public Result offline(@PathVariable String userId){
String result = WebSocketServer.offline(userId);
return Result.ok(result);
}
}
4.3前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket</title>
</head>
<body>
<div id="message"></div>
</body>
<script>
let websocket = null;
// 用时间戳模拟登录用户
const username = new Date().getTime();
// alert(username)
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
console.log("浏览器支持Websocket");
websocket = new WebSocket('ws://localhost:9002/websocket/' + username);
} else {
alert('当前浏览器 不支持 websocket');
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket连接成功");
};
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
};
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket连接关闭");
};
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWebSocket();
};
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
</script>
</html>