You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

README.md 13 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. # spring-boot-demo-websocket
  2. > 此 demo 主要演示了 Spring Boot 如何集成 WebSocket,实现后端主动往前端推送数据。网上大部分websocket的例子都是聊天室,本例主要是推送服务器状态信息。前端页面基于vue和element-ui实现。
  3. ## 1. 代码
  4. ### 1.1. pom.xml
  5. ```xml
  6. <?xml version="1.0" encoding="UTF-8"?>
  7. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  8. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  9. <modelVersion>4.0.0</modelVersion>
  10. <artifactId>spring-boot-demo-websocket</artifactId>
  11. <version>1.0.0-SNAPSHOT</version>
  12. <name>spring-boot-demo-websocket</name>
  13. <description>Demo project for Spring Boot</description>
  14. <parent>
  15. <groupId>com.xkcoding</groupId>
  16. <artifactId>spring-boot-demo</artifactId>
  17. <version>1.0.0-SNAPSHOT</version>
  18. </parent>
  19. <properties>
  20. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  21. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  22. <java.version>1.8</java.version>
  23. <oshi.version>3.9.1</oshi.version>
  24. </properties>
  25. <dependencies>
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-web</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-websocket</artifactId>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-starter-test</artifactId>
  37. <scope>test</scope>
  38. </dependency>
  39. <dependency>
  40. <groupId>com.github.oshi</groupId>
  41. <artifactId>oshi-core</artifactId>
  42. <version>${oshi.version}</version>
  43. </dependency>
  44. <dependency>
  45. <groupId>cn.hutool</groupId>
  46. <artifactId>hutool-all</artifactId>
  47. </dependency>
  48. <dependency>
  49. <groupId>com.google.guava</groupId>
  50. <artifactId>guava</artifactId>
  51. </dependency>
  52. <dependency>
  53. <groupId>org.projectlombok</groupId>
  54. <artifactId>lombok</artifactId>
  55. <optional>true</optional>
  56. </dependency>
  57. </dependencies>
  58. <build>
  59. <finalName>spring-boot-demo-websocket</finalName>
  60. <plugins>
  61. <plugin>
  62. <groupId>org.springframework.boot</groupId>
  63. <artifactId>spring-boot-maven-plugin</artifactId>
  64. </plugin>
  65. </plugins>
  66. </build>
  67. </project>
  68. ```
  69. ### 1.2. WebSocketConfig.java
  70. ```java
  71. /**
  72. * <p>
  73. * WebSocket配置
  74. * </p>
  75. *
  76. * @author yangkai.shen
  77. * @date Created in 2018-12-14 15:58
  78. */
  79. @Configuration
  80. @EnableWebSocket
  81. @EnableWebSocketMessageBroker
  82. public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  83. @Override
  84. public void registerStompEndpoints(StompEndpointRegistry registry) {
  85. // 注册一个 /notification 端点,前端通过这个端点进行连接
  86. registry.addEndpoint("/notification")
  87. //解决跨域问题
  88. .setAllowedOrigins("*")
  89. .withSockJS();
  90. }
  91. @Override
  92. public void configureMessageBroker(MessageBrokerRegistry registry) {
  93. //定义了一个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息
  94. registry.enableSimpleBroker("/topic");
  95. }
  96. }
  97. ```
  98. ### 1.3. 服务器相关实体
  99. > 此部分实体 参见包路径 [com.xkcoding.websocket.model](./src/main/java/com/xkcoding/websocket/model)
  100. ### 1.4. ServerTask.java
  101. ```java
  102. /**
  103. * <p>
  104. * 服务器定时推送任务
  105. * </p>
  106. *
  107. * @author yangkai.shen
  108. * @date Created in 2018-12-14 16:04
  109. */
  110. @Slf4j
  111. @Component
  112. public class ServerTask {
  113. @Autowired
  114. private SimpMessagingTemplate wsTemplate;
  115. /**
  116. * 按照标准时间来算,每隔 2s 执行一次
  117. */
  118. @Scheduled(cron = "0/2 * * * * ?")
  119. public void websocket() throws Exception {
  120. log.info("【推送消息】开始执行:{}", DateUtil.formatDateTime(new Date()));
  121. // 查询服务器状态
  122. Server server = new Server();
  123. server.copyTo();
  124. ServerVO serverVO = ServerUtil.wrapServerVO(server);
  125. Dict dict = ServerUtil.wrapServerDict(serverVO);
  126. wsTemplate.convertAndSend(WebSocketConsts.PUSH_SERVER, JSONUtil.toJsonStr(dict));
  127. log.info("【推送消息】执行结束:{}", DateUtil.formatDateTime(new Date()));
  128. }
  129. }
  130. ```
  131. ### 1.5. server.html
  132. ```html
  133. <!DOCTYPE html>
  134. <html lang="en">
  135. <head>
  136. <meta charset="UTF-8">
  137. <title>服务器信息</title>
  138. <link href="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.4.11/theme-chalk/index.css" rel="stylesheet">
  139. <style>
  140. .el-row {
  141. margin-bottom: 20px;
  142. }
  143. .el-row:last-child {
  144. margin-bottom: 0;
  145. }
  146. .sysFile {
  147. margin-bottom: 5px;
  148. }
  149. .sysFile:last-child {
  150. margin-bottom: 0;
  151. }
  152. </style>
  153. </head>
  154. <body>
  155. <div id="app">
  156. <el-container>
  157. <el-header>
  158. <el-button @click="_initSockJs" type="primary" :disabled="isConnected">手动连接</el-button>
  159. <el-button @click="_destroySockJs" type="danger" :disabled="!isConnected">断开连接</el-button>
  160. </el-header>
  161. <el-main>
  162. <el-row :gutter="20">
  163. <el-col :span="12">
  164. <el-card>
  165. <div slot="header">
  166. <span>CPU信息</span>
  167. </div>
  168. <el-table size="small" border :data="server.cpu" style="width: 100%">
  169. <el-table-column prop="key" label="属性">
  170. </el-table-column>
  171. <el-table-column prop="value" label="值">
  172. </el-table-column>
  173. </el-table>
  174. </el-card>
  175. </el-col>
  176. <el-col :span="12">
  177. <el-card>
  178. <div slot="header">
  179. <span>内存信息</span>
  180. </div>
  181. <el-table size="small" border :data="server.mem" style="width: 100%">
  182. <el-table-column prop="key" label="属性">
  183. </el-table-column>
  184. <el-table-column prop="value" label="值">
  185. </el-table-column>
  186. </el-table>
  187. </el-card>
  188. </el-col>
  189. </el-row>
  190. <el-row>
  191. <el-col :span="24">
  192. <el-card>
  193. <div slot="header">
  194. <span>服务器信息</span>
  195. </div>
  196. <el-table size="small" border :data="server.sys" style="width: 100%">
  197. <el-table-column prop="key" label="属性">
  198. </el-table-column>
  199. <el-table-column prop="value" label="值">
  200. </el-table-column>
  201. </el-table>
  202. </el-card>
  203. </el-col>
  204. </el-row>
  205. <el-row>
  206. <el-col :span="24">
  207. <el-card>
  208. <div slot="header">
  209. <span>Java虚拟机信息</span>
  210. </div>
  211. <el-table size="small" border :data="server.jvm" style="width: 100%">
  212. <el-table-column prop="key" label="属性">
  213. </el-table-column>
  214. <el-table-column prop="value" label="值">
  215. </el-table-column>
  216. </el-table>
  217. </el-card>
  218. </el-col>
  219. </el-row>
  220. <el-row>
  221. <el-col :span="24">
  222. <el-card>
  223. <div slot="header">
  224. <span>磁盘状态</span>
  225. </div>
  226. <div class="sysFile" v-for="(item,index) in server.sysFile" :key="index">
  227. <el-table size="small" border :data="item" style="width: 100%">
  228. <el-table-column prop="key" label="属性">
  229. </el-table-column>
  230. <el-table-column prop="value" label="值">
  231. </el-table-column>
  232. </el-table>
  233. </div>
  234. </el-card>
  235. </el-col>
  236. </el-row>
  237. </el-main>
  238. </el-container>
  239. </div>
  240. </body>
  241. <script src="js/sockjs.min.js"></script>
  242. <script src="js/stomp.js"></script>
  243. <script src="https://cdn.bootcss.com/vue/2.5.21/vue.min.js"></script>
  244. <script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.4.11/index.js"></script>
  245. <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script>
  246. <script>
  247. const wsHost = "http://localhost:8080/demo/notification";
  248. const wsTopic = "/topic/server";
  249. const app = new Vue({
  250. el: '#app',
  251. data: function () {
  252. return {
  253. isConnected: false,
  254. stompClient: {},
  255. socket: {},
  256. server: {
  257. cpu: [],
  258. mem: [],
  259. jvm: [],
  260. sys: [],
  261. sysFile: []
  262. }
  263. }
  264. },
  265. methods: {
  266. _getServerInfo() {
  267. axios.get('/demo/server')
  268. .then((response) => {
  269. this.server = response.data
  270. });
  271. },
  272. _initSockJs() {
  273. this._getServerInfo();
  274. this.socket = new SockJS(wsHost);
  275. this.stompClient = Stomp.over(this.socket);
  276. this.stompClient.connect({}, (frame) => {
  277. console.log('websocket连接成功:' + frame);
  278. this.isConnected = true;
  279. this.$message('websocket服务器连接成功');
  280. // 另外再注册一下消息推送
  281. this.stompClient.subscribe(wsTopic, (response) => {
  282. this.server = JSON.parse(response.body);
  283. });
  284. });
  285. },
  286. _destroySockJs() {
  287. if (this.stompClient != null) {
  288. this.stompClient.disconnect();
  289. this.socket.onclose;
  290. this.socket.close();
  291. this.stompClient = {};
  292. this.socket = {};
  293. this.isConnected = false;
  294. this.server.cpu = [];
  295. this.server.mem = [];
  296. this.server.jvm = [];
  297. this.server.sys = [];
  298. this.server.sysFile = [];
  299. }
  300. console.log('websocket断开成功!');
  301. this.$message.error('websocket断开成功!');
  302. }
  303. },
  304. mounted() {
  305. this._initSockJs();
  306. },
  307. beforeDestroy() {
  308. this._destroySockJs();
  309. }
  310. })
  311. </script>
  312. </html>
  313. ```
  314. ## 2. 运行方式
  315. 1. 启动 `SpringBootDemoWebsocketApplication.java`
  316. 2. 访问 http://localhost:8080/demo/server.html
  317. ## 3. 运行效果
  318. ![image-20181217110240322](http://static.xkcoding.com/spring-boot-demo/websocket/064107.jpg)
  319. ![image-20181217110304065](http://static.xkcoding.com/spring-boot-demo/websocket/064108.jpg)
  320. ![image-20181217110328810](http://static.xkcoding.com/spring-boot-demo/websocket/064109.jpg)
  321. ![image-20181217110336017](http://static.xkcoding.com/spring-boot-demo/websocket/064109-1.jpg)
  322. ## 4. 参考
  323. ### 4.1. 后端
  324. 1. Spring Boot 整合 Websocket 官方文档:https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/web.html#websocket
  325. 2. 服务器信息采集 oshi 使用:https://github.com/oshi/oshi
  326. ### 4.2. 前端
  327. 1. vue.js 语法:https://cn.vuejs.org/v2/guide/
  328. 2. element-ui 用法:http://element-cn.eleme.io/#/zh-CN
  329. 3. stomp.js 用法:https://github.com/jmesnil/stomp-websocket
  330. 4. sockjs 用法:https://github.com/sockjs/sockjs-client
  331. 5. axios.js 用法:https://github.com/axios/axios#example