表白墙链接
之前完成的表白墙有两个非常严重的问题:
1、如果刷新页面或者关闭重开,此时表白墙之前保存的消息就消失了
2、表白墙的数据只能在本地浏览器中看见
此时如果刷新页面或者关闭重开,此时表白墙的数据就消失了。
解决思路
让服务器来存储用户提交的数据,有服务器保存。
当有新的浏览器打开页面的时候,从服务器获取数据。
实现一个 web 程序,务必要考虑前后端如何交互,要约定好前后端交互的数据格式。
比如说:
设计前后端交互接口、请求是什么样的、响应是什么样的、浏览器什么时候发这个请求、浏览器按照什么样的格式来解析。
有两个环节涉及到了前后端的交互:
一个是 点击提交 的时候,也就是浏览器把表白信息发到服务器。
一个是 页面加载 的时候,也就是浏览器从服务器获取到表白信息。
请求 可以使用 POST,再使用一个 /message 这样的路径。
约定当前 body 是按照 json 格式来进行提供
{
from: "张三",
to: "李四",
message: "卧槽"
}
响应 可以约定为,HTTP/1.1 200 OK
请求 可以约定为 GET,再来一个 /message 路径,响应 也可以约定为 HTTP/1.1 200 OK。
只不过此时的响应要按照 json数组 的格式来进行解析。
[
{
from: "张三",
to: "李四",
message: "卧槽"
},
{
from: "张三",
to: "李四",
message: "卧槽"
}
]
此处的约定没有固定的强制要求,只要能保证必要的需求即可。
此处的目的就是为了前端代码和后端代码能够对上。
第一个Servlet 程序链接
1、首先要引入 Servlet、jackson、mysql 依赖,将这三个依赖引入到 dependencies 标签中。
打开 https://mvnrepository.com/,搜索 servlet,找到 3.1.0 版本。
选择如上图的 Servlet,点击进去选择 3.1.0版。
点击进去之后将 Maven 下的代码复制到 pom.xml 文件的 dependencies 标签中。
jackson 选择如下图的
点击 2.14.2 版本,其余的步骤与 servlet 一样。
mysql 依赖选择如下图的点击
这里引入 mysql 依赖是因为可以使用 数据库来保存用户提交的数据。
选择 5.1.49 版本。
其余的步骤也与 servlet 一样。
以上是三个依赖引入好的样子。
2、接下来开始创建目录
3、往web.xml 中添加一点内容
这里的路径要和之前约定好的相同。
1、重写 doPost 和 doGet 方法
2、实现向服务器提交数据的 POST 请求
要先定义一个 Message 类,描述请求 body 的内容,方便 jackson 进行 json 解析。
class Message {
public String from;
public String to;
public String message;
}
先构建一个 ObjectMapper,再读取 body 里的内容,然后再解析成一个 Message 对象,
最后就是填到 List 变量里。
// 暂时使用 List 变量保存所有消息 —— 比较简单粗暴
private List<Message> messageList = new ArrayList<>();
// 向服务器提交数据
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 构造一个 ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
// 把 body 的内容读取出来,解析成一个 Massage 对象
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
messageList.add(message);
// 设置状态码,不设置默认也是 200
resp.setStatus(200);
}
3、实现从服务器获取数据的 GET 请求
获取数据直接从 List 变量获取即可,也就是把 List 对应的结果给转成 json 格式的字符串,返回即可。
// 从服务器获取数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 显示的告诉浏览器,数据格式是 json 格式,字符集是 utf-8
resp.setContentType("application/json; charset = utf-8");
// 第一个参数表示写到哪里,第二个参数表示写的内容是什么
objectMapper.writeValue(resp.getWriter(), messageList);
}
getWriter() 这个方法同时把 java 对象转成了 json 字符串和吧这个字符串写到响应对象中。
针对 doGet,只是把 messageList 转成 json 字符串,然后返回给浏览器即可。
此时后端代码就写完了,启动服务器,然后打开 postman 发送请求验证一下。
先选择 POST,在 body 里输入内容后点击 Send 发送一个请求。
之后,切换为 GET 接收,再次点击 Send 就可以得到下面的结果了,下面是发送了三次请求的结果。
编写前端代码,让页面能够发起请求,并解析响应。
post 是点击提交按钮的时候发起的,get 是页面加载的时候发起的。
首先要将前端代码的文件复制到 webapp 目录下。
然后以 vscode 打开。
首先要引入 jQuery
搜索 jQuery cdn,将链接复制到 script 标签里的 src 属性里。
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
POST 是在点击提交按钮的时候触发的,点击这里的 button 就是点击了按钮。
此时就会触发了 POST 请求。
let button = document.querySelector('#submit');
当前的代码再点击提交按钮后,会触发 onclick 回调方法,这里的回调方法只是构造了一个新的 div。
接下来要做的就是实现一个新的步骤,把上述代码中的数据提交到服务器里。
// [新增]4. 使用 ajax 构造一个 post 请求, 把上述数据提交到服务器里
// 构造一个 body
let body = {
"from": from,
"to": to,
"message": message
}
$.ajax({
// 这里是要构造的请求类型
type: 'post',
// 这里的路径要和之前约定好的前后端交互接口一致
url: i'message',
// post 请求是有 body 的,所以就要设置一个 body
data:
});
这里设置的 body 是 定义了一个 js 对象,类似于 json 的键值对。
key 是一个字符串,也就是图中的 “from”、“to”、“message” 三个。
value 则是 js 中的变量/常量,也就是字符串后面的值。
在 js 中要求对象的 key 务必是字符串,因此此处的代码还可以是以下的写法:
当前 body 里的 vlaue 值是由页面上的输入框读取到的内容放到变量里的。
需要注意的是当前的 js 对象不是字符串,在网络传输中,只能传字符串,不能传对象。
接下来需要把这个对象转成一个字符串。
接下来进行的操作是将 js 对象转成一个 json 格式的字符串。
在 js 中内置了 json 的转换库,此时就不需要像 java 好要有第三方库 jackson 了。
可以使用 JSON.stringify 转为 json 格式的字符串。
// 将 js 对象转为 json 格式的字符串
let strBody = JSON.stringify(body);
// 打印日志
console.log("strBody:" + strBody);
$.ajax({
// 这里是要构造的请求类型
type: 'post',
// 这里的路径要和之前约定好的前后端交互接口一致
url: 'message',
// post 请求是有 body 的,所以就要设置一个 body
data: strBody,
// 指定 body 的具体格式
setContentType: "application/json; charset = utf-8",
success: function(body) {
console.log("数据发布成功!")
}
});
接下来启动服务器观察结果。
在启动服务器之后,就可以通过浏览器来打开表白墙的页面了。
在地址栏输入路径,就可以看到表白墙页面了。
打开 fiddler 抓包观察结果。
根据前端代码的设置可以看到抓包的结果各个设置是一样的。
这里构造的就是一个 get 请求了。
// [新增] 在页面加载的时候,发送 GET 请求,从服务器获取数据并添加到页面中
$.ajax({
type: 'get',
url: 'message',
success: function(body) {
// 这里的 body 已经是一个 js 的对象数组了
// 本来服务器返回的是一个 json 格式的字符串,但是 jQuery 的 ajax 自动识别并转化了
// 接下来遍历这个数据,把元素取出来构造页面中即可
for(let message of body) {
// 针对每一个元素构造一个 div
let rowDiv = document.createElement('div');
rowDiv.className = 'row'; // 有了一个 row 的属性
rowDiv.innerHTML = message.from + ' 对 ' + message.to + ' 说: ' + message.message;
containerDiv.appendChild(rowDiv);
}
}
});
本来需要将 json 格式的字符串转为一个 js 对象数组,但是由于 jQuery 的 ajax 自动转化了。
因此这里的 body 已经是一个 js 对象了。
由于当前的数据是借助变量保存的,重启服务器后就会导致数据消失。
因此想要永久的保存,就需要来借助数据库保存。
1、首先要创建一个数据表。
当前已创建完成。
2、创建一个 DBUtil 类。
需要注意的是 jdbc:mysql://127.0.0.1:3306/web?characterEncoding=utf8&useSSL=false。
这条语句里的 web 是我的数据库名,在这里要根据实际情况来更改。
// 通过这个类把数据库连接过程封装一下
// 此处把 DBUtil 作为一个工具类,提供 static 方法来供其他代码调用
public class DBUtil {
private static DataSource dataSource = new MysqlDataSource();
static {
// 使用静态代码块,针对 dataSource 进行初始化操作
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/web?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
// 密码是什么,写什么
((MysqlDataSource)dataSource).setPassword("000000");
}
// 通过这个方法来建立连接
public static Connection getConnection() throws SQLException {
return (Connection)dataSource.getConnection();
}
// 通过这个方法来释放资源
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
// 此处的 三个 try catch 分开写,避免前面的异常导致后面的代码不能运行
if (resultSet != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3、提供一对方法来保存数据
save 方法是用来往数据库中存数据。
private void save(Message messageWall) {
// JDBC 操作
Connection connection = null;
PreparedStatement statement = null;
try {
// 1.建立连接
connection = DBUtil.getConnection();
// 2.构造 sql 语句
String sql = "insert into messagewall values(?, ?, ?)";
statement = (PreparedStatement)connection.prepareStatement(sql);
// 将上面的三个 ? 占位符替换为 from to message
statement.setString(1, messageWall.from);
statement.setString(2, messageWall.to);
statement.setString(3, messageWall.message);
// 3.执行 sql
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 4.关闭连接
DBUtil2.close(connection, statement, null);
}
}
load 方法是用来从数据库中拿数据。
private List<Message> load() {
List<Message> messageList = new ArrayList<>();
Connection connection = null;
ResultSet resultSet = null;
PreparedStatement statement = null;
try {
// 1.和数据库建立连接
connection = DBUtil.getConnection();
// 2.构造 sql 语句
String sql = "select * from messagewall";
statement = (PreparedStatement)connection.prepareStatement(sql);
// 3.执行 sql
resultSet = statement.executeQuery();
// 4.遍历结果集合
while (resultSet.next()) {
Message message = new Message();
message.from = resultSet.getString("from");
message.to = resultSet.getString("to");
message.message = resultSet.getString("message");
// 存到 messageList 中
messageList.add(message);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 5.断开连接
DBUtil2.close(connection, statement, resultSet);
}
return messageList;
}