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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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. * @package: com.xkcoding.websocket.config
  77. * @description: WebSocket配置
  78. * @author: yangkai.shen
  79. * @date: Created in 2018-12-14 15:58
  80. * @copyright: Copyright (c) 2018
  81. * @version: V1.0
  82. * @modified: yangkai.shen
  83. */
  84. @Configuration
  85. @EnableWebSocket
  86. @EnableWebSocketMessageBroker
  87. public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  88. @Override
  89. public void registerStompEndpoints(StompEndpointRegistry registry) {
  90. // 注册一个 /notification 端点,前端通过这个端点进行连接
  91. registry.addEndpoint("/notification")
  92. //解决跨域问题
  93. .setAllowedOrigins("*")
  94. .withSockJS();
  95. }
  96. @Override
  97. public void configureMessageBroker(MessageBrokerRegistry registry) {
  98. //定义了一个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息
  99. registry.enableSimpleBroker("/topic");
  100. }
  101. }
  102. ```
  103. ### 1.3. 服务器相关实体
  104. > 此部分实体 参见包路径 [com.xkcoding.websocket.model](./src/main/java/com/xkcoding/websocket/model)
  105. ### 1.4. ServerTask.java
  106. ```java
  107. /**
  108. * <p>
  109. * 服务器定时推送任务
  110. * </p>
  111. *
  112. * @package: com.xkcoding.websocket.task
  113. * @description: 服务器定时推送任务
  114. * @author: yangkai.shen
  115. * @date: Created in 2018-12-14 16:04
  116. * @copyright: Copyright (c) 2018
  117. * @version: V1.0
  118. * @modified: yangkai.shen
  119. */
  120. @Slf4j
  121. @Component
  122. public class ServerTask {
  123. @Autowired
  124. private SimpMessagingTemplate wsTemplate;
  125. /**
  126. * 按照标准时间来算,每隔 2s 执行一次
  127. */
  128. @Scheduled(cron = "0/2 * * * * ?")
  129. public void websocket() throws Exception {
  130. log.info("【推送消息】开始执行:{}", DateUtil.formatDateTime(new Date()));
  131. // 查询服务器状态
  132. Server server = new Server();
  133. server.copyTo();
  134. ServerVO serverVO = ServerUtil.wrapServerVO(server);
  135. Dict dict = ServerUtil.wrapServerDict(serverVO);
  136. wsTemplate.convertAndSend(WebSocketConsts.PUSH_SERVER, JSONUtil.toJsonStr(dict));
  137. log.info("【推送消息】执行结束:{}", DateUtil.formatDateTime(new Date()));
  138. }
  139. }
  140. ```
  141. ### 1.5. server.html
  142. ```html
  143. <!DOCTYPE html>
  144. <html lang="en">
  145. <head>
  146. <meta charset="UTF-8">
  147. <title>服务器信息</title>
  148. <link href="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.4.11/theme-chalk/index.css" rel="stylesheet">
  149. <style>
  150. .el-row {
  151. margin-bottom: 20px;
  152. }
  153. .el-row:last-child {
  154. margin-bottom: 0;
  155. }
  156. .sysFile {
  157. margin-bottom: 5px;
  158. }
  159. .sysFile:last-child {
  160. margin-bottom: 0;
  161. }
  162. </style>
  163. </head>
  164. <body>
  165. <div id="app">
  166. <el-container>
  167. <el-header>
  168. <el-button @click="_initSockJs" type="primary" :disabled="isConnected">手动连接</el-button>
  169. <el-button @click="_destroySockJs" type="danger" :disabled="!isConnected">断开连接</el-button>
  170. </el-header>
  171. <el-main>
  172. <el-row :gutter="20">
  173. <el-col :span="12">
  174. <el-card>
  175. <div slot="header">
  176. <span>CPU信息</span>
  177. </div>
  178. <el-table size="small" border :data="server.cpu" style="width: 100%">
  179. <el-table-column prop="key" label="属性">
  180. </el-table-column>
  181. <el-table-column prop="value" label="值">
  182. </el-table-column>
  183. </el-table>
  184. </el-card>
  185. </el-col>
  186. <el-col :span="12">
  187. <el-card>
  188. <div slot="header">
  189. <span>内存信息</span>
  190. </div>
  191. <el-table size="small" border :data="server.mem" style="width: 100%">
  192. <el-table-column prop="key" label="属性">
  193. </el-table-column>
  194. <el-table-column prop="value" label="值">
  195. </el-table-column>
  196. </el-table>
  197. </el-card>
  198. </el-col>
  199. </el-row>
  200. <el-row>
  201. <el-col :span="24">
  202. <el-card>
  203. <div slot="header">
  204. <span>服务器信息</span>
  205. </div>
  206. <el-table size="small" border :data="server.sys" style="width: 100%">
  207. <el-table-column prop="key" label="属性">
  208. </el-table-column>
  209. <el-table-column prop="value" label="值">
  210. </el-table-column>
  211. </el-table>
  212. </el-card>
  213. </el-col>
  214. </el-row>
  215. <el-row>
  216. <el-col :span="24">
  217. <el-card>
  218. <div slot="header">
  219. <span>Java虚拟机信息</span>
  220. </div>
  221. <el-table size="small" border :data="server.jvm" style="width: 100%">
  222. <el-table-column prop="key" label="属性">
  223. </el-table-column>
  224. <el-table-column prop="value" label="值">
  225. </el-table-column>
  226. </el-table>
  227. </el-card>
  228. </el-col>
  229. </el-row>
  230. <el-row>
  231. <el-col :span="24">
  232. <el-card>
  233. <div slot="header">
  234. <span>磁盘状态</span>
  235. </div>
  236. <div class="sysFile" v-for="(item,index) in server.sysFile" :key="index">
  237. <el-table size="small" border :data="item" style="width: 100%">
  238. <el-table-column prop="key" label="属性">
  239. </el-table-column>
  240. <el-table-column prop="value" label="值">
  241. </el-table-column>
  242. </el-table>
  243. </div>
  244. </el-card>
  245. </el-col>
  246. </el-row>
  247. </el-main>
  248. </el-container>
  249. </div>
  250. </body>
  251. <script src="js/sockjs.min.js"></script>
  252. <script src="js/stomp.js"></script>
  253. <script src="https://cdn.bootcss.com/vue/2.5.21/vue.min.js"></script>
  254. <script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.4.11/index.js"></script>
  255. <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script>
  256. <script>
  257. const wsHost = "http://localhost:8080/demo/notification";
  258. const wsTopic = "/topic/server";
  259. const app = new Vue({
  260. el: '#app',
  261. data: function () {
  262. return {
  263. isConnected: false,
  264. stompClient: {},
  265. socket: {},
  266. server: {
  267. cpu: [],
  268. mem: [],
  269. jvm: [],
  270. sys: [],
  271. sysFile: []
  272. }
  273. }
  274. },
  275. methods: {
  276. _getServerInfo() {
  277. axios.get('/demo/server')
  278. .then((response) => {
  279. this.server = response.data
  280. });
  281. },
  282. _initSockJs() {
  283. this._getServerInfo();
  284. this.socket = new SockJS(wsHost);
  285. this.stompClient = Stomp.over(this.socket);
  286. this.stompClient.connect({}, (frame) => {
  287. console.log('websocket连接成功:' + frame);
  288. this.isConnected = true;
  289. this.$message('websocket服务器连接成功');
  290. // 另外再注册一下消息推送
  291. this.stompClient.subscribe(wsTopic, (response) => {
  292. this.server = JSON.parse(response.body);
  293. });
  294. });
  295. },
  296. _destroySockJs() {
  297. if (this.stompClient != null) {
  298. this.stompClient.disconnect();
  299. this.socket.onclose;
  300. this.socket.close();
  301. this.stompClient = {};
  302. this.socket = {};
  303. this.isConnected = false;
  304. this.server.cpu = [];
  305. this.server.mem = [];
  306. this.server.jvm = [];
  307. this.server.sys = [];
  308. this.server.sysFile = [];
  309. }
  310. console.log('websocket断开成功!');
  311. this.$message.error('websocket断开成功!');
  312. }
  313. },
  314. mounted() {
  315. this._initSockJs();
  316. },
  317. beforeDestroy() {
  318. this._destroySockJs();
  319. }
  320. })
  321. </script>
  322. </html>
  323. ```
  324. ## 2. 运行方式
  325. 1. 启动 `SpringBootDemoWebsocketApplication.java`
  326. 2. 访问 http://localhost:8080/demo/server.html
  327. ## 3. 运行效果
  328. ![image-20181217110240322](assets/image-20181217110240322-5015760.png)
  329. ![image-20181217110304065](assets/image-20181217110304065-5015784.png)
  330. ![image-20181217110328810](assets/image-20181217110328810-5015808.png)
  331. ![image-20181217110336017](assets/image-20181217110336017-5015816.png)
  332. ## 4. 参考
  333. ### 4.1. 后端
  334. 1. Spring Boot 整合 Websocket 官方文档:https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/web.html#websocket
  335. 2. 服务器信息采集 oshi 使用:https://github.com/oshi/oshi
  336. ### 4.2. 前端
  337. 1. vue.js 语法:https://cn.vuejs.org/v2/guide/
  338. 2. element-ui 用法:http://element-cn.eleme.io/#/zh-CN
  339. 3. stomp.js 用法:https://github.com/jmesnil/stomp-websocket
  340. 4. sockjs 用法:https://github.com/sockjs/sockjs-client
  341. 5. axios.js 用法:https://github.com/axios/axios#example

一个用来深度学习并实战 spring boot 的项目,目前总共包含 66 个集成demo,已经完成 55 个。