一、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;
}
}