Initial commit
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package com.ruoyi.playingmethod.websocket;
|
||||
|
||||
import com.ruoyi.playingmethod.websocket.entity.SessionData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class WebSocketUsers {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class);
|
||||
|
||||
private static Map<String, SessionData> USERS = new ConcurrentHashMap<String, SessionData>();
|
||||
|
||||
public static void put(String key, SessionData sessionData) {
|
||||
USERS.put(key, sessionData);
|
||||
}
|
||||
|
||||
public static boolean remove(SessionData sessionData) {
|
||||
String key = null;
|
||||
boolean flag = USERS.containsValue(sessionData);
|
||||
if (flag) {
|
||||
Set<Map.Entry<String, SessionData>> entries = USERS.entrySet();
|
||||
for (Map.Entry<String, SessionData> entry : entries) {
|
||||
SessionData value = entry.getValue();
|
||||
if (value.getSession().equals(sessionData.getSession())) {
|
||||
key = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return remove(key);
|
||||
}
|
||||
|
||||
public static boolean remove(String key) {
|
||||
LOGGER.info("\n 正在移出用户 - {}", key);
|
||||
SessionData remove = USERS.remove(key);
|
||||
if (remove.getSession() != null) {
|
||||
boolean containsValue = USERS.containsValue(remove);
|
||||
LOGGER.info("\n 移出结果 - {}", containsValue ? "失败" : "成功");
|
||||
LOGGER.info("\n 当前人数 - {}", WebSocketUsers.getUsers().size());
|
||||
return containsValue;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, SessionData> getUsers() {
|
||||
return USERS;
|
||||
}
|
||||
|
||||
public static void sendMessageToUsersByText(String message) {
|
||||
Collection<SessionData> values = USERS.values();
|
||||
for (SessionData value : values) {
|
||||
try {
|
||||
value.getSession().getBasicRemote().sendText(message);
|
||||
} catch (IOException e) {
|
||||
//LOGGER.error("\n[发送消息异常]", e);
|
||||
LOGGER.error("\n[发送消息异常]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendMessageToUserByText(Integer userId, String message) {
|
||||
Collection<SessionData> values = USERS.values();
|
||||
if (userId == 0) {
|
||||
for (SessionData value : values) {
|
||||
if (value.getSession() != null && Objects.equals(value.getUserId(), userId)) {
|
||||
try {
|
||||
value.getSession().getBasicRemote().sendText(message);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("\n[发送消息异常]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (SessionData value : values) {
|
||||
if (value.getSession() != null && Objects.equals(value.getUserId(), userId)) {
|
||||
try {
|
||||
value.getSession().getBasicRemote().sendText(message);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("\n[发送消息异常]", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package com.ruoyi.playingmethod.websocket;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.ruoyi.admin.mapper.TtUserMapper;
|
||||
import com.ruoyi.admin.service.TtBoxService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordStatus;
|
||||
import com.ruoyi.domain.common.constant.sys.UserStatus;
|
||||
import com.ruoyi.domain.dto.boxRecords.queryCondition;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.vo.boxRecords.TtBoxRecordsVO;
|
||||
import com.ruoyi.playingmethod.service.ApiBoxRecordsService;
|
||||
import com.ruoyi.playingmethod.websocket.constant.SMsgKey;
|
||||
import com.ruoyi.playingmethod.websocket.util.WsResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.websocket.*;
|
||||
import jakarta.websocket.server.PathParam;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 绑定箱子
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ServerEndpoint("/ws/bindBox/{boxId}/{userId}")
|
||||
public class WsBindBox {
|
||||
|
||||
// 用来记录当前连接数的变量
|
||||
private static volatile int onlineCount = 0;
|
||||
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象
|
||||
private static ConcurrentHashMap<String, WsBindBox> allRoomUserMap = new ConcurrentHashMap<>();
|
||||
// 与某个客户端的连接会话,需要通过它来与客户端进行数据收发
|
||||
private Session session;
|
||||
private static ApiBoxRecordsService apiBoxRecordsService;
|
||||
private static TtBoxService boxService;
|
||||
private static TtUserService userService;
|
||||
private static TtUserMapper userMapper;
|
||||
private Integer userId = null;
|
||||
private Integer boxId = null;
|
||||
private String key = "";
|
||||
|
||||
@Autowired
|
||||
public void ttFightService(ApiBoxRecordsService apiBoxRecordsService){
|
||||
WsBindBox.apiBoxRecordsService = apiBoxRecordsService;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void ttFightService(TtBoxService boxService){
|
||||
WsBindBox.boxService = boxService;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void ttFightService(TtUserService userService){
|
||||
WsBindBox.userService = userService;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void ttFightService(TtUserMapper userMapper){
|
||||
WsBindBox.userMapper = userMapper;
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session,
|
||||
@PathParam("boxId") Integer boxId,
|
||||
@PathParam("userId") Integer userId) throws IOException {
|
||||
// 更新连接池
|
||||
addBoxRoomUser(userId, boxId, session);
|
||||
|
||||
// 用户信息
|
||||
TtUser player = new LambdaQueryChainWrapper<>(userMapper)
|
||||
.eq(TtUser::getUserId, userId)
|
||||
.eq(TtUser::getStatus, UserStatus.NORMAL.getCode())
|
||||
.eq(TtUser::getDelFlag, 0)
|
||||
.one();
|
||||
// 检查连接
|
||||
R check = connectCheck(player, boxId);
|
||||
if (!check.getCode().equals(200)) {
|
||||
session.getBasicRemote().sendText(check.getMsg());
|
||||
session.close();
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("/ws/bindBox > > onOpen");
|
||||
log.info("用户{}进入盲盒游戏房间,在线人数{}", userId, WsBindBox.onlineCount);
|
||||
sendMessage("用户" + userId + "进入盲盒游戏房间,在线人数" + WsBindBox.onlineCount);
|
||||
|
||||
// 首次连接获取最新的一组盲盒开箱数据
|
||||
List<Integer> sources = Arrays.asList(TtboxRecordSource.BLIND_BOX.getCode());
|
||||
List<Integer> status = Arrays.asList(
|
||||
TtboxRecordStatus.IN_PACKSACK_ON.getCode(),
|
||||
TtboxRecordStatus.DELIVERY_YET.getCode(),
|
||||
TtboxRecordStatus.APPLY_DELIVERY.getCode(),
|
||||
TtboxRecordStatus.RESOLVE.getCode());
|
||||
queryCondition param = queryCondition.builder()
|
||||
.boxId(boxId)
|
||||
// .userType(player.getUserType())
|
||||
.source(sources)
|
||||
.status(status)
|
||||
.orderByFie(0)
|
||||
.page(1)
|
||||
.size(10)
|
||||
.build();
|
||||
List<TtBoxRecordsVO> ttBoxRecordsVOS = apiBoxRecordsService.byCondition(param);
|
||||
|
||||
sendMessage(WsResult.ok(SMsgKey.Blind_Box_Init_Data.name(),ttBoxRecordsVOS,"初始化历史开箱记录"));
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session) {
|
||||
removeRoomUser();
|
||||
log.info("关闭连接,正常在线人数:" + WsBindBox.onlineCount);
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String message, Session session) {
|
||||
log.info("收到消息");
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable exception) throws Exception {
|
||||
log.info("出现错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接检查
|
||||
*/
|
||||
private R connectCheck(TtUser player, Integer boxId) {
|
||||
if (ObjectUtil.isNotEmpty(WsFightRoom.allRoomUserMap.get(player.getUserId() + "_" + boxId))) {
|
||||
R.fail("用户" + player.getUserId() + "已经连接宝箱房间" + boxId + "。请勿重复调用。");
|
||||
}
|
||||
TtBox box = boxService.getById(boxId);
|
||||
if (ObjectUtil.isEmpty(box)) {
|
||||
return R.fail("不存在的宝箱,id:" + boxId);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新在线人数
|
||||
*/
|
||||
private int addOnlineCount(int number) {
|
||||
synchronized (this){
|
||||
WsBindBox.onlineCount += number;
|
||||
return WsBindBox.onlineCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局广播
|
||||
*/
|
||||
public static void broadcast(Object message) {
|
||||
|
||||
// ObjectMapper objectMapper = new ObjectMapper();
|
||||
String msg = JSON.toJSONString(message);
|
||||
|
||||
Collection<WsBindBox> wss = WsBindBox.allRoomUserMap.values();
|
||||
for (WsBindBox ws : wss){
|
||||
try {
|
||||
ws.session.getBasicRemote().sendText(msg);
|
||||
} catch (IOException e) {
|
||||
log.warn("WS大厅广播消息异常。错误信息:{}", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间广播
|
||||
*/
|
||||
public static void broadcastToBoxRoom(Integer boxId, Object message) {
|
||||
|
||||
String msg = JSON.toJSONString(message);
|
||||
|
||||
Collection<WsBindBox> wslist = WsBindBox.allRoomUserMap.values();
|
||||
|
||||
for (WsBindBox ws : wslist) {
|
||||
try {
|
||||
if (!ws.boxId.equals(boxId)) continue;
|
||||
// log.info(String.valueOf(ws.session.isOpen()));
|
||||
if (!ws.session.isOpen()) continue;
|
||||
RemoteEndpoint.Basic basicRemote = ws.session.getBasicRemote();
|
||||
basicRemote.sendText(msg);
|
||||
} catch (IOException e) {
|
||||
log.warn("WS推送广播给{}_{}消息异常。", ws.userId, ws.boxId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现服务器主动推送
|
||||
*/
|
||||
public void sendMessage(Object message) {
|
||||
try {
|
||||
this.session.getBasicRemote().sendText(JSON.toJSONString(message));
|
||||
} catch (IOException e) {
|
||||
log.warn("服务器推送消息异常。");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加连接用户
|
||||
*/
|
||||
private WsBindBox addBoxRoomUser(Integer userId, Integer boxId, Session session) {
|
||||
addOnlineCount(1);
|
||||
this.userId = userId;
|
||||
this.boxId = boxId;
|
||||
this.key = userId + "_" + boxId;
|
||||
this.session = session;
|
||||
WsBindBox.allRoomUserMap.put(key, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除连接用户
|
||||
*/
|
||||
private WsBindBox removeRoomUser(){
|
||||
addOnlineCount(-1);
|
||||
WsBindBox.allRoomUserMap.remove(this.key);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
package com.ruoyi.playingmethod.websocket;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.admin.mapper.TtBoxMapper;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.vo.fight.FightBoxVO;
|
||||
import com.ruoyi.playingmethod.service.ApiFightService;
|
||||
import com.ruoyi.playingmethod.websocket.constant.SMsgKey;
|
||||
import com.ruoyi.playingmethod.websocket.util.WsResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.websocket.OnClose;
|
||||
import jakarta.websocket.OnError;
|
||||
import jakarta.websocket.OnMessage;
|
||||
import jakarta.websocket.OnOpen;
|
||||
import jakarta.websocket.Session;
|
||||
import jakarta.websocket.server.PathParam;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 对战大厅
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ServerEndpoint("/ws/fight/hall/{userId}")
|
||||
public class WsFightHall {
|
||||
|
||||
// 用来记录当前连接数的变量
|
||||
private static int onlineCount = 0;
|
||||
|
||||
public static synchronized void increaseOnlineCount() {
|
||||
onlineCount++;
|
||||
}
|
||||
|
||||
public static synchronized void decreaseOnlineCount() {
|
||||
if (onlineCount > 0) {
|
||||
onlineCount--;
|
||||
}
|
||||
}
|
||||
|
||||
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象
|
||||
private static ConcurrentHashMap<String, WsFightHall> allHallUserMap = new ConcurrentHashMap<>();
|
||||
// 与某个客户端的连接会话,需要通过它来与客户端进行数据收发
|
||||
private Session session;
|
||||
private static ApiFightService apiFightService;
|
||||
private String userId = "";
|
||||
|
||||
@Autowired
|
||||
public void ttFightService(ApiFightService apiFightService) {
|
||||
WsFightHall.apiFightService = apiFightService;
|
||||
}
|
||||
|
||||
private static TtBoxMapper ttBoxMapper;
|
||||
|
||||
@Autowired
|
||||
public void ttBoxMapper(TtBoxMapper ttBoxMapper) {
|
||||
WsFightHall.ttBoxMapper = ttBoxMapper;
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @PathParam("userId") String userId) throws IOException {
|
||||
// 获取请求参数
|
||||
Map<String, List<String>> parameters = session.getRequestParameterMap();
|
||||
int pageNum = 1;
|
||||
int pageSize = 10;
|
||||
String status = null;
|
||||
String model = null;
|
||||
BigDecimal boxPriceTotalMin = null;
|
||||
BigDecimal boxPriceTotalMax = null;
|
||||
List<String> pageNums = parameters.get("pageNum");
|
||||
if (!Objects.isNull(pageNums) && !pageNums.isEmpty()) {
|
||||
pageNum = Integer.parseInt(pageNums.get(0));
|
||||
}
|
||||
List<String> pageSizes = parameters.get("pageSize");
|
||||
if (!Objects.isNull(pageSizes) && !pageSizes.isEmpty()) {
|
||||
pageSize = Integer.parseInt(pageSizes.get(0));
|
||||
pageSize = Math.min(pageSize, 10);
|
||||
}
|
||||
List<String> statuses = parameters.get("status");
|
||||
if (!Objects.isNull(statuses) && !statuses.isEmpty()) {
|
||||
status = statuses.get(0);
|
||||
}
|
||||
List<String> models = parameters.get("model");
|
||||
if (!Objects.isNull(models) && !models.isEmpty()) {
|
||||
model = models.get(0);
|
||||
}
|
||||
List<String> boxPriceTotalMines = parameters.get("boxPriceTotalMin");
|
||||
if (!Objects.isNull(boxPriceTotalMines) && !boxPriceTotalMines.isEmpty()) {
|
||||
boxPriceTotalMin = new BigDecimal(boxPriceTotalMines.get(0));
|
||||
}
|
||||
List<String> boxPriceTotalMaxes = parameters.get("boxPriceTotalMax");
|
||||
if (!Objects.isNull(boxPriceTotalMaxes) && !boxPriceTotalMaxes.isEmpty()) {
|
||||
boxPriceTotalMax = new BigDecimal(boxPriceTotalMaxes.get(0));
|
||||
}
|
||||
|
||||
if (ObjectUtil.isNotEmpty(WsFightHall.allHallUserMap.get(userId))) {
|
||||
session.getBasicRemote().sendText("用户" + userId + "已连接,请勿重复调用");
|
||||
session.close();
|
||||
return;
|
||||
}
|
||||
addHallUser(userId, session);
|
||||
|
||||
|
||||
Page<TtFight> pageInfo = new Page<>(pageNum, pageSize);
|
||||
pageInfo.setOptimizeCountSql(false);
|
||||
|
||||
LambdaQueryWrapper<TtFight> fightQuery = new LambdaQueryWrapper<>();
|
||||
fightQuery
|
||||
.eq(status != null, TtFight::getStatus, status)
|
||||
.eq(model != null, TtFight::getModel, model)
|
||||
.between(boxPriceTotalMin != null && boxPriceTotalMax != null,
|
||||
TtFight::getBoxPriceTotal, boxPriceTotalMin, boxPriceTotalMax)
|
||||
.ge(boxPriceTotalMin == null && boxPriceTotalMax != null,
|
||||
TtFight::getBoxPriceTotal, boxPriceTotalMax)
|
||||
.orderByDesc(TtFight::getCreateTime);
|
||||
List<TtFight> fightList = apiFightService.page(pageInfo, fightQuery).getRecords();
|
||||
if (CollectionUtils.isEmpty(fightList)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("rows", new ArrayList<>());
|
||||
map.put("total", 0);
|
||||
sendMessage(WsResult.ok(SMsgKey.ALL_FIGHT_ROOM.name(), map, "所有对战房间"));
|
||||
} else {
|
||||
List<Integer> boxIds = new ArrayList<>();
|
||||
for (TtFight fight : fightList) {
|
||||
if (fight == null || MapUtils.isEmpty(fight.getBoxData())) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, FightBoxVO> entry : fight.getBoxData().entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
Integer boxId;
|
||||
if (value instanceof LinkedHashMap) {
|
||||
Map<String, Object> map = (LinkedHashMap) value;
|
||||
boxId = map.get("boxId") != null ? Integer.valueOf(map.get("boxId").toString()) : null;
|
||||
} else {
|
||||
boxId = ((FightBoxVO) value).getBoxId();
|
||||
}
|
||||
if (boxId == null) {
|
||||
continue;
|
||||
}
|
||||
boxIds.add(boxId);
|
||||
}
|
||||
}
|
||||
LambdaQueryWrapper<TtBox> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.in(TtBox::getBoxId, boxIds);
|
||||
List<TtBox> boxes = ttBoxMapper.selectList(queryWrapper);
|
||||
Map<Integer, BigDecimal> boxMap = boxes.stream().filter(Objects::nonNull).collect(Collectors.toMap(TtBox::getBoxId, TtBox::getPrice));
|
||||
for (TtFight fight : fightList) {
|
||||
if (fight == null || MapUtils.isEmpty(fight.getBoxData())) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, FightBoxVO> entry : fight.getBoxData().entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
FightBoxVO vo;
|
||||
Integer boxId;
|
||||
if (value instanceof LinkedHashMap) {
|
||||
Map<String, Object> map = (LinkedHashMap) value;
|
||||
vo = new FightBoxVO(map.get("boxId") != null ? Integer.valueOf(map.get("boxId").toString()) : null,
|
||||
map.get("number") != null ? Integer.valueOf(map.get("number").toString()) : null,
|
||||
map.get("boxImg01") != null ? map.get("boxImg01").toString() : null,
|
||||
map.get("boxImg02") != null ? map.get("boxImg02").toString() : null);
|
||||
vo.setPrice(boxMap.getOrDefault(vo.getBoxId(), new BigDecimal(0)));
|
||||
} else {
|
||||
vo = (FightBoxVO) value;
|
||||
vo.setPrice(boxMap.getOrDefault(vo.getBoxId(), new BigDecimal(0)));
|
||||
}
|
||||
fight.getBoxData().put(vo.getBoxId().toString(), vo);
|
||||
}
|
||||
}
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("rows", fightList);
|
||||
map.put("total", apiFightService.page(pageInfo, fightQuery).getTotal());
|
||||
|
||||
sendMessage(WsResult.ok(SMsgKey.ALL_FIGHT_ROOM.name(), map, "所有对战房间"));
|
||||
}
|
||||
|
||||
|
||||
log.info("用户{}进入对战游戏大厅,广播{}个房间,在线人数{}", userId, fightList.size(), WsFightHall.onlineCount);
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session) {
|
||||
// removeHallUser();
|
||||
removeHallUser(session.getPathParameters().get("userId"));
|
||||
log.info("关闭连接,正常在线人数:" + WsFightHall.onlineCount);
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String message, Session session) {
|
||||
log.info("收到消息");
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable exception) throws Exception {
|
||||
log.error("出现错误", exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新在线人数
|
||||
*/
|
||||
private int addOnlineCount(int number) {
|
||||
synchronized (new Object()) {
|
||||
WsFightHall.onlineCount += number;
|
||||
return WsFightHall.onlineCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局广播
|
||||
*/
|
||||
public static void broadcast(Object message) {
|
||||
|
||||
// ObjectMapper objectMapper = new ObjectMapper();
|
||||
String msg = JSON.toJSONString(message);
|
||||
|
||||
Collection<WsFightHall> wss = WsFightHall.allHallUserMap.values();
|
||||
|
||||
for (WsFightHall ws : wss) {
|
||||
try {
|
||||
ws.session.getBasicRemote().sendText(msg);
|
||||
} catch (IOException e) {
|
||||
log.warn("ws大厅广播给消息异常。msg:{}", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现服务器主动推送
|
||||
*/
|
||||
public void sendMessage(Object message) {
|
||||
try {
|
||||
this.session.getBasicRemote().sendText(JSON.toJSONString(message));
|
||||
} catch (IOException e) {
|
||||
log.warn("服务器推送消息异常");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加连接用户
|
||||
*/
|
||||
private WsFightHall addHallUser(String userId, Session session) {
|
||||
addOnlineCount(1);
|
||||
this.userId = userId;
|
||||
this.session = session;
|
||||
WsFightHall.allHallUserMap.put(userId, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除连接用户
|
||||
*/
|
||||
private WsFightHall removeHallUser() {
|
||||
addOnlineCount(-1);
|
||||
WsFightHall.allHallUserMap.remove(userId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除连接用户
|
||||
*/
|
||||
private WsFightHall removeHallUser(String userId) {
|
||||
addOnlineCount(-1);
|
||||
WsFightHall.allHallUserMap.remove(userId);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
package com.ruoyi.playingmethod.websocket;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxMapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxOrnamentsMapper;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.fight.FightSeat;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.other.FightBoutData;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.vo.TtBoxOrnamentsDataVO;
|
||||
import com.ruoyi.domain.vo.fight.FightBoxVO;
|
||||
import com.ruoyi.domain.vo.fight.FightResultVO;
|
||||
import com.ruoyi.playingmethod.service.ApiFightService;
|
||||
import com.ruoyi.playingmethod.service.ApiFightUserService;
|
||||
import com.ruoyi.playingmethod.websocket.constant.SMsgKey;
|
||||
import com.ruoyi.playingmethod.websocket.util.WsResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.websocket.*;
|
||||
import jakarta.websocket.server.PathParam;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 对战房间
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ServerEndpoint("/ws/fight/room/{userId}/{fightId}")
|
||||
public class WsFightRoom {
|
||||
|
||||
static Map<String, Session> sessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
static volatile AtomicInteger viewersCount = new AtomicInteger(0);
|
||||
|
||||
// 用来记录当前连接数的变量
|
||||
private static volatile int onlineCount = 0;
|
||||
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象
|
||||
public static ConcurrentHashMap<String, WsFightRoom> allRoomUserMap = new ConcurrentHashMap<>();
|
||||
// 与某个客户端的连接会话,需要通过它来与客户端进行数据收发
|
||||
private Session session;
|
||||
private static ApiFightService apiFightService;
|
||||
private static TtBoxRecordsService boxRecordsService;
|
||||
private static TtBoxOrnamentsMapper boxOrnamentsMapper;
|
||||
// private static TtBoxOrnamentsService ttBoxOrnamentsService;
|
||||
private static ApiFightUserService apiFightUserService;
|
||||
private static Integer fightRoundTime = null; // 战斗回合时间
|
||||
private Integer userId = null;
|
||||
private Integer fightId = null;
|
||||
private String key = "";
|
||||
private static RedisCache redisCache;
|
||||
|
||||
private static TtBoxMapper ttBoxMapper;
|
||||
|
||||
@Value("${mkcsgo.fight.roundTime}")
|
||||
public void ttFightService(Integer fightRoundTime) {
|
||||
WsFightRoom.fightRoundTime = fightRoundTime;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void ttFightService(ApiFightService apiFightService) {
|
||||
WsFightRoom.apiFightService = apiFightService;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void boxOrnamentsMapper(TtBoxOrnamentsMapper boxOrnamentsMapper) {
|
||||
WsFightRoom.boxOrnamentsMapper = boxOrnamentsMapper;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void boxRecordsService(TtBoxRecordsService boxRecordsService) {
|
||||
WsFightRoom.boxRecordsService = boxRecordsService;
|
||||
}
|
||||
|
||||
public void apiFightUserService(ApiFightUserService apiFightUserService) {
|
||||
WsFightRoom.apiFightUserService = apiFightUserService;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void ttBoxMapper(TtBoxMapper ttBoxMapper) {
|
||||
WsFightRoom.ttBoxMapper = ttBoxMapper;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void redisCache(RedisCache redisCache) {
|
||||
WsFightRoom.redisCache = redisCache;
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session,
|
||||
@PathParam("userId") Integer userId,
|
||||
@PathParam("fightId") Integer fightId) {
|
||||
try {
|
||||
addFightRoomUser(userId, fightId, session);
|
||||
|
||||
R check = connectCheck(userId, fightId);
|
||||
if (!check.getCode().equals(200)) {
|
||||
session.getBasicRemote().sendText(check.getMsg());
|
||||
session.close();
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("/ws/fight/room > > onOpen");
|
||||
log.info("用户{}进入房间{}," + "在线人数{}", userId, fightId, WsFightRoom.onlineCount);
|
||||
sendMsgToPlayers("用户:" + userId + "进入房间," + fightId + "在线人数" + WsFightRoom.onlineCount, null);
|
||||
|
||||
// 首次连接获取房间最新数据
|
||||
LambdaQueryWrapper<TtFight> fightQuery = new LambdaQueryWrapper<>();
|
||||
fightQuery
|
||||
.eq(TtFight::getId, fightId);
|
||||
// .eq(TtFight::getStatus,0);
|
||||
TtFight fight = apiFightService.getOne(fightQuery);
|
||||
|
||||
// 构建结果集
|
||||
// 根据宝箱id查询关联的所有饰品
|
||||
Map<String, FightBoxVO> boxData = fight.getBoxData();
|
||||
|
||||
ArrayList<FightBoxVO> fightBoxVOList = new ArrayList<>();
|
||||
boxData.keySet().forEach(boxId -> {
|
||||
List<TtBoxOrnamentsDataVO> boxOrnamentsVOS = boxOrnamentsMapper.selectTtBoxOrnamentsList(Integer.valueOf(boxId));
|
||||
FightBoxVO fightBoxVO = JSONUtil.toBean(JSONUtil.toJsonStr(boxData.get(boxId)), FightBoxVO.class);
|
||||
fightBoxVO.setOrnaments(boxOrnamentsVOS);
|
||||
boxData.put(boxId,fightBoxVO);
|
||||
fightBoxVOList.add(boxData.get(boxId));
|
||||
});
|
||||
|
||||
LambdaQueryWrapper<TtBox> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.in(TtBox::getBoxId, boxData.keySet());
|
||||
List<TtBox> boxes = ttBoxMapper.selectList(queryWrapper);
|
||||
var boxMapObj = boxes.stream().filter(Objects::nonNull).collect(Collectors.groupingBy(TtBox::getBoxId));
|
||||
Map<Integer, BigDecimal> boxMap = boxes.stream().filter(Objects::nonNull).collect(Collectors.toMap(TtBox::getBoxId, TtBox::getPrice));
|
||||
|
||||
for (FightBoxVO vo : fightBoxVOList) {
|
||||
vo.setPrice(boxMap.getOrDefault(vo.getBoxId(), new BigDecimal(0)));
|
||||
if (boxMapObj.get(vo.getBoxId()) != null) {
|
||||
vo.setBoxName(boxMapObj.get(vo.getBoxId()).getFirst().getBoxName());
|
||||
}
|
||||
}
|
||||
|
||||
fight.getBoxData().keySet().forEach(boxId -> {
|
||||
Object value = fight.getBoxData().get(boxId);
|
||||
FightBoxVO a = null;
|
||||
if (value instanceof LinkedHashMap) {
|
||||
Map<String, Object> map = (LinkedHashMap) value;
|
||||
a = new FightBoxVO(map.get("boxId") != null ? Integer.valueOf(map.get("boxId").toString()) : null,
|
||||
map.get("number") != null ? Integer.valueOf(map.get("number").toString()) : null,
|
||||
map.get("boxImg01") != null ? map.get("boxImg01").toString() : null,
|
||||
map.get("boxImg02") != null ? map.get("boxImg02").toString() : null);
|
||||
a.setPrice(boxMap.getOrDefault(a.getBoxId(), BigDecimal.ZERO));
|
||||
} else {
|
||||
a = (FightBoxVO) value;
|
||||
a.setPrice(boxMap.getOrDefault(a.getBoxId(), BigDecimal.ZERO));
|
||||
}
|
||||
fight.getBoxData().put(boxId, a);
|
||||
});
|
||||
|
||||
// 如果游戏结束,发送结果集数据
|
||||
if (fight.getStatus().equals(2) || fight.getStatus().equals(3)) {
|
||||
|
||||
// 所有对战结果记录
|
||||
LambdaQueryWrapper<TtBoxRecords> boxRecordsQuery = new LambdaQueryWrapper<>();
|
||||
boxRecordsQuery
|
||||
.eq(TtBoxRecords::getFightId, fightId);
|
||||
List<TtBoxRecords> allBoxRecords = boxRecordsService.list(boxRecordsQuery);
|
||||
|
||||
FightResultVO resultVO = FightResultVO.builder()
|
||||
.currentRound(-1L)
|
||||
.fight(fight)
|
||||
.winnerIds(fight.getWinnerIds())
|
||||
.fightResult(allBoxRecords)
|
||||
.fightBoxVOList(fightBoxVOList)
|
||||
.build();
|
||||
|
||||
broadcastFight(fightId, WsResult.ok(SMsgKey.FIGHT_RESULT.name(), resultVO, "对局已结束,对局最新信息"));
|
||||
log.info("room onOpen 广播数据成功。");
|
||||
|
||||
} else if (fight.getStatus().equals(1)) {
|
||||
|
||||
// 所有对战结果记录
|
||||
LambdaQueryWrapper<TtBoxRecords> boxRecordsQuery = new LambdaQueryWrapper<>();
|
||||
boxRecordsQuery
|
||||
.eq(TtBoxRecords::getFightId, fightId)
|
||||
.eq(TtBoxRecords::getStatus, 0);
|
||||
// 首先从Redis中查询对战结果,没有则在从数据库中查询
|
||||
List<TtBoxRecords> allBoxRecords;
|
||||
String key = "fight_result:fight_" + fightId;
|
||||
allBoxRecords = redisCache.getCacheObject(key);
|
||||
|
||||
if (Objects.isNull(allBoxRecords)) {
|
||||
allBoxRecords = boxRecordsService.list(boxRecordsQuery);
|
||||
}
|
||||
|
||||
// 时间差,计算当前进行到第几回合
|
||||
// LocalDateTime now = LocalDateTime.now();
|
||||
// Timestamp beginTime = fight.getBeginTime();
|
||||
// LocalDateTime beginTime1 = beginTime.toLocalDateTime();
|
||||
// Duration duration = Duration.between(beginTime1, now);
|
||||
// Long currentRound = duration.toMillis() / fightRoundTime; // 这个秒要和前端的【每回合时间】同步
|
||||
|
||||
// 获取当前回合数
|
||||
Integer currentRound = 0;
|
||||
FightBoutData fightBoutData = redisCache.getCacheObject("fight_bout_data:fight_" + fightId);
|
||||
if (!Objects.isNull(fightBoutData)) {
|
||||
currentRound = fightBoutData.getBoutNum();
|
||||
}
|
||||
|
||||
FightResultVO resultVO = FightResultVO.builder()
|
||||
.currentRound(currentRound.longValue())
|
||||
.fight(fight)
|
||||
.winnerIds(fight.getWinnerIds())
|
||||
.fightResult(allBoxRecords)
|
||||
.fightBoxVOList(fightBoxVOList)
|
||||
.build();
|
||||
|
||||
// broadcastFight(fightId, WsResult.ok(SMsgKey.FIGHT_RESULT.name(), resultVO, "对局进行中,对局最新信息"));
|
||||
sendMsgToPlayers(WsResult.ok(SMsgKey.FIGHT_RESULT.name(), resultVO, "对局进行中,对局最新信息"), null);
|
||||
|
||||
log.info("room onOpen 广播数据成功。");
|
||||
} else if (fight.getStatus().equals(0)) {
|
||||
// 没有结束,返回房间信息即可
|
||||
sendMsgToPlayers(WsResult.ok(SMsgKey.FIGHT_ROOM_INFO.name(), fight, "对局准备中,对战房间最新信息"), null);
|
||||
log.info("room onOpen 广播数据成功。");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.warn("onopen warn");
|
||||
}
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session) {
|
||||
removeFightRoomUser();
|
||||
log.info("onClose");
|
||||
log.info("正常在线人数:" + WsFightRoom.onlineCount);
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable exception) throws Exception {
|
||||
log.info("onError");
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String message, Session session) {
|
||||
log.info("onMessage");
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接检查
|
||||
*/
|
||||
private R connectCheck(Integer userId, Integer fightId) {
|
||||
if (ObjectUtil.isNotEmpty(WsFightRoom.allRoomUserMap.get(userId + "_" + fightId))) {
|
||||
R.fail("用户" + userId + "已经连接对局" + fightId + "。请勿重复调用。");
|
||||
}
|
||||
TtFight fight = apiFightService.getById(fightId);
|
||||
if (ObjectUtil.isEmpty(fight)) {
|
||||
return R.fail("不存在的对局ID:" + fightId);
|
||||
}
|
||||
// if (fight.getStatus().equals(2) || fight.getStatus().equals(3)){
|
||||
// return R.fail("对局"+fightId+"已结束。");
|
||||
// }
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新在线人数
|
||||
*/
|
||||
private int addOnlineCount(int number) {
|
||||
synchronized (new Object()) {
|
||||
WsFightRoom.onlineCount = WsFightRoom.onlineCount + number;
|
||||
return WsFightRoom.onlineCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单点推送
|
||||
*/
|
||||
public void sendMsgToPlayers(Object message, List<String> keys) {
|
||||
String msg = JSON.toJSONString(message);
|
||||
|
||||
try {
|
||||
if (ObjectUtil.isEmpty(keys) || keys.size() == 0) {
|
||||
// 推送给自己
|
||||
this.session.getBasicRemote().sendText(msg);
|
||||
} else {
|
||||
for (String key : keys) {
|
||||
WsFightRoom ws = WsFightRoom.allRoomUserMap.get(key);
|
||||
ws.session.getBasicRemote().sendText(msg);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("推送消息异常。msg:" + msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间广播
|
||||
*/
|
||||
public static void broadcastFight(Integer fightId, Object message) {
|
||||
String msg = JSON.toJSONString(message);
|
||||
|
||||
Collection<WsFightRoom> wslist = WsFightRoom.allRoomUserMap.values();
|
||||
|
||||
for (WsFightRoom ws : wslist) {
|
||||
try {
|
||||
if (!ws.fightId.equals(fightId)) continue;
|
||||
log.info(String.valueOf(ws.session.isOpen()));
|
||||
if (!ws.session.isOpen()) continue;
|
||||
RemoteEndpoint.Basic basicRemote = ws.session.getBasicRemote();
|
||||
basicRemote.sendText(msg);
|
||||
} catch (IOException e) {
|
||||
log.warn("ws推送广播给{}_{}消息异常。", ws.userId, ws.fightId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局广播
|
||||
*/
|
||||
public static void broadcast(Object message) {
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String msg = "";
|
||||
try {
|
||||
msg = objectMapper.writeValueAsString(message);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("ws解析广播消息异常。");
|
||||
}
|
||||
|
||||
Collection<WsFightRoom> wss = WsFightRoom.allRoomUserMap.values();
|
||||
for (WsFightRoom ws : wss) {
|
||||
try {
|
||||
ws.session.getBasicRemote().sendText(msg);
|
||||
} catch (IOException e) {
|
||||
log.warn("ws推送广播给{}_{}消息异常。", ws.userId, ws.fightId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加连接用户
|
||||
*/
|
||||
private WsFightRoom addFightRoomUser(Integer userId, Integer fightId, Session session) {
|
||||
addOnlineCount(1);
|
||||
this.userId = userId;
|
||||
this.fightId = fightId;
|
||||
this.key = userId + "_" + fightId;
|
||||
this.session = session;
|
||||
WsFightRoom.allRoomUserMap.put(key, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除连接用户
|
||||
*/
|
||||
private WsFightRoom removeFightRoomUser() {
|
||||
addOnlineCount(-1);
|
||||
WsFightRoom.allRoomUserMap.remove(userId + "_" + fightId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开房间所有连接
|
||||
*/
|
||||
public static Boolean batchClose(TtFight fight) {
|
||||
List<FightSeat> seats = fight.getSeats();
|
||||
|
||||
for (int i = 0; i < seats.size(); i++) {
|
||||
FightSeat seat = JSONUtil.toBean(JSONUtil.toJsonStr(seats.get(i)), FightSeat.class);
|
||||
|
||||
Integer playerId = seat.getPlayerId();
|
||||
String key = ObjectUtil.isNotEmpty(playerId) ? String.valueOf(playerId) : "" + "_" + fight.getId();
|
||||
|
||||
WsFightRoom ws = WsFightRoom.allRoomUserMap.get(key);
|
||||
if (ObjectUtil.isEmpty(ws)) continue;
|
||||
|
||||
try {
|
||||
ws.session.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("关闭连接异常");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.ruoyi.playingmethod.websocket;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxOrnamentsMapper;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.vo.TtBoxOrnamentsDataVO;
|
||||
import com.ruoyi.domain.vo.fight.FightBoxVO;
|
||||
import com.ruoyi.domain.vo.fight.FightResultVO;
|
||||
import com.ruoyi.playingmethod.service.ApiFightService;
|
||||
import com.ruoyi.playingmethod.websocket.constant.SMsgKey;
|
||||
import com.ruoyi.playingmethod.websocket.util.WsResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.websocket.*;
|
||||
import jakarta.websocket.server.PathParam;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
||||
/**
|
||||
* 对战房间
|
||||
*/
|
||||
@Slf4j
|
||||
@ServerEndpoint("/ws/fight/room2/{userId}/{fightId}")
|
||||
@Component
|
||||
public class WsFightRoom2 {
|
||||
|
||||
static Map<String, Session> sessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
static Map<String, Session> viewersSessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
private String sessionKey;
|
||||
|
||||
private static Integer fightRoundTime;
|
||||
|
||||
private static ApiFightService apiFightService;
|
||||
|
||||
private static TtBoxRecordsService ttBoxRecordsService;
|
||||
|
||||
private static TtBoxOrnamentsMapper ttBoxOrnamentsMapper;
|
||||
|
||||
@Value("${mkcsgo.fight.roundTime}")
|
||||
public void setFightRoundTime(Integer fightRoundTime) {
|
||||
WsFightRoom2.fightRoundTime = fightRoundTime;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void ttFightService(ApiFightService apiFightService) {
|
||||
WsFightRoom2.apiFightService = apiFightService;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void boxRecordsService(TtBoxRecordsService ttBoxRecordsService) {
|
||||
WsFightRoom2.ttBoxRecordsService = ttBoxRecordsService;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void boxOrnamentsMapper(TtBoxOrnamentsMapper ttBoxOrnamentsMapper) {
|
||||
WsFightRoom2.ttBoxOrnamentsMapper = ttBoxOrnamentsMapper;
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session,
|
||||
@PathParam("userId") Integer userId,
|
||||
@PathParam("fightId") Integer fightId) {
|
||||
|
||||
sessionKey = fightId.toString() + "#" + userId.toString();
|
||||
|
||||
// 检查用户是否已经进入某个房间
|
||||
if (!Objects.isNull(sessionMap.get(sessionKey))) {
|
||||
try {
|
||||
session.getBasicRemote().sendText("用户" + userId + "已加入" + fightId +"房间,不得重复进入");
|
||||
session.close();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
sessionMap.put(sessionKey, session);
|
||||
}
|
||||
|
||||
// 首次连接获取房间最新数据
|
||||
LambdaQueryWrapper<TtFight> fightQuery = new LambdaQueryWrapper<>();
|
||||
fightQuery
|
||||
.eq(TtFight::getId, fightId);
|
||||
TtFight ttFight = apiFightService.getOne(fightQuery);
|
||||
|
||||
// 检查房间是否存在
|
||||
if (Objects.isNull(ttFight)) {
|
||||
try {
|
||||
session.getBasicRemote().sendText("房间" + fightId + "不存在");
|
||||
session.close();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// 判断对战状态
|
||||
if (ttFight.getStatus() == 2) {
|
||||
// 构建结果集
|
||||
// 根据宝箱ID查询关联的所有饰品
|
||||
Map<String, FightBoxVO> boxData = ttFight.getBoxData();
|
||||
ArrayList<FightBoxVO> fightBoxVOList = new ArrayList<>();
|
||||
boxData.keySet().forEach(boxId -> {
|
||||
fightBoxVOList.add(boxData.get(boxId));
|
||||
});
|
||||
|
||||
// 所有对战结果记录
|
||||
LambdaQueryWrapper<TtBoxRecords> boxRecordsQuery = new LambdaQueryWrapper<>();
|
||||
boxRecordsQuery
|
||||
.eq(TtBoxRecords::getFightId, fightId)
|
||||
.eq(TtBoxRecords::getStatus, 0);
|
||||
List<TtBoxRecords> allBoxRecords = ttBoxRecordsService.list(boxRecordsQuery);
|
||||
|
||||
FightResultVO resultVO = FightResultVO.builder()
|
||||
.currentRound(-1L)
|
||||
.fight(ttFight)
|
||||
.winnerIds(ttFight.getWinnerIds())
|
||||
.fightResult(allBoxRecords)
|
||||
.fightBoxVOList(fightBoxVOList)
|
||||
.build();
|
||||
|
||||
WsResult<FightResultVO> result = WsResult.ok(SMsgKey.FIGHT_RESULT.name(), resultVO, "对局已结束,对局最新信息");
|
||||
String message = JSON.toJSONString(result);
|
||||
broadcastMessageToFight(fightId, message);
|
||||
} else if (ttFight.getStatus() == 1) {
|
||||
// 构建结果集
|
||||
// 根据宝箱ID查询关联的所有饰品
|
||||
Map<String, FightBoxVO> boxData = ttFight.getBoxData();
|
||||
ArrayList<FightBoxVO> fightBoxVOList = new ArrayList<>();
|
||||
boxData.keySet().forEach(boxId -> {
|
||||
List<TtBoxOrnamentsDataVO> boxOrnamentsVOS = ttBoxOrnamentsMapper.selectTtBoxOrnamentsList(Integer.valueOf(boxId));
|
||||
FightBoxVO fightBoxVO = JSONUtil.toBean(JSONUtil.toJsonStr(boxData.get(boxId)), FightBoxVO.class);
|
||||
fightBoxVO.setOrnaments(boxOrnamentsVOS);
|
||||
boxData.put(boxId,fightBoxVO);
|
||||
fightBoxVOList.add(boxData.get(boxId));
|
||||
});
|
||||
|
||||
// 所有对战结果记录
|
||||
LambdaQueryWrapper<TtBoxRecords> boxRecordsQuery = new LambdaQueryWrapper<>();
|
||||
boxRecordsQuery
|
||||
.eq(TtBoxRecords::getFightId, fightId)
|
||||
.eq(TtBoxRecords::getStatus, 0);
|
||||
List<TtBoxRecords> allBoxRecords = ttBoxRecordsService.list(boxRecordsQuery);
|
||||
|
||||
// 时间差,计算当前进行到第几回合
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Timestamp beginTime = ttFight.getBeginTime();
|
||||
LocalDateTime localDateTime = beginTime.toLocalDateTime();
|
||||
|
||||
Duration duration = Duration.between(localDateTime, now);
|
||||
Long currentRound = duration.toMillis() / fightRoundTime; // 这个秒要和前端的【每回合时间】同步
|
||||
|
||||
FightResultVO resultVO = FightResultVO.builder()
|
||||
.currentRound(currentRound)
|
||||
.fight(ttFight)
|
||||
.winnerIds(ttFight.getWinnerIds())
|
||||
.fightResult(allBoxRecords)
|
||||
.fightBoxVOList(fightBoxVOList)
|
||||
.build();
|
||||
|
||||
WsResult<FightResultVO> result = WsResult.ok(SMsgKey.FIGHT_RESULT.name(), resultVO, "对局进行中,对局最新信息");
|
||||
String message = JSON.toJSONString(result);
|
||||
broadcastMessageToFight(fightId, message);
|
||||
|
||||
// 存储观战人,广播房间内观战人数
|
||||
viewersSessionMap.put(sessionKey, session);
|
||||
broadcastViewersCountToFight(fightId);
|
||||
} else if (ttFight.getStatus() == 0) {
|
||||
WsResult<TtFight> result = WsResult.ok(SMsgKey.FIGHT_ROOM_INFO.name(), ttFight, "对局准备中,对战房间最新信息");
|
||||
String message = JSON.toJSONString(result);
|
||||
try {
|
||||
session.getBasicRemote().sendText(message);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(Session session, String message) {
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session) {
|
||||
if (session.equals(sessionMap.get(sessionKey))) {
|
||||
sessionMap.remove(sessionKey);
|
||||
}
|
||||
if (session.equals(viewersSessionMap.get(sessionKey))) {
|
||||
viewersSessionMap.remove(sessionKey);
|
||||
Integer fightId = Integer.parseInt(sessionKey.substring(0, sessionKey.indexOf('#')));
|
||||
broadcastViewersCountToFight(fightId);
|
||||
}
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable throwable) {
|
||||
}
|
||||
|
||||
// 向指定房间的用户广播消息
|
||||
private void broadcastMessageToFight(Integer fightId, String message) {
|
||||
for (Map.Entry<String, Session> entry : sessionMap.entrySet()) {
|
||||
String sessionKey = entry.getKey();
|
||||
Session session = entry.getValue();
|
||||
if (sessionKey.substring(0, sessionKey.indexOf('#')).equals(fightId.toString())) {
|
||||
try {
|
||||
session.getBasicRemote().sendText(message);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 向指定房间的用户广播观战人数
|
||||
private void broadcastViewersCountToFight(Integer fightId) {
|
||||
int viewersCount = 0;
|
||||
// 统计本房间的观战人数
|
||||
for (Map.Entry<String, Session> entry : viewersSessionMap.entrySet()) {
|
||||
String sessionKey = entry.getKey();
|
||||
if (sessionKey.substring(0, sessionKey.indexOf('#')).equals(fightId.toString())) {
|
||||
viewersCount++;
|
||||
}
|
||||
}
|
||||
// 向该房间所有用户广播
|
||||
for (Map.Entry<String, Session> entry : sessionMap.entrySet()) {
|
||||
String sessionKey = entry.getKey();
|
||||
Session session = entry.getValue();
|
||||
if (sessionKey.substring(0, sessionKey.indexOf('#')).equals(fightId.toString())) {
|
||||
try {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("viewersCount", viewersCount);
|
||||
map.put("message", "当前房间观战人数");
|
||||
String message = JSON.toJSONString(map);
|
||||
session.getBasicRemote().sendText(message);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ruoyi.playingmethod.websocket.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
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.websocket.constant;
|
||||
|
||||
// 客户端发送消息key
|
||||
public enum CMsgKey {
|
||||
|
||||
FIGHT_END;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.playingmethod.websocket.constant;
|
||||
|
||||
// 服务端发送消息key
|
||||
public enum SMsgKey {
|
||||
|
||||
// 所有对战房间列表
|
||||
ALL_FIGHT_ROOM,
|
||||
|
||||
// 对战结果
|
||||
FIGHT_RESULT,
|
||||
|
||||
// 房间信息
|
||||
FIGHT_ROOM_INFO,
|
||||
|
||||
// 盲盒房间连接第一次数据
|
||||
Blind_Box_Init_Data,
|
||||
|
||||
// 盲盒房间实时数据
|
||||
Blind_Box_Current_Data;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.ruoyi.playingmethod.websocket.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class ResultData<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int code;
|
||||
|
||||
private String typeName;
|
||||
|
||||
private T data;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.playingmethod.websocket.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.websocket.Session;
|
||||
|
||||
@Data
|
||||
public class SessionData {
|
||||
private Integer userId = 0;
|
||||
private Session session;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.playingmethod.websocket.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
public class SemaphoreUtils {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SemaphoreUtils.class);
|
||||
|
||||
public static boolean tryAcquire(Semaphore semaphore) {
|
||||
boolean flag = false;
|
||||
|
||||
try {
|
||||
flag = semaphore.tryAcquire();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("获取信号量异常", e);
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
public static void release(Semaphore semaphore) {
|
||||
|
||||
try {
|
||||
semaphore.release();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("释放信号量异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.ruoyi.playingmethod.websocket.util;
|
||||
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 响应信息主体
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class WsResult<T> implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 成功 */
|
||||
public static final int SUCCESS = HttpStatus.SUCCESS;
|
||||
|
||||
/** 失败 */
|
||||
public static final int FAIL = HttpStatus.ERROR;
|
||||
|
||||
private String key;
|
||||
|
||||
private T data;
|
||||
|
||||
private int code;
|
||||
|
||||
private String msg;
|
||||
|
||||
public static <T> WsResult<T> ok()
|
||||
{
|
||||
return new WsResult(null,null, SUCCESS, "操作成功");
|
||||
}
|
||||
|
||||
public static <T> WsResult<T> ok(String key, T data)
|
||||
{
|
||||
return new WsResult(key,data, SUCCESS, "操作成功");
|
||||
}
|
||||
|
||||
public static <T> WsResult<T> ok(String key,T data,String msg)
|
||||
{
|
||||
return new WsResult(key,data, SUCCESS, msg);
|
||||
}
|
||||
|
||||
public static <T> WsResult<T> ok(String msg)
|
||||
{
|
||||
return new WsResult(null,null, SUCCESS, msg);
|
||||
}
|
||||
|
||||
public static <T> WsResult<T> fail()
|
||||
{
|
||||
return new WsResult(null,null, FAIL, "操作失败");
|
||||
}
|
||||
|
||||
public static <T> WsResult<T> fail(String key,T data,String msg)
|
||||
{
|
||||
return new WsResult(key,data, FAIL, msg);
|
||||
}
|
||||
|
||||
public static <T> WsResult<T> fail(String msg)
|
||||
{
|
||||
return new WsResult(null,null, FAIL, msg);
|
||||
}
|
||||
|
||||
public static <T> Boolean isError(WsResult<T> ret)
|
||||
{
|
||||
return !isSuccess(ret);
|
||||
}
|
||||
|
||||
public static <T> Boolean isSuccess(WsResult<T> ret)
|
||||
{
|
||||
return WsResult.SUCCESS == ret.getCode();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user