项目一:使用 Spring + SpringMVC + Mybatis + lombok 实现网络五子棋
一:系统展示:
二:约定前后端接口
2.1 登陆
登陆请求:
GET /login HTTP/1.1
Content-Type: application/x-www-form-urlencodedusername=zhangsan&password=123
登陆响应:
- 正常对象:正常对象会在数据库中存储,直接从数据库中取出即可,无需通过 set 方法进行设置
HTTP/1.1 200 OK
Content-Type: application/json{"userid": 1,"username": "zhangsan","password": "123","score": 1000,"totalcount": 0,"wincount": 0
}
- 登陆失败返回空对象
HTTP/1.1 200 OK
Content-Type: application/json{"userid": 0,"username": null,"score": 0,"totalcount": 0,"wincount": 0
}
2.2 注册
- 注册请求
GET /register HTTP/1.1
Content-Type: application/x-www-form-urlencodedusername=zhangsan&password=123
- 注册响应
HTTP/1.1 200 OK
Content-Type: application/json{"userid": 1,"username": "zhangsan","password": "123","score": 1000,"totalcount": 0,"wincount": 0
}
- 获取当前用户信息
GET /userinfo HTTP/1.1
Content-Type: application/json{"userid": 1,"username": "zhangsan","score": 1000,"totalcount": 0,"wincount": 0
}
2.2 匹配
- 匹配请求
ws://127.0.1:8080/Match{"message": "startMatch" / ”stopMatch“ //startMatch 代表开始匹配,stopMatch 代表停止匹配
}
请求不必带有用户的信息。因为在登陆成功后会把用户信息保存在 httpsession 中,websocket 可以拿到这个 httpseesion中存储的数据。
- 匹配响应 1
ws://127.0.0.1:8080/Match{"ok": true, // 进入成功为 true 匹配成功,否则为 false"reason": "失败原因", // 失败原因(若匹配失败则返回此字段)"message": "startMatch" / "stopMatch"/'matchSuccess'/'repeatConnection' //startMatch 代表开始匹配,stopMatch 代表停止匹配 ,matchSuccess 代表匹配成功 ,'repeatConnection 代表用户多开
}
2.3 对战
对战和匹配使用两套逻辑,使用不同的 websocket 路径处理,能够更好的解耦合
- 响应 1 gameready
{"message": "gameReady","ok": true, // 匹配成功为 true,否则为 false,false 代表有某些问题"reason": "", // 出错原因(若匹配失败则返回此字段)"roomid": "123456", // 房间 ID"thisuserid": 1, // 自己的用户 ID"thatuserid": 2, // 对手的用户 ID"whiteuser": 1 // 先手玩家 ID,1 表示自己先,2 表示对手先
}
这个请求是玩家匹配成功后,由服务器生成的内容,把这个内容返回给浏览器
- 下棋请求 1
{"message": "putChess","userid": 1,"row": 0,"col": 0
}
- 下棋响应
{"message": "putChess","userid": 1,"row": 0, // 行"col": 0, // 列"winner": 0 // 当前是否分出胜负,0 代表无胜负,非 0 代表获胜方用户 ID
}
三: websocket 前置知识
对于 http 来说,能够实现客户端向服务器发送数据,但是很难实现服务器向客户端发送数据,虽然可以通过轮转实现,但是这种实现太消耗cpu,性能也不好,而且实现起来也比较麻烦,所以我们选择使用 websocket 协议,websocket 协议能实现客户端和服务器的双向通信,符合我们目前的需求场景。
WebSocket 与 HTTP 的区别
- 持久连接:WebSocket 连接建立后会一直保持,直到显式关闭,减少了多次连接开销。
- 双向通信:WebSocket 支持全双工通信,即客户端和服务器可以在任意时间发送消息。
- 低延迟:WebSocket 的头部信息少,通信延迟低,非常适合实时性要求高的应用场景。
使用 WebSocket 的场景
- 实时聊天系统(如在线客服、聊天应用)
- 在线游戏(如棋类对战、竞技游戏)
- 实时数据推送(如股票行情、天气更新、体育比分)
3.1 websocket 连接流程
使用 WebSocket 连接的步骤:
- 后端配置 WebSocket 端点:定义 WebSocket 端点的 URL,客户端通过该 URL 连接到服务器。
- 实现 TextWebSocketHandler 类:处理 WebSocket 连接事件,包括连接建立、接收消息、连接关闭等。
- 前端建立 WebSocket 连接:前端使用 JavaScript 创建 WebSocket 连接,发送和接收消息。
步骤一:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(new ChatHandler(), "/chat"); // 将 /chat 端点与 ChatHandler 绑定}
}
- /chat:这是客户端连接的 WebSocket URL(端点)。
- ChatHandler:我们自定义的 WebSocket 处理器,用来处理 WebSocket 的事件。
步骤 2:实现 TextWebSocketHandler 类
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;public class ChatHandler extends TextWebSocketHandler {//TextWebSocketHandler 是 Spring 提供的一个辅助类,用于处理 WebSocket 的文本消息。// 1. 连接建立时调用@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {System.out.println("连接已建立:" + session.getId());}// 2. 接收到消息时调用@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {System.out.println("收到消息:" + message.getPayload());// 将收到的消息返回给客户端session.sendMessage(new TextMessage("服务器响应:" + message.getPayload()));}// 3. 连接关闭时调用@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {System.out.println("连接已关闭:" + session.getId());}// 4. 处理错误时调用@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {System.err.println("传输错误:" + exception.getMessage());}
}
- afterConnectionEstablished:当客户端成功连接时调用,比如用户打开 WebSocket
页面。这时候我们可以进行一些初始化操作。 - handleTextMessage:当服务器接收到客户端发送的消息时调用。这里我们简单地把接收到的消息再发送回客户端。
- afterConnectionClosed:当 WebSocket 连接关闭时调用,比如用户关闭页面或断开连接。我们可以在这里进行一些清理操作。
- handleTransportError:当连接出错时调用,例如网络故障。可以在这里记录错误日志或进行错误处理。
步骤三:前端实现
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>WebSocket 示例</title>
</head>
<body><h2>WebSocket 简单示例</h2><div><input type="text" id="messageInput" placeholder="输入消息"><button onclick="sendMessage()">发送消息</button></div><div id="response"></div><script>// 1. 创建 WebSocket 连接let websocket = new WebSocket("ws://" + location.host + "/chat");//建立连接// 2. 连接建立时调用websocket.onopen = function() {console.log("WebSocket 连接已建立");};// 3. 接收消息时调用websocket.onmessage = function(event) {console.log("收到消息:" + event.data);document.getElementById("response").innerText = "服务器:" + event.data;};// 4. 连接关闭时调用websocket.onclose = function() {console.log("WebSocket 连接已关闭");};// 5. 出错时调用websocket.onerror = function(error) {console.error("WebSocket 出现错误:" + error);};// 发送消息给服务器function sendMessage() {let message = document.getElementById("messageInput").value;websocket.send(message); // 发送消息}</script>
</body>
</html>
- new WebSocket(“ws://” + location.host + “/chat”):创建 WebSocket 连接到服务器 /chat 端点绑定的 WebSocket 处理器上。
- onopen:当连接成功时调用,可以在这里通知用户连接已建立。
- onmessage:当接收到服务器发送的消息时调用。我们将接收到的消息显示在页面上。
- onclose:当连接关闭时调用,可以在这里通知用户连接已关闭。
- onerror:当连接出错时调用。
通过这三个步骤就可以把特定的前端页面和特定的后端类进行连接并通信了
3.2 客户端和服务器互发数据
在 WebSocket 通信中,客户端和服务器都可以通过特定的方法发送数据。
3.2.1 客户端发送数据给服务器
在前端,客户端使用 WebSocket.send() 方法向服务器发送数据。
// 假设已经创建了 WebSocket 连接
let websocket = new WebSocket("ws://" + location.host + "/chat");// 定义一个函数,通过 WebSocket 向服务器发送消息
function sendMessage() {let message = document.getElementById("messageInput").value; // 获取输入框中的消息websocket.send(message); // 使用 send 方法发送消息给服务器console.log("发送消息给服务器:" + message);
}
3.2.2 服务器发送数据给客户端
在 Spring Boot 中,服务器使用 WebSocketSession.sendMessage() 方法向客户端发送数据。
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;public class ChatHandler extends TextWebSocketHandler {@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {System.out.println("收到客户端消息:" + message.getPayload());// 构造服务器的响应消息TextMessage responseMessage = new TextMessage("服务器响应:" + message.getPayload());// 发送响应消息给客户端session.sendMessage(responseMessage);System.out.println("发送消息给客户端:" + responseMessage.getPayload());}
}
数据通过 sendMessage 发送数据后,前端会调用 onmessage 方法
3.3 Json数据 Java对象 JS对象的互相转换
在 Web 开发中,我们经常需要在客户端(JavaScript)和服务器端(Java)之间传递数据,通常会使用 JSON 格式。以下是 JSON 字符串与 JavaScript 对象、Java 对象之间的互相转换方法。
在前端,前端需要 JS 对象,而在后端,后端需要 Java 对象,而他们传输的数据都是 Json 格式的数据,所以当我们传输数据的时候就涉及到了这三个对象的互相转换
- 前端发送数据:JavaScript 对象 → JSON 字符串 → 发送给后端。
- 后端接收数据:JSON 字符串 → Java 对象 → 处理。
- 后端返回数据:Java 对象 → JSON 字符串 → 发送给前端。
- 前端接收数据:JSON 字符串 → JavaScript 对象 → 处理。
3.3.1 JSON 字符串与 JavaScript 对象的转换
3.3.1.1 JavaScript 对象转为 JSON 字符串
使用 JSON.stringify() 方法将 JavaScript 对象转换为 JSON 字符串。
let jsObject = {name: "张三",age: 25,city: "北京"
};// 将 JavaScript 对象转换为 JSON 字符串
let jsonString = JSON.stringify(jsObject);
console.log(jsonString);
// 输出: {"name":"张三","age":25,"city":"北京"}
3.3.3.2 JSON 字符串转为 JavaScript 对象
使用 JSON.parse() 方法将 JSON 字符串转换为 JavaScript 对象。
let jsonString = '{"name":"张三","age":25,"city":"北京"}';// 将 JSON 字符串转换为 JavaScript 对象
let jsObject = JSON.parse(jsonString);
console.log(jsObject.name);
// 输出: 张三
3.3.2、JSON 字符串与 Java 对象的转换
3.3.2.1 引入 Jackson 依赖
如果使用 Maven 项目,在 pom.xml 中添加 Jackson 的依赖:
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.3</version>
</dependency>
3.2.2.2 JSON 字符串转为 Java 对象
使用 ObjectMapper 类的 readValue() 方法将 JSON 字符串转换为 Java 对象。
import com.fasterxml.jackson.databind.ObjectMapper;public class JsonExample {public static void main(String[] args) throws Exception {String jsonString = "{\"name\":\"张三\",\"age\":25,\"city\":\"北京\"}";// 创建 ObjectMapper 实例ObjectMapper objectMapper = new ObjectMapper();// 将 JSON 字符串转换为 Java 对象Person person = objectMapper.readValue(jsonString, Person.class);System.out.println(person.getName()); // 输出: 张三}
}class Person {private String name;private int age;private String city;// Getters and Setterspublic String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }public String getCity() { return city; }public void setCity(String city) { this.city = city; }
}
3.2.2.3 Java 对象转为 JSON 字符串
使用 ObjectMapper 类的 writeValueAsString() 方法将 Java 对象转换为 JSON 字符串。
import com.fasterxml.jackson.databind.ObjectMapper;public class JsonExample {public static void main(String[] args) throws Exception {// 创建一个 Java 对象Person person = new Person();person.setName("张三");person.setAge(25);person.setCity("北京");// 创建 ObjectMapper 实例ObjectMapper objectMapper = new ObjectMapper();// 将 Java 对象转换为 JSON 字符串String jsonString = objectMapper.writeValueAsString(person);System.out.println(jsonString); // 输出: {"name":"张三","age":25,"city":"北京"}}
}
四:会话的相关知识
会话创建和持续时间
- 当用户第一次访问服务器(例如第一次登录或访问某个页面)时,服务器会为用户创建一个新的会话,并生成一个唯一的会话 ID。
- 服务器会将这个会话 ID 发送给客户端。
- 在这个会话期间,用户的所有请求都会携带这个会话 ID,服务器通过这个 ID 识别请求来自同一用户,继续使用相同的会话对象。
会话过期或手动销毁
- 会话有一个过期时间(通常设置在服务器的配置中,例如 30 分钟或其他时间),如果用户在过期时间内没有任何活动(即没有请求发给服务器),会话将自动失效。
- 用户也可以通过主动“退出登录”来销毁会话,此时服务器会手动调用 session.invalidate() 方法,立即销毁会话,并清除所有会话中的数据。
再次登录会创建新会话
- 如果用户退出登录或会话过期,用户再进行登录时,服务器通常会创建一个新的会话。
- 新的会话会重新生成会话 ID,不再使用之前的 ID,因此之前的会话数据不会继续保留。
五:源代码
gittee 地址
相关文章:

项目一:使用 Spring + SpringMVC + Mybatis + lombok 实现网络五子棋
一:系统展示: 二:约定前后端接口 2.1 登陆 登陆请求: GET /login HTTP/1.1 Content-Type: application/x-www-form-urlencodedusernamezhangsan&password123登陆响应: 正常对象:正常对象会在数据库中存储&…...
openEuler 系统中 Samba 文件共享服务器管理(windows、linux文件共享操作方法)
一、Samba 简介 Samba 是在 Linux 和 Unix 系统上实现 SMB/CIFS 协议的一个免费软件,使得这些系统可以与 Windows 系统进行文件和打印机共享。通过 Samba,可以将 openEuler 系统配置为文件服务器,让 Windows、Linux 和其他支持 SMB/CIFS 协议…...

使用 Elasticsearch 进行语义搜索
Elasticsearch 是一款功能强大的开源搜索引擎,可用于全文搜索、分析和数据可视化。传统上,Elasticsearch 以其执行基于关键字/词汇的搜索的能力而闻名,其中文档基于精确或部分关键字匹配进行匹配。然而,Elasticsearch 已经发展到支…...

软考:中间件
中间件 中间件是一类位于操作系统软件与用户应用软件之间的计算机软件,它包括一组服务,以便于运行在一台或多台机器上的多个软件通过网络进行交互。 中间件的主要功能包括通信支持和应用支持。 通信支持为应用软件提供平台化的运行环境,屏蔽…...
银行家算法(Banker’s Algorithm)
银行家算法(Banker’s Algorithm)是计算机操作系统中一种避免死锁发生的著名算法。该算法由艾兹格迪杰斯特拉(Edsger Dijkstra)在1965年为T.H.E系统设计,其核心理念基于银行借贷系统的分配策略,以确保系统的…...
用魔数严谨的判别文件类型:杜绝上传风险
在文件处理和管理中,确定文件的类型是一个常见的需求。虽然文件扩展名可以提供一些信息,但并不总是可靠的。魔数(Magic Numbers)是一种更为准确的方法,通过检查文件开头的特定字节序列来识别文件类型。本文将介绍如何在…...

【MacOS实操】如何基于SSH连接远程linux服务器
MacOS上远程连接linux服务器,可以使用ssh命令pem秘钥文件连接。 一、准备pem秘钥文件 如果已经有pem文件,则跳过这一步。如果手上有ppk文件,那么需要先转换为pem文件。 macOS 的默认 SSH 客户端不支持 PPK 格式,你需要将 PPK 文…...
EXPLAIN 针对性优化 SQL 查询
在数据库管理和应用中,高效的 SQL 查询是确保系统性能的关键。随着数据量的不断增长和业务需求的日益复杂,优化 SQL 查询变得尤为重要。而 EXPLAIN 命令是一种强大的工具,可以帮助我们深入了解 SQL 查询的执行计划,从而进行有针对…...

MR30分布式IO:石化行业的智能化革新
在浩瀚的工业领域中,石化行业如同一座巨大的化工厂,将自然界的原始资源转化为人们日常生活中不可或缺的各种产品。然而,随着生产规模的扩大和工艺复杂度的提升,石化行业面临着前所未有的挑战:如何在保证生产效率的同时…...
linux图形化X窗口
【linux图形化协议框架】 X、X11系统:X协议,X服务器,窗口管理器,X客户端(客户端库Xcb,Xlib库等),输入、绘制 Wayland系统:Wayland 协议,合成器、客户端&#…...

练习LabVIEW第三十五题
学习目标: 刚学了LabVIEW,在网上找了些题,练习一下LabVIEW,有不对不好不足的地方欢迎指正! 第三十五题: 使用labview模拟一个3-8译码器 开始编写: 用LabVIEW做3-8译码器首先要知道它是个啥…...
Decision Tree Regressor (决策树) --- 论文实战
一、前言 在《机器学习论文复现实战---linear regression》中通过Pearson 相关性分析,去除了2个高相关性特征 "PN" 和 "AN" ,数据维度变为890*25。(数据集地址) 这里我们不做前期处理,直接就将数据放入 DecisionTreeRegressor 模型中进行训练了。 二…...

三层交换技术,eNSP实验讲解
三层交换技术,eNSP实验讲解 一、简要介绍1、概念2、工作原理3、优点4、应用场景5、与路由器的区别 二、eNSP仿真实验1、步骤一:创建连接,明确参数。2、步骤二:设置PC1和PC2参数3、步骤三:配置交换机,通过命…...

单链表OJ题(3):合并两个有序链表、链表分割、链表的回文结构
目录 一、合并两个有序链表 二、链表分割 三、链表的回文结构 u解题的总体思路: 合并两个有序链表:首先创建新链表的头节点(哨兵位:本质上是占位子),为了减少一些判断情况,简化操作。然后我们…...

研究了100个小绿书十万加之后,我们发现2024小绿书独家秘籍就是:在于“先抄后超,持续出摊,量大管饱”!
小绿书作为今年最大的红利,很多人已经吃到了螃蟹。看——: 今天我们总结了100个10万爆款,我们发现要在这个平台上脱颖而出,找到属于自己的方法尤为重要。在这里分享一个主题——小绿书的秘诀就是“先抄后超,持续出摊”…...
Java 中 HashMap集合使用
目录 一. HashMap概述 二. HashMap特点 三. HashMap构造方法 四. HashMap的常用方法 五. 使用注意事项 六. 代码示例 一. HashMap概述 HashMap 是 Java 中的一个非常重要的类,它实现了 Map 接口,用于存储键值对(key-value pairs&#…...

#渗透测试#SRC漏洞挖掘# 信息收集-Shodan进阶之Mongodb未授权访问
免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停…...

平台化运营公司如何在创业市场招商
在当今商业环境中,平台化运营的公司正成为推动经济发展的重要力量。对于这类公司而言,在创业市场招商意义重大。 平台化运营公司具有独特特点:通过搭建开放共享平台连接供需双方,实现资源优化配置与价值创造。比如电子商务平台、社…...

飞书API-获取tenant_access_token
1.在飞书工作台创建应用,跳到开发者后台,选创建企业自建应用 2.设置并发布应用 必须要发布应用才可以开始使用了!!! 3.调用获取token的API 参考链接: 开发文档 - 飞书开放平台https://open.feishu.cn/do…...

(新)docker desktop镜像迁移
背景 docker desktop默认安装在系统c盘,久而久之随着镜像拉取的越多,系统盘占用则越来越大。现有的网络资源关于docker desktop迁移都是旧版本的,即4.30版本之前。在4.30版本及以后,在运行wsl -l -v时只有docker-desktop只有这一项…...

JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving
地址:LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂,正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...
[特殊字符] 手撸 Redis 互斥锁那些坑
📖 手撸 Redis 互斥锁那些坑 最近搞业务遇到高并发下同一个 key 的互斥操作,想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁,也顺便跟 Redisson 的 RLock 机制对比了下,记录一波,别踩我踩过…...

【51单片机】4. 模块化编程与LCD1602Debug
1. 什么是模块化编程 传统编程会将所有函数放在main.c中,如果使用的模块多,一个文件内会有很多代码,不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数声明,其他.c文…...
CppCon 2015 学习:REFLECTION TECHNIQUES IN C++
关于 Reflection(反射) 这个概念,总结一下: Reflection(反射)是什么? 反射是对类型的自我检查能力(Introspection) 可以查看类的成员变量、成员函数等信息。反射允许枚…...