前言
最近微服务的项目,需要集成websocket
的功能,我在其中的一个微服务模块中集成websocket
代码实现,通过模块的端口测试正常,但是通过springboot cloud gateway
的端口访问,连接失败!我通过各种百度、和AI问答都没能解决我的问题。后来经过我的不断调试和结合之前搜索和Ai获取的知识终于解决了!本文使用是原始的websocket
协议,没有使用更高级STOMP
协议,因为postman
工具不支持这种协议的测试,所以使用原始的websocket
协议,方便后期出现问题排查!
教程
本人使用的是基于开源项目SpringBlade
搭建的微服务框架。
1. 添加配置
在gateway
模块下的application.yml
配置文件中添加以下配置
spring:
cloud:
gateway:
routes:
- id: websocket_route
uri: ws://127.0.0.1:8105 # WebSocket 目标服务器地址
predicates:
- Path=/blade-desk/** # 匹配 WebSocket 请求路径
metadata:
cors:
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
blade-desk
是项目中某个微服务的Nacos
注册服务名.url
也可以修改成uri: ws://blade-desk
或者uri: lb:ws://blade-desk
,lb:ws
可以实现微服务的负载均衡- 请求地址
ws://127.0.0.1:8080/blade-desk/ws
,需要加上服务名,才能正确转发到对应的微服务的websocket
的ServerEndpoint
上。这个我尝试了各种办法,没办法像http接口一样,可以不用加服务名,可以根据接口路径自动配置到对应的微服务中,如果你有更好的办法,可以在评论区留言! - 注意
gateway
网关模块不要引入spring-boot-starter-web
或spring-boot-starter-undertow
2. 禁用鉴权
以SpringBlade
为例,配置放行请求路径,其他鉴权框架内,根据自己的需求修改
#blade配置
blade:
secure:
skip-url:
- /ws/**
- 如果不放行鉴权,会报错
401 Unauthorized
postman 测试
- 通过网关访问
- 通过指定服务访问
WebSocket代码集成
Springboot3
集成WebSocket
,实现消息接收和发送。
创建WebSocketConfig类
代码如下:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册 WebSocket 处理器,指定路径为 "/ws"
registry.addHandler(new WebSocketHandler(), "/ws")
.setAllowedOrigins("*"); // 允许跨域访问
}
}
- registry.addHandler(new WebSocketHandler(), “/ws”) 配置websocket 服务端点
- setAllowedOrigins(“*”); 允许跨域访问
创建WebSocketHandler类
代码如下:
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
@Component
@Slf4j
public class WebSocketHandler extends TextWebSocketHandler {
// 保存所有活跃的 WebSocket 会话
private static final CopyOnWriteArrayList<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
@Override
public void afterConnectionEstablished(@NotNull WebSocketSession session) throws Exception {
// 当客户端连接成功时,将其添加到会话列表
sessions.add(session);
log.info("新连接:{}", session.getId());
}
@Override
protected void handleTextMessage(@NotNull WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
log.info("收到消息: {}", payload);
// 向客户端发送消息
//session.sendMessage(new TextMessage("服务器已收到: " + payload));
}
@Override
public void afterConnectionClosed(@NotNull WebSocketSession session, @NotNull CloseStatus status) throws Exception {
// 当客户端断开连接时,从会话列表中移除
sessions.remove(session);
log.info("连接关闭:{}", session.getId());
}
// 广播消息给所有客户端
public void broadcastMessage(String message) {
for (WebSocketSession session : sessions) {
try {
if (session.isOpen()) {
session.sendMessage(new TextMessage(message));
}
} catch (IOException e) {
log.error("广播消息失败", e);
}
}
}
}
- 该类实现了客户端连接、断开和接收消息监听、以及服务端像客户端发送消息的方法。
服务端像客户端发送消息的方法代码示例
import com.alibaba.fastjson.JSON;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springblade.core.tool.api.R;
import org.springblade.desk.vo.DishesVO;
import org.springblade.desk.websocket.WebSocketHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* 首页
*
* @author tarzan
*/
@RestController
@RequestMapping("canteen")
@AllArgsConstructor
@Tag(name = "智慧餐厅", description = "智慧餐厅")
public class CanteenController {
private final WebSocketHandler webSocketHandler;
/**
* 今日菜单
*/
@GetMapping("/today/menu")
@Operation(summary = "今日菜单", description = "今日菜单")
public R<List<DishesVO>> todayMenu() {
List<DishesVO> list = new ArrayList<>();
list.add(new DishesVO("土豆丝",""));
list.add(new DishesVO("红烧肉",""));
list.add(new DishesVO("清蒸鲈鱼",""));
list.add(new DishesVO("馒头",""));
webSocketHandler.broadcastMessage(JSON.toJSONString(list));
return R.data(list);
}
}