WebSocket 使用
2025年4月17日大约 2 分钟
WebSocket 使用
注意
- 浏览器地址栏不能直接发起websocket请求,需要通过接口工具或者写页面
- 发起请求协议为ws
使用 WebSocket
1.业务类
用于写WebSocket连接和关闭的业务代码
@Slf4j
@Component
public class WSServicer implements WebSocketHandler {
//当前操作图片的用户,这里设定只能一个用户进行操作
public static Map<Long, Long> pictureHandleUsersMap = new ConcurrentHashMap<>();
//建立连接的所有图片session会话集合
public static Map<Long, Set<WebSocketSession>> sessionMap = new ConcurrentHashMap<>();
//建立连接后,把session会话连接放到map中
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String requestUri = String.valueOf(session.getUri());
String imageId = requestUri.substring(requestUri.lastIndexOf("/") + 1);
Long picId = Long.valueOf(imageId);
//初始化一个set集合
sessionMap.putIfAbsent(picId, ConcurrentHashMap.newKeySet());
// 将当前会话添加到该图片ID的会话集合中
Set<WebSocketSession> sessions = sessionMap.get(picId);
sessions.add(session);
boad(picId);
}
//连接关闭,移除关闭连接的session
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
sessionMap.remove(session.getId());
log.info("该会话关闭连接{}", session.getId());
}
//自定义广播方法, 给当前图片的连接用户广播信息
public static void boad(Long piutureId) throws IOException {
Set<WebSocketSession> sessions = sessionMap.get(piutureId);
sessionMap.putIfAbsent(piutureId, ConcurrentHashMap.newKeySet());
String strId = String.valueOf(piutureId);
for (WebSocketSession session : sessions) {
session.sendMessage(new TextMessage(String.format("你是正在处理图片%s的用户", strId)));
}
}
}
2.WebSocket拦截器
1. WebSocket 的握手阶段本质上就是一次普通的 HTTP 请求
2. 所以在握手阶段(beforeHandshake 方法里),可以把ServerHttpRequest 转成 HttpServletRequest,就像处理http请求一样去拦截鉴权:
@Component
public class WsInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
HttpServletRequest httpServletRequest = null;
HttpServletResponse httpServletResponse = null;
if (request instanceof ServletServerHttpRequest servletRequest) {
httpServletRequest = servletRequest.getServletRequest();
}
if (response instanceof ServletServerHttpResponse servletResponse) {
httpServletResponse = servletResponse.getServletResponse();
}
//登录鉴权
//1.拿到请求头中的token
String token = httpServletRequest.getHeader("Authorization");
if (token == null) {
httpServletResponse.setStatus(401);
throw new CustomerException(410, "用户token不存在");
}
Map<String, Object> userMap = JwtUtil.parseToken(token);
Object idObj = userMap.get("id");
Long userId;
if (idObj instanceof Number) {
userId = ((Number) idObj).longValue();
} else {
throw new CustomerException("JWTAuthorization 中的 id 不是有效的数字类型");
}
//2.检验是否和redis中的一致
String redisToken = (String) redisTemplate.opsForValue().get(USER_TOKEN_KEY + userId);
if (!token.equals(redisToken)) {
throw new CustomerException(401, "token无效");
}
//权限鉴权....等
//放行
return true;
}
}
3.WebSocket 配置类
- addHandler为配置注册处理器,参数为业务处理类和请求路径
- addInterceptors 为WebSocket拦截器,握手建立之前进行拦截。类似于 HTTP 请求的前置拦截器。
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer{
@Autowired
WSServicer wsServicer;
@Autowired
WsInterceptor WsInterceptor
// WebSocket 配置
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// WebSocket的注册处理器和拦截处理器
registry.addHandler(wsServicer, "/test/{id}") //注册处理器
.addInterceptors(WsInterceptor) // 自定义拦截器
.setAllowedOrigins("*"); // 允许跨域
}
}
WebSocket 扩展玩法
通过发送http请求给ws连接发布消息
//新玩法,通过请求给ws的连接发送消息
@RestController
public class WebSocketController {
// 通过 API 触发向处理图片1的所有用户发送消息
@GetMapping("/sendMessage")
public String sendMessage(@RequestParam Long pictureId) throws IOException {
// 调用 WebSocket 服务的广播方法
WSServicer.boad(pictureId);
return "消息已发送给所有处理图片" + pictureId + "的用户";
}
}