Springboot+WebSocket实现实时更新数据Demo

1.推送的信息全部都是模拟的所以不存在ORM的操作

要模拟推送的实体类

思路是创建连接时就创建线程,并使用定时线程池不断像session中进行推送信息

package com.hua.queerdemo.domain.response;

public class UserInfoSendResponse {
    private Long userId;
    private String name;
    private String age;
    private String address;
    private Integer fraction;

    public Integer getFraction() {
        return fraction;
    }

    public void setFraction(Integer fraction) {
        this.fraction = fraction;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

 websocket配置类

package com.hua.queerdemo.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    //这个bean的注册,用于扫描带有@ServerEndpoint的注解成为websocket  ,如果你使用外置的tomcat就不需要该配置文件
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

websocket类

package com.hua.queerdemo.utils;


import com.hua.queerdemo.service.WebSocketService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

@Slf4j
@ServerEndpoint(value = "/websocket/{id}/{modId}")
@Component
public class WebSocketUtils {

    private static WebSocketService webSocketService;

    @Autowired
    public void setWebSocketService(WebSocketService webSocketService) {
        WebSocketUtils.webSocketService = webSocketService;
    }

    //连接用户
    private Session session;

    //用户ID
    private Long id;

    //存放每个用户的websocketUtils
    private static ConcurrentHashMap<Long,WebSocketUtils> websocketHashMap = new ConcurrentHashMap<Long,WebSocketUtils>();

    @OnOpen
    public synchronized void onOpen(Session session, @PathParam("id") Long id,@PathParam("modId") Long modId){
        log.debug("创建连接");
        System.out.println("创建连接");
        this.session=session;
        this.id=id;
        websocketHashMap.put(this.id,this);
        log.debug(session.getId()+"连接成功");
        //线程执行
        webSocketService.get(session,this.id,modId);
    }


    @OnClose
    public void onClose(@PathParam("id") Long id){
        System.out.println("清除线程");
        ConcurrentHashMap<Long, ScheduledFuture> scheduledFutureConcurrentHashMap = webSocketService.getScheduledFutureConcurrentHashMap();
        //清除用户的登录记录
        if (null!=websocketHashMap.get(id)){
            websocketHashMap.remove(id);
        }
        //结束线程 GC回收机制会自动清理资源
        if (null!=scheduledFutureConcurrentHashMap.get(this.id)){
            scheduledFutureConcurrentHashMap.get(this.id).cancel(false);
            scheduledFutureConcurrentHashMap.remove(this.id);
        }
    }

    @OnMessage
    public void onMessage(String message,Session session){
        System.out.println("收到的消息");
    }

    @OnError
    public void onError(Session session, Throwable error){
        System.out.println("发生错误");
        System.out.println("清除线程");
        ConcurrentHashMap<Long, ScheduledFuture> scheduledFutureConcurrentHashMap = webSocketService.getScheduledFutureConcurrentHashMap();
        //清除用户的登录记录
        if (null!=websocketHashMap.get(id)){
            websocketHashMap.remove(id);
        }
        //结束线程 GC回收机制会自动清理资源
        if (null!=scheduledFutureConcurrentHashMap.get(this.id)){
            scheduledFutureConcurrentHashMap.get(this.id).cancel(false);
            scheduledFutureConcurrentHashMap.remove(this.id);
        }
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
}

webSocket业务类

package com.hua.queerdemo.service.impl;

import com.alibaba.fastjson.JSON;
import com.hua.queerdemo.domain.response.UserInfoSendResponse;
import com.hua.queerdemo.service.WebSocketService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.annotation.*;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import javax.websocket.Session;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

@Slf4j
@EnableScheduling
@EnableAsync(proxyTargetClass = true)
@Service
public class  WebSocketServiceImpl implements WebSocketService, ApplicationRunner {
    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    @Autowired
    Scheduler scheduled;

    UserInfoSendResponse userInfoOne = new UserInfoSendResponse();

    UserInfoSendResponse userInfoTwo = new UserInfoSendResponse();

    UserInfoSendResponse userInfoThree = new UserInfoSendResponse();

    //初始化三条模拟数据
    @Override
    public void run(ApplicationArguments args) throws Exception {
        userInfoOne.setUserId(1L);
        userInfoOne.setName("张山");
        userInfoOne.setAge("18");
        userInfoOne.setAddress("北京西安");
        userInfoOne.setFraction(20);

        userInfoTwo.setUserId(2L);
        userInfoTwo.setName("小明");
        userInfoTwo.setAge("18");
        userInfoTwo.setAddress("火葬场");
        userInfoOne.setFraction(30);

        userInfoThree.setUserId(3L);
        userInfoThree.setName("马鞍山");
        userInfoThree.setAge("18");
        userInfoThree.setAddress("龙归");
        userInfoOne.setFraction(40);
    }

    //模拟三条数据随机更新数值
    @Scheduled(cron = "0/1 * * * * ?")
    public void updateInfo(){
        userInfoOne.setFraction((int) (Math.random()*100+1));
        userInfoTwo.setFraction((int) (Math.random()*100+1));
        userInfoThree.setFraction((int) (Math.random()*100+1));
    }


    private static ConcurrentHashMap<Long, ScheduledFuture> scheduledFutureConcurrentHashMap = new ConcurrentHashMap<Long,ScheduledFuture>();

    Integer test = 0 ;

    public ConcurrentHashMap<Long, ScheduledFuture> getScheduledFutureConcurrentHashMap() {
        return scheduledFutureConcurrentHashMap;
    }



    @Async("asyncServiceExecutor")
    public void get(Session session, Long id,Long modId){
        System.out.println("开启线程"+Thread.currentThread().getName());
        String cron = "0/1 * * * * ?";
        //在该线程下执行定时任务
        threadPoolTaskScheduler.setPoolSize(1);
        //判断之前是否是相同的用户,是则清除之前的线程
        if (null!=scheduledFutureConcurrentHashMap.get(id)) {
            System.out.println("清理之前存在的线程");
            scheduledFutureConcurrentHashMap.get(id).cancel(false);
            scheduledFutureConcurrentHashMap.remove(id);
        }
        Thread thread = new Thread(() -> {
            System.out.println("id:" + id + "的线程");
            System.out.println("查询对应数据" + Thread.currentThread().getName());
            String userInfoJson = null;
            switch (modId.intValue()){
                case 1:  userInfoJson = JSON.toJSONString(userInfoOne); break;
                case 2:  userInfoJson = JSON.toJSONString(userInfoTwo); break;
                case 3:  userInfoJson = JSON.toJSONString(userInfoThree); break;
            }
            try {
                session.getBasicRemote().sendText(userInfoJson);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        ScheduledFuture<?> schedule = threadPoolTaskScheduler.schedule(thread, new CronTrigger(cron));
        scheduledFutureConcurrentHashMap.put(id,schedule);
    }
}

线程池配置信息

package com.hua.queerdemo.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
@Slf4j
public class ExecutorConfig {

    @Value("${async.executor.thread.core_pool_size}")
    private int corePoolSize;
    @Value("${async.executor.thread.max_pool_size}")
    private int maxPoolSize;
    @Value("${async.executor.thread.queue_capacity}")
    private int queueCapacity;
    @Value("${async.executor.thread.name.prefix}")
    private String namePrefix;

    @Bean(name = "taskScheduler")
    public ThreadPoolTaskScheduler taskScheduler(){
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("task-");
        scheduler.setAwaitTerminationSeconds(600);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        return scheduler;
    }


    @Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        log.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(corePoolSize);
        //配置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //配置队列大小
        executor.setQueueCapacity(queueCapacity);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix(namePrefix);

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }
}

properties

# 异步线程配置
# 配置核心线程数
async.executor.thread.core_pool_size = 5
# 配置最大线程数
async.executor.thread.max_pool_size = 5
# 配置队列大小
async.executor.thread.queue_capacity = 99999
# 配置线程池中的线程的名称前缀
async.executor.thread.name.prefix = wulababa

controller

package com.hua.queerdemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class UserController {
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "/login";
    }

    @RequestMapping("/mLogin")
    public String login(Long modId,Long userId,String username,String password, Model model){
        model.addAttribute("userId",userId);
        model.addAttribute("modId",modId);
        return "/websocket";
    }
}

login页面

登录页中的用户名和密码实际是无效数据,真正做出登录判断的是用户ID

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script src="/webjars/jquery/3.4.1/jquery.js"></script>
<body>
<form action="/mLogin" method="post">
    选择要登录的用户ID:
    <select name="userId" id="userId">
        <option>1</option>
        <option>2</option>
        <option>3</option>
        <option>4</option>
    </select>
    <br>
    选择要实时观看的modId:
    <select name="modId" id="modId">
        <option>1</option>
        <option>2</option>
        <option>3</option>
    </select>

    </div>
    <br>
<!--    <input id="userId" name="userId">-->
    用户名:<input id="username" name="username" >
    密码:<input id="password" name="password">
    <input type="submit" value="登录">
</form>
</body>
</html>

websocket展示页面

<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script src="/webjars/jquery/3.4.1/jquery.js"></script>
<script>
    window.onload=function(){
        if ('WebSocket' in window) {
            console.log("浏览器支持Websocket")
            var id = $("#id").text();
            var modId = $("#modId").text();
            alert(id)
            websocket = new WebSocket("ws://127.0.0.1:8080/websocket/"+id+"/"+modId);
        } else {
            console.log('当前浏览器 Not support websocket')
        }

        //连接发生错误的回调方法
        websocket.onerror = function() {
            alert("WebSocket连接发生错误")
        };

        //连接成功建立的回调方法
        websocket.onopen = function() {
            console.log("WebSocket连接成功")
        }


        //接收到消息的回调方法
        websocket.onmessage = function(event) {
            var parse = JSON.parse(event.data);
            $("#userId").text(parse.userId);
            $("#name").text(parse.name)
            $("#age").text(parse.age)
            $("#address").text(parse.address)
            $("#fraction").text(parse.fraction)
            var size = parse.size;
            parse.facingMode
            // alert("这是后台推送的消息:"+event.data);
        }


        //连接关闭的回调方法
        websocket.onclose = function() {

        }


        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function() {
            closeWebSocket();
        }

        //关闭WebSocket连接
        function closeWebSocket() {

            websocket.close();

        }
    }


</script>
<body>
<div>欢迎登录,测试Demo</div>
目前的用户是:<div id="id" th:text="${userId}"></div>
目前的modId是:<div id="modId" th:text="${modId}"></div>
目前的游戏类型是:<div th:text="${modId}"></div>
<table border="1px solid black">
    <tr>
        <th>用户ID</th>
        <th>名称</th>
        <th>年龄</th>
        <th>地址</th>
        <th>成绩</th>
    </tr>

    <tr>
        <td id="userId">1</td>
        <td id="name">1</td>
        <td id="age">1</td>
        <td id="address">1</td>
        <td id="fraction">1</td>
    </tr>
</table>

</body>
</html>

完整代码地址UpdateDataInRealTime: 基于Springboot websocket 整合的实时更新数据Demohttps://gitee.com/xiaoming_in_the_sky/update-data-in-real-time

/**
	 * 注释
	 */

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值