目录
前言
在现代化的Web应用中,实时性是越来越重要的一种需求。当涉及到多个用户同时进行操作,例如在线游戏,WebSockets变得越来越流行。WebSockets提供了一个双向通信通道,使得服务器和客户端能够进行实时通信,而无需HTTP请求/响应周期。在本篇教程中,我们将使用SpringBoot和WebSocket技术,构建一个简单的猜数字游戏,以演示如何在Web应用程序中使用WebSockets进行实时通信。通过本教程,您将了解如何使用SpringBoot快速构建Web应用程序,并使用WebSocket技术实现实时通信。
一、涉及环境
SpringBoot/MAVEN/MYSQL
二、使用步骤
1.pom配置
代码如下(示例):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>guess</groupId>
<artifactId>guess-game</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>guess-game</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
</dependencies>
</project>
2.四个实类层
1.参赛者的名称和猜的数字类
public class JoinGameRequest implements Serializable {
private String memberName;
private Integer factor;
public String getMemberName() {
return memberName;
}
2.响应消息类
public class JoinGameResponse {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
3. ListData
public class ListData {
// 声明私有成员变量
private Integer messageType;
private String memberName;
private String guessTime;
private Integer guessPosition;
private Integer guessNumber;
private String gameCode;
private Integer guessResult;
{
4.猜的信息类
public class StatisticsMessage {
private Integer messageType;
private Integer alreadyGuessCount;
private Integer alreadyGuessRightPosition;
private Long durationMin;
}
3.webSocket层
这是一个用于在Spring Boot应用程序中配置WebSocket的配置类。 该类实现了WebSocketConfigurer接口,重写了registerWebSocketHandlers方法, 用于注册WebSocket处理程序和设置WebSocket服务端的路径。 在这个例子中,我们使用WebSocketServer作为WebSocket处理程序,并设置了路径为"/socket",并允许来自任何来源的请求。
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketServer(), "/socket").setAllowedOrigins("*");
}
}
4.Controller层
GameController类是一个RESTful Web服务的控制器类
@RestController
@RequestMapping("gameinfo")
@CrossOrigin
public class GameController {
@Autowired
private GameHandler gameHandler;
@PostMapping("/joinGame")
public JoinGameResponse joinGame(@RequestBody JoinGameRequest request){
return gameHandler.join(request);
}
}
5.DAO层
1.游戏表实体类
@Table(name = "t_game")
public class Game {
@Id
@GeneratedValue(
generator = "JDBC",
strategy = GenerationType.IDENTITY
)
private Long id;
private Integer status;
private Date createTime;
}
2.游戏用户表实体类
@Table(name = "t_game_user")
public class GameUser {
@Id
@GeneratedValue(
generator = "JDBC",
strategy = GenerationType.IDENTITY
)
private Long id;
private Long gameId;
private String hash;
private String username;
private Integer numberLocation;
private Integer number;
private Boolean isRight;
private Date createTime;
}
3. 逻辑层
@Component
@Transactional(rollbackFor = Exception.class)
public class GameHandler {
@Autowired
private WebSocketServer webSocketServer;
@Autowired
private GameMapper gameMapper;
@Autowired
private GameUserMapper userMapper;
private static String hash = null;
private static Integer maxLocation = null;
public JoinGameResponse join(JoinGameRequest request) {
synchronized (this) {
Game gameing = gameMapper.selectGameing();
if (gameing == null) {
gameing = new Game();
gameing.setCreateTime(new Date());
gameing.setStatus(1);
gameMapper.insertSelective(gameing);
}
//查询当前用户
GameUser user = userMapper.selectByGameId(gameing.getId(), request.getMemberName());
Integer location = user == null ? 1 : user.getNumberLocation() + 1;
//开始游戏
if (hash == null) {
this.generateRandom();
}
//最大位置为空 则去数据库查询最大数据,没有则赋值0,最大数据相等则时间最早为第一名
if (maxLocation == null) {
GameUser fristUser = userMapper.selectMaxLaction(gameing.getId());
if (fristUser != null) {
//本身存在第一名则广播汇总数据
this.broadcastCount(fristUser);
}
maxLocation = fristUser == null ? 0 : fristUser.getNumberLocation();
}
//用户数据入库
GameUser userNewData = new GameUser();
//这里charAt位置是从0开始所以要减1
userNewData.setRight(Integer.parseInt(String.valueOf(hash.charAt(location - 1))) == request.getFactor());
userNewData.setGameId(gameing.getId());
userNewData.setHash(hash);
userNewData.setNumberLocation(location);
userNewData.setNumber(request.getFactor());
userNewData.setUsername(request.getMemberName());
userNewData.setCreateTime(new Date());
userMapper.insertSelective(userNewData);
if (userNewData.getRight()) {
//答对 刷新hash
this.generateRandom();
//判断该答对位置是否为最大 则广播数据
if (userNewData.getNumberLocation() > maxLocation) {
maxLocation = userNewData.getNumberLocation();
this.broadcastCount(userNewData);
}
}
//等于32 该轮次结束
if (maxLocation == 32) {
gameing.setStatus(0);
gameMapper.updateByPrimaryKey(gameing);
hash = null;
maxLocation = null;
}
this.broadcastListCount(userNewData);
JoinGameResponse response = new JoinGameResponse();
response.setMessage("当前位数:" + userNewData.getNumberLocation() + (userNewData.getRight() ? "答对了" : "答错了"));
return response;
}
}
//生成32位随机数
private void generateRandom() {
SecureRandom random = new SecureRandom();
hash = String.format("%01d", random.nextInt(10)) +
String.format("%09d", random.nextInt(1000000000)) +
String.format("%010d", random.nextInt(1000000000)) +
String.format("%010d", random.nextInt(1000000000)) +
String.format("%02d", random.nextInt(100));
}
//广播汇总数据
private void broadcastCount(GameUser user) {
StatisticsMessage statisticsMessage = userMapper.selectCountData(user.getGameId(), user.getUsername());
statisticsMessage.setMessageType(6);
webSocketServer.sendMessageToAll(JSONObject.toJSONString(statisticsMessage));
}
//广播列表数据
private void broadcastListCount(GameUser user) {
ListData data = new ListData();
data.setMessageType(1);
data.setGameCode(user.getHash());
data.setGuessNumber(user.getNumber());
data.setGuessPosition(user.getNumberLocation());
Date date = new Date(user.getCreateTime().getTime());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timeString = sdf.format(date);
data.setGuessTime(timeString);
data.setMemberName(user.getUsername());
data.setGuessResult(user.getRight() ? 1 : 2);
webSocketServer.sendMessageToAll(JSONObject.toJSONString(data));
}
}
4.mapper层
1.查询是否有进行中的游戏
public interface GameMapper extends Mapper<Game> {
/**
* 查询是否有进行中的游戏
*/
@Select("select * from t_game where status=1")
Game selectGameing();
}
2.查询当前最高猜对位数
public interface GameUserMapper extends Mapper<GameUser> {
/**
* 查询用户 进行中的游戏当前最高猜对位数
* @param gameId
* @param name
* @return
*/
@Select("select * from t_game_user where is_right=1 and game_id=#{gameId} and username=#{name} order by number_location desc limit 1")
GameUser selectByGameId(Long gameId,String name);
@Select("select a.* from t_game_user as a where `is_right`=1 and game_id=#{gameId} and number_location = (select max(number_location) from t_game_user) order by create_time asc limit 1")
GameUser selectMaxLaction(Long gameId);
//查询统计数据
@Select("select count(*) as already_guess_count,sum(`is_right`) as already_guess_right_position,max(create_time)-min(create_time) as duration_min from t_game_user where `username`=#{name} and game_id=#{gameId}")
StatisticsMessage selectCountData(Long gameId,String name);
}
5.webSocket serve层
@Component
public class WebSocketServer extends TextWebSocketHandler {
private static final List<WebSocketSession> sessions = new ArrayList<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
// WebSocket连接建立时将该session加入集合
sessions.add(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
sessions.remove(session);
}
public void sendMessageToAll(String message) {
for (WebSocketSession session : sessions) {
try {
session.sendMessage(new TextMessage(message));
} catch (IOException e) {
System.out.println("Error sending message to session " + session.getId());
}
}
}
}
总结
如果你喜欢本篇教程并想深入了解SpringBoot和WebSocket的应用,欢迎联系我们千锋教育我们将为您提供更深入的学习内容和实践机会。同时,如果您对本篇教程有任何疑问或建议,也欢迎随时联系我进行交流。