1. 博客系统的基本情况
1.1 四个页面
1.博客列表页:显示出当前网站上都有哪些博客 2.博客详情页:点击列表上的某个博客,就能进入对应详情页,(显示出博客的具体内容) 3.博客编辑页:让用户编写博客内容,并且发送到服务器 4. 博客系统登录页
总所周知,我们的博客是markdown编辑器,markdown(md)是程序员常用的一种用来写文档的语言,主流的博客系统, 都是支持 md;我们所编写的博客系统使用到的md编辑器不是我们所写的,而是我们引第三方库editor.md,这是一个开源的项目,从 github 上下载好,放到这个目录里了,如下图所示;
1.2 项目编写逻辑
当下要完成的任务,是基于上述页面,来进行编写服务器/前后端交互代码
1)、实现博客列表页. 让页面从服务器拿到博客数据 (数据库) 2)、实现博客详情页, 点击博客详情的时候,可以从服务器拿到博客的完整数据
3) 、实现登录功能 4)、实现强制要求登录 (当前处于未登录的状态下,其他的页面,博客列表,博客详情,博客编辑...就会强制跳转到登录页),要求用户登录之后才能进入博客系统 5)、实现显示用户信息 从服务器获取到下面两种信息:
博客列表页拿到的是当前登录的用户的信息;
博客详情页拿到的是文章作者的信息. 6)、实现退出登录 7)、发布博客 博客编辑页,输入文章标题和内容之后,点击发布,就能把这个数据上传到服务器上并保存
1.3 准备工作
1) 、创建项目,引入依赖,把当前的这些前端页面也导入到项目中.
分别引入javax.servlet、mysql、jackson-databind的相关依赖,如下所示:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2)、数据库设计 设计好对应的表结构,并且把数据库相关代码,也进行封装 ,步骤如下:
a)、找到实体 博客 (blog 表) 用户 (user 表)
数据库语言来创建用户表和博客表:
create database if not exists java109_blog_system charset utf8;
use java109_blog_system;
drop table if exists blog;
drop table if exists user;
create table blog (
blogId int primary key auto_increment,
title varchar(1024),
content varchar(4096),
postTime datetime,
userId int
);
create table user (
userId int primary key auto_increment,
username varchar(50) unique, -- 用户名也要求是不能重复的.
password varchar(50)
);
-- 插入一些测试数据, 方便后续进行测试工作
insert into blog values (1, '这是第一篇博客', '# 从今天开始我要认真写代码', now(), 1);
insert into blog values (2, '这是第二篇博客', '# 从昨天开始我要认真写代码', now(), 1);
insert into blog values (3, '这是第三篇博客', '# 从前天开始我要认真写代码', now(), 1);
insert into user values (1, 'smallye', '111');
insert into user values (2, 'shengmengyao', '222');
b)、确认实体之间的关系 一对多的关系:
一个博客,只能属于一个用户 一个用户,可以发布多个博客,下面是blog和user的相关属性:
blog (blogld, title, content, postTime, userld) user (userld, username, password)
3)、把数据库操作的代码进行一些封装.
进行网站开发的过程中,一种常见的代码组织结构是MVC 结构. M mode!: 操作数据的代码 V view: 操作/构造界面的代码 C controller: 业务逻辑,处理前端请求
懒汉模式是线程不安全的,当前 Servlet 本身就是在多线程环境下执行的,且Tomcat 收到多个请求的时候,就会使用多线程的方式,执行不同的 Servlet 代码,
DBUtil主要是完成对于数据库建立连接和关闭连接的实现,下面是对于dbutil的代码:
package model;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
// 通过这个类, 封装数据库建立连接的操作.
// 由于接下来代码中, 有多个 Servlet 都需要使用数据库. 就需要有一个单独的地方来把 DataSource 这里的操作进行封装.
// 而不能只是放到某个 Servlet 的 init 中了.
// 此处可以使用 单例模式(懒汉模式更安全) 来表示 dataSource
public class DBUtil {
private volatile static DataSource dataSource = null;
private static DataSource getDataSource() {
if (dataSource == null) {
synchronized (DBUtil.class) {
if (dataSource == null) {
dataSource = new MysqlDataSource();
((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java109_blog_system?characterEncoding=utf8&useSSL=false");
((MysqlDataSource) dataSource).setUser("root");
((MysqlDataSource) dataSource).setPassword("111111");
}
}
}
return dataSource;
}
public static Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
之后就是创建实体类,每个表都需要搞一个专门的类来表示,表里的一条数据,就会对应到这个类的一个对象,把数据库中的数据和代码联系起来了,如下所示:
这是博客类:
package model;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
// Blog 对象就是对应到 blog 表中的一条记录.
// 表里有哪些列, 这个类里就应该有哪些属性
public class Blog {
private int blogId;
private String title;
private String content;
private Timestamp postTime;
private int userId;
public int getBlogId() {
return blogId;
}
public void setBlogId(int blogId) {
this.blogId = blogId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getPostTime() {
// Java 标准库提供了一个 SimpleDateFormat 类, 完成时间戳到格式化时间的转换.
// 这个类的使用, 千万不要背!!! 都要去查一下!! 背大概率会背错!!!
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(postTime);
}
public void setPostTime(Timestamp postTime) {
this.postTime = postTime;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
@Override
public String toString() {
return "Blog{" +
"blogId=" + blogId +
", title='" + title + '\'' +
", content='" + content + '\'' +
", postTime=" + postTime +
", userId=" + userId +
'}';
}
}
这是用户user类:
package model;
// User 对象就对应到 user 表中的一条记录.
public class User {
private int userId;
private String username;
private String password;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
还需要创建两个类,来完成针对博客表和用户表的增删改查操作:
BlogDao:
package model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
// 通过 BlogDao 来完成针对 blog 表的操作
public class BlogDao {
// 1. 新增操作 (提交博客就会用到)
public void insert(Blog blog) {
Connection connection = null;
PreparedStatement statement = null;
try {
// 1. 建立连接
connection = DBUtil.getConnection();
// 2. 构造 SQL
String sql = "insert into blog values (null, ?, ?, now(), ?)";
statement = connection.prepareStatement(sql);
statement.setString(1, blog.getTitle());
statement.setString(2, blog.getContent());
statement.setInt(3, blog.getUserId());
// 3. 执行 SQL
statement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException();
} finally {
DBUtil.close(connection, statement, null);
}
}
// 2. 查询博客列表 (博客列表页)
// 把数据库里所有的博客都拿到.
public List
List
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select * from blog order by postTime desc";
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
while (resultSet.next()) {
Blog blog = new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("title"));
// 此处读到的正文是整个文章内容. 太多了. 博客列表页, 只希望显示一小部分. (摘要)
// 此处需要对 content 做一个简单截断. 这个截断长度 100 这是拍脑门出来的. 具体截取多少个字好看, 大家都可以灵活调整.
String content = resultSet.getString("content");
if (content.length() > 100) {
content = content.substring(0, 100) + "...";
}
blog.setContent(content);
blog.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
blogList.add(blog);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
DBUtil.close(connection, statement, resultSet);
}
return blogList;
}
// 3. 根据博客 id 查询指定的博客
public Blog getBlog(int blogId) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select * from blog where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, blogId);
resultSet = statement.executeQuery();
// 由于此处是拿着 blogId 进行查询. blogId 作为主键, 是唯一的.
// 查询结果非 0 即 1 , 不需要使用 while 来进行遍历
if (resultSet.next()) {
Blog blog = new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("title"));
// 这个方法是期望在获取博客详情页的时候, 调用. 不需要进行截断, 应该要展示完整的数据内容
blog.setContent(resultSet.getString("content"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
return blog;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
DBUtil.close(connection, statement, resultSet);
}
return null;
}
// 4. 根据博客 id, 删除博客
public void delete(int blogId) {
Connection connection = null;
PreparedStatement statement = null;
try {
connection = DBUtil.getConnection();
String sql = "delete from blog where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, blogId);
statement.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
DBUtil.close(connection, statement, null);
}
}
}
UserDao:
package model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
// 通过 UserDao 完成针对 user 表的操作
public class UserDao {
// 新增暂时不需要. 不需要实现 "注册" 功能.
// 删除暂时不需要. 不需要实现 "注销帐户" 功能.
// 1. 根据 userId 来查到对应的用户信息 (获取用户信息)
public User getUserById(int userId) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select * from user where userId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, userId);
resultSet = statement.executeQuery();
if (resultSet.next()) {
User user = new User();
user.setUserId(resultSet.getInt("userId"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
DBUtil.close(connection, statement, resultSet);
}
return null;
}
// 2. 根据 username 来查到对应的用户信息 (登录)
public User getUserByName(String username) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select * from user where username = ?";
statement = connection.prepareStatement(sql);
statement.setString(1, username);
resultSet = statement.executeQuery();
if (resultSet.next()) {
User user = new User();
user.setUserId(resultSet.getInt("userId"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
DBUtil.close(connection, statement, resultSet);
}
return null;
}
}
DAO =>Data Access Object 数据访问对象,通过这两个类的对象,来完成针对数据库表的操作.
2. 博客系统的编写
2.1 获取博客列表页
在博客列表页加载的时候,通过 ajax 给服务器发起请求,从服务器(数据库) 拿到博客列表数据,并且显示到页面上;
工作步骤:
1)、约定前后端交互接口
请求和格式如下图所示;
2)、让浏览器给服务器发起请求了
在前端写完请求代码之后一定要在后面进行调用该请求方法,这样才能成功向服务器发起请求;
3)、服务器处理上述请求,返回响应数据(查询数据库)
4)、让前端代码处理上述响应数据 构造成 html 片段,显示到页面上
关于前端代码的转义字符的表示:
上述代码的专有名词的说明:
这里的构造页面的过程,还是之前的 api,主要如下所示: 1)、querySelector: 获取页面已有的元素 2)、createElement: 创建新的元素 3)、innerHtml: 设置元素里的内容 4)、className: 设置元素的 class 属性 5)、appendChild: 把这个元素添加到另一个元素的末尾.
上述代码中a 标签在 html 中称为"超链接”,点击之后能够跳转到一个新的页面
前端代码生成页面如下所示:
此处使用的是比较朴素的方式是基于 dom api 的方式,dom api 就属于是浏览器提供的标准的 api(不属于任何的第三方框架和库);
存在的问题一:
我们期望的是格式化时间,当发布完文章之后就需要把时间戳转成格式化时间,即形如2023-12-31 22:23:59的形式;
归根到底就是要修改服务器的代码,让返回给前端的时间数据就是格式化数据,而不是简单的时间戳,如下所示:
1)、jackson 发现 blogs 是一个 List,于是就会循环遍历里面的每个元素 2)、针对每个元素(Blog 对象),通过反射的方式,获取到都有哪些属性, 属性的名字,属性的值
关于格式化时间的语法:
上述代码指定一个格式化字符串,描述了当前时间日期具体的格式;
修改之后的服务器getPostTime的方法如下所示:
public String getPostTime() {
// Java 标准库提供了一个 SimpleDateFormat 类, 完成时间戳到格式化时间的转换.
// 这个类的使用, 千万不要背!!! 都要去查一下!! 背大概率会背错!!!
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(postTime);
}
存在的问题二:希望我们写好的博客,能够按照发布时间就近从上往下排序,而不是乱序;
博客列表里面的数据都是从数据库里拿出来的,所以我们需要展示blog表的数据的时候按照有序展示即可,代码如下所示:
String sql = "select * from blog order by postTime desc";
2.2 博客详情页
点不同的博客,跳转过去之后,都会带有不同的 blogld 的query string,后续在博客详情页中,就可以也给服务器发起 ajax 请求,根据这里的 bogld查询数据库中该博客的具体内容,并且再返回给博客详情页,即前端还是把得到的数据给构造到页面上.
1)、约定前后端交互接口
这个请求是在进入博客详情页的时候,通过ajax 发给服务器. 请求和相应格式如下所示:
2) 、让前端代码,通过 ajax 发起请求.
此处发起 ajax 请求的时候要带有 blogld,但是blogld 当前就处于博客详情页 的 url 中. 我们就可以通过 location.search 方式拿到 详情页面 url 中的 query string;
综上所述:
当前是使用一个 Servlet 处理两种请求, 博客列表页,不带 query string 博客详情页,带有 query string,所以就可以根据 query string 是否存在的情况,来区分是哪种请求并相对应的分别返回不同的数据即可.
3)、让服务器处理这个请求
4)、前端拿到响应之后, 把响应数据, 构造成页面的 html 片段
写完代码之后,再点击某个博客,就可以看到有的博客里面详情页还是之前的旧的内容. 这个问题实际上是浏览器缓存引起的,浏览器加载页面的时候,是通过网络获取的.(网络速度比较慢),浏览器有时候就会把已经加载过的页面,在本地硬盘保存一份,后续再访问同一个页面,这样就不需要通过网络加载,直接加载本地硬盘的这一份,对于这种情况, 用ctrl + F5来强制刷新页面。
当前博客详情页虽然能够显示出博客的正文了,为了显示正文被md 渲染后的效果,所以我们要使用第三方库(editor.md);
editor.md 官方文档上,给出了具体的例子,来完成上述操作:
2.3. 实现登录
在登录页面,在输入框中填写用户名和密码.之后点击登录,就会给服务器发起 HTTP 请求,(可以使用 ajax,也可以使用 form表单,服务器就会处理登录请求.读取用户名和密码,在数据库査询是否匹配,如果正确,就登录成功,创建会话,跳转到博客列表页 ;由于这里登录成功,,直接进行重定向跳转,这种重定向操作,就不需要浏览器额外写代码处理,直接浏览器就自动跳转了
1)、约定前后端交互接口
2)、让前端发起请求.
3)、让服务器处理请求,并返回响应
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 读取参数中的用户名和密码
req.setCharacterEncoding("utf8");
String username = req.getParameter("username");
String password = req.getParameter("password");
// 验证一下参数, 看下是否合理.
if (username == null || username.length() == 0 || password == null || password.length() == 0) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("您输入的用户名或者密码为空!");
return;
}
// 2. 查询数据库, 看看这里的用户名密码是否正确.
UserDao userDao = new UserDao();
User user = userDao.getUserByName(username);
if (user == null) {
// 用户名不存在!
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("您输入的用户名或密码不正确!");
return;
}
if (!password.equals(user.getPassword())) {
// 密码不正确!
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("您输入的用户名或密码不正确!");
return;
}
// 3. 创建会话
HttpSession session = req.getSession(true);
session.setAttribute("user", user);
// 4. 跳转到主页了.
resp.sendRedirect("blog_list.html");
}
2.4. 强制要求登录
在博客列表页,详情页,编辑页,判定当前用户是否已经登录(在这几个页面中,在每一次页面刷新都会给服务器发起ajax请求,从服务器获取当前的登录状态),如果未登录,则强制跳转到登录页.(要求用户必须登录后才能使用);
1)、约定前后端交互接口
2)、让前端代码发起这个请求
一个页面触发的 ajax 是可以有多个的,一个页面通常都会触发多个 ajax.这些 ajax 之间是“并发执行"的。js 中, 没有"多线程”这样的机制;而 ajax 是一种特殊情况, 能够起到类似于"多线程"的效果,所以当页面中发起两个或者多个 ajax 的时候,这些 ajax 请求就相当于并发的发送的,彼此之间不会相互干预.(不是串行执行,也不是执行完一个 ajax,得到响应之后再执行下一个....)
同时对于从服务器传来的响应,谁的响应先回来了,就先执行谁的回调;
3)、让服务器处理上述请求.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法, 来反馈当前用户的登录状态.
// 一个简单的判定方式, 直接看会话是否存在.
// 此处使用一个更严格的方式. 不仅要求会话要存在, 还要求会话中要能保存 user 对象.
// (之所以给出这种设定, 也是为了后面实现 "退出登录" 这样的功能来做个铺垫)
HttpSession session = req.getSession(false);
if (session == null) {
// 会话不存在, 用户属于未登录状态.
resp.setStatus(403);
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
// user 对象也不存在. 同样也属于未登录状态
resp.setStatus(403);
return;
}
// 两个都存在, 返回 200
// 此处 200 不写也行. 默认就是 200
resp.setStatus(200);
}
当前虽然登录过了,但是一旦重新启动服务器,此时仍然会被判定为未登录状态.登录状态是通过服务器这里的 session 来存储的.session是服务器内存中的类似于 hashmap 这样的结构,一旦服务器重启了,hashmap 里面原有的内容就没了.
当前只是让博客列表页,能够有强制登录.但是实际上,博客编辑页和博客详情页,也应该要有这种机制:
如上图所示,可以把一些公共的 js 代码(判断当前的登录状态,没有进行登录的话,就强制进入到登录页面),单独提取出来放到某个 js 文件中,然后通过 html 中的 script 标签,来引用这样的文件内容,此时如上图所示, 就可以在 html 中调用对应的公共代码了.(可以理解为java中的导包import),即将下面一部分重新创建类,然后后续在需要这个过程的时候只要通过类名进行调用即可;
// 定义新的函数, 获取登录状态
function getLoginStatus() {
$.ajax({
type: 'get',
url: 'login',
success: function(body) {
// 已经登录的状态.
console.log("已经登录了!");
},
error: function() {
// error 这里对应的回调函数, 就是在响应状态码不为 2xx 的时候会触发.
// 当服务器返回 403 的时候, 就会触发当前这个 error 部分的逻辑了.
// 强制要求页面跳转到博客登录页.
// 为啥不在服务器直接返回一个 302 这样的重定向响应, 直接跳转到登录页呢?
// 302 这种响应, 无法被 ajax 直接处理. (如果是通过提交 form, 点击 a 标签这种触发的 http 请求, 浏览器可以响应 302)
// 前端页面跳转的实现方式.
location.assign('login.html');
}
})
}
2.5. 显示用户信息
博客列表页: 显示的是当前登录的用户的信息 博客详情页:显示的是当前文章的作者信息 在页面加载的时候,给服务器发起 ajax 请求,服务器返回对应的用户数据,根据发起请求不同的页面,服务器返回不同的信息即可;
1)、约定前后端交互接口
2)、先让前端代码,发起这样的请求.
3)、编写服务器代码,来处理上述请求
userInfo类:用户信息服务器
package servlet;
import com.fasterxml.jackson.databind.ObjectMapper;
import model.User;
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("/userInfo")
public class UserInfoServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从会话中, 拿到用户的信息返回即可.
HttpSession session = req.getSession(false);
if (session == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前用户未登录!");
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前用户未登录!");
return;
}
// 此时把 user 对象转成 json , 并返回给浏览器.
resp.setContentType("application/json; charset=utf8");
// 注意, user 中还有 password 属性呢!! 把密码再返回回去, 不太合适的.
user.setPassword("");
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
}
}
博客作者信息服务器:
package servlet;
import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
import model.User;
import model.UserDao;
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;
@WebServlet("/getAuthorInfo")
public class AuthorInfoServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 先拿到请求中的 blogId
String blogId = req.getParameter("blogId");
if (blogId == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("请求中缺少 blogId!");
return;
}
// 2. 在 blog 表中查询到对应的 Blog 对象
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.getBlog(Integer.parseInt(blogId));
if (blog == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("blogId 没有找到!");
return;
}
// 3. 根据 blog 对象中的 userId, 从 user 表中查到对应的作者.
UserDao userDao = new UserDao();
User user = userDao.getUserById(blog.getUserId());
if (user == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("userId 没有找到!");
return;
}
// 4. 把这个 user 对象, 返回到浏览器这边.
user.setPassword("");
String respJson = objectMapper.writeValueAsString(user);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respJson);
}
}
此处是通过两步 sql分别查询的,先査的 blog 表里面的 blog 对象,再查的 user 表; 4)、在前端代码中,处理响应. 把响应中的数据,给写到刚才页面的对应位置上
2.6 退出登录
博客列表/博客详情/博客编辑 导航栏中,都有一个"注销"按钮,让用户点击"注销"的时候,就能够触发一个 HTTP 请求(GET 请求),服务器收到这个 GET 请求的时候,就把会话里的 user 这个Attribute 给删了;由于在判定用户是否是登录状态的逻辑中,需要同时验证会话存在且和这里的 user Attribute 也存在,只要破坏一个上述条件,就可以使登录状态发生改变了. 为啥不直接删除 session 本身,其主要因为servlet 没有提供删除 session 的方法.虽然有间接的方式(session 可以设置过期时间,设置一个非常短的过期时间),也可以起到删除的效果,但是实现效果其实不是特别很好;session 提供了 removeAttribute 这样的方法, 可以把 user 这个 Attribute 给删了
1)、约定前后端交互接口
2)、编写前端代码, 发送请求, 不用写 ajax, 直接就给 a 标签设置 href 属性即可;
3)、编写后端代码,处理这个请求,完成退出登录的操作.
package servlet;
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("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 先拿到会话对象
HttpSession session = req.getSession(false);
if (session == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前您尚未登录");
return;
}
// 再把会话中的 user 的属性给删除掉
session.removeAttribute("user");
// 跳转到博客登录页
resp.sendRedirect("login.html");
}
}
2.7 发布博客
当点击提交的时候,就需要构造 http 请求,把此时的页面中的标题和正文都传输到服务器这过 服务器把这个数据存入数据库即可.此处这里的 http 请求,可以使用 ajax,也可以使用 form (这种填写输入框,提交数据的场景,使用 form 会更方便)
1)、约定前后端交互接口
2)、编写前端代码构造请求. 标题,本身就是一个属性,咱们自己写的 input,给他加上 name 属性也很容易.但是博客正文,是由 editor md 构成的一个编辑器,这里如何添加 name 属性呢:
我们可以将 editor md 和 form 表单配合使用.
如上图所示,这个 div 就是 editor.md 的编辑器的容器,在这个 div 里,设置一个隐藏的 textarea 标签(多行编辑框,把 name 属性加到这个 textarea.),并且在初始化 editormd 对象的时候,加上一个对应的属性即可;
请求抓包如下所示:
3)、编写服务器代码, 处理刚才的请求
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 读取请求中的参数
req.setCharacterEncoding("utf8");
String title = req.getParameter("title");
String content = req.getParameter("content");
if (title == null || title.length() == 0 || content == null || content.length() == 0) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前传过来的标题或正文为空! 无法新增博客!");
return;
}
// 2. 从会话中, 拿到当前登录的用户的 userId
HttpSession session = req.getSession(false);
if (session == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前用户未登录");
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前用户未登录");
return;
}
// 3. 构造 blog 对象
Blog blog = new Blog();
blog.setTitle(title);
blog.setContent(content);
// 从会话中拿到 当前正在登录的用户的 userId, 设置进去即可
blog.setUserId(user.getUserId());
// 4. 插入到数据库中
BlogDao blogDao = new BlogDao();
blogDao.insert(blog);
// 5. 返回一个 302 这样的重定向报文, 跳转到博客列表页.
resp.sendRedirect("blog_list.html");
}
3. 项目展示
1、进入登录页面
2、博客列表页及用户信息页
3、博客详情页及作者信息页
ps:关于博客系统就到这里了,如果感兴趣的话就请一键三连哦!!!
相关文章
发表评论