小编在做微服务项目上遇到一个问题,正如标题描述一样,但是百度好久未果(太多文章都是互相copy的)
小编决定自己写一下解决方案。。。
下面只贴部分代码,如需完整demo请上github获取
1. 首先搭建 websocket 的服务端
1.1. 在 websocket 服务端的 pom 配置文件中需要有 websocket 的依赖(由于在pom中配置了统一版本管理,这里就不需要写版本了)
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-websocket</artifactId> 4 </dependency>
1.2. 略过启动类,配置 websocket 工具类;配置的说明,在代码中都有体现
1 package com.beyond.config; 2 3 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 6 import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 7 import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 8 9 @Configuration 10 // 此注解表示使用STOMP协议来传输基于消息代理的消息,此时可以在@Controller类中使用@MessageMapping 11 @EnableWebSocketMessageBroker 12 public class WebSocketAutoConfig implements WebSocketMessageBrokerConfigurer { 13 @Override 14 public void registerStompEndpoints(StompEndpointRegistry registry) { 15 //本实例前端 sockjs 请求路径为 http://localhost:8888/client/websocket/web/socket 16 registry.addEndpoint("/websocket/web/socket") //websocket监听地址,sockjs连接地址(如果有配置项目路径【server.servlet.context-path】,需要加上) 17 .setAllowedOrigins("*") //允许跨域访问 18 .withSockJS(); //使用sockJS 19 } 20 }
1.3. 使用 websocket 【使用方式,只写了 请求回复和定时推送 两种】
1.3.1. 请求回复模式【编写方式和普通的 http 的请求回复很像】
1 package com.beyond.controller; 2 3 import com.alibaba.fastjson.JSONObject; 4 import lombok.extern.slf4j.Slf4j; 5 import org.springframework.messaging.handler.annotation.MessageMapping; 6 import org.springframework.messaging.handler.annotation.SendTo; 7 import org.springframework.web.bind.annotation.RestController; 8 9 @RestController 10 @Slf4j 11 public class FirstController { 12 13 /** 14 * 此方法用于点对点,一发一答 15 * 16 * @param message 17 * @return 18 */ 19 @SendTo("/websocket/read/response/message")//前端订阅消息的地址如果是这个,就会收到该方法的返回结果 20 //MessageMapping 注解可写在类上,和 RequestMapping 类似 21 @MessageMapping("/websocket1")//前端往这个地址推送数据,类似于 http 的方法请求 22 public String readMessage(String message) { 23 //todo something 24 log.info("message {}", message); 25 return JSONObject.parseObject(message).get("message") + " say: hello word."; 26 } 27 }
1.3.2. 定时推送模式【这里需要注入 SimpMessagingTemplate 对象进行操作】;这里引入一点题外知识【注入对象的方式】
1 package com.beyond.task; 2 3 import lombok.extern.slf4j.Slf4j; 4 import org.springframework.messaging.simp.SimpMessagingTemplate; 5 import org.springframework.scheduling.annotation.Async; 6 import org.springframework.scheduling.annotation.Scheduled; 7 import org.springframework.stereotype.Component; 8 9 @Component 10 @Async 11 @Slf4j 12 public class WebsocketTask { 13 14 15 /* 16 * 17 * 方式一 Autowired 属性注入 (不推荐使用) 18 // @Autowired 19 // private SimpMessagingTemplate simpMessagingTemplate; 20 */ 21 22 /* 23 * 24 * 方式二 set注入,在属性多的情况下推荐使用 25 // private SimpMessagingTemplate simpMessagingTemplate; 26 27 // @Autowired 28 // public void setSimpMessagingTemplate(SimpMessagingTemplate simpMessagingTemplate) { 29 // this.simpMessagingTemplate = simpMessagingTemplate; 30 // } 31 */ 32 33 /** 34 * 方式三 构造函数注入,在属性少的情况下推荐使用 35 */ 36 private final SimpMessagingTemplate simpMessagingTemplate; 37 38 public WebsocketTask(SimpMessagingTemplate simpMessagingTemplate) { 39 this.simpMessagingTemplate = simpMessagingTemplate; 40 } 41 42 43 /** 44 * 此定时任务用于往 /websocket/send/response/message 订阅地址定时发送消息;发送消息需要依赖 SimpMessagingTemplate 消息模板 45 */ 46 @Scheduled(cron = "*/5 * * * * ?") 47 public void roundSendMessage() { 48 //todo something 49 //向 /websocket/send/response/message 订阅地址发送消息 [hello word!!] 50 simpMessagingTemplate.convertAndSend("/websocket/send/response/message", "hello word!!"); 51 } 52 }
2. 现在就可以用前端代码测试了,这个代码是从百度上 copy 后改写的
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8" /> 5 <title>Spring cloud gateway WebSocket</title> 6 </head> 7 <body> 8 <div> 9 <div> 10 <button id="connect" onclick="connect()">连接</button> 11 <button id="disconnect" onclick="disconnect();">断开连接</button> 12 </div> 13 <div id="conversationDiv"> 14 <label>输入消息</label> <input type="text" id="messgae" /> 15 <button id="send" onclick="send();">发送</button> 16 <p id="response"></p> 17 </div> 18 </div> 19 <script src="sockjs.min.js"></script> 20 <script src="stomp.min.js"></script> 21 <script src="jquery.min.js"></script> 22 <script type="text/javascript"> 23 var connectioned = false; 24 25 function connect() { 26 // websocket的连接地址,此值等于WebSocketMessageBrokerConfigurer中registry.addEndpoint("/websocket/web/socket").withSockJS()配置的地址;如果有项目前缀,需要加上 27 //var socket = new SockJS('http://192.168.1.95:19999/server/websocket/web/socket');//网关websocket代理地址 28 var socket = new SockJS('http://192.168.1.95:8888/server/websocket/web/socket');//子项目websocket地址 29 stompClient = Stomp.over(socket); 30 stompClient.connect({}, function(frame) { 31 connectioned = true; 32 console.log('Connected: ' + frame); 33 // 客户端订阅消息的目的地址:此值等于BroadcastCtl中@SendToUser("/topic/getResponse")注解的里配置的值。这是请求的地址必须使用/user前缀 34 stompClient.subscribe('/websocket/read/response/message', function(respnose){ 35 console.log("read -------------------"); 36 console.log(respnose.body); 37 }); 38 39 stompClient.subscribe('/websocket/send/response/message', function(respnose){ 40 console.log("------------------- send"); 41 console.log(respnose.body); 42 }); 43 }); 44 } 45 46 47 function disconnect() { 48 if (stompClient != null) { 49 stompClient.disconnect(); 50 } 51 connectioned = false; 52 console.log("Disconnected"); 53 } 54 55 function send() { 56 if(connectioned){ 57 var name = $('#messgae').val(); 58 console.log("message --- " +name); 59 //客户端消息发送的目的:服务端使用BroadcastCtl中@MessageMapping("/websocket1")注解的方法来处理发送过来的消息;如果类上有配置 @MessageMapping 注解,请求路径需要拼接 [类上/方法上] 60 stompClient.send("/websocket1", {}, JSON.stringify({ 'message': name })); 61 } 62 } 63 </script> 64 </body> 65 </html>
测试结果就不上图了,亲测可用
3. 网关配置
先说明下,这里小编踩过坑,在这里要提醒各位同学,在配置 spring cloud gateway 的时候,必须要用 yml 格式的配置文件,否则不生效。但是不知道为什么,有知道的大神可以告诉我!
3.1. 网关的pom配置文件中需要引入 springcloud 的 gateway
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
3.2. 只需要配置路由转发规则即可,其他帖子上说要配置其他的,全部都是瞎扯
要使用 yml 格式的配置文件
properties 和 yml 配置文件可以共存【properties 优先级比 yml 高】
#必须使用 yml 格式配置文件,转发 socket 才会生效 spring: cloud: gateway: #路由转发 routes: #转发web请求 - id: customize-web #lb 代表从注册中心获取 serverId 来做负载请求 uri: lb://server predicates: - Path=/server/websocket/web/socket/info/** #如果需要加访问前缀,转发是需要去掉;前缀加一层,StripPrefix 值+1 #filters: # - StripPrefix=1 #转发websocket - id: customize-websocket uri: lb:ws://server predicates: - Path=/server/websocket/web/socket/**
到这里就已经配置完成了,在刚刚那个前端实例里面,端口改成 gateway 的端口即可
亲测可用
有问题的同学可以留言讨论