SpringBoot集成websocket、多线程【双人pk答题案例】

一、pk答题表结构

二、多线程相关配置

1、线程池配置

package com.yougogo.platform.config;

import com.yougogo.platform.utils.PkSocket;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @author mn
 * time 2022-06-06 11:50
 * description
 */
@EnableAsync //开启异步执行
@Configuration
public class AsyncConfig {
    private static Logger logger = LogManager.getLogger(AsyncConfig.class.getName());
    /**
     * 覆盖springboot中默认的线程池
     *
     * @return
     */
    @Bean
    public TaskExecutor taskExecutor() {
        logger.info("start asyncExecutor......");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(16);
        //配置最大线程数
        executor.setMaxPoolSize(64);
        //配置队列大小
        executor.setQueueCapacity(9999);
        //配置线程池中的线程的名称前缀 (指定一下线程名的前缀)
        executor.setThreadNamePrefix("async-pk");
        // rejection-policy:当pool已经达到max pool size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是由调用线程(提交任务的线程)处理该任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //配置守护线程
        executor.setDaemon(true);
        //执行初始化
        executor.initialize();
        return executor;
    }

}

2、放线程的包

 3、监听器

package com.yougogo.platform.thread;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.yougogo.course.dto.PkQuestionDto;
import com.yougogo.course.service.PkQuestionService;
import com.yougogo.platform.utils.PkSocket;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @description: 监听器
 * @author: ChengZengWen
 * @createDate: 2022/6/7 16:38
 */
@Component
public class Listener implements Runnable {

    @DubboReference
    private PkQuestionService pkQuestionService;
    @Autowired
    private PkSocket pkSocket;

    private static Logger logger = LogManager.getLogger(PkQuestionAsync.class.getName());

    @PostConstruct
    public void init() {
        new Thread(this).start();
    }

    @Override

    public void run() {
        synchronized (this) {
            while (true) {
                PkQuestionDto pkQuestionDto = new PkQuestionDto();
                List<Long> userIds = pkSocket.getUseIds();
                if (CollectionUtils.isNotEmpty(userIds)) {
                    if (userIds.size() > 1) {
                        //继续匹配用户
                        Random random = new Random();
                        PkSocket pk = pkSocket.matchUser(userIds.get(random.nextInt(userIds.size())));
                        if (pk != null) {
                            pkQuestionDto.setIdWork(pk.getIdWork());
                            pkQuestionDto.setUserId(pk.getUserId());
                            pkQuestionDto.setMatchUserId(pk.getMatchUserId());
                            PkQuestionDto result = pkQuestionService.addPkQuestion(pkQuestionDto);
                            //发送消息给两个用户
                            //将结果对象转为json
                            String str = JSONArray.toJSONString(result);
                            try {
                                pkSocket.sendMessageToUser(str, pk.getUserId());
                                pkSocket.sendMessageToUser(str, pk.getMatchUserId());
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
}

4、异步线程文件

package com.yougogo.platform.thread;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.yougogo.course.dto.PkQuestionDto;
import com.yougogo.course.service.PkQuestionService;
import com.yougogo.platform.utils.PkSocket;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @author mn
 * time 2022-06-06 11:54
 * description
 */
//@Component
public class PkQuestionAsync {
    @DubboReference
    private PkQuestionService pkQuestionService;
    @Autowired
    private PkSocket pkSocket;

    private static Logger logger = LogManager.getLogger(PkQuestionAsync.class.getName());

    /**
     * 匹配用户
     *
     * @return
     */
    @Async("taskExecutor")
    public void matchUser() {
        //1.获取待匹配用户
        //2.判断用户人数是否满足匹配条件 满足后进行下一步 不满足重新循环
        //3.随机选取一名用户 调用匹配方法 匹配
        //4.匹配成功后继续循环至1.
        while (true) {
            PkQuestionDto pkQuestionDto = new PkQuestionDto();
            List<Long> userIds = pkSocket.getUseIds();
            if (CollectionUtils.isNotEmpty(userIds)) {
                if (userIds.size() <= 1) {
                    //待匹配用户只有一个人/没有人
                    continue;
                } else {
                    //继续匹配用户
                    Random random = new Random();
                    PkSocket pk = pkSocket.matchUser(userIds.get(random.nextInt(userIds.size())));
                    if (pk != null) {
                        pkQuestionDto.setIdWork(pk.getIdWork());
                        pkQuestionDto.setUserId(pk.getUserId());
                        pkQuestionDto.setMatchUserId(pk.getMatchUserId());
                        PkQuestionDto result = pkQuestionService.addPkQuestion(pkQuestionDto);
                        //发送消息给两个用户
                        //将结果对象转为json
                        String str =JSONArray.toJSONString(result);
                        try {
                            pkSocket.sendMessageToUser(str, pk.getUserId());
                            pkSocket.sendMessageToUser(str, pk.getMatchUserId());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    } else {
                        continue;
                    }

                }
            } else {
                continue;
            }
        }
    }
}

5、service

6、servicImpl

 @Override
    public PkQuestionDto addPkQuestion(PkQuestionDto pkQuestionDto) {
        Preconditions.checkArgument(pkQuestionDto !=null,"参数pkQuestionDto不能为空");
        Preconditions.checkArgument(pkQuestionDto.getIdWork() !=null,"参数idWork不能为空");
        Preconditions.checkArgument(pkQuestionDto.getUserId() !=null,"参数userId不能为空");
        Preconditions.checkArgument(pkQuestionDto.getMatchUserId() !=null,"参数matchUserId不能为空");

        //查询两个用户的真是名字
        List<Long> userIds=new ArrayList<>();
        userIds.add(pkQuestionDto.getUserId());
        userIds.add(pkQuestionDto.getMatchUserId());
        List<UserResultDto> userResultDtos=pkQuestionMapper.queryUserRealName(userIds);
        Long result=pkQuestionMapper.selectCount(new QueryWrapper<PkQuestionPo>().eq("id_work",pkQuestionDto.getIdWork()));
        if(result==0L){
            //未生成pk答题id==>生成
            PkQuestionPo pkQuestionPo=new PkQuestionPo();
            BeanUtil.copyProperties(pkQuestionDto,pkQuestionPo);
            pkQuestionPo.setCreateBy(pkQuestionDto.getUserId());
            pkQuestionPo.setCreateDate(new Date());
            pkQuestionPo.setStatus(PkStatus.UNDERWAY);
            pkQuestionPo.setDeleteFlag(false);
            pkQuestionMapper.insert(pkQuestionPo);
            PkQuestionPo pkQuestion=pkQuestionMapper.selectOne(new QueryWrapper<PkQuestionPo>().eq("id_work",pkQuestionDto.getIdWork()));
            //并且生成pk_user
            List<PkUserPo> pkUserPos=new ArrayList<>();
            PkUserPo userPk=new PkUserPo();
            userPk.setUserId(pkQuestionDto.getUserId());
            userPk.setQuestionCount(0);
            userPk.setCorrectCount(0);
            userPk.setCreateDate(new Date());
            userPk.setPkId(pkQuestion.getId());
            pkUserPos.add(userPk);

            PkUserPo matchUserPk=new PkUserPo();
            matchUserPk.setUserId(pkQuestionDto.getMatchUserId());
            matchUserPk.setQuestionCount(0);
            matchUserPk.setCorrectCount(0);
            matchUserPk.setPkId(pkQuestion.getId());
            matchUserPk.setCreateDate(new Date());
            pkUserPos.add(matchUserPk);
            pkUserMapper.insertBatch(pkUserPos);
            PkQuestionDto pkQuestionResult=new PkQuestionDto();
            BeanUtil.copyProperties(pkQuestionPo,pkQuestionResult);
            pkQuestionResult.setUserId(pkQuestionDto.getUserId());
            pkQuestionResult.setMatchUserId(pkQuestionDto.getMatchUserId());
            //查询pk_user表的id
            PkUserPo pkUserResult=pkUserMapper.selectOne(new QueryWrapper<PkUserPo>().eq("pk_id",pkQuestionPo.getId()).eq("user_id",pkQuestionDto.getUserId()));
            pkQuestionResult.setPkUserId(pkUserResult.getId());
            if(userResultDtos.get(0).getId().equals(pkQuestionResult.getUserId())){
                pkQuestionResult.setUsername(userResultDtos.get(0).getRealName());
                pkQuestionResult.setMatchUsername(userResultDtos.get(1).getRealName());
            }else{
                pkQuestionResult.setMatchUsername(userResultDtos.get(0).getRealName());
                pkQuestionResult.setUsername(userResultDtos.get(1).getRealName());
            }
            return pkQuestionResult;
        }else{
            //已生成pk答题id===查询出相关信息
            PkQuestionDto pkQuestionResult=pkQuestionMapper.queryPkQuestion(pkQuestionDto.getIdWork(),pkQuestionDto.getUserId());
            pkQuestionResult.setUserId(pkQuestionDto.getUserId());
            pkQuestionResult.setMatchUserId(pkQuestionDto.getMatchUserId());
            if(userResultDtos.get(0).getId().equals(pkQuestionResult.getUserId())){
                pkQuestionResult.setUsername(userResultDtos.get(0).getRealName());
                pkQuestionResult.setMatchUsername(userResultDtos.get(1).getRealName());
            }else{
                pkQuestionResult.setMatchUsername(userResultDtos.get(0).getRealName());
                pkQuestionResult.setUsername(userResultDtos.get(1).getRealName());
            }
            return pkQuestionResult;
        }
    }

三、websocke相关配置

1、加依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

2、websocket推送类【工具】

package com.yougogo.platform.utils;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * websocket推送类
 *
 * @author ChenZengWeng
 */
@ServerEndpoint(value = "/pk/{userId}")
@Component
public class PkSocket {


    private static Logger logger = LogManager.getLogger(PkSocket.class.getName());

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<PkSocket> wsClientMap = new CopyOnWriteArraySet<>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    private String idWork;

    private Long status;
    /**
     * 发起人用户id
     */
    private Long userId;
    /**
     * 匹配用户id
     */
    private Long matchUserId;

    /**
     * 连接建立成功调用的方法
     *
     * @param session 当前会话session
     */
    @OnOpen
    public void onOpen(@PathParam("userId") String userId, Session session) {
        this.session = session;
        this.status = 1L;
        this.userId = Long.valueOf(userId);
        wsClientMap.add(this);
        addOnlineCount();
        logger.info(session.getId() + "有新链接加入,当前链接数为:" + wsClientMap.size());
    }

    /**
     * 连接关闭
     */
    @OnClose
    public void onClose() {
        wsClientMap.remove(this);
        subOnlineCount();
        logger.info("有一链接关闭,当前链接数为:" + wsClientMap.size());
    }

    /**
     * 收到客户端消息
     *
     * @param message 客户端发送过来的消息
     * @param session 当前会话session
     * @throws IOException
     */
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        logger.info("收到客户端消息:" + message);
    }

    /**
     * 发生错误
     */
    @OnError
    public void onError(Session session, Throwable error) {
        logger.info("wsClientMap发生错误!");
        error.printStackTrace();
    }

    /**
     * 匹配用户
     *
     * @param userId 发起者id
     */
    public PkSocket matchUser(Long userId) {
        if (wsClientMap.size() <= 1) {
            return null;
        }
        String id = IdWorker.getId() + "";
        for (PkSocket pkSocket : wsClientMap) {
            if (pkSocket.userId.equals(userId)) {
                if (pkSocket.matchUserId != null) {
                    return pkSocket;
                }
            }
        }
        List<PkSocket> userPk = wsClientMap.stream().filter(item -> item.status == 1L && !item.userId.equals(userId)).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(userPk)) {
            for (PkSocket item : wsClientMap) {
                if (item.userId.equals(userPk.get(0).userId)) {
                    item.status = 2L;
                    item.matchUserId = userId;
                    item.idWork = id;
                }
                if (item.userId.equals(userId)) {
                    item.idWork = id;
                    item.status = 2L;
                    item.matchUserId = userPk.get(0).userId;
                }
            }
            return userPk.get(0);
        } else {
            return null;
        }
    }


    /**
     * 获取待匹配用户id
     * @return 用户id数组
     */
    public List<Long> getUseIds() {
        return wsClientMap.stream().filter(item -> 1L == item.getStatus()).map(PkSocket::getUserId).collect(Collectors.toList());
    }

    /**
     * 给所有客户端群发消息
     *
     * @param message 消息内容
     * @throws IOException
     */
    public void sendMsgToAll(String message) throws IOException {
        for (PkSocket item : wsClientMap) {
            item.session.getBasicRemote().sendText(message);
        }
        logger.info("成功群送一条消息:" + wsClientMap.size());
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
        logger.info("成功发送一条消息:" + message);
    }

    public void sendMessageToUser(String message, Long userId) throws IOException {
        for (PkSocket item : wsClientMap) {
            if (item.userId.equals(userId)) {
                item.session.getBasicRemote().sendText(message);
            }
        }
        logger.info("成功发送一条消息给用户:" + message + ",用户id为:" + userId);
    }

    public static synchronized int getOnlineCount() {
        return PkSocket.onlineCount.get();
    }

    public static synchronized void addOnlineCount() {
        PkSocket.onlineCount.addAndGet(1);
    }

    public static synchronized void subOnlineCount() {
        PkSocket.onlineCount.getAndDecrement();
    }

    public static Logger getLogger() {
        return logger;
    }

    public static CopyOnWriteArraySet<PkSocket> getWsClientMap() {
        return wsClientMap;
    }

    public Session getSession() {
        return session;
    }

    public String getIdWork() {
        return idWork;
    }

    public Long getStatus() {
        return status;
    }

    public Long getUserId() {
        return userId;
    }

    public Long getMatchUserId() {
        return matchUserId;
    }

}

3、配置类

package com.yougogo.platform.config;

/**
 * @description: websocket配置类
 * @author: ChengZengWen
 * @createDate: 2022/2/25 16:44
 */

import com.yougogo.course.service.CourseUserLogService;
import com.yougogo.platform.utils.StudentTimeSocket;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author Gjing
 **/
@Configuration
public class WebsocketConfiguration {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    @DubboReference
    public void setCourseUserLogService(CourseUserLogService courseUserLogService) {
        StudentTimeSocket.courseUserLogService = courseUserLogService;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值