|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- # spring-boot-demo-websocket
-
- > 此 demo 主要演示了 Spring Boot 如何集成 WebSocket,实现后端主动往前端推送数据。网上大部分websocket的例子都是聊天室,本例主要是推送服务器状态信息。前端页面基于vue和element-ui实现。
-
- ## 1. 代码
-
- ### 1.1. pom.xml
-
- ```xml
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <artifactId>spring-boot-demo-websocket</artifactId>
- <version>1.0.0-SNAPSHOT</version>
-
- <name>spring-boot-demo-websocket</name>
- <description>Demo project for Spring Boot</description>
-
- <parent>
- <groupId>com.xkcoding</groupId>
- <artifactId>spring-boot-demo</artifactId>
- <version>1.0.0-SNAPSHOT</version>
- </parent>
-
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <java.version>1.8</java.version>
- <oshi.version>3.9.1</oshi.version>
- </properties>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-websocket</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>com.github.oshi</groupId>
- <artifactId>oshi-core</artifactId>
- <version>${oshi.version}</version>
- </dependency>
-
- <dependency>
- <groupId>cn.hutool</groupId>
- <artifactId>hutool-all</artifactId>
- </dependency>
-
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- </dependencies>
-
- <build>
- <finalName>spring-boot-demo-websocket</finalName>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
-
- </project>
- ```
-
- ### 1.2. WebSocketConfig.java
-
- ```java
- /**
- * <p>
- * WebSocket配置
- * </p>
- *
- * @author yangkai.shen
- * @date Created in 2018-12-14 15:58
- */
- @Configuration
- @EnableWebSocket
- @EnableWebSocketMessageBroker
- public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
-
- @Override
- public void registerStompEndpoints(StompEndpointRegistry registry) {
- // 注册一个 /notification 端点,前端通过这个端点进行连接
- registry.addEndpoint("/notification")
- //解决跨域问题
- .setAllowedOrigins("*")
- .withSockJS();
- }
-
- @Override
- public void configureMessageBroker(MessageBrokerRegistry registry) {
- //定义了一个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息
- registry.enableSimpleBroker("/topic");
- }
-
- }
- ```
-
- ### 1.3. 服务器相关实体
-
- > 此部分实体 参见包路径 [com.xkcoding.websocket.model](./src/main/java/com/xkcoding/websocket/model)
-
- ### 1.4. ServerTask.java
-
- ```java
- /**
- * <p>
- * 服务器定时推送任务
- * </p>
- *
- * @author yangkai.shen
- * @date Created in 2018-12-14 16:04
- */
- @Slf4j
- @Component
- public class ServerTask {
- @Autowired
- private SimpMessagingTemplate wsTemplate;
-
- /**
- * 按照标准时间来算,每隔 2s 执行一次
- */
- @Scheduled(cron = "0/2 * * * * ?")
- public void websocket() throws Exception {
- log.info("【推送消息】开始执行:{}", DateUtil.formatDateTime(new Date()));
- // 查询服务器状态
- Server server = new Server();
- server.copyTo();
- ServerVO serverVO = ServerUtil.wrapServerVO(server);
- Dict dict = ServerUtil.wrapServerDict(serverVO);
- wsTemplate.convertAndSend(WebSocketConsts.PUSH_SERVER, JSONUtil.toJsonStr(dict));
- log.info("【推送消息】执行结束:{}", DateUtil.formatDateTime(new Date()));
- }
- }
- ```
-
- ### 1.5. server.html
-
- ```html
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>服务器信息</title>
- <link href="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.4.11/theme-chalk/index.css" rel="stylesheet">
- <style>
- .el-row {
- margin-bottom: 20px;
- }
-
- .el-row:last-child {
- margin-bottom: 0;
- }
-
- .sysFile {
- margin-bottom: 5px;
- }
-
- .sysFile:last-child {
- margin-bottom: 0;
- }
- </style>
- </head>
- <body>
- <div id="app">
- <el-container>
- <el-header>
- <el-button @click="_initSockJs" type="primary" :disabled="isConnected">手动连接</el-button>
- <el-button @click="_destroySockJs" type="danger" :disabled="!isConnected">断开连接</el-button>
- </el-header>
- <el-main>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-card>
- <div slot="header">
- <span>CPU信息</span>
- </div>
- <el-table size="small" border :data="server.cpu" style="width: 100%">
- <el-table-column prop="key" label="属性">
- </el-table-column>
- <el-table-column prop="value" label="值">
- </el-table-column>
- </el-table>
- </el-card>
- </el-col>
- <el-col :span="12">
- <el-card>
- <div slot="header">
- <span>内存信息</span>
- </div>
- <el-table size="small" border :data="server.mem" style="width: 100%">
- <el-table-column prop="key" label="属性">
- </el-table-column>
- <el-table-column prop="value" label="值">
- </el-table-column>
- </el-table>
- </el-card>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="24">
- <el-card>
- <div slot="header">
- <span>服务器信息</span>
- </div>
- <el-table size="small" border :data="server.sys" style="width: 100%">
- <el-table-column prop="key" label="属性">
- </el-table-column>
- <el-table-column prop="value" label="值">
- </el-table-column>
- </el-table>
- </el-card>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="24">
- <el-card>
- <div slot="header">
- <span>Java虚拟机信息</span>
- </div>
- <el-table size="small" border :data="server.jvm" style="width: 100%">
- <el-table-column prop="key" label="属性">
- </el-table-column>
- <el-table-column prop="value" label="值">
- </el-table-column>
- </el-table>
- </el-card>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="24">
- <el-card>
- <div slot="header">
- <span>磁盘状态</span>
- </div>
- <div class="sysFile" v-for="(item,index) in server.sysFile" :key="index">
- <el-table size="small" border :data="item" style="width: 100%">
- <el-table-column prop="key" label="属性">
- </el-table-column>
- <el-table-column prop="value" label="值">
- </el-table-column>
- </el-table>
- </div>
- </el-card>
- </el-col>
- </el-row>
- </el-main>
- </el-container>
- </div>
- </body>
- <script src="js/sockjs.min.js"></script>
- <script src="js/stomp.js"></script>
- <script src="https://cdn.bootcss.com/vue/2.5.21/vue.min.js"></script>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.4.11/index.js"></script>
- <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script>
- <script>
- const wsHost = "http://localhost:8080/demo/notification";
- const wsTopic = "/topic/server";
-
- const app = new Vue({
- el: '#app',
- data: function () {
- return {
- isConnected: false,
- stompClient: {},
- socket: {},
- server: {
- cpu: [],
- mem: [],
- jvm: [],
- sys: [],
- sysFile: []
- }
- }
- },
- methods: {
- _getServerInfo() {
- axios.get('/demo/server')
- .then((response) => {
- this.server = response.data
- });
- },
- _initSockJs() {
- this._getServerInfo();
- this.socket = new SockJS(wsHost);
- this.stompClient = Stomp.over(this.socket);
-
- this.stompClient.connect({}, (frame) => {
- console.log('websocket连接成功:' + frame);
- this.isConnected = true;
- this.$message('websocket服务器连接成功');
-
- // 另外再注册一下消息推送
- this.stompClient.subscribe(wsTopic, (response) => {
- this.server = JSON.parse(response.body);
- });
- });
- },
- _destroySockJs() {
- if (this.stompClient != null) {
- this.stompClient.disconnect();
- this.socket.onclose;
- this.socket.close();
- this.stompClient = {};
- this.socket = {};
- this.isConnected = false;
- this.server.cpu = [];
- this.server.mem = [];
- this.server.jvm = [];
- this.server.sys = [];
- this.server.sysFile = [];
- }
- console.log('websocket断开成功!');
- this.$message.error('websocket断开成功!');
- }
- },
- mounted() {
- this._initSockJs();
- },
- beforeDestroy() {
- this._destroySockJs();
- }
-
- })
- </script>
- </html>
- ```
-
- ## 2. 运行方式
-
- 1. 启动 `SpringBootDemoWebsocketApplication.java`
- 2. 访问 http://localhost:8080/demo/server.html
-
- ## 3. 运行效果
-
- 
-
- 
-
- 
-
- 
-
- ## 4. 参考
-
- ### 4.1. 后端
-
- 1. Spring Boot 整合 Websocket 官方文档:https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/web.html#websocket
- 2. 服务器信息采集 oshi 使用:https://github.com/oshi/oshi
-
- ### 4.2. 前端
-
- 1. vue.js 语法:https://cn.vuejs.org/v2/guide/
- 2. element-ui 用法:http://element-cn.eleme.io/#/zh-CN
- 3. stomp.js 用法:https://github.com/jmesnil/stomp-websocket
- 4. sockjs 用法:https://github.com/sockjs/sockjs-client
- 5. axios.js 用法:https://github.com/axios/axios#example
|