基于SpringBoot+MyBatis 五子棋双人对战
Posted 粉色的志明
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于SpringBoot+MyBatis 五子棋双人对战相关的知识,希望对你有一定的参考价值。
1. 核心功能
技术:
前端:html + CSS + javascript + AJAX
后端:SpringBoot + MyBatis + WebSocket + mysql 5.7
2. 演示效果
3. 创建项目
4. 数据库设计
create database if not exists java_gobang;
use java_gobang;
drop table if exists user;
create table user (
userId int primary key auto_increment,
username varchar(50) unique,
password varchar(255),
score int, -- 天梯积分
totalCount int, -- 比赛总场数
winCount int -- 获胜场数
);
insert into user values(null,"cm","$2a$10$Bs4wNEkledVlGZa6wSfX7eCSD7wRMO0eUwkJH0WyhXzKQJrnk85li",1000,0,0);
5. 配置文件
application.yml
debug: true
logging:
level:
com:
example: DEBUG
example.onlinemusic.mapper: debug
druid:
sql:
Statement: DEBUG
root: INFO
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: root
url: jdbc:mysql://localhost:3306/java_gobang?characterEncoding=utf8&serverTimezone=UTC
username: root
mybatis:
mapper-locations: classpath:mybatis/**Mapper.xml
server:
port: 8081
mybatis.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.java_gobang.mapper.UserMapper">
</mapper>
6. 用户模块
6.1 登录实现
6.1.1 前后端交互接口
每一次我们设计都需要先设计前后端交互接口
请求
POST /login HTTP/1.1
username: "",password: ""
响应
HTTP/1.1 200 OK
Content-Type: application/json
userId: 1,
username: 'cm',
score: 1000,
totalCount: 0,
winCount: 0
6.1.2 model 层
创建 User 类
@Data
public class User
private int userId;
private String username;
private String password;
private int score;
private int totalCount;
private int winCount;
6.1.3 mapper 层
@Mapper 注解不要忘了
@Mapper
public interface UserMapper
//往数据里插入一个用户,用于注册功能
int insert(User user);
//根据用户名,来查询用户的详细信息,用于登录功能
User selectByName(String username);
// 总比赛场数 + 1, 获胜场数 + 1, 天梯分数 + 30
int userWin(int userId);
// 总比赛场数 + 1, 获胜场数 不变, 天梯分数 - 30
int userLose(int userId);
6.1.4 xml 层
resources 底下创建 mybatis 包,在创建UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.java_gobang.mapper.UserMapper">
<!-- 新增用户 -->
<insert id="insert">
insert into user values (null,#username,#password,1000,0,0);
</insert>
<!-- 根据用户名查找用户用户 -->
<select id="selectByName" resultType="com.example.java_gobang.model.User">
select * from user where username=#username;
</select>
<update id="userWin">
update user set totalCount = totalCount + 1, winCount = winCount + 1, score = score + 30
where userId = #userId
</update>
<update id="userLose">
update user set totalCount = totalCount + 1, score = score - 30
where userId = #userId
</update>
</mapper>
6.1.5 service 层
调用 mapper 层的方法
@Service
public class UserService
@Resource
private UserMapper userMapper;
//往数据里插入一个用户,用于注册功能
public int insert(User user)
return userMapper.insert(user);
//根据用户名,来查询用户的详细信息,用于登录功能
public User selectByName(String username)
return userMapper.selectByName(username);
// 总比赛场数 + 1, 获胜场数 + 1, 天梯分数 + 30
public int userWin(int userId)
return userMapper.userWin(userId);
// 总比赛场数 + 1, 获胜场数 不变, 天梯分数 - 30
public int userLose(int userId)
return userMapper.userLose(userId);
6.1.6 controller 层
用来存储 session 字符串
@RestController
public class UserController
@Autowired
private UserService userService;
@Resource
private BCryptPasswordEncoder bCryptPasswordEncoder;
@RequestMapping("/login")
@ResponseBody
public Object login(String username, String password, HttpServletRequest request)
// 查询用户是否在数据库中存在
User user = userService.selectByName(username);
// 没有查到
if(user == null)
System.out.println("登录失败!");
return new User();
else
//查到了,但密码不一样
if(!bCryptPasswordEncoder.matches(password,user.getPassword()))
return new User();
// 匹配成功,创建 session
request.getSession().setAttribute(Constant.USER_SESSION_KEY,user);
return user;
6.1.7 使用 BCrypt 进行密码加密
<!-- security依赖包 (加密)-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
启动类添加注解
@SpringBootApplication(exclude = org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class)
6.1.8 添加拦截器
创建 config 包
LoginInterceptor 类
public class LoginInterceptor implements HandlerInterceptor
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute(Constant.USER_SESSION_KEY) != null)
return true;
response.sendRedirect("/login.html");
return false;
AppConfig 类
@Override
public void addInterceptors(InterceptorRegistry registry)
LoginInterceptor loginInterceptor = new LoginInterceptor();
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/**/login.html")
.excludePathPatterns("/**/register.html")
.excludePathPatterns("/**/css/**.css")
.excludePathPatterns("/**/images/**")
.excludePathPatterns("/**/js/**.js")
.excludePathPatterns("/**/login")
.excludePathPatterns("/**/register")
.excludePathPatterns("/**/logout");
6.1.9 测试
6.2 注册实现
6.2.1 前后端交互接口
请求
POST /register HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=cm&password=123456
响应
HTTP/1.1 200 OK
Content-Type: application/json
userId: 1,
username: 'dingding',
score: 1000,
totalCount: 0,
winCount: 0
注册失败(比如说用户名已经存在了),就返回 username 为 null 的对象
6.2.2 controller 层
@RequestMapping("/register")
@ResponseBody
public Object register(String username,String password)
User user1 = userService.selectByName(username);
if(user1 != null)
System.out.println("当前用户已存在");
return new User();
else
User user2 = new User();
user2.setUsername(username);
String password1 = bCryptPasswordEncoder.encode(password);
user2.setPassword(password1);
userService.insert(user2);
return user2;
6.2.3 测试
再注册相同的就不行了
6.3. 获取用户信息
6.3.1 前后端交互接口
请求
GET /userinfo HTTP/1.1
响应
HTTP/1.1 200 OK
Content-Type: application/json
userId: 1,
username: 'cm',
score: 1000,
totalCount: 0,
winCount: 0
6.3.2 controller 层
@RequestMapping("/userinfo")
@ResponseBody
public Object getUserInfo(HttpServletRequest request)
try
HttpSession session = request.getSession(false);
User user = (User)session.getAttribute("user");
User newUser = userService.selectByName(user.getUsername());
return newUser;
catch (NullPointerException e)
System.out.println("没有该用户");
return new User();
6.3.3 测试
因为我们之前用这个账户登录的已经保存了这个 JSESSIONID
6.4 退出登录
6.4.1 前后端交互接口
请求
GET /logout HTTP/1.1
响应
HTTP/1.1 200
6.4.2 controller 层
@RequestMapping("/logout")
public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException
HttpSession session = request.getSession(false);
// 拦截器的拦截, 所以不可能出现session为空的情况
session.removeAttribute(Constant.USER_SESSION_KEY);
response.sendRedirect("login.html");
6.4.3 测试
重定向了我的登录页面
7. 匹配模块
客户端主动向服务器发起请求,返回一个响应,如果客户端不主动发起请求,服务器不能主动联系客户端,在这里,我们需要服务器主动给客户端发消息,就要用到"消息推送"
这里就要约定前后端交互接口了,也都是基于websocket
来展开的,他可以传输文本数据,也能传输二进制数据,这里就设计成传输 json 格式的文本数据
7.1 前后端交互接口
连接(URL)
ws://127.0.0.1:8080/findMatch
匹配请求
message: 'startMatch' / 'stopMatch', // 开始/结束匹配
这里匹配是登录之后,已经拿到了用户信息,保存到了 HttpSession 中了
匹配响应1(这一个响应是发送请求后,服务器立即返回的匹配响应)
ok: true, // 是否成功. 比如用户 id 不存在, 则返回 false
reason: '', // 错误原因
message: 'startMatch' / 'stopMatch'
匹配响应2(匹配到了对手,服务器主动推送回消息,匹配到的对手不需要在响应中体现,仍然都放到了服务器这边)
ok: true, // 是否成功. 比如用户 id 不存在, 则返回 false
reason: '', // 错误原因
message: 'matchSuccess',
7.2 匹配功能前端开发
// 此处进行初始化 websocket, 并且实现前端的匹配逻辑.
// 此处的路径必须写作 /findMatch, 千万不要写作 /findMatch/
let websocketUrl = 'ws://' + location.host + '/findMatch';
let websocket = new WebSocket(websocketUrl);
websocket.onopen = function()
console.log("onopen");
websocket.onclose = function()
console.log("onclose");
websocket.onerror = function()
console.log("onerror");
// 监听页面关闭事件. 在页面关闭之前, 手动调用这里的 websocket 的 close 方法.
window.onbeforeunload = function()
websocket.close();
// 一会重点来实现, 要处理服务器返回的响应
websocket.onmessage = function(e)
// 处理服务器返回的响应数据. 这个响应就是针对 "开始匹配" / "结束匹配" 来对应的
// 解析得到的响应对象. 返回的数据是一个 JSON 字符串, 解析成 js 对象
let resp = JSON.parse(e.data);
let matchButton = document.querySelector('#match-button');
if (!resp.ok)
console.log("游戏大厅中接收到了失败响应! " + resp.reason);
return;
if (resp.message == 'startMatch')
// 开始匹配请求发送成功
console.log("进入匹配队列成功!");
matchButton.innerHTML = '匹配中...(点击停止)'
else if (resp.message == 'stopMatch')
// 结束匹配请求发送成功
console.log("离开匹配队列成功!");
matchButton.innerHTML = '开始匹配';
else if (resp.message == 'matchSuccess')
// 已经匹配到对手了.
console.log("匹配到对手! 进入游戏房间!");
// location.assign("/game_room.html");
location.replace("/game_room.html");
else if (resp.message == 'repeatConnection')
alert("当前检测到多开! 请使用其他账号登录!");
location.replace("/login.html");
else 以上是关于基于SpringBoot+MyBatis 五子棋双人对战的主要内容,如果未能解决你的问题,请参考以下文章
Springboot+mybatis双数据源(Druid和jdbc)