初始提交,实现WebSocket聊天室基本功能
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package cn.airnan.dg;
|
||||
|
||||
import cn.airnan.dg.service.TopicService;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class DrawAndGuessApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DrawAndGuessApplication.class, args);
|
||||
TopicService.loadTopics();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.airnan.dg.configs;
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 配置跨域
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**").allowedOriginPatterns("*")
|
||||
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowCredentials(true).maxAge(3600);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.airnan.dg.configs;
|
||||
|
||||
import cn.airnan.dg.entity.Player;
|
||||
import cn.airnan.dg.entity.Room;
|
||||
import cn.airnan.dg.entity.Topic;
|
||||
import jakarta.websocket.Session;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* 全局缓存变量
|
||||
*/
|
||||
public class Global {
|
||||
|
||||
/**
|
||||
* 消息类型-房间聊天(回答)
|
||||
*/
|
||||
public static final String MSG_TYPE_ROOM_CHAT = "room-chat";
|
||||
|
||||
/**
|
||||
* 消息类型-房间绘画
|
||||
*/
|
||||
public static final String MSG_TYPE_ROOM_DRAW = "room-draw";
|
||||
|
||||
/**
|
||||
* 消息类型-房间通知消息
|
||||
*/
|
||||
public static final String MSG_TYPE_ROOM_MSG = "room-msg";
|
||||
|
||||
/**
|
||||
* 题库
|
||||
*/
|
||||
public static List<Topic> TOPICS = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 房间列表
|
||||
*/
|
||||
public static final Map<String, Room> ROOMS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 玩家列表
|
||||
*/
|
||||
public static final Map<String, Player> PLAYERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 会话列表
|
||||
*/
|
||||
public static final CopyOnWriteArraySet<Session> SESSIONS = new CopyOnWriteArraySet<>();
|
||||
|
||||
/**
|
||||
* 在线连接池
|
||||
*/
|
||||
public static final Map<String, Session> SESSION_POOL = new HashMap<>();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.airnan.dg.configs;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
|
||||
@Configuration
|
||||
public class WebSocketConfiguration {
|
||||
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
package cn.airnan.dg.controller;
|
||||
|
||||
import cn.airnan.dg.configs.Global;
|
||||
import cn.airnan.dg.entity.Player;
|
||||
import cn.airnan.dg.entity.Room;
|
||||
import cn.airnan.dg.service.PlayerService;
|
||||
import cn.airnan.dg.service.RoomService;
|
||||
import cn.airnan.dg.utils.CommonResponse;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 玩家相关接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/player")
|
||||
public class PlayerController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PlayerController.class);
|
||||
@Resource
|
||||
private PlayerService service;
|
||||
|
||||
/**
|
||||
* 获取玩家列表
|
||||
*
|
||||
* @return 玩家列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public CommonResponse list() {
|
||||
try {
|
||||
return CommonResponse.success(service.getPlayers());
|
||||
} catch (Exception e) {
|
||||
return CommonResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断玩家是否还在
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
@PostMapping("/isExist")
|
||||
public CommonResponse isExist(@RequestBody Player player) {
|
||||
try {
|
||||
return CommonResponse.success(Global.PLAYERS.containsKey(player.getId()));
|
||||
} catch (Exception e) {
|
||||
return CommonResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建玩家
|
||||
*
|
||||
* @return 玩家信息
|
||||
*/
|
||||
@PostMapping("/create")
|
||||
public CommonResponse create(@RequestBody Player player) {
|
||||
try {
|
||||
if (service.canCreatePlayer(player.getId())) {
|
||||
var result = service.createPlayer(player);
|
||||
if (result != null) {
|
||||
log.info("【创建玩家】名称:{}", player.getId());
|
||||
return CommonResponse.success(result);
|
||||
} else {
|
||||
return CommonResponse.error("创建玩家失败,请重试!");
|
||||
}
|
||||
} else {
|
||||
return CommonResponse.error("玩家名称已存在,换一个吧!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return CommonResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
package cn.airnan.dg.controller;
|
||||
|
||||
import cn.airnan.dg.configs.Global;
|
||||
import cn.airnan.dg.entity.Player;
|
||||
import cn.airnan.dg.entity.Room;
|
||||
import cn.airnan.dg.service.RoomService;
|
||||
import cn.airnan.dg.utils.CommonResponse;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* 房间相关接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/room")
|
||||
public class RoomController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RoomController.class);
|
||||
@Resource
|
||||
private RoomService service;
|
||||
|
||||
/**
|
||||
* 获取房间列表
|
||||
*
|
||||
* @return 房间列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public CommonResponse list() {
|
||||
try {
|
||||
return CommonResponse.success(service.getRooms());
|
||||
} catch (Exception e) {
|
||||
return CommonResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建房间
|
||||
*
|
||||
* @return 房间信息
|
||||
*/
|
||||
@PostMapping("/create")
|
||||
public CommonResponse create(@RequestBody Room room) {
|
||||
try {
|
||||
if (service.canCreateRoom(room.getId())) {
|
||||
var result = service.createRoom(room);
|
||||
if (result != null) {
|
||||
log.info("【创建房间】{}创建房间:{}", room.getMaster(), room.getId());
|
||||
return CommonResponse.success(result);
|
||||
} else {
|
||||
return CommonResponse.error("创建房间失败,请重试!");
|
||||
}
|
||||
} else {
|
||||
return CommonResponse.error("房间名已存在,换一个吧!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return CommonResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否可以加入房间
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
@PostMapping("/canJoin")
|
||||
public CommonResponse canJoin(@RequestBody Room room) {
|
||||
try {
|
||||
if (service.canJoinRoom(room.getId())) {
|
||||
List<Player> players = Global.ROOMS.get(room.getId()).getPlayers();
|
||||
for (Player player : players) {
|
||||
if (Objects.equals(room.getPlayers().getFirst().getId(), player.getId())) {
|
||||
return CommonResponse.error("已经在房间里了!");
|
||||
}
|
||||
}
|
||||
return CommonResponse.success(true);
|
||||
} else {
|
||||
return CommonResponse.error("房间已满或已经开始游戏,再换一个吧!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return CommonResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
package cn.airnan.dg.controller;
|
||||
|
||||
import cn.airnan.dg.service.TopicService;
|
||||
import cn.airnan.dg.utils.CommonResponse;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
/**
|
||||
* 题目相关接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/topic")
|
||||
public class TopicController {
|
||||
|
||||
@Resource
|
||||
private TopicService service;
|
||||
|
||||
/**
|
||||
* 随机获取题目列表
|
||||
*
|
||||
* @return 题目列表
|
||||
*/
|
||||
@GetMapping("/random")
|
||||
public CommonResponse random() {
|
||||
try {
|
||||
return CommonResponse.success(service.randomTopics(4));
|
||||
} catch (Exception e) {
|
||||
return CommonResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
package cn.airnan.dg.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
* Web首页
|
||||
*/
|
||||
@Controller
|
||||
public class indexController {
|
||||
|
||||
@RequestMapping("/")
|
||||
public String index() {
|
||||
return "index.html";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.airnan.dg.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 画板实体类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Board {
|
||||
/**
|
||||
* 画板ID
|
||||
*/
|
||||
private int id;
|
||||
/**
|
||||
* 画笔点位列表
|
||||
*/
|
||||
private List<Brush> brushes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 添加画笔点位
|
||||
*
|
||||
* @param brush 画笔点位
|
||||
*/
|
||||
private void addDraw(Brush brush) {
|
||||
this.brushes.add(brush);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空画板
|
||||
*/
|
||||
public void clear() {
|
||||
brushes.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.airnan.dg.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 画笔实体类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Brush {
|
||||
|
||||
/**
|
||||
* 画笔颜色
|
||||
*/
|
||||
private String color;
|
||||
/**
|
||||
* 画笔粗细
|
||||
*/
|
||||
private String width;
|
||||
/**
|
||||
* 画笔坐标点
|
||||
*/
|
||||
private Point point;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.airnan.dg.entity;
|
||||
|
||||
import cn.airnan.dg.utils.SocketMsg;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import jakarta.websocket.Session;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 玩家实体类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Player {
|
||||
|
||||
/**
|
||||
* 玩家要绑定的会话
|
||||
*/
|
||||
private Session session;
|
||||
|
||||
/**
|
||||
* 玩家ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 玩家位置
|
||||
*/
|
||||
private Integer index;
|
||||
|
||||
/**
|
||||
* 玩家分数
|
||||
*/
|
||||
private Integer score = 0;
|
||||
|
||||
/**
|
||||
* 加分
|
||||
*
|
||||
* @param add 要增加的分数
|
||||
*/
|
||||
private void addScore(Integer add) {
|
||||
this.score += add;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置分数
|
||||
*/
|
||||
private void clearScore() {
|
||||
this.score = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息给玩家
|
||||
*
|
||||
* @param message 要发送的消息
|
||||
*/
|
||||
public void sendMessage(String type, String message) {
|
||||
Session session = getSession();
|
||||
if (session != null && session.isOpen()) {
|
||||
try {
|
||||
SocketMsg msg = new SocketMsg();
|
||||
msg.setType(type);
|
||||
msg.setData(message);
|
||||
session.getAsyncRemote().sendText(JSON.toJSONString(msg));
|
||||
} catch (Exception e) {
|
||||
// 处理发送消息失败的情况
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.airnan.dg.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 坐标点实体类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Point {
|
||||
/**
|
||||
* X轴坐标点
|
||||
*/
|
||||
private double x;
|
||||
/**
|
||||
* Y轴坐标点
|
||||
*/
|
||||
private double y;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package cn.airnan.dg.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 房间实体类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Room {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Room.class);
|
||||
/**
|
||||
* 房间ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 房主ID
|
||||
*/
|
||||
private String master;
|
||||
|
||||
/**
|
||||
* 玩家列表(最多五人)
|
||||
*/
|
||||
private List<Player> players = new ArrayList<>(5);
|
||||
|
||||
/**
|
||||
* 画板
|
||||
*/
|
||||
private Board board;
|
||||
|
||||
/**
|
||||
* 题目
|
||||
*/
|
||||
private Topic topic;
|
||||
|
||||
/**
|
||||
* 游戏开始状态
|
||||
*/
|
||||
private boolean isBegin = false;
|
||||
|
||||
/**
|
||||
* 倒计时
|
||||
*/
|
||||
private Integer countDown = 0;
|
||||
|
||||
/**
|
||||
* 添加玩家
|
||||
*
|
||||
* @param player 要添加的玩家
|
||||
*/
|
||||
public void addPlayer(Player player) {
|
||||
if (players.size() < 5) {
|
||||
players.add(player);
|
||||
} else {
|
||||
System.out.println("房间已满,无法添加更多玩家。");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据玩家id移除房间内的玩家
|
||||
*
|
||||
* @param playerId 要移除的玩家id
|
||||
*/
|
||||
public void removePlayerById(String playerId) {
|
||||
Player player = new Player();
|
||||
player.setId(playerId);
|
||||
players.remove(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播消息给房间内所有玩家
|
||||
*
|
||||
* @param type 消息类型
|
||||
* @param message 要广播的消息
|
||||
* @param playerId 发送者的ID(用于排除发送者)
|
||||
*/
|
||||
public void sendMessage(String type, String playerId, String message) {
|
||||
log.info("【房间消息】{}: {}", id, message);
|
||||
for (Player player : players) {
|
||||
// 向除自己之外的玩家发送消息
|
||||
// if (!player.getId().equals(playerId)) {
|
||||
// player.sendMessage(type, message);
|
||||
// }
|
||||
// 向房间内所有玩家发送
|
||||
player.sendMessage(type, message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.airnan.dg.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 题目实体类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Topic {
|
||||
|
||||
/**
|
||||
* 题目名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 题目提示
|
||||
*/
|
||||
private String tip;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.airnan.dg.pojo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class RoomListVO {
|
||||
private String id;
|
||||
private String master;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.airnan.dg.service;
|
||||
|
||||
import cn.airnan.dg.configs.Global;
|
||||
import cn.airnan.dg.entity.Player;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 玩家相关业务逻辑
|
||||
*/
|
||||
@Service
|
||||
public class PlayerService {
|
||||
|
||||
/**
|
||||
* 获取玩家列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, Player> getPlayers() {
|
||||
return Global.PLAYERS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建玩家
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Player createPlayer(Player player) {
|
||||
try {
|
||||
Global.PLAYERS.put(player.getId(), player);
|
||||
return player;
|
||||
}catch (Exception e){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否可以创建玩家
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
public boolean canCreatePlayer(String playerId) {
|
||||
boolean exist = Global.PLAYERS.containsKey(playerId);
|
||||
if (exist) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.airnan.dg.service;
|
||||
|
||||
import cn.airnan.dg.configs.Global;
|
||||
import cn.airnan.dg.entity.Room;
|
||||
import cn.airnan.dg.pojo.RoomListVO;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 房间相关业务逻辑
|
||||
*/
|
||||
@Service
|
||||
public class RoomService {
|
||||
|
||||
/**
|
||||
* 获取房间列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<RoomListVO> getRooms() {
|
||||
List<RoomListVO> list = new ArrayList<>();
|
||||
Global.ROOMS.values().forEach(room -> {
|
||||
RoomListVO roomListVO = new RoomListVO();
|
||||
roomListVO.setId(room.getId());
|
||||
roomListVO.setMaster(room.getMaster());
|
||||
list.add(roomListVO);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建房间
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Room createRoom(Room room) {
|
||||
try {
|
||||
Global.ROOMS.put(room.getId(), room);
|
||||
return room;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否可以创建房间
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
public boolean canCreateRoom(String roomId) {
|
||||
boolean exist = Global.ROOMS.containsKey(roomId);
|
||||
if (exist) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否可以加入房间
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
public boolean canJoinRoom(String roomId) {
|
||||
int result = 5 - Global.ROOMS.get(roomId).getPlayers().size();
|
||||
// 有空位并且游戏没有开始
|
||||
if (result > 0 && !Global.ROOMS.get(roomId).isBegin()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package cn.airnan.dg.service;
|
||||
|
||||
import cn.airnan.dg.DrawAndGuessApplication;
|
||||
import cn.airnan.dg.configs.Global;
|
||||
import cn.airnan.dg.entity.Topic;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 题目相关业务逻辑
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TopicService {
|
||||
|
||||
/**
|
||||
* 从文件加载题库
|
||||
*/
|
||||
public static void loadTopics() {
|
||||
try (InputStream inputStream = DrawAndGuessApplication.class.getResourceAsStream("/topic.json")) {
|
||||
if (inputStream != null) {
|
||||
String content = new String(inputStream.readAllBytes());
|
||||
JSONArray jsonArray = JSON.parseArray(content);
|
||||
for (Object obj : jsonArray) {
|
||||
JSONObject jsonObject = (JSONObject) obj;
|
||||
for (String key : jsonObject.keySet()) {
|
||||
String value = jsonObject.getString(key);
|
||||
Global.TOPICS.add(new Topic(key, value));
|
||||
}
|
||||
}
|
||||
log.info("共加载{}条题目", Global.TOPICS.size());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机取出N道题目
|
||||
*
|
||||
* @param n 取出的题目数量
|
||||
* @return 题目列表
|
||||
*/
|
||||
public List<Topic> randomTopics(int n) {
|
||||
Map<Integer, String> map = new HashMap();
|
||||
List<Topic> news = new ArrayList();
|
||||
if (Global.TOPICS.size() <= n) {
|
||||
return Global.TOPICS;
|
||||
} else {
|
||||
while (map.size() < n) {
|
||||
int random = (int) (Math.random() * Global.TOPICS.size());
|
||||
if (!map.containsKey(random)) {
|
||||
map.put(random, "");
|
||||
news.add(Global.TOPICS.get(random));
|
||||
}
|
||||
}
|
||||
return news;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.airnan.dg.utils;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class CommonResponse<T> {
|
||||
private int code;
|
||||
private String msg;
|
||||
private T data;
|
||||
|
||||
public static <T> CommonResponse<T> success(T data) {
|
||||
return new CommonResponse<T>(0, "success", data);
|
||||
}
|
||||
|
||||
public static <T> CommonResponse<T> success() {
|
||||
return new CommonResponse<T>(0, "success", null);
|
||||
}
|
||||
|
||||
public static <T> CommonResponse<T> error(String msg) {
|
||||
return new CommonResponse<T>(1, msg, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.airnan.dg.utils;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Socket响应体
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SocketMsg<T> {
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 消息发送人
|
||||
*/
|
||||
private String playerId;
|
||||
|
||||
/**
|
||||
* 消息发送房间
|
||||
*/
|
||||
private String roomId;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private T data;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package cn.airnan.dg.utils;
|
||||
|
||||
import cn.airnan.dg.configs.Global;
|
||||
import cn.airnan.dg.entity.Player;
|
||||
import cn.airnan.dg.entity.Room;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import jakarta.websocket.OnClose;
|
||||
import jakarta.websocket.OnMessage;
|
||||
import jakarta.websocket.OnOpen;
|
||||
import jakarta.websocket.Session;
|
||||
import jakarta.websocket.server.PathParam;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@ServerEndpoint("/websocket/{playerId}/{roomId}")
|
||||
public class WebSocket {
|
||||
|
||||
|
||||
/**
|
||||
* 加入房间时触发WebSocket事件
|
||||
*
|
||||
* @param session 当前会话
|
||||
* @param playerId 玩家id
|
||||
* @param roomId 房间id
|
||||
*/
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @PathParam(value = "playerId") String playerId, @PathParam(value = "roomId") String roomId) {
|
||||
try {
|
||||
// 将会话添加到会话列表
|
||||
Global.SESSIONS.add(session);
|
||||
// 将会话添加到连接池
|
||||
Global.SESSION_POOL.put(playerId, session);
|
||||
// 将会话与玩家绑定
|
||||
Player player = Global.PLAYERS.get(playerId);
|
||||
if (player != null) {
|
||||
player.setSession(session);
|
||||
}
|
||||
|
||||
// 加入房间
|
||||
Room room = Global.ROOMS.get(roomId);
|
||||
if (room != null) {
|
||||
room.addPlayer(player);
|
||||
}
|
||||
|
||||
// 通知其他玩家有新玩家加入
|
||||
if (room != null) {
|
||||
String message = "玩家【" + playerId + "】加入了房间";
|
||||
room.sendMessage(Global.MSG_TYPE_ROOM_MSG, playerId, message);
|
||||
room.sendMessage(Global.MSG_TYPE_ROOM_CHAT, playerId, "系统:"+message);
|
||||
}
|
||||
|
||||
log.info("【加入房间】玩家 {} 进入房间 {},房间人数为:{}", playerId, roomId, Global.SESSIONS.size());
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session) {
|
||||
try {
|
||||
// 从会话列表中移除关闭的会话
|
||||
Global.SESSIONS.remove(session);
|
||||
// 从连接池中移除关闭的会话
|
||||
String playerIdToRemove = null;
|
||||
for (Map.Entry<String, Session> entry : Global.SESSION_POOL.entrySet()) {
|
||||
if (entry.getValue().equals(session)) {
|
||||
playerIdToRemove = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (playerIdToRemove != null) {
|
||||
Global.SESSION_POOL.remove(playerIdToRemove);
|
||||
}
|
||||
String playerId = "";
|
||||
// 清除玩家与会话的绑定
|
||||
for (Player player : Global.PLAYERS.values()) {
|
||||
if (player.getSession() != null && player.getSession().equals(session)) {
|
||||
player.setSession(null);
|
||||
playerId = player.getId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 从房间中移除玩家
|
||||
for (Room room : Global.ROOMS.values()) {
|
||||
room.removePlayerById(playerId);
|
||||
String message = "玩家【" + playerId + "】退出了游戏,房间人数为:" + room.getPlayers().size();
|
||||
room.sendMessage(Global.MSG_TYPE_ROOM_MSG, playerId, message);
|
||||
room.sendMessage(Global.MSG_TYPE_ROOM_CHAT, playerId, "系统:"+message);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String message) {
|
||||
// log.info("【WebSocket消息】收到客户端消息:{}", message);
|
||||
try {
|
||||
// 解析消息为 SocketMsg 结构体
|
||||
SocketMsg<String> socketMsg = JSON.parseObject(message, new TypeReference<>() {
|
||||
});
|
||||
// 匹配房间并转发消息
|
||||
Global.ROOMS.get(socketMsg.getRoomId()).sendMessage(socketMsg.getType(), socketMsg.getPlayerId(), socketMsg.getData());
|
||||
} catch (Exception e) {
|
||||
log.error("解析消息失败:{}", message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 此为广播消息
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void sendAllMessage(String message) {
|
||||
for (Session session : Global.SESSIONS) {
|
||||
try {
|
||||
if (session.isOpen()) {
|
||||
session.getAsyncRemote().sendText(message);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 此为单点消息
|
||||
*
|
||||
* @param playerId 用户编号
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void sendOneMessage(String playerId, String message) {
|
||||
Session session = Global.SESSION_POOL.get(playerId);
|
||||
if (session != null && session.isOpen()) {
|
||||
try {
|
||||
synchronized (session) {
|
||||
session.getAsyncRemote().sendText(message);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 此为单点消息(多人)
|
||||
*
|
||||
* @param playerIds 用户编号列表
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void sendMoreMessage(String[] playerIds, String message) {
|
||||
for (String playerId : playerIds) {
|
||||
Session session = Global.SESSION_POOL.get(playerId);
|
||||
if (session != null && session.isOpen()) {
|
||||
try {
|
||||
session.getAsyncRemote().sendText(message);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Integer getOnLine() {
|
||||
return Global.SESSION_POOL.size();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user