编写代码
写一个MessageWall(表白墙)的网站
提交消息
- (1) 先写前端代码,发送请求
(2) 再写后端代码,解析请求,构造响应
(3) 最后写前端代码,解析响应
把刚才的网页放到 webapp 目录中
tomcat 这样的一个项目,可以包含一些html,css,js
这些内容都是在webapp目录中的
第一级目录是Context path,第二级目录是html这个文件名(html)了
编写前端代码,发送请求
编写前端代码,发送http post请求
构造如下这样的请求:
- 使用ajax,需要引入jquery这个库(在搜索栏中搜jquery cdn这个网站)
把第二个网址复制一下
前端引入第三方库,往往就是通过 script 标签,写一个地址即可
放入html代码中的这里:
2. 构造请求并发送请求
(1) 让前端发起一个 ajax 请求
这个代码在用户点击按钮的回调函数中,会在点击按钮值之后被调用到
前端ajax请求,url路径,写作 message,前面不带 /,此时这是一个相对路径的写法
后端处理ajax请求,url路径,写作 /message,前面带 /,此时是Servlet要求的写法
(2) 服务器读取上述请求,并计算出响应
前端发来的json字符串,需要使用jackson读取到前端这里的数据,并且进行解析
要确保java代码中类的属性的名字和json中属性的名字保持一致
读取请求,处理响应的流程:
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
class Message{
public String from;
public String to;
public String message;
@Override
public String toString() {
return "Message{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", message='" + message + '\'' +
'}';
}
}
public class MessageServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private ArrayList<Message> meassageList = new ArrayList<>();
@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都保存在内存中
// 保存在内存中并不是一个合理的方法,后续一旦重启服务器,数据就丢失了
// 相比之下,把数据存储在持久化的数据库中是更合理的
meassageList.add(message);
// 返回一个响应
resp.setStatus(200);
resp.getWriter().write("ok");
}
}
(3) 回到前端代码,处理服务器返回的响应
请求的body和响应的body是不一样的
为了和请求对应上,一般服务器返回的数据,也会按照json格式来组织数据
前端把日志打印到控制台上
提交消息的过程(结果)
- 不能在浏览器里直接访问message这个路径(message这个路径是按照post请求来处理的,此时还没到post请求这个时机),开始是get方法需要获取这里的网页
post请求处理的时机(在用户点击按钮时才能访问这个message这个路径):
服务器收到的请求和浏览器收到的响应:
相对路径会被转化为绝对路径
前端代码构造出一个请求(json字符串格式的)
后端这里返回一个响应:
页面加载时,能够在服务器中拿到数据到浏览器中显示出来
- 当前已经把数据提交到服务器保存了。目前还需要能够把服务器的数据拿回到客户端页面上,并显示。
页面加载的时候,发起这个get请求
为什么要从服务器拿消息?
1.当前页面上显示的数据,也是在浏览器内存中保存的。刷新页面/关闭了重新打开,之前的数据就没了。
2.其他的客户端打开页面也是有数据的
(1) 客户端在页面加载的时候,发起一个http请求
(2) 服务器收到这个请求,要处理这个请求并生成响应
把list里面的元素都转成json格式的字符串了
要确保这几个代码的执行顺序,setStatus和setContentType必须在getWriter前面,否则可能不会生效(构造出一个非法的http响应报文)这个事情可以认为是Servlet bug
几行代码就可以搞定
(3) 客户端收到响应,就需要针对响应数据进行解析处理。把响应中的信息,构成页面元素(html片段),并显示出来。
这里是最复杂的代码,需要拼接出html片段
总结
前后端交互的总体逻辑
页面加载
把返回的数据构造成html片段,显示到网页上
把messageList里的数据返回给浏览器
点击按钮,提交消息
把这个数据保存到messageList中
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;
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();
private ArrayList<Message> meassageList = new ArrayList<>();
@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都保存在内存中
// 保存在内存中并不是一个合理的方法,后续一旦重启服务器,数据就丢失了
// 相比之下,把数据存储在持久化的数据库中是更合理的
meassageList.add(message);
// 返回一个响应
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 {
resp.setStatus(200);
resp.setContentType("application/json; charset=utf8");
String respJson = objectMapper.writeValueAsString(meassageList);
resp.getWriter().write(respJson);
}
}
把消息数据存储到数据库中
如果把服务器关闭,再刷新页面,消息就消失了,因为消息是存在内存中的,这里就需要引入数据库保存这些消息
如何把消息数据存储到数据库中
把数据库引入到代码中
-
引入数据库的依赖
-
建库建表
建库建表,需要用到sql,都可以写到文件中,后续如果需要把表什么的往其他的机器上迁移,建表操作就会比较方便
create database if not exists MessageWall charset utf8;
use message_wall;
-- 删表的目的是为了,防止之前数据库中有一样的表,对我们的代码产生影响
drop table if exists massage;
-- from和 to 是sql中的关键字,可能执行不了,所以加上反引号
create table message(`from` varchar(1024),`to` varchar(1024),message varchar(1024));
- 编写数据库代码
JDBC
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 javax.xml.transform.Result;
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();
private DataSource dataSource = new MysqlDataSource();
// private MysqlDataSource mysqlDataSource = 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");
// TODO 找了很久的问题,原来是数据库密码不一致的问题,导致登录的不是这个数据库(123456)
// TODO 登上了其他的数据库(123)
((MysqlDataSource)dataSource).setPassword("123456");
// this.dataSource = mysqlDataSource;
}
// 引入数据库,就不需要meassageList了
// private ArrayList<Message> meassageList = new ArrayList<>();
@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都保存在内存中
// 保存在内存中并不是一个合理的方法,后续一旦重启服务器,数据就丢失了
// 相比之下,把数据存储在持久化的数据库中是更合理的
// 插入数据库
try {
save(message);
} catch (SQLException e) {
throw new RuntimeException(e);
}
// meassageList.add(message);
// 返回一个响应
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 {
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插入到数据库中
// 2.建立连接
Connection connection = dataSource.getConnection();
// 3.构造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);
// 4. 执行sql
statement.executeUpdate();
// 5. 回收资源
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;
}
}
Cookie和Session
- Cookie:http请求header中的一个属性
比如:你去医院看病,你的就诊卡就是cookie,就诊卡里就存储了用户的身份信息(身份标识,也只有身份id),通过就诊卡刷卡,就可以在医生的电脑上显示你的完整的基本信息了
- session:服务器存储数据的机制(不算持久化存储)
Cookie:是客户端存储数据的机制
这两者往往会相互配合使用,在cookie中存储的用户身份标识,也经常会理解成sessionId
每个用户都有一个自己的session,也有不同的sessionId(服务器会存储很多的session)
用户可以通过就诊卡(cookie),就诊卡中就存有一份身份标识(sessionId),通过身份标识可以在服务器中找到它的基本信息(Session),Session中可以存储用户的自定义的信息
描述一下上面的过程:
我们学过的键值对结构:
3. 通过Servlet api来操作上述结构,Cookie是浏览器的机制,Servlet提供了api获取到Cookie。
Session 是服务器的机制,Servlet内部已经实现好了,也提供了api可以让我们进行使用
(1) HttpServletRequest
HttpSession getSession()
(2) HttpServletResponse
服务器给浏览器返回一个响应的信息,使用addCookie
(3) HttpSession
(4) Cookie
用户登录
基于上述的api实现 用户登录的功能
cookie / session 机制,其中很重要的作用,就是辅助完成登录功能的实现
- 登录页面(html)
用户发起一个HTTP请求,触发登录逻辑(带上用户输入的用户名和密码)
- 通过一个Servlet处理上述的登录请求
通过这个Servlet读取用户名和密码,并且验证是否登录成功
如果登录成功,就会给当前这个用户,创建一个会话(保存用户当前的信息),并且把得到的sessionId,通过cookie返回给客户端。(客户端就把cookie保存起来了)
- 网站主页,通过另一个Servlet生成的动态页面。在这个页面中,就会把刚才这里的用户数据给显示到页面上。
比如是使用 zhangsan 用户来登录,主页上就会显示 欢迎你zhangsan 这样的标语
登录页面(html)
- 先写一个登录页面
会构造一个形如下面的页面
预期发送的请求:
写一个Servlet处理请求
-
equals方法的另一种使用方式
-
创建一个session对象,getSession在背后做的事情
-
Attribute的作用
-
通过Servlet获取不同浏览器中的数据
- 重定向跳转到index这个页面上(后续会写一个Servlet生成这个页面)
package login;
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.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 读取请求传来的数据(用户名和密码)
// 最好先设置一下字符集,否则如果usename是中文的话,此处 getParameter 可能会乱码
req.setCharacterEncoding("utf8");
String usename = req.getParameter("usename");
String password = req.getParameter("password");
// 2.验证用户名密码,是否是正确的,一般来说,验证用户名和密码是要通过数据库的
// 此处为了简单一点,使用固定的用户名和密码的方式,比如此处假设用户名是zhangsan 密码是 123
// 此处还需要注意,如果getParameter拿到的值时null,为了避免空指针异常,下面这种比较方式是更合适的写法
if(!"zhangsan".equals(usename) || !"123".equals(password)){
// 登录失败
// 给用户返回一个提示
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("用户名或密码错误");
return;
}
// 3.登录成功了,给用户创建一个会话出来
// 在会话中可以保存一些自定义的数据,通过Attribute的方式来保存
HttpSession session = req.getSession(true);
// 此处Attribute 也是键值对,这里的内容存储什么都可以,是程序员自定义的
// 这样的数据存储好之后,后续跳转到其他页面,也是随时可以把这里的数据从会话中取出来的
session.setAttribute("usename",usename);
session.setAttribute("loginTime",System.currentTimeMillis());
// 4.此处相当于登录成功,让页面跳转到网站首页
resp.sendRedirect("index");
}
}
实现登录后的主页
- 获取当前用户的会话对象
- getSession内部使用同一个sessionId,所以才能够拿到同一个session对象,这里是封装好的,所以我们体现的不那么明显
3. 这是两个不同的Servlet,一个是登录的页面,一个是登录后跳转的页面,不同的Servlet之间,数据的共享
4. 这里需要进行强转
package login;
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.servlet.http.HttpSession;
import java.io.IOException;
// 通过这个Servlet生成一个主页
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 重定向操作会发送一个get请求
// 1. 先获取当前用户对应的会话对象,生成的页面要根据当前的用户信息来构造
HttpSession session = req.getSession(false);
if(session == null){
// 如果session不存在,或者 sessionId 在哈希表中没有找到
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("您当前还没有登录!");
return;
}
// 2. 从会话中拿到之前存储的用户信息
// 这里的强转,需要程序员自行保证,类型是靠谱的
String usename = (String) session.getAttribute("usename");
Long longTime = (Long) session.getAttribute("loginTime");
// 3. 生成一个页面,把上述的数据显示到页面上
// 构造一个显示的字符串
resp.setContentType("text/html; charset=utf8");
String respBody = "欢迎你 " + usename + "!" + "上次的登录时间为: " + longTime;
resp.getWriter().write(respBody);
}
}
总结上述的代码逻辑
- 点击登录后就会触发一个 POST请求
2. 到达服务器这边
getSession的作用:
3. 重定向到主页(index)
总结
- 利用Cookie和Session写一个登录的逻辑:
2. 登录时间过期的问题
上述就是一个网站的基本开发流程了!!!