网络知识 娱乐 基于 SpringBoot + MyBatis 的在线五子棋对战

基于 SpringBoot + MyBatis 的在线五子棋对战

文章目录

  • 1. 项目设计
  • 2. 效果图展示
  • 3. 创建项目以及配置文件
    • 3.1 创建项目
    • 3.2 配置文件
      • 3.2.1 在 application.properties 中添加配置文件
      • 3.2.2 在 resources 目录下创建mapper
  • 4. 数据库设计与实现
  • 5. 登录注册模块
    • 5.1 设计登录注册交互接口
    • 5.2 设置登录注册功能返回的响应类
    • 5.3 使用 BCrypt 对密码进行加密
    • 5.4 完成 MyBatis 操作
    • 5.5 后端的实现
      • 5.5.1 登录功能后端实现
      • 5.5.2 注册功能后端实现
      • 5.5.3 注销功能
    • 5.6 前端的实现
      • 5.6.1 登录前端实现
      • 5.6.2 注册前端实现
    • 5.7 添加拦截器
  • 6. 大厅界面
    • 6.1 交互接口设计
    • 6.2 用户加载前后交互接口
    • 6.3 前端和后端实现用户信息加载
      • 6.3.1 后端的实现
      • 6.3.2 前端的实现
    • 6.4 实现匹配功能的前端代码
    • 6.5 实现匹配功能的后端代码
      • 6.5.1 创建在线状态
      • 6.5.2 创建房间对象
      • 6.5.3 创建房间管理器
      • 6.5.4 创建匹配队列
      • 6.5.5 写完 MatchController
    • 6.6 大厅界面总结
  • 7. 房间界面
    • 7.1 交互接口设计
    • 7.2 实现房间界面前端代码
      • 7.2.1 设置棋盘界面, 以及显示框.
      • 7.2.2 对应的js文件
      • 7.2.3 初始化 websocket
      • 7.2.4 落子时,发送落子请求
      • 7.2.5 落子时, 发送落子响应
    • 7.3 实现房间界面后端代码
      • 7.3.1 注册GameController
      • 7.3.2 创建GameController
      • 7.3.3 创建对应的响应类和请求类
      • 7.3.4 完成用户房间在线状态管理
      • 7.3.5 添加 MyBatis 用来更新玩家积分
      • 7.3.6 完成处理连接方法
      • 7.3.7 完成处理连接断开的方法和连接异常的方法
      • 7.3.8 在房间管理器中添加代码
      • 7.3.9 Room类添加棋盘代码
      • 7.3.10 实现handleTextMessage方法
      • 7.3.11 实现putChess方法
      • 7.3.12 完成用户胜负判断

1. 项目设计

前端 : HTML + CSS + JavaScript + Jquery + AJAX
后端 : Spring MVC + Spring Boot + MyBatis
在这里插入图片描述

2. 效果图展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. 创建项目以及配置文件

3.1 创建项目

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2 配置文件

3.2.1 在 application.properties 中添加配置文件

spring.datasource.url=jdbc:mysql://localhost:3306/onlineGobang?characterEncoding=utf8&useSSL=true
spring.datasource.username=root
spring.datasource.password=0000
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.mapper-locations=classpath:mapper/**Mapper.xml

3.2.2 在 resources 目录下创建mapper

mapper下添加 目录 **.xml 并添加代码





</mapper>

4. 数据库设计与实现

在这里插入图片描述
这里使用数据库存储每一个用户的信息, 初始的时候, 天梯分和场次都是默认的.

create database if not exists onlineGobang;

use onlineGobang;

drop table if exists user;
create table user(
    userId int primary key auto_increment,
    username varchar(20) unique,
    password varchar(255) not null,
    score int,
    totalCount int,
    winCount int
);

5. 登录注册模块

5.1 设计登录注册交互接口

登录功能

请求
POST /user/login HTTP/1.1

{username: "",password: ""}

响应
{
	status: 1/-1,
	message: "",
	data: ""
}

注销功能

请求
GET /user/logout HTTP/1.1

响应
HTTP/1.1 200

注册功能

请求
POST /user/register HTTP/1.1

{username: "",password: ""}

响应
{
	status: 1/-1,
	message: "",
	data: ""
}

5.2 设置登录注册功能返回的响应类

通过这个类, 方便前端接收内容

@Data
public class ResponseBodyMessage<T> {
    private int status;
    private String message;
    private T data;

    public ResponseBodyMessage(int status, String message, T data) {
        this.status = status;
        this.message = message;
        this.data = data;
    }
}

5.3 使用 BCrypt 对密码进行加密

在 pom.xml中添加依赖


		<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})

在 cofig 包下, 创建一个类 AppConfig.

@Configuration
public class AppConfig implements WebMvcConfigurer {

    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

5.4 完成 MyBatis 操作

在 model 包中, 创建 User 实体类

@Data
public class User {
    private int userId;
    private String username;
    private String password;
    private int score;
    private int totalCount;
    private int winCount;
}

在 mapper 包中, 创建 UserMapper 接口
这个接口中 主要是完成

  1. 注册, 插入一个用户
  2. 登录的时候, 通过名字查询当前用户是否存在.
@Mapper
public interface UserMapper {
    // 注册一个用户, 初始的天梯积分默认为1000, 场次默认为0
    int insert(User user);

    // 通过username查询当前用户是否存在
    User selectByName(String username);
}

在 resources 目录下, 创建一个目录 mapper, 在目录下创建 UserMapper.xml
在 UserMapper.xml 中写对应UserMapper接口中对应的操作



<mapper namespace="com.example.gobang.mapper.UserMapper">
    <insert id="insert">
        insert into user values(null,#{username},#{password},1000,0,0)
    </insert>

    <select id="selectByName" resultType="com.example.gobang.model.User">
        select * from user where username = #{username}
    </select>
</mapper>

创建 service 包, 在包下创建 UserService 类, 这个类调用 Mapper接口中的方法


@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public int insert(User user){
        return userMapper.insert(user);
    }

    public User selectByName(String username){
        return userMapper.selectByName(username);
    }
}

5.5 后端的实现

创建 controller包, 在包下创建一个 UserController 类
这个类是实现登录模块的功能的

  1. 这里需要注入 UserService, 调用数据库中的方法
  2. 还需要注入 BCryptPasswordEncoder, 对密码进行加密和比较

5.5.1 登录功能后端实现

注意这里的登录.

  1. 首先去数据库根据用户名查询是否存在当前用户.
  2. 如果不存在, 登录失败.
  3. 如果存在, 用输入的密码, 和数据库中的密码进行比较, 看是否相等. (注: 数据中的密码是加密的)
  4. 如果不相等, 登录失败.
  5. 如果相等, 创建 session, 并登录成功.
 @RequestMapping("/login")
    public ResponseBodyMessage<User> login(@RequestBody User user, HttpServletRequest request) {
        User truUser = userService.selectByName(user.getUsername());
        if (truUser == null) {
            System.out.println("登录失败!");
            return new ResponseBodyMessage<>(-1,"用户名密码错误!",user);
        }else {
            boolean flg = bCryptPasswordEncoder.matches(user.getPassword(),truUser.getPassword());
            if (!flg) {
                return new ResponseBodyMessage<>(-1,"用户名密码错误!",user);
            }
            System.out.println("登录成功!");
            HttpSession session = request.getSession(true);
            session.setAttribute(Constant.USER_SESSION_KEY,truUser);
            return new ResponseBodyMessage<>(1,"登录成功!",truUser);
        }
    }

5.5.2 注册功能后端实现

  1. 首先查看是否该用户是否存在
  2. 存在, 就注册失败
  3. 不存在, 就进行注册, 首先对当前密码进行加密.
  4. 加密之后对这个用户添加到数据库中.
    @RequestMapping("/register")
    public ResponseBodyMessage<User> register(@RequestBody User user) {
        User truUser = userService.selectByName(user.getUsername());
        if (truUser != null) {
            return new ResponseBodyMessage<>(-1,"当前用户名已经存在!",user);
        } else{
            String password = bCryptPasswordEncoder.encode(user.getPassword());
            user.setPassword(password);
            userService.insert(user);
            return new ResponseBodyMessage<>(1,"注册成功!",user);
        }
    }

5.5.3 注销功能

直接删除对应session 为 Constant.USER_SESSION_KEY, 然后跳转到login.html

@RequestMapping("/logout")
    public void userLogout(HttpServletRequest request, HttpServletResponse response) throws IOException, IOException {
        HttpSession session = request.getSession(false);
        // 拦截器的拦截, 所以不可能出现session为空的情况
        session.removeAttribute(Constant.USER_SESSION_KEY);
        response.sendRedirect("login.html");
    }

注意: 这里的Constant.USER_SESSION_KEY 是存储的 session 字符串, 由于该 字符串是不变的, 所以存入 Constant 类中.

5.6 前端的实现

5.6.1 登录前端实现

在这里插入图片描述

let loginButton = document.querySelector('#loginButton');
		loginButton.onclick = function() {
			let username = document.querySelector('#loginUsername');
			let password = document.querySelector('#loginPassword');
			if (username.value.trim() == ""){
                alert('请先输入用户名!');
                username.focus();
                return;
            }
            if (password.value.trim() == ""){
                alert('请先输入密码!');
                password.focus();
                return;
            }
            $.ajax({
                url: "user/login",
                method: "POST",
                data: JSON.stringify({username: username.value.trim(), password: password.value.trim()}),
                contentType: "application/json;charset=utf-8",
                success: function(data, status) {
                    if(data.status == 1) {
                        location.assign("index.html");
                    }else{
                        alert(data.message);
                        username.value="";
                        password.value="";
                        username.focus();
                    }
                }
			})
		}

5.6.2 注册前端实现

在这里插入图片描述


		let Reg = document.querySelector('#Reg');
		Reg.onclick = function() {
			let username = document.querySelector('#RegUsername');
			let password1 = document.querySelector('#RegPassword1');
			let password2 = document.querySelector('#RegPassword2');
			if(!$('#checkbox').is(':checked')) {
				alert("请勾选条款");
				return;
			}
			if(username.value.trim() == ""){
                alert("请先输入用户名!");
                username.focus();
                return;
            }
            if(password1.value.trim() == ""){
                alert('请先输入密码!');
                password1.focus();
                return;
            }
            if(password2.value.trim() == ""){
                alert('请再次输入密码!');
                password2.focus();
                return;
            }
			if(username.value.trim().length > 20) {
				alert("用户名长度过长");
				username.value="";
				username.focus();
				return;
			}
			if(password1.value.trim() != password2.value.trim()) {
                alert('两次输入的密码不同!');
                passwrod1.value="";
                password2.value="";
                return;
            }
			if(password1.value.trim().length > 255) {
				alert("当前密码长度过长!");
				password1.value="";
				password2.value="";
				password1.focus();
				return;
			}
			$.ajax({
                url: "user/register",
                method: "POST",
                data: JSON.stringify({username: username.value.trim(), password: password1.value.trim()}),
                contentType: "application/json;charset=utf-8",
                success: function(data,status){
                    if(data.status == 1) {
						alert(data.message);
						location.assign("login.html");
					}else{
						alert(data.message);
						username.value="";
                        password1.value="";
                        password2.value="";
                        username.focus();
					}
                }
            })
		}

5.7 添加拦截器

LoginIntercepter

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

@Configuration
@EnableWebSocket
public class AppConfig implements WebSocketConfigurer,WebMvcConfigurer{
	@Override
    public void addInterceptors(InterceptorRegistry registry) {
        LoginInterceptor loginInterceptor = new LoginInterceptor();
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/**/login.html")
                .excludePathPatterns("/**/css/**.css")
                .excludePathPatterns("/**/images/**")
                .excludePathPatterns("/**/fonts/**")
                .excludePathPatterns("/**/js/**.js")
                .excludePathPatterns("/**/scss/**")
                .excludePathPatterns("/**/user/login")
                .excludePathPatterns("/**/user/register")
                .excludePathPatterns("/**/user/logout");
    }
}

6. 大厅界面

6.1 交互接口设计

这里客户端1, 点击匹配发送消息给服务器, 客户端2, 也点击匹配发送消息给服务器, 当服务器收到两个人的请求之后, 就需要服务器主动向客户端发送消息, 这里就需要用到 websocket
在这里插入图片描述

URL: ws://127.0.0.1:8080/findMatch

匹配请求

{
	message: ' startMatch ' / ' stopMatch'
}

匹配响应1 这个响应是点击匹配之后, 立刻返回的响应

{
	status: '1' / '-1'  
	message: ' startMatch ' / ' stopMatch '
}

匹配响应2 这个响应是匹配成功之后的响应

{
	status: '1' / '-1'
	message: 'matchSuccess'
}

6.2 用户加载前后交互接口

请求
GET /user/userInfo HTTP/1.1

响应
{
	status: 1/-1 (1 为成功, -1 为失败),
	message: "对应信息",
	data: "内容",  (用户信息)
}

6.3 前端和后端实现用户信息加载

6.3.1 后端的实现

根据当前存储的session对象, 来查找对应的用户

    @RequestMapping("/userInfo")
    public ResponseBodyMessage<User> getUserInfo(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        User user = (User) session.getAttribute(Constant.USER_SESSION_KEY);
        if (user == null) {
            return new ResponseBodyMessage<>(-1,"当前用户不存在",null);
        }else{
            return new ResponseBodyMessage<>(1,"查找成功!", newUser);
        }
    }

6.3.2 前端的实现

在这里插入图片描述

      load()