接上次博客:初阶JavaEE(13)(安装、配置:Smart Tomcat;访问出错怎么办?Servlet初识、调试、运行;HttpServlet:HttpServlet;HttpServletResponse)-CSDN博客
目录
表白墙程序
“表白墙”的前后端交互:
提交信息接口:
获取信息接口:
我们先来编写提交消息的代码:
当前我们已经把数据提交到服务器保存了,但是我们还需要能够把服务器的数据拿回到客户端页面并显示。
数据存入数据库
1、在 pom.xml 中引入 mysql 的依赖:
2、建库建表:
3、编写数据库代码:
表白墙程序
我们可以先来一个最简单的网站,只有一个页面——“表白墙”。
类似这样:
网站 = 前端+前后端交互+后端
这个“表白墙 ”的前端部分直接提供给大家,我们现在主要是关注后面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表白墙</title>
<style>
/* * 通配符选择器, 是选中页面所有元素 */
* {
/* 消除浏览器的默认样式. */
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 600px;
margin: 20px auto;
}
h1 {
text-align: center;
}
p {
text-align: center;
color: #666;
margin: 20px 0;
}
.row {
/* 开启弹性布局 */
display: flex;
height: 40px;
/* 水平方向居中 */
justify-content: center;
/* 垂直方向居中 */
align-items: center;
}
.row span {
width: 80px;
}
.row input {
width: 200px;
height: 30px;
}
.row button {
width: 280px;
height: 30px;
color: white;
background-color: orange;
/* 去掉边框 */
border: none;
border-radius: 5px;
}
/* 点击的时候有个反馈 */
.row button:active {
background-color: grey;
}
</style>
</head>
<body>
<div class="container">
<h1>表白墙</h1>
<p>输入内容后点击提交, 信息会显示到下方表格中</p>
<div class="row">
<span>谁: </span>
<input type="text">
</div>
<div class="row">
<span>对谁: </span>
<input type="text">
</div>
<div class="row">
<span>说: </span>
<input type="text">
</div>
<div class="row">
<button id="submit">提交</button>
</div>
<!-- <div class="row">
xxx 对 xx 说 xxxx
</div> -->
</div>
<script>
// 实现提交操作. 点击提交按钮, 就能够把用户输入的内容提交到页面上显示.
// 点击的时候, 获取到三个输入框中的文本内容
// 创建一个新的 div.row 把内容构造到这个 div 中即可.
let containerDiv = document.querySelector('.container');
let inputs = document.querySelectorAll('input');
let button = document.querySelector('#submit');
button.onclick = function() {
// 1. 获取到三个输入框的内容
let from = inputs[0].value;
let to = inputs[1].value;
let msg = inputs[2].value;
if (from == '' || to == '' || msg == '') {
return;
}
// 2. 构造新 div
let rowDiv = document.createElement('div');
rowDiv.className = 'row message';
rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
containerDiv.appendChild(rowDiv);
// 3. 清空之前的输入框内容
for (let input of inputs) {
input.value = '';
}
}
</script>
</body>
</html>
我们现在简单分析一下这段代码:
这段HTML代码定义了一个表白墙页面,其中包含一些HTML元素和与之相关的JavaScript代码。以下是对代码的分析:
-
HTML文档的基本结构:页面中的HTML,包括
<!DOCTYPE>
声明、<html>
根元素、<head>
头部和<body>
主体部分。 -
在
<head>
部分,包括了一些<meta>
标签用于设置文档的字符编码和视口配置。还包括了页面标题("表白墙")以及内联的CSS样式定义。 -
在
<style>
标签内,定义了一些CSS规则,用于样式化页面元素。这些规则包括:- 消除浏览器默认样式。
- 定义一个容器(
.container
)的宽度和居中对齐。 - 设置标题(
h1
)和提示文字(p
)的文本对齐、颜色和间距。 - 定义了具有弹性布局的行(
.row
),水平和垂直居中,以及子元素(span
、input
、button
)的样式,包括宽度、高度和颜色。 - 设置了按钮的点击反馈效果。
-
<style> /* * 通配符选择器, 是选中页面所有元素 */ * { /* 消除浏览器的默认样式. */ margin: 0; padding: 0; box-sizing: border-box; } .container { width: 600px; margin: 20px auto; } h1 { text-align: center; } p { text-align: center; color: #666; margin: 20px 0; } .row { /* 开启弹性布局 */ display: flex; height: 40px; /* 水平方向居中 */ justify-content: center; /* 垂直方向居中 */ align-items: center; } .row span { width: 80px; } .row input { width: 200px; height: 30px; } .row button { width: 280px; height: 30px; color: white; background-color: orange; /* 去掉边框 */ border: none; border-radius: 5px; } /* 点击的时候有个反馈 */ .row button:active { background-color: grey; } </style>
-
在
<body>
部分,创建了一个容器(.container,选择器,里面是样式属性
),其中包括:- 一个标题(
<h1>,标签
)。 - 一段提示文字(
<p>,标签
)。 - 四个输入行(
.row,类选择器
):分别用于输入谁(from)、对谁(to)、说什么(message)。 - 一个提交按钮(
<button>
)。 - CSS中选择器的种类非常多,目的就是选中某个/某些标签,后面的{ }就是在描述这个标签应该有的样式。上面的CSS代码包含了多个CSS选择器和相应的样式规则,用于定义页面中不同元素的外观和排版。以下是对其中的一些选择器和规则的解释:
-
通配符选择器 (
*
):*
选择器用于选择页面中的所有元素。- 在这个例子中,通配符选择器被用来消除浏览器默认样式,将页面中所有元素的
margin
、padding
和box-sizing
属性设置为特定的值。
-
类选择器 (
.container
,.row
):- 类选择器以
.
开头,用于选择具有特定类名的元素。 - 例如,
.container
选择器选择所有具有container
类的元素,.row
选择器选择所有具有row
类的元素。 - 这些选择器用于样式化容器和行的元素,以设置宽度、居中等样式属性。
- 类选择器以
-
元素选择器 (
h1
,p
):- 元素选择器用于选择特定类型的HTML元素,如
h1
和p
。 - 例如,
h1
选择器选择所有<h1>
标题元素,p
选择器选择所有<p>
段落元素。 - 这些选择器用于设置文本对齐、颜色等样式属性。
- 元素选择器用于选择特定类型的HTML元素,如
-
后代选择器 (
.row span
,.row input
):- 后代选择器选择元素的后代元素,即在特定元素内部的元素。
- 例如,
.row span
选择器选择.row
类下的<span>
元素,.row input
选择器选择.row
类下的<input>
元素。 - 这些选择器用于设置特定元素内部的样式属性。
-
伪类选择器 (
.row button:active
):- 伪类选择器用于选择元素的特定状态或条件。
- 例如,
.row button:active
选择器选择.row
类下的<button>
元素在被激活(鼠标点击)时的状态。 - 这些选择器用于为元素的不同状态定义样式。
- 里面的
.row { /* 开启弹性布局 */ display: flex; height: 40px; /* 水平方向居中 */ justify-content: center; /* 垂直方向居中 */ align-items: center; }
对应调用:
<body> <div class="container"> <h1>表白墙</h1> <p>输入内容后点击提交, 信息会显示到下方表格中</p> <div class="row"> <span>谁: </span> <input type="text"> </div> <div class="row"> <span>对谁: </span> <input type="text"> </div> <div class="row"> <span>说: </span> <input type="text"> </div> <div class="row"> <button id="submit">提交</button> </div> <!-- <div class="row"> xxx 对 xx 说 xxxx </div> --> </div>
- 一个标题(
-
在
<script>
标签中包含了JavaScript代码,这个代码就是进行前端编写逻辑的核心部分。HTML和CSS都属于描述性的语言,能够描述一个界面上有什么东西,长什么样子,但是并不表示逻辑。JS才能描述逻辑,通过这些逻辑我们主要做两件事:1、网页和用户的交互;2、网页和后端服务器的交互。该代码实现了以下功能:<script> // 实现提交操作. 点击提交按钮, 就能够把用户输入的内容提交到页面上显示. // 点击的时候, 获取到三个输入框中的文本内容 // 创建一个新的 div.row 把内容构造到这个 div 中即可. let containerDiv = document.querySelector('.container'); let inputs = document.querySelectorAll('input'); let button = document.querySelector('#submit'); button.onclick = function() { // 1. 获取到三个输入框的内容 let from = inputs[0].value; let to = inputs[1].value; let msg = inputs[2].value; if (from == '' || to == '' || msg == '') { return; } // 2. 构造新 div let rowDiv = document.createElement('div'); rowDiv.className = 'row message'; rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg; containerDiv.appendChild(rowDiv); // 3. 清空之前的输入框内容 for (let input of inputs) { input.value = ''; } } let revertButton = document.querySelector('#revert'); revertButton.onclick = function() { // 删除最后一条消息. // 选中所有的 row, 找出最后一个 row, 然后进行删除 let rows = document.querySelectorAll('.message'); if (rows == null || rows.length == 0) { return; } containerDiv.removeChild(rows[rows.length - 1]); } </script>
let containerDiv = document.querySelector('.container'); let inputs = document.querySelectorAll('input'); let button = document.querySelector('#submit');
- JS使用let关键字定义变量,不需要写类型,变量的类型是由后面的初始化的表达式来确定的。document是浏览器提供的全局变量。浏览器给JS提供了很多API,其中一部分API就是通过document全局变量提供的。上述代码后面的query Selector就是一个很重要的API,选中指定的HTML元素,参数就是CSS选择器,通过选择器定位到元素。后面的querySelectorAll表示选中所有的imput标签,获取到标签目的是得到输入框用户里写的内容。后面的submit表示选中按钮。
- 当用户点击"提交"按钮(
button
)时,获取三个输入框的文本内容,并构造一个新的div.row
元素,将内容显示在这个div
中。 - 如果输入框中的任何一个内容为空,提交操作不会执行。
- 在构造新消息后,清空输入框的内容。
-
总的来说,开头这几行代码是要选中页面的元素,具体是怎么选中的?API是什么意思?选择器是什么意思?……这些其实对于我们现在都不重要。我们选中它们就是后续要操作元素:一个是“获取元素中的内容”,比如对于input输入框就可以拿到用户输入的字符串。还有就是“修改元素中的内容”。
-
button.onclick = function() { // 1. 获取到三个输入框的内容 let from = inputs[0].value; let to = inputs[1].value; let msg = inputs[2].value; if (from == '' || to == '' || msg == '') { return; } // 2. 构造新 div let rowDiv = document.createElement('div'); rowDiv.className = 'row message'; rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg; containerDiv.appendChild(rowDiv); // 3. 清空之前的输入框内容 for (let input of inputs) { input.value = ''; } }
给按钮注册一个点击事件的回调函数,点击按钮就会执行这个函数。里面value拿到输入框的内容,后面点击之后能够显示出内容,就是需要在页面中创建出新的div。
总的来说,这段HTML代码创建了一个简单的页面,允许用户输入表白信息,单击提交按钮后将这些信息显示在页面上。
通过上述介绍,你只需要知道:
-
前端的构成:
- 前端开发通常包括HTML(页面的结构)、CSS(页面的样式)和JavaScript(页面的交互)。
- HTML用于定义页面的结构,如标题、段落、表格等。
- CSS用于样式化页面,包括颜色、字体、布局等。
- JavaScript用于处理页面的交互,例如用户输入、按钮点击等。
-
JavaScript交互:
- JavaScript在前端开发中用于添加交互性。它可以找到HTML元素(DOM元素)并通过API操作这些元素的属性和内容。
- 操作DOM元素包括获取元素、修改元素的文本内容、样式、添加事件处理程序等。
-
基本编程概念:
- 前端开发涉及到基本的编程概念,如变量定义、条件语句、循环、表达式、运算符和函数。
- 这些概念是通用的,可以应用于JavaScript以及其他编程语言。
虽然前端开发可能更加复杂,但这些基本概念和流程是入门前端开发的重要基础。理解这些概念可以帮助我们更好地理解前端代码和进行更高级的开发工作,并且为我们后续的重点做一个理解的准备。
虽然这里看起来好像是已经构建成功了,但是现在这里的数据都是在浏览器的内存中保存到,刷新/关闭页面就没了!我们希望这里的数据是可以长期存在的。
而且我们希望不同的浏览器/页面都可以看到同一个数据。
此处服务器要实现的功能主要是两个页面:
1、页面加载的时候,网页要从服务器这里获取到表白数据(让网页端给服务器发起HTTP请求,服务器返回响应里就带着刚才的数据)
2、点击提交的时候网页就要把用户输入的信息发送到服务器这边,让服务器负责保存。
所以,在一个网站中,服务器起到的最主要的效果往往就是“存储数据”。
当然,光“存储”还不够,服务器这边也就需要能够提供两种风格的接口,存数据和取数据。
服务器端应用程序需要包括以下关键部分:
-
路由:定义用于处理客户端请求的不同端点(URL)和HTTP请求方法(GET、POST等)的路由。例如,一个路由可以用于获取表白数据,另一个用于接受用户提交的表白信息。
-
数据库:用于存储表白数据的数据库。服务器应该能够将用户提交的数据存储在数据库中,并能够从数据库中检索数据以供客户端获取。
-
API:服务器应提供API端点,使客户端能够通过HTTP请求与服务器进行通信。例如,通过GET请求获取数据,通过POST请求将新数据发送到服务器。API应负责处理这些请求,执行适当的数据库操作,并返回响应。
-
前后端通信:客户端需要使用JavaScript通过HTTP请求与服务器进行通信。客户端代码将发送请求到服务器的API端点,接收服务器的响应,并根据响应更新页面。
-
持久化存储:服务器端应确保数据在服务器上进行持久化存储,以便不同的客户端和页面可以访问和共享相同的数据。
“表白墙”的前后端交互:
使用服务器目的是为了能够在服务器这边存储用户提交的信息,并能够把信息获取下来,所以服务器这边就需要给网页提供两个HTTP接口:
提交信息接口:
客户端请求:
- 当用户点击提交按钮时,客户端会触发一个Ajax POST请求,将用户输入的信息提交给服务器。
请求示例:
POST /message
请求数据示例:
{
"from": "张三",
"to": "李四",
"message": "我喜欢你!!!"
}
服务器响应:
- 服务器接收到请求后,会返回一个HTTP响应,状态码为200 OK,并使用Content-Type标头指定响应数据为JSON格式。
获取信息接口:
客户端请求:
- 当网页加载时,客户端会发起一个Ajax GET请求到服务器,请求获取已提交的信息。
请求示例:
GET /message
服务器响应:
- 服务器接收到请求后,会返回一个HTTP响应,状态码为200 OK,并使用Content-Type标头指定响应数据为JSON格式。
响应示例:
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"from": "张三",
"to": "李四",
"message": "我喜欢你!!!"
},
{
"from": "小米",
"to": "旺仔",
"message": "我喜欢你!!!"
},
{
"from": "虹猫",
"to": "蓝兔",
"message": "我喜欢你!!!"
},
// ...
]
这两个接口允许用户从客户端向服务器提交表白信息,同时也允许客户端获取已提交的信息,从而实现"表白墙"功能。
这些HTTP接口的具体设计可以根据需求进行自定义,但一旦确定了接口的设计,它将成为前后端交互的约定。前端需要编写代码来构造HTTP请求并解析HTTP响应,而后端需要编写代码来解析HTTP请求并构造HTTP响应。这个过程就涉及到自定义应用层协议的设计。
虽然具体的前后端交互数据格式可以有多种设计方式,但在开始编写代码之前,确保明确接口设计方式是非常重要的。这将成为后续开发的基础。
编写前端代码:构造HTTP请求、解析HTTP响应;
编写后端代码:解析HTTP请求、构造HTTP响应。
这个过程就是自定义应用层协议。
虽然说这里的具体的前后端交互数据的格式怎么设计都行,存在无数种设计方式,但是我们现在还是来明确一下具体的设计方式吧……
既然我们现在已经约定好了前后端交互的接口,接下来我们就可以开始编写代码了:
(1)先写前端代码、发送请求,提交消息;
(2)再写后端代码,解析请求,构造响应;
(3)最后再写前端代码,解析响应;
我们先来编写提交消息的代码:
我们创建一个新的项目,引入依赖、创建目录,然后把刚才的网页放到webapp目录里面:
创建一个Tomcat来看看:
于是此处我们就通过Tomcat网络的形式把这个HTML加载出来了:
接下来就是编写我们的前端代码、发送HTTP POST请求:
你需要打开原网页编写:
我们通过VSCode打开:
关于VSCode,它的下载安装视频都可以在b站上找到,它的国内下载速度贼慢,我通过一个博主的视频知道了一种快速下载的方法,你可以看看这个视频一起学习一下:
解决vscode国内下载速度过慢问题_哔哩哔哩_bilibili
下载完成之后,安装视频也顺便看看,你也可以安装一些插件以获得更好的使用体验:
说回来,打开:
首先我们需要引入一个Jquery ,这样我们才能够使用Ajax发起请求:
https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js
前端引入第三方库,往往就是通过script标签写一个地址即可:
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
这个代码在点击按钮的回调函数中,会在点击按钮之后被调用到:
// 4. 把用户填写的内容, 发送给服务器. 让服务器来保存.
// $ 是 jquery 提供的全局变量. ajax 就是 $ 的一个方法.
// ajax 的参数是一个 js 对象, 可以有很多属性
let body = {
"from": from, // from 变量里的值, 就是第一个输入框的内容, "张三"
"to": to, // to 变量的值, 就是第二个输入框的内容, "李四"
"message": msg // msg 变量的值, 就是第三个输入框的内容, "我喜欢你很久了"
};
// 上述 body 是一个 js 对象, 还需要转成 json 字符串.
let jsonString = JSON.stringify(body);
$.ajax({
type: 'post',
url: 'message',
contentType: 'application/json; charset=utf8',
data: jsonString,
success: function(body) {
// 这个回调就是收到响应之后要执行的代码了.
}
});
}
JSON.stringify() 是 JavaScript 中的一个方法,用于将 JavaScript 对象或值转换为 JSON 格式的字符串。这个方法接受一个对象或值作为参数,并返回一个包含 JSON 字符串表示的对象的方法。
例如,如果你有一个 JavaScript 对象:
const person = {
name: "John",
age: 30,
city: "New York"
};
可以使用 JSON.stringify() 将它转换为 JSON 字符串:
const jsonString = JSON.stringify(person);
jsonString 现在包含以下 JSON 字符串:
{"name":"John","age":30,"city":"New York"}
这个方法非常有用,因为它允许我们在不同的应用程序和平台之间轻松传递和解析数据。在前后端通信中,通常会使用 JSON 格式来交换数据,而 JSON.stringify() 可以将对象转换为 JSON 字符串,以便在 HTTP 请求中发送给服务器或在前端和后端之间传递数据。
注意,JSON.stringify() 方法还可以接受第二个参数,用于指定要包含在生成的 JSON 字符串中的属性或过滤函数,以及缩进选项,以使生成的 JSON 字符串更易于阅读。
还有一点:
$.ajax({
type: 'post',
url: 'message',
contentType: 'application/json; charset=utf8',
data: jsonString,
success: function(body) {
// 这个回调就是收到响应之后要执行的代码了.
}
前端Ajax请求的URL路径,写作“message”,前面没加“/”,此时这是一个相对路径的写法。
后端处理Ajax请求,URL路径写作“/message”,前面是要加“/”的,此时是Servlet要求的写法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表白墙</title>
<style>
/* * 通配符选择器, 是选中页面所有元素 */
* {
/* 消除浏览器的默认样式. */
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 600px;
margin: 20px auto;
}
h1 {
text-align: center;
}
p {
text-align: center;
color: #666;
margin: 20px 0;
}
.row {
/* 开启弹性布局 */
display: flex;
height: 40px;
/* 水平方向居中 */
justify-content: center;
/* 垂直方向居中 */
align-items: center;
}
.row span {
width: 80px;
}
.row input {
width: 200px;
height: 30px;
}
.row button {
width: 280px;
height: 30px;
color: white;
background-color: orange;
/* 去掉边框 */
border: none;
border-radius: 5px;
}
/* 点击的时候有个反馈 */
.row button:active {
background-color: grey;
}
</style>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<body>
<div class="container">
<h1>表白墙</h1>
<p>输入内容后点击提交, 信息会显示到下方表格中</p>
<div class="row">
<span>谁: </span>
<input type="text">
</div>
<div class="row">
<span>对谁: </span>
<input type="text">
</div>
<div class="row">
<span>说: </span>
<input type="text">
</div>
<div class="row">
<button id="submit">提交</button>
</div>
<!-- <div class="row">
xxx 对 xx 说 xxxx
</div> -->
</div>
<script>
// 实现提交操作. 点击提交按钮, 就能够把用户输入的内容提交到页面上显示.
// 点击的时候, 获取到三个输入框中的文本内容
// 创建一个新的 div.row 把内容构造到这个 div 中即可.
let containerDiv = document.querySelector('.container');
let inputs = document.querySelectorAll('input');
let button = document.querySelector('#submit');
button.onclick = function() {
// 1. 获取到三个输入框的内容
let from = inputs[0].value;
let to = inputs[1].value;
let msg = inputs[2].value;
if (from == '' || to == '' || msg == '') {
return;
}
// 2. 构造新 div
let rowDiv = document.createElement('div');
rowDiv.className = 'row message';
rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
containerDiv.appendChild(rowDiv);
// 3. 清空之前的输入框内容
for (let input of inputs) {
input.value = '';
}
// 4. 把用户填写的内容, 发送给服务器. 让服务器来保存.
// $ 是 jquery 提供的全局变量. ajax 就是 $ 的一个方法.
// ajax 的参数是一个 js 对象, 可以有很多属性
let body = {
"from": from, // from 变量里的值, 就是第一个输入框的内容, "张三"
"to": to, // to 变量的值, 就是第二个输入框的内容, "李四"
"message": msg // msg 变量的值, 就是第三个输入框的内容, "我喜欢你很久了"
};
// 上述 body 是一个 js 对象, 还需要转成 json 字符串.
let jsonString = JSON.stringify(body);
$.ajax({
type: 'post',
url: 'message',
contentType: 'application/json; charset=utf8',
data: jsonString,
success: function(body) {
// 这个回调就是收到响应之后要执行的代码了.
}
});
}
</script>
</body>
</html>
通过以上代码,我们就可以构造并且发送一个请求。
服务器读取上述请求,并计算响应:
此时我们需要使用Jackson读取前端的数据:
//读取前端发来的数据,把这个数据保存到服务器这边.
resp.setContentType("application/json;charset=utf-8");
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
既然如此,关于第二个参数也就是我们想要转化成的类,还需要构造一下:
public class Message {
public String from;
public String to;
public String message;
}
注意,这里定义的Java这个类里面属性的名称要和我们之前约定好的json前端代码属性的名字保存一致:
package org.example;
public class Message {
public String from;
public String to;
public String message;
@Override
public String toString() {
return "Message{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", message='" + message + '\'' +
'}';
}
}
package org.example;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
// 用于保存所有的留言
private List<Message> messageList = new ArrayList<Message>();
// 用于转换 JSON 字符串
private ObjectMapper objectMapper = new ObjectMapper();
// 新增留言
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
//读取前端发来的数据,把这个数据保存到服务器这边.
resp.setContentType("application/json;charset=utf-8");
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
System.out.println("请求中收到的message"+message);
// 最简单的办法, 直接在内存中保存. 我们可以使用一个集合类, 把所有收到的 message 都存到内存中.
messageList.add(message);
// 很明显, 保存到内存, 并非是一个非常合理的办法. 后续一旦重启服务器, 数据丢失了.
// 相比之下, 把这个数据持久化存储到数据库中, 更科学的.
resp.setStatus(200);
resp.getWriter().write( "ok");
}
}
回到前端代码,处理服务器返回的响应。
此处success回调函数不是立即执行的,而是在浏览器收到服务器返回的“成功”这样的响应之后才会执行到function。
这个函数就是来执行响应的,并且这个函数的第一个参数就是响应中body中的内容。
注意区分:
算了,我们直接改一下名字来区分:
// 4. 把用户填写的内容, 发送给服务器. 让服务器来保存.
// $ 是 jquery 提供的全局变量. ajax 就是 $ 的一个方法.
// ajax 的参数是一个 js 对象, 可以有很多属性
let requestbody = {
"from": from, // from 变量里的值, 就是第一个输入框的内容, "张三"
"to": to, // to 变量的值, 就是第二个输入框的内容, "李四"
"message": msg // msg 变量的值, 就是第三个输入框的内容, "我喜欢你很久了"
};
// 上述 body 是一个 js 对象, 还需要转成 json 字符串.
let jsonString = JSON.stringify(requestbody);
$.ajax({
type: 'post',
url: 'message',
contentType: 'application/json; charset=utf8',
data: jsonString,
success: function(responsebody) {
// 这个回调就是收到响应之后要执行的代码了.
}
});
此时,由于我们刚刚返回的响应是“ok”,此处的body就是“ok”。
其实一般来说,为了和请求对应上,服务器返回的数据也会按照json格式来组织。
resp.setStatus(200);
resp.setContentType("application/json");
resp.getWriter().write("{ \"ok\": true }");
现在我们先简单返回一个“ok”。
什么是开发者工具的控制台?
我们随便打开一个页面:
这样就写好了:
<script>
// 实现提交操作. 点击提交按钮, 就能够把用户输入的内容提交到页面上显示.
// 点击的时候, 获取到三个输入框中的文本内容
// 创建一个新的 div.row 把内容构造到这个 div 中即可.
let containerDiv = document.querySelector('.container');
let inputs = document.querySelectorAll('input');
let button = document.querySelector('#submit');
button.onclick = function() {
// 1. 获取到三个输入框的内容
let from = inputs[0].value;
let to = inputs[1].value;
let msg = inputs[2].value;
if (from == '' || to == '' || msg == '') {
return;
}
// 2. 构造新 div
let rowDiv = document.createElement('div');
rowDiv.className = 'row message';
rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
containerDiv.appendChild(rowDiv);
// 3. 清空之前的输入框内容
for (let input of inputs) {
input.value = '';
}
// 4. 把用户填写的内容, 发送给服务器. 让服务器来保存.
// $ 是 jquery 提供的全局变量. ajax 就是 $ 的一个方法.
// ajax 的参数是一个 js 对象, 可以有很多属性
let requestbody = {
"from": from, // from 变量里的值, 就是第一个输入框的内容, "张三"
"to": to, // to 变量的值, 就是第二个输入框的内容, "李四"
"message": msg // msg 变量的值, 就是第三个输入框的内容, "我喜欢你很久了"
};
// 上述 body 是一个 js 对象, 还需要转成 json 字符串.
let jsonString = JSON.stringify(requestbody);
$.ajax({
type: 'post',
url: 'message',
contentType: 'application/json; charset=utf8',
data: jsonString,
success: function(responsebody) {
// 这个回调就是收到响应之后要执行的代码了.
// 前端使用 console.log 打印日志到控制台. (chrome 开发者工具的控制台)
console.log("responseBody: " + responseBody);
}
});
}
//直接在script里面写的JS代码,就是在页面加载时被执行到的
//发起一个get请求,从服务器获取到数据
//get请求不需要body
</script>
我们现在就来运行一下看看效果:
注意,这里不可以直接在浏览器里面访问message这个路径,因为message这个路径当前是按照post请求来处理的,得通过Ajax的方式触发。
现在用户还没有点击按钮呢,你就发起请求了。
这边服务器也收到了响应的请求:
与此同时,浏览器也收到了响应 :
通过抓包也可以清楚的看到:
当前我们已经把数据提交到服务器保存了,但是我们还需要能够把服务器的数据拿回到客户端页面并显示。
等会儿!我们刚刚都已经可以从页面上直接读到消息了,为啥还要从服务器拿数据?
-
持久性:页面上显示的数据通常是在浏览器内存中保存的,刷新页面或重启浏览器后,这些数据会丢失。通过从服务器获取数据,可以保持数据的持久性,确保用户数据不会丢失。
-
共享数据:如果多个客户端打开同一页面,它们可以共享服务器上的数据。这意味着多个用户可以查看相同的数据,而不是每个用户都有自己的本地副本。
所以我们就需要客户端在页面加载的时候就发起这个GET请求。
前端script标签里面放的代码都是页面加载的时候会执行的代码,所以我们把它放到最外层就好:
(1)客户端在页面加载的时候发起一个HTTP请求:
// 直接在 script 里面写的 js 代码, 就是在页面加载时被执行到的.
// 发起一个 get 请求, 从服务器获取到数据
// get 请求不需要 body, 也就不需要上述 data 和 contentType 属性了.
$.ajax({
type: 'get',
url: 'message',
success: function(body) {
// 处理服务器返回的响应数据. (json 格式的数组了)
}
});
(2) 服务器收到这个请求,要处理这个请求并生成响应。
我们需要写一个对应的doGet方法:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法来处理当前获取消息列表的 get 请求. 不需要解析参数, 直接返回数据即可.
resp.setStatus(200);
resp.setContentType("application/json; charset=utf8");
String respJson =;
resp.getWriter().write(respJson);
}
这只是我们的直观想法,但是现实中的情况比我们设想的简单多了!
Java里面的Jackson本身就支持把List类型的数据转成json数组。
Jackson看到了MessageList是一个 List<Message> 类型的数据,Jackson 库会自动将其转换为 JSON 数组,其中每个 Message 对象都会被转换为一个 JSON 对象。所以Jackson自动便遍历List里面的每一个元素,把每个元素分别转成 json字符串。
于是乎,这几行代码就搞定了这件事情。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法来处理当前获取消息列表的 get 请求. 不需要解析参数, 直接返回数据即可.
resp.setStatus(200);
resp.setContentType("application/json; charset=utf8");
String respJson =objectMapper.writeValueAsString(messageList);
resp.getWriter().write(respJson);
}
但是!一定要确保这几个代码的执行顺序!!!
setStatus和setContentType必须在getWriter前面,否则就不可能生效,还会构造出一个非法的http响应报文。
这个事情可以认为是Servlet的bug:
setStatus 和 setContentType 这两个方法应该在调用 getWriter() 之前设置,以确保正确设置 HTTP 响应的状态码和内容类型。这是因为一旦 getWriter() 被调用,响应头信息就已经被发送给客户端,此时再调用 setStatus 或 setContentType 将无效。
这是 Servlet 规范的要求,以确保生成正确的 HTTP 响应报文。所以,确保以下顺序是正确的:
response.setStatus(HttpServletResponse.SC_OK); // 设置状态码,例如 200 OK response.setContentType("text/html"); // 设置内容类型,例如 HTML PrintWriter out = response.getWriter(); // 获取输出流 // 在这之后,将数据写入输出流
如果你不按正确的顺序执行这些方法,可能会导致生成非法的 HTTP 响应,从而引发问题或错误。这是 Servlet 编程中的一个常见陷阱,需要谨慎处理。确保遵循 Servlet 规范以生成有效的 HTTP 响应。
(3)客户端收到响应,就需要针对响应数据进行解析处理,把响应中的信息构造成页面元素(HTML片段)并显示出来。
这个代码是整个“表白墙 ”程序最复杂的代码,我们需要拼接出HTML片段。
此处的 body 就是服务器返回的响应,数据中 JSON 格式的数组。
当响应中,header 带有 Content-Type: application/json,jQuery 就会自动的把 JSON 字符串解析成 JavaScript 对象了。
如果没有带 Content-Type: application/json,就需要通过 JavaScript 代码 JSON.parse 方法来手动将 JSON 字符串转成 JavaScript 对象。
很明显,我们Java代码中是有这行代码的:
所以此时 jQuery 就会自动的把 JSON 字符串解析好,当下的body就已经是一个JS数组了。
success: function(body) {
// 由于响应中已经有 Content-Type: application/json 了, 就不需要使用 parse 方法手动转换了.
// body = JSON.parse(body);
// 处理服务器返回的响应数据. (json 格式的数组了)
for (let i = 0; i < body.length; i++) {
// body 是一个数组, 此时 message 也就是 js 对象了.
// 这个 message 对象里, 有三个属性, from, to, message
let message = body[i];
// 根据 message 对象构建 html 片段, 把这个片段给显示到网页上.
}
}
success: function(body) {
// 由于响应中已经有 Content-Type: application/json 了, 就不需要使用 parse 方法手动转换了.
// body = JSON.parse(body);
// 拿到 container 这个元素
let containerDiv = document.querySelector('.container');
// 处理服务器返回的响应数据. (json 格式的数组了)
for (let i = 0; i < body.length; i++) {
// body 是一个数组, 此时 message 也就是 js 对象了.
// 这个 message 对象里, 有三个属性, from, to, message
let message = body[i];
// 根据 message 对象构建 html 片段, 把这个片段给显示到网页上.
// createElement 方法就能构造出一个 html 标签.
// 此时就得到了 <div></div>
let div = document.createElement('div');
// 还需要往里面设置一个 属性 , class="row" (设置这个属性, 是为了让 css 能够给这个元素设置一些样式)
// 此时就得到了 <div class="row"></div>
div.className = 'row';
// 给这个 div 里设置内容
// 此时就得到了 <div class="row">张三 对 李四 说: 我喜欢你很久了</div>
div.innerHTML = message.from + " 对 " + message.to + " 说: " + message.message;
// 把 div 添加到 containerDiv 的末尾
containerDiv.appendChild(div);
}
}
HTML里面的每个元素/标签都可以用一个JS对象来表示,反之JS的对象也可以设置到页面中。这就是DOM(Document Object Model)文档对象模型。
DOM(Document Object Model)是一种用来表示和操作HTML文档的编程接口。在DOM中,HTML文档中的每个元素(标签)都被表示为JavaScript对象,这使得开发人员可以使用JavaScript来访问、修改和操作HTML文档的结构和内容。
DOM允许开发人员:
- 访问文档中的所有元素和其属性。
- 修改文档的结构,例如添加、删除或移动元素。
- 更改元素的内容、样式和属性。
- 响应用户的交互事件,如单击、键盘输入等。
DOM 的树状结构表示了HTML文档的层次结构,其中顶层的元素是文档元素 <html>,然后包含了 <head> 和 <body> 元素,以及其他嵌套的元素。每个元素都是DOM树中的一个节点,可以使用JavaScript来访问和操作这些节点。
<!DOCTYPE html> <html> <head> <title>DOM Example</title> </head> <body> <h1>Hello, World!</h1> <p>This is a paragraph.</p> </body> </html>
这样总的前端代码就写好了:
<script>
// 实现提交操作. 点击提交按钮, 就能够把用户输入的内容提交到页面上显示.
// 点击的时候, 获取到三个输入框中的文本内容
// 创建一个新的 div.row 把内容构造到这个 div 中即可.
let containerDiv = document.querySelector('.container');
let inputs = document.querySelectorAll('input');
let button = document.querySelector('#submit');
button.onclick = function() {
// 1. 获取到三个输入框的内容
let from = inputs[0].value;
let to = inputs[1].value;
let msg = inputs[2].value;
if (from == '' || to == '' || msg == '') {
return;
}
// 2. 构造新 div
let rowDiv = document.createElement('div');
rowDiv.className = 'row message';
rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
containerDiv.appendChild(rowDiv);
// 3. 清空之前的输入框内容
for (let input of inputs) {
input.value = '';
}
// 4. 把用户填写的内容, 发送给服务器. 让服务器来保存.
// $ 是 jquery 提供的全局变量. ajax 就是 $ 的一个方法.
// ajax 的参数是一个 js 对象, 可以有很多属性
let requestbody = {
"from": from, // from 变量里的值, 就是第一个输入框的内容, "张三"
"to": to, // to 变量的值, 就是第二个输入框的内容, "李四"
"message": msg // msg 变量的值, 就是第三个输入框的内容, "我喜欢你很久了"
};
// 上述 body 是一个 js 对象, 还需要转成 json 字符串.
let jsonString = JSON.stringify(requestbody);
$.ajax({
type: 'post',
url: 'message',
contentType: 'application/json; charset=utf8',
data: jsonString,
success: function(responsebody) {
// 这个回调就是收到响应之后要执行的代码了.
// 前端使用 console.log 打印日志到控制台. (chrome 开发者工具的控制台)
console.log("responseBody: " + responseBody);
}
});
}
// 直接在 script 里面写的 js 代码, 就是在页面加载时被执行到的.
// 发起一个 get 请求, 从服务器获取到数据
// get 请求不需要 body, 也就不需要上述 data 和 contentType 属性了.
$.ajax({
type: 'get',
url: 'message',
success: function(body) {
// 由于响应中已经有 Content-Type: application/json 了, 就不需要使用 parse 方法手动转换了.
// body = JSON.parse(body);
// 拿到 container 这个元素
let containerDiv = document.querySelector('.container');
// 处理服务器返回的响应数据. (json 格式的数组了)
for (let i = 0; i < body.length; i++) {
// body 是一个数组, 此时 message 也就是 js 对象了.
// 这个 message 对象里, 有三个属性, from, to, message
let message = body[i];
// 根据 message 对象构建 html 片段, 把这个片段给显示到网页上.
// createElement 方法就能构造出一个 html 标签.
// 此时就得到了 <div></div>
let div = document.createElement('div');
// 还需要往里面设置一个 属性 , class="row" (设置这个属性, 是为了让 css 能够给这个元素设置一些样式)
// 此时就得到了 <div class="row"></div>
div.className = 'row';
// 给这个 div 里设置内容
// 此时就得到了 <div class="row">张三 对 李四 说: 我喜欢你很久了</div>
div.innerHTML = message.from + " 对 " + message.to + " 说: " + message.message;
// 把 div 添加到 containerDiv 的末尾
containerDiv.appendChild(div);
}
}
});
//直接在script里面写的JS代码,就是在页面加载时被执行到的
//发起一个get请求,从服务器获取到数据
//get请求不需要body
</script>
现在我们还是再来看看效果:
提交后再次刷新页面会发现,数据还保留:
甚至我们单开一个标签页,数据仍然存在:
但是,我们目前只是把数据保存在内存中,一旦服务器重启,数据就消失了。
所以我们接下来就来看看如何把消息数据存储到数据库中,并且把数据库引入到代码中。
数据存入数据库
使用文件的方式存储留言固然可行, 但是 ,我们还可以借助数据库完成存储工作。
1、在 pom.xml 中引入 mysql 的依赖:
2、建库建表:
建库建表需要用到SQL,都可以写到文件中,后续如果需要把表啥的往其他机器上迁移,建表操作就会比较方便。
注意:我们在上下文中使用了 "from"、'to' 作为属性名或变量名,且它与JavaScript的保留关键字冲突,需要使用反引号(backticks)将它作为属性名,这样可以确保使用关键字 "from" 、"to"作为属性名不会导致语法错误。
create database if not exists message_wall charset utf8;
use message_wall;
-- 删表目的是为了, 防止之前数据库里有一样的表, 对咱们的代码产生干扰.
drop table if exists message;
create table message (`from` varchar(1024), `to` varchar(1024), message varchar(1024));
然后我们打开MySQL数据库复制粘贴:
3、编写数据库代码:
使用JDBC编写代码:
首先,既然我们引入了数据库,那么:
就可以删除了。
先创建数据源:
private DataSource dataSource = new MysqlDataSource();
@Override
public void init() throws ServletException {
// 1. 创建数据源
((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/message_wall?characterEncoding=utf8&useSSL=false");
((MysqlDataSource) dataSource).setUser("root");
((MysqlDataSource) dataSource).setPassword("2222");
}
写一个保存、插入数据的方法:
private void save(Message message) throws SQLException {
// 通过这个方法把 message 插入到数据库中
// 1. 建立连接
Connection connection = dataSource.getConnection();
// 2. 构造 SQL
String sql = "insert into message values(?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, message.from);
statement.setString(2, message.to);
statement.setString(3, message.message);
// 3. 执行 SQL
statement.executeUpdate();
// 4. 回收资源
statement.close();
connection.close();
}
最后是一个从数据库中读取的方法:
private List<Message> load() throws SQLException {
// 通过这个方法从数据库读取到 message.
// 1. 建立连接
Connection connection = dataSource.getConnection();
// 2. 构造 SQL
String sql = "select * from message";
PreparedStatement statement = connection.prepareStatement(sql);
// 3. 执行 sql
ResultSet resultSet = statement.executeQuery();
// 4. 遍历结果集合
List<Message> messageList = new ArrayList<>();
while (resultSet.next()) {
Message message = new Message();
message.from = resultSet.getString("from");
message.to = resultSet.getString("to");
message.message = resultSet.getString("message");
messageList.add(message);
}
// 5. 回收资源
resultSet.close();
statement.close();
connection.close();
// 6. 返回 messageList
return messageList;
}
我们总代码就在这里:
package org.example;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
// 引入数据库, 此时 messageList 就不再需要了.
// private List<Message> messageList = new ArrayList<>();
private DataSource dataSource = new MysqlDataSource();
@Override
public void init() throws ServletException {
// 1. 创建数据源
((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/message_wall?characterEncoding=utf8&useSSL=false");
((MysqlDataSource) dataSource).setUser("root");
((MysqlDataSource) dataSource).setPassword("2222");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 读取前端发来的数据, 把这个数据保存到服务器这边.
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
System.out.println("请求中收到的 message: " + message);
// 最简单的办法, 直接在内存中保存. 可以使用一个集合类, 把所有收到的 message 都存到内存中.
// 很明显, 保存到内存, 并非是一个非常合理的办法. 后续一旦重启服务器, 数据丢失了.
// 相比之下, 把这个数据持久化存储到数据库中, 更科学的.
// messageList.add(message);
// 插入数据库
try {
save(message);
} catch (SQLException e) {
throw new RuntimeException(e);
}
// 返回一个响应
resp.setStatus(200);
resp.getWriter().write("ok");
// resp.setContentType("application/json");
// resp.getWriter().write("{ ok: true }");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法来处理当前获取消息列表的 get 请求. 不需要解析参数, 直接返回数据即可.
resp.setStatus(200);
resp.setContentType("application/json; charset=utf8");
// 从数据库查询
List<Message> messageList = null;
try {
messageList = load();
} catch (SQLException e) {
throw new RuntimeException(e);
}
String respJson = objectMapper.writeValueAsString(messageList);
resp.getWriter().write(respJson);
}
private void save(Message message) throws SQLException {
// 通过这个方法把 message 插入到数据库中
// 1. 建立连接
Connection connection = dataSource.getConnection();
// 2. 构造 SQL
String sql = "insert into message values(?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, message.from);
statement.setString(2, message.to);
statement.setString(3, message.message);
// 3. 执行 SQL
statement.executeUpdate();
// 4. 回收资源
statement.close();
connection.close();
}
private List<Message> load() throws SQLException {
// 通过这个方法从数据库读取到 message.
// 1. 建立连接
Connection connection = dataSource.getConnection();
// 2. 构造 SQL
String sql = "select * from message";
PreparedStatement statement = connection.prepareStatement(sql);
// 3. 执行 sql
ResultSet resultSet = statement.executeQuery();
// 4. 遍历结果集合
List<Message> messageList = new ArrayList<>();
while (resultSet.next()) {
Message message = new Message();
message.from = resultSet.getString("from");
message.to = resultSet.getString("to");
message.message = resultSet.getString("message");
messageList.add(message);
}
// 5. 回收资源
resultSet.close();
statement.close();
connection.close();
// 6. 返回 messageList
return messageList;
}
}
现在让我们来重启服务器看看效果:
现在随着我们刷新网页,刚刚写进去的信息就显示出来了。
我们也可以重新提交一条信息:
与此同时数据库里也有了新的信息:
如此这般,我们的表白墙就大功告成了!
我们已经完成了 "表白墙" 程序的前端和后端开发,数据也能够保存在MySQL数据库中,但其他客户端仍然无法访问,我们需要执行以下步骤来使我们的应用对外可访问:
-
部署后端服务:确保我们的后端服务(Java应用)已经部署到一个可访问的服务器上。这可以是我们自己的服务器,也可以使用云服务提供商(如AWS、Heroku、Google Cloud等)提供的云服务器。
-
配置服务器防火墙和网络:确保服务器上的防火墙设置允许来自外部客户端的HTTP请求访问我们的后端服务。我们需要配置服务器的网络设置,以便客户端可以通过公共IP地址或域名访问服务器。
-
域名和DNS设置:如果我们有一个自定义域名,确保将域名与服务器的IP地址关联起来。如果没有域名,我们可以使用服务器的公共IP地址来访问应用。如果使用域名,还需要在DNS设置中创建相应的记录,以确保域名解析到正确的IP地址。
-
Web服务器配置:如果我们使用的是Java Web应用,通常会使用Web服务器(如Tomcat)来托管我们的应用。确保Web服务器已正确配置,以便处理HTTP请求,并将它们路由到我们的Java应用。
-
HTTPS加密:对于任何涉及用户数据或敏感信息的应用,强烈建议启用HTTPS加密以保护通信安全。我们可以获得SSL/TLS证书并配置服务器以支持HTTPS。
-
防止安全漏洞:确保我们的应用程序没有常见的安全漏洞,如SQL注入、跨站脚本(XSS)、跨站请求伪造(CSRF)等。定期进行安全审计和漏洞扫描,以确保应用的安全性。
-
监控和维护:建立监控和日志记录机制,以便随时检测应用问题。定期维护服务器和应用程序,确保它们保持最新版本并正常运行。
-
测试访问:确保我们可以从其他客户端设备(例如移动设备或不同浏览器)访问我们的应用,并进行充分的测试以确保一切正常。
-
文档和用户支持:提供文档和用户支持,以帮助其他人了解如何使用我们的应用。
一旦我们完成了上述步骤,我们的 "表白墙" 应用就应该可以从其他客户端设备访问了。确保维护和监控我们的应用,以确保它始终可用和安全。