1. 引入依赖
重点是去除gateWay项目中的所有tomcat依赖,不然会在gateWay阶段报错,java.lang.ClassCastException
<!--spring cloud 依赖 和 alibaba springcloud 依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2023.0.3.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--去除spring gateWay 项目中的所有tomcat-->
<dependency>
<groupId>jieyicai.v3</groupId>
<artifactId>base-core</artifactId>
<version>1.0.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>tomcat-embed-el</artifactId>
<groupId>org.apache.tomcat.embed</groupId>
</exclusion>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--我的项目中这个依赖是在core包下 所以我只需要对core包进行去除tomcat的操作就可以了-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- SpringCloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2. 网关配置文件 properties版本
# 跨域配置 也可以放代码里面 或者nginx里面等
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowed-origin-patterns[0]=http://10.0.*.*:*
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowed-origin-patterns[1]=https://*.*lydjiezhibao.com/
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedHeaders=*
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowCredentials=true
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedMethods=GET,POST,OPTIONS,DELETE,PUT,HEAD,PATCH
# 服务发现中 服务的名称为小写
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
# 开启服务发现 不配置静态路由 网关仍然可以根据服务注册中心(如Eureka、Consul等)中注册的服务信息自动生成路由规则。这意味着你可以通过服务ID访问后端服务,而不需要手动定义每个路由。
spring.cloud.gateway.discovery.locator.enabled=true
# knife4j 简单配置
# 指定服务发现的模式聚合微服务文档
knife4j.gateway.enabled=true
# 排序规则(tag/operation排序自4.2.0版本新增)
# 取值:alpha-默认排序规则,官方swagger-ui默认实现,order-Knife4j提供的增强排序规则,开发者可扩展x-order,根据数值来自定义排序
knife4j.gateway.tags-sorter=order
knife4j.gateway.operations-sorter=order
# 指定版本号(Swagger2|OpenAPI3)
knife4j.gateway.discover.version=openapi3
# 排除的服务
knife4j.gateway.discover.excluded-services=${spring.application.name}
# 指定服务发现的模式聚合微服务文档,并且是默认`default`分组
knife4j.gateway.strategy=discover
knife4j.gateway.discover.enabled=true
# WebSocket路由配置(多个的话 就需要在这里配置多个)
spring.cloud.gateway.routes[0].id=jieyicai_boss_websocket_msg
spring.cloud.gateway.routes[0].uri=wss://jieyicai-boss
# 同一个项目服务下的的websocket建议前缀一致 这样的话添加其他的websocket的时候就不需要重新配置路由了。
spring.cloud.gateway.routes[0].predicates[0]= Path=/boss_web_socket/**
spring.cloud.gateway.routes[0].predicates[1]= Header=Connection,Upgrade
spring.cloud.gateway.httpclient.pool.max-connections=1000
spring.cloud.gateway.httpclient.pool.max-idle-time=60000
nginx 配置
只有配置了对应的websocket的必要条件,才能将请求以ws的形式转发给微服务,不配置的话,微服务方会将请求当作http或者静态资源请求。
location / {
proxy_pass http://10.0.14.175:7800;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# nginx 在正常转发之余,添加websocket的必须条件:proxy_http_version 1.1和Upgrade头
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s; # 读取超时时间,这里设置为 1 小时
proxy_send_timeout 3600s; # 发送超时时间,这里设置为 1 小时
}
websocket配置:
// 公共配置:
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
// 私有service (举例)
/**
* @author QQQtx
* @since 2025/2/21
*/
@Slf4j
@Component
@ServerEndpoint("/boss_web_socket/msg/{user}")
public class BossWebSocketMsgServer {
private static final ConcurrentHashMap<String, List<Session>> ONLINE_SESSION_POOL = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam("user") String user) {
try {
List<Session> sessions = new ArrayList<>();
if (ONLINE_SESSION_POOL.containsKey(user)) {
sessions = ONLINE_SESSION_POOL.get(user);
}
if (CollectionUtils.isNotEmpty(sessions)) {
log.info("聊天人员:{}加入聊天会话{}", session, user);
} else {
log.info("聊天通道:{}创建,{}加入聊天通道", user, session);
}
sessions.add(session);
ONLINE_SESSION_POOL.put(user, sessions);
logInfo();
} catch (Exception exception) {
log.error(exception.getMessage());
}
}
@OnClose
public void onClose(@PathParam("user") String user, Session session) {
try {
if (ONLINE_SESSION_POOL.containsKey(user)) {
List<Session> sessions = ONLINE_SESSION_POOL.get(user);
if (sessions.size() == 1) {
ONLINE_SESSION_POOL.remove(user);
log.info("聊天通道:{}下线", user);
} else {
sessions.remove(session);
log.info("聊天人员:{}离开聊天通道{}", session, user);
}
}
logInfo();
} catch (Exception exception) {
log.error(exception.getMessage());
}
}
@OnError
public void onError(Throwable error) {
log.error(error.getMessage());
}
@OnMessage
public void onMessage(String message, @PathParam("user") String user) {
log.info("收到消息:{},来自:{}", message, user);
}
public static void sendMassage(String user, String message) {
if (!ONLINE_SESSION_POOL.containsKey(user)) {
return;
}
List<Session> session = ONLINE_SESSION_POOL.get(user);
if (CollectionUtils.isEmpty(session)) {
ONLINE_SESSION_POOL.remove(user);
return;
}
for (Session s : session) {
if (s != null) {
s.getAsyncRemote().sendText(message);
}
}
}
public static void sendAllMassage(String message) {
ONLINE_SESSION_POOL.forEach((k, v) -> v.forEach(j -> j.getAsyncRemote().sendText(message)));
}
private void logInfo() {
log.info("当前在线聊天通道:{}", ONLINE_SESSION_POOL.size());
}
}
验证
# 通过 wss://或者 ws:// 一个带证书加密 一个不带
# 通过 wss://gateway-jyc.lydjiezhibao.com/jieyicai-boss/boss_web_socket/msg/374414311525650432(举例) 的方式去进行验证是否建立链接