我们要把表白墙程序修改成服务器版本
这样即使页面关闭, 表白墙的内容也不会丢失
此处,服务器要实现的功能,主要是两个方面:
在一个网站中,服务器起到最主要的效果,往往就是“存储数据”
因此服务器这边往往也就需要能够提供两种风格的接口,存数据,取数据
服务器这边就需要给网页提供两个 http 的接口
1)获取消息
网页加载的时候,给服务器发送一个 ajax 请求
请求:
GET/message
响应:
HTTP/1.1 200 OK
Content-Type: application/json
此处的请求和响应的细节,都是可以随意设计的,只要能达成效果
2)提交消息
用户点击 提交 按钮的时候 ajax 给服务器器发送一个请求
目的是为了把用户咋输入框输入的内容,给发送给服务器
正式编写代码之前,一定要把前后端交互的接口给确定下来
这个就是后续编写代码的依据
编写前端代码:
构造 HTTP 请求(请求是什么样子的)
解析 HTTP 响应(响应是什么样子的)
编写后端代码:
解析 HTTP 请求(请求是什么样子的)
构造 HTTP 响应(响应是什么样子的)
这些都是需要设计好前后端交互接口才嫩个回答的问题
这个过程,就是 自定义应用层协议
我们需要把 网页 放入到 webapp 目录里
tomcat 这样的项目,可以包含一些 html、css、js
这些内容都是在 webapp 目录中
使用 ajax,需要先引入 jquery 这个库
前端引入第三方库,往往就是通过 script 标签,写一个地址即可
这个代码在点击按钮的回调函数中
会在点击按钮之后被调用
前端 ajax 请求,url 路径,写作“message”,前面不带 / ,此时这是一个相对路径的写法
后端处理 ajax 请求,url 路径,写作“/message”,前面带 / ,此时是 Servlet 要求的写法
服务器读取上述请求,并计算出响应
要确定 java 代码中,类的属性的名字 和 json 中的属性的名字保持一致
回到前端代码,处理服务器返回的响应
此处 success 回调函数,不是立即执行的,而是在浏览器收到服务器返回的,成功,这样的响应的时候,才会执行到 function
这个函数的第一个参数,就是响应数据的 body 中的内容
为了和请求对的上
一般,服务器返回的时候,也是用 json 格式
在代码中写的是一个 相对路径
最终发送的请求,会被转成绝对路径
就是把相对路径前面,品尚当前 html 所在的 context path 里
当前已经把 数据提交到服务器保存了
目前还是需要能够把 服务器的数据 拿回到 客户端页面上,并显示
为什么还要从服务器拿小希?
这个时候,就需要在页面加载的时候,发起请求
1)客户端在页面加载的时候,发起一个 http 请求
2)服务器收到这个请求,要处理这个请求并生成响应
服务器收到的每条消息,都转换成了 Message 对象,放到上述 List 中了
返回的结果,也就是这个 List 的数据
需要把 List 里的每个 Message 取出来,转成 json 字符创,最终拼到一起,得到了响应结果
jackson 看到了 messageList 是一个 List 类型
转成的json 字符串就是一个 json 数组[]
jackson 自动遍历 List 里的每个元素把每个元素,分别都转成 json 字符串
确保这几个代码的执行顺序 setStatus 和 setContentType 必须在 getWriter 前面
否则可能不会生效(构造出一个非法的 http 响应报文)
这个事情可以认为是 Servlet bug
3)客户端收到响应,就需要针对响应数据进行解析处理
把响应中的信息,构造成页面元素(html片段),并显示出来
这段代码中,需要拼接出 html 片段
body 就是服务器返回的响应
数据 json 格式的数组
当响应中,header 带有 Content-Type: application/jsonjquery
就会自动的把 json 字符串,解析成 js 对象了
如果没有带 Content-Type: application/json
就需要通过 js 代码 JSON.parse 方法来手动把 json 字符串转成js 对象
此时,响应数据中,带有 content type 的
所以此时 jquery 自定帮我们完成解析了
当下 body 就已经是一个 js 对象了(数组对象)
通过类选择器,针对 class 属性进行选择
html 中的每一个元素/标签,都存在一个 js 的对应的对象,来表示
称为 DOM(文档对象模型)
如何把消息数据存储到数据库中
把数据库引入到代码中
1)引入数据库的依赖
2)建库建表
建库建表,需要用到 sql,都可以写到 文件 中,后续如果需要把表啥的往其他的机器上迁移,建表操作就会比较方便
3)编写数据库代码
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;
class Message {
public String from;
public String to;
public String message;
@Override
public String toString() {
return "Message{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", message='" + message + '\'' +
'}';
}
}
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
// 引入数据库, 此时 messageList 就不再需要了.
// private List 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("123456");
}
@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;
}
}
到此一个简单的,依靠 servlet 的web 页面到此结束了~
下次再见~