SpringBoot项目——实现微服务:匹配系统
回顾:
SpringBoot项目——创建个人中心页面
SpringBoot项目——前端页面授权+注册页面实现+登录状态持久化
SpringBoot项目——jwt 登录验证与编写前后端分离API
SpringBoot项目——配置Mysql与session注册登录验证
SpringBoot项目——创建菜单与游戏页面
SpringBoot项目——配置git环境与项目创建
文章目录
1. 系统整体介绍
1.1 匹配系统原理
点击匹配向服务器发出请求匹配系统不会立即返回结果,一般会匹配个几秒。整个游戏是异步的过程,计算量比较大的过程,所以需要另外写一个进程。后端接收的请求,会将用户的请求,发送给我们的匹配系统,匹配系统维护了一堆用户的集合。
匹配系统里有很多很多的用户,将当前用户中战斗力最相近的几个人匹配到一起。然后将匹配结果返回给serverSpringBoot。返回之后后端就会把结果返回给前端、在前端就可以看到匹配的对手。
整个匹配的过程其实是一个异步的过程、匹配的过程会经过一段比较长的时间。
1.2 WebSocket协议
http:一问一答式
websocket 协议:问一次返回多次中间还有间隔时间
这种情况下我们的https就不能满足要求了、websocket协议
不仅客户端可以主动像服务器发送请求、服务器端也可以主动向客户端发送请求
是两遍对称的一个通信方式。
1.3 云端维护游戏的整个流程
-
首先生成一个地图(在后端生成:地图一致且防止作弊),将两个地图传给两个客户端,传完之后等待用户输入。
-
既可以从客户端获得操作输入,也可以有微服务获得代码输入。
-
waiting 操作:可以写一个死循环,每次循环前先sleep一秒钟,然后判断一下是否两条蛇的下一步操作都有了、如果有的话进行下一步,如果没有的话继续等待。当然我们可以设定一个最大等待时间。
-
判输赢:如果超时,判超时方输;如果获得输入写一个judging程序、判断是否合法和撞墙。
-
整体流程:
- 用户开始匹配的时候向后端发送一个请求、就会在后端websocket里new一个新的实例开一个线程来维护这个链接。那么接收到这个请求之后、会把相应信息发送给匹配系统。
- 匹配系统是一个单独的额外程序、当接收到很多的用户之后,若出现两名玩家的战斗力接近匹配出来一局、匹配系统就会将信息返回给后端websocket服务器。
- websocket服务器接受到这个信息之后就会将这个信息返回给这局对战的两名玩家、根据两名玩家建立的链接返还到他们的前端的浏览器里面。
- 同时在我们的服务器端创建一个游戏的过程、整个游戏的判断地图的生成都是在云端进行。
1.4 创建 WebSocket
-
基本原理就是,后端创建WebSocket类,会把前端建立的每一个websocket连接在后端维护起来。比如Client1连接到服务器、其实一个链接就是一个websocketserver实例,每来一个连接,其实就是new一个这个类的实例。
-
每一个连接都是这个类的一个实列来维护的、所有和这个连接相关的信息都会存到这个类里面、如果是连接有自己独有信息(比如说维护这个连接对应的用户是谁),可以存成私有变量;如果是维护所有连接的公共信息(比如我们想去维护一下当前哪些用户建立的连接),那么可以存成一个静态变量。
-
WebSocket就是一个多线程、每来一个连接就会开一个新的线程来维护它。
2. 后端具体操作
-
在pom.xml文件中添加依赖(maven仓库):
- spring-boot-starter-websocket
- fastjson
-
添加config.WebSocketConfig配置类:
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();
}
}
- 添加consumer.WebSocketServer类
package com.kob.backend.consumer;
import...
@Component
@ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾
public class WebSocketServer {
// 全局(对于所有的WebSocket)存储所有链接
// 匹配系统接收到匹配成功的两个用户后,需要将消息发送给两名玩家,需要根据用户id找到链接是谁,才可以利用该链接向前端发请求
// ConcurrentHashMap:线程安全的哈希表,可将userid映射到WebSocketServer
private static ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>(); // 静态变量属于类本身,所有实例共用
// 存储每个链接对应的用户是谁,用户信息存到每个session里,才知道匹配的是哪个用户。
private User user;
// 后端向前端发送信息,每个链接用这里的session维护
private Session session = null;
// 非Spring里的单例模式(该类同一时间只能由一个实例),注入时不能直接Autowired
private static UserMapper userMapper;
@Autowired
public void setUserMapper(UserMapper userMapper) {
// 静态变量访问时,需要用类名访问
WebSocketServer.userMapper = userMapper;
}
// 连接建立时触发
@OnOpen
public void onOpen(Session session, @PathParam("token") String token) {
this.session = session;
System.out.println("connected!");
// 从token里读取当前userid???
Integer userId = Integer.parseInt(token);
// 注入userMapper后使用,获取token对应id的用户
this.user = userMapper.selectById(userId);
users.put(userId, this);
}
// 链接关闭时触发
@OnClose
public void onClose() {
System.out.println("disconnected!");
// 断开链接时需要将user从users里删掉
if( this.user != null) {
users.remove(this.user.getId());
}
}
// 后端从Client接收信息时触发:前端——→后端
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("receive message!");
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
// 后端向前端发信息:后端——→前端
public void sendMessage(String message) {
synchronized (this.session) {
try {
this.session.getBasicRemote().sendText(message);
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 配置config.SecurityConfig
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/websocket/**");
}
- 验证jwt
3. 实现前端部分
- store里开新的modules,用来存储和pk相关的所有全局变量
- 未知用户的图片:
https://cdn.acwing.com/media/article/image/2022/08/09/1_1db2488f17-anonymous.png
3. 在数据库中创建表record
record表用来记录每局对战的信息
表中的列:
- id: int
- a_id: int
- a_sx: int
- a_sy: int
- b_id: int
- b_sx: int
- b_sy: int
- a_steps: varchar(1000)
- b_steps: varchar(1000)
- map: varchar(1000)
- loser: varchar(10)
- createtime: datetime