Springboot项目——网页版本五子棋

网页五子棋:本项目简单实现了网页版本的五子棋对战功能,同时会根据用户的天梯分数来匹配,可供多位用户同时提供对战功能。大致可分为三个模块,用户模块,匹配模块,对战模块,下面重点介绍以下三个模块。

101.42.44.62:10010/login.html

一、全局处理

  1. 拦截器

1)自定义拦截器类实现 HandlerInterceptor 接口,重写 preHandle 方法,用于拦截未登录用户的请求。

2)自定义配置类实现 WebSocketConfigurer 接口,重写 registerWebSocketHandlers 方法,用于注册后续重写 TextWebSocketHandler 的类,使得后续客户端和服务端的通信能正确进行,同时为该配置类加上 @EnableWebSocket 注解,使得 Spring 知道该类是 WebSocket 的配置类;实现 WebMvcConfigurer 接口,重写 addInterceptors 方法,表示对哪些请求进行拦截。

  1. 统一数据格式返回

1)自定义结果实体类 Result,其属性包括 code(业务码),errMsg(错误信息),data(接口响应的数据,泛型);其静态方法有 success(T data),在请求成功时可调用,参数 data,表示给前端返回的数据内容;fail(String errMsg),在请求非法时可调用,参数 errMsg,表示非法请求的错误信息。其中,Result 类中的属性 code 通过自定义枚举类来实现,保证 code 值的正确性。

2)自定义响应通知类,实现 ResponseBodyAdvice 接口,并重写其 supports 方法和 beforeBodyWrite 方法,supports 方法:判断是否要执行 beforeBodyWrite 方法,true 为执行,false不执行;beforeBodyWrite 方法:对response方法进行的具体操作处理,如果返回的结果已经是Result类时,需直接返回,另外,如果返回的结果时 String类型,需通过 ObjectMapper 进行特殊处理。

二、用户模块

通过 UserController 类实现

1. 登录

1)前端页面

2)后端实现

使用 JWT 令牌(Json Web Token)存储用户登录信息,同时将 user 对象存储在 Session 中,方便后续使用WebSocketSession 获取到用户信息;后端首先校验用户信息的合法性,当校验成功时,为该用户生成令牌,服务器将用户的用户名存储在 token 中 (方便后续获取用户信息),并设置过期时间,**客户端将令牌存储在浏览器的 Local Storage 中,并将 token 设置在请求头 Header 中,**后续客户端的请求都会带着 token,服务器会校验令牌,来决定是否拦截用户的请求。

2. 注册

1)前端页面

2)后端实现

使用 MD5 算法加密、UUID 加盐共同加密用户的密码,保证用户密码的安全性。服务器首先判断用户注册信息的合法性,用户名不能重复,两次密码需输入一致等;判断合法后,将用户的密码和UUID 生成的随机盐值,使用 MD5 算法进行加密后,存储在数据库中。

3. 获取用户信息

服务器从请求头 Header 中获取到 token,并获取到存储在 token 中的用户名,根据用户名在数据库中查询相应的用户信息并返回。

三、model类

1. 用户类 User

其属性包括 int userId 用户 id,String username 用户名,String password 密码,int score 天梯分数,int totalCount 总场数,int winCount 获胜场数;

2. 自定义匹配请求实体类 MatchRequest

其属性包括 String message,表示请求内容;

3. 自定义匹配响应实体类 MatchResponse

其属性包括 boolean ok,表示响应的状态,String reason,表示响应状态错误时的错误原因,String message,表示响应内容;

4. 自定义游戏就绪响应类 GameReadyResponse

因为在服务器确认游戏匹配成功后,此时直接就向客户端发送游戏就绪响应,不需要游戏就去请求;其属性包括:String message 响应信息,boolean ok 响应状态,String reason 失败时的原因,String roomId 游戏房间 id,int thisUserId 玩家 1 的 id,int thatUserId 玩家 2 的 id,int blackUser 执黑子的玩家 id;

5. 自定义游戏请求类 GameRequest

用来表示一次游戏请求(即落子请求),其属性包括 String message 请求信息,int userId 落子玩家,int row 落子的行数,int col 落子的列数;

6. 自定义游戏响应类 GameResponse

用来表示一次游戏响应(即落子响应),其属性包括 String message 响应信息,int userId 响应返回的玩家,int row 落子的行数,int col 落子的列数(由于需要实时的将棋盘上的信息反馈给每个玩家),int winner 表示获胜方玩家 id,未分出胜负用 -1 表示,int isOut 表示是否有玩家掉线,当有玩家掉线时,则认为游戏结束,掉线玩家判负;

四、游戏处理类

1. 自定义用户在线状态管理类 OnlineUserManager

其包含两个 ConcurrentHashMap 类型的属性 gameLobby,gameRoom,将用户 id 和 WebSocketSession 会话通过键值对方式存储,gameLobby 表示用户在游戏大厅的在线状态,gameroom 表示用户在游戏房间的在线状态;并为该类添加相应的方法

enterGameLobby:进入游戏大厅,在 gameLobby 中添加用户 id 和相应的 WebSocketSession;

exitGameLobby:离开游戏大厅,从 gameLobby 中删除 id 对应的键值对;

getOnline:获取用户大厅会话,通过 id 从 gameLobby 中获取相应的 WebSocketSession;

enterGameRoom:进入游戏房间,在 gameRoom中添加用户 id 和相应的 WebSocketSession;

exitGameRoom:离开游戏房间,从 gameRoom 中删除 id 对应的键值对;

getOnlineRoom:获取用户房间会话,通过 id 从 gameRoom 中获取相应的 WebSocketSession;

即 将 ConcurrentHashMap 的 put,get,remove 等方法进行封装,分别表示用户上线,获取用户在线状态,用户下线;

2. 自定义匹配类 Matcher

其属性包含三个队列,分别表示用户的实力等级(分为正常水平,高水平和超高水平),根据用户的实力等级来匹配实力相近的玩家;其方法包括用户

进入匹配队列 add方法;用户离开匹配队列 remove 方法;构造方法,使用三个线程分别判断三个匹配队列是否匹配成功,使用 wait notify 解决忙等问题;handlerMatch 方法判断是否匹配成功,当某个队列中有两名玩家时,即匹配成功,将这两个玩家放入同一个房间中,并为两名玩家分别做出响应;

3. 自定义的房间类 Room

表示一次游戏中两名玩家对战的游戏房间,其属性分别包括 roomId 房间号,User user1 表示玩家 1,User user2 表示玩家 2,int blackUser 表示执黑子的玩家(先手权),int[][] board 表示棋盘,由于该类不应该是唯一的,有可能有多场对局同时进行,就需要多个游戏房间,故需通过构造器使用 getBean 方法注入对象,roomId 用 UUID 随机生成;其方法有

putChess 用来处理一次落子请求:首先将客户端传递的落子请求转换为 GameRequest 对象,根据其属性 userId 判断是哪个玩家的轮次,并将棋盘 board 中对应的 row col 位置填上对应轮次玩家的标志,例如 1 表示黑子,2 表示白子,(同时判断是否有玩家离线,如果有玩家离线,则将胜方指定为未离线的玩家),同时判断游戏是否分出胜负,并将以上信息构造为 GameResponse 对象返回给每个玩家;

printBoard 在服务器端打印棋盘落子情况,以便观察是否出现异常情况;

checkWinner 判断是否决出胜负,遍历棋盘 board,判断是否存在五子连珠情况;

4. 自定义房间管理器类 RoomManager

用来管理游戏房间,其属性包括 ConcurrentHashMap<String, Room> rooms,ConcurrentHashMap<Integer, String> userToRoom,其中 rooms 将房间 id 和房间关联起来,userToRoom 的作用是将玩家 id 和房间 id 关联起来;其方法包括

add,将对应的房间 id 和房间放入 rooms 中,将 玩家 1 和房间 id 放入 玩具 2 和房间 id 分别放入userToRoom中;

remove 方法将放假 id 从 rooms 中删除,将 玩家 1 id 和玩家 2 id 分别从 userToRoom 中删除;

getRoomByUserId,通过 userId 获取到房间 id,再通过房间 id 获取到房间信息;

五、匹配模块

通过 MatchController 类实现,处理匹配时的 WebSocket 请求;该类继承自 Spring 中的TextWebSocketHandler 类,并重写其中的一些方法

**1)afterConnectionEstablished 方法,**该方法用来处理前后端通信时的 WebSocket 连接建立成功之后服务器应该做的逻辑,并将响应结果返回给客户端;

在连接建立之后,通过 WebSocketSession 类中提供的 getAttributes().get 方法获取到用户信息,通过用户 id 得到的 WebSocket 会话,若不为 null,则说明用户已经在其他地方登录,此时不应重复登录(由于此时还没有进行任何操作,得到的 id 对应的 WebSocket 应为 null),当 id 对应的 WebSocket 会话为null 时,将此时的 WebSocket 会话和 id 对应放入 map 中,表示用户进入游戏大厅,即用户上线;

**2)handleTextMessage 方法,**该方法用来处理客户端与服务器通信过程中客户端发送的请求,并将请求处理之后的响应返回给客户端;

首先通过 WebSocketSession 类中提供的 getAttributes().get 方法获取到用户信息,通过TextMessage 提供的方法 getPayload 将请求解析为字符串,再通过 ObjectMapper类 提供的 readValue 方法将字符串 转化为 MatchRequest 对象,服务器根据客户端请求中的内容(开始匹配或者结束匹配),决定用户进入匹配队列还是离开匹配队列,并构造相应的响应并返回给客户端;

3)handleTransportError 方法,该方法用来处理当通信连接出现异常时,服务器应做的逻辑;

此时通信连接异常,说明玩家已经下线,需要将 用户 id 从 map 中删除;考虑到这样一种情况:同一个账号使用两个不同的浏览器先后登录同一个账号,此时经过 afterConnectionEstablished 方法判定之后不能重复登录,此时需将后登陆的连接关闭(但先登录的连接不应该关闭),为了防止将先登录的 webSecoket会话错误删除,此处需要再做一个判定,当当前的 WebSocketSession == 用户 id 对应的 WebSocketSession 时再进行删除,同时将用户从匹配队列中移除;

4)afterConnectionClosed 方法,该方法用户处理通信连接关闭之后,服务器应做的逻辑;同上连接异常时的处理情况;

六、游戏模块

通过 GameController 类实现,处理游戏中的 WebSocket 请求;该类继承自 Spring 中的TextWebSocketHandler 类,并重写其中的一些方法

1)afterConnectionEstablished 方法

首先,通过 WebSocketSession 类中提供的 getAttributes().get 方法获取到用户信息,如果用户为null 则直接返回一个 reason 为用户未登录的 GameReadyResponse 响应;否则,根据 RoomManager 类中的 getRoomByUserId 方法获取到玩家所在房间;如果房间不存在,则说明尚未匹配到,并返回响应;再接着判断用户是否在其他地方已经登录,通过 OnlineUserManager 获得当前用户的游戏房间状态和游戏大厅状态(WebSocketSession),如果有一个不为 null 则说明用户已经登录,返回用户多开响应;否则,就通过 OnlineUserManager 中的 enterGameRoom 方法设置该用户的游戏房间状态;判断 room 中的 user1 是否为空,若为空,则将当前玩家设置为user1,并将先手方设置为当前玩家,此时设置过一个玩家之后应该直接返回,等待第二个玩家的请求到来时,此时 room 中的 user1 已经不为空了,已经被上一个玩家先占了,此时将该玩家设置为 user2,此时 room 中的两个玩家均已连接上,需分别通知两个玩家游戏准备就绪,通过 noticeGameReady 方法实现;注意判断 user1 是否为空时,有可能两个玩家的请求同时到达,同时判定 user1 为空,此时 room 中的 user1 被设置了两次,而 user2 没有被设置,为了防止这种情况的发生,需要在判断时使用 synchronized 对 room 加锁;

2)noticeGameReady方法的实现

该方法参数列表包含 Room room,User thisUser,User thatUser,分别表示游戏房间,玩家自己和另一个玩家,这个方法需要向客户端发送一个游戏准备就绪 GameReadyResponse 的响应,就需要通过 WebSocketSession 中的 sendMessage 方法发送,故需先通过 OnlineUserManager 中的 getOnlineRoom 方法获取到 session会话,然后构造一个 GameReadyResponse 响应并设置其中的 roomId,thisUserId,thatUserId等属性,再通过 sendMessage 发送;

3)handleTextMessage

先通过 WebSocketSession 类中提供的 getAttributes().get 方法获取到用户信息,并判断用户信息是否为空,再通过 RoomManager 中的 getRoomByUserId 获取到当前房间 room 的信息,通过调用 room 中的 putChess 方法处理一次落子请求;

4)handleTransportError

当游戏的某一方连接异常时,此处逻辑是通知另一方直接获胜,通过 noticeUserWin 方法;并改变玩家的游戏房间在线状态;

5)noticeUserWin 方法的实现

该方法参数为一个 User 对象(指掉线玩家),主要完成的功能是通知未掉线的玩家获胜,并返回响应给该玩家,并修改两名玩家的天梯分数,获胜场次,总场次等信息;

先通过 RoomManager 中的 getRoomByUserId 获取到相应的房间信息,根据房间信息,判断该通知哪个玩家获胜,构造一个 GameResponse 对象,并设置其Winner,isOut等相关信息,再将响应发送给该玩家,同时修改两名玩家数据库中的相应信息;

6)afterConnectionClosed同上连接异常时的处理情况;

相关推荐
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
码农小旋风3 小时前
详解K8S--声明式API
后端
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7894 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet