Initial commit

This commit is contained in:
2026-04-23 16:58:11 +08:00
commit 267eba1eca
2582 changed files with 273338 additions and 0 deletions

View File

@@ -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;
}
}
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,8 @@
package com.ruoyi.playingmethod.websocket.constant;
// 客户端发送消息key
public enum CMsgKey {
FIGHT_END;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}