写在前面
Hello,在上一篇中,我们已经实现了对于博客系统的页面构建任务。本次主要解决的问题就是针对这四个界面,实现后端的 servlet 程序,规范前后端交互的接口,编写客户端和服务端代码,处理请求并反馈。博客系统的完整代码已上传至 gitee,见文末链接。
博客系统的页面构建:【JavaEE】前后端分离实现博客系统(页面构建)
文章目录
- 写在前面
- 1 MVC模式与前期准备
- 2 model 层实现
- 2.1 数据库的设计
- 2.2 数据库表对应的实体类实现
- 2.3 JDBC 工具类实现
- 2.4 UserDao 的实现
- 2.5 BlogDao 的实现
- 3 controller 层实现
- 3.1 博客列表页
- 3.2 博客详情页
- 3.3 登录功能
- 3.4 检查用户的登录状态
- 3.5 显示用户信息
- 3.6 注销功能
- 3.7 发布博客功能
- 3.8 删除博客功能
- 写在最后
1 MVC模式与前期准备
MVC(Model View Controller)是一种软件设计的框架模式,它采用模型(Model)-视图(View)-控制器(controller)的方法把业务逻辑、数据与界面显示分离。把众多的业务逻辑聚集到一个部件里面,当然这种比较官方的解释是不能让我们足够清晰的理解什么是MVC的。用通俗的话来讲,MVC的理念就是把数据处理、数据展示(界面)和程序/用户的交互三者分离开的一种编程模式。
而在本文所讲解的博客系统中,所谓的前后端分离,正是采用了这种形式。项目目录分层如下:controller层主要实现人机交互,将用户输入的指令和数据传递给model层,而model层主要包含数据表对应的实体类以及封装了对数据库的基本操作。
首先,我们需要创建一个 maven 项目,并在pom.xml文件进行相应的配置。主要是 servlet、jdbc以及jackson。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<modelVersion>4.0.0</modelVersion>
<groupId>groupId</groupId>
<artifactId>BlogSystem</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.6.1</version>
</dependency>
</dependencies>
</project>
2 model 层实现
2.1 数据库的设计
在博客系统中,主要包含登录功能、注销功能、发布博客、删除博客、博客展示的功能。涉及到的实体即博客和用户。数据库表设计如下,具体见代码注释:
db.sql
create database if not exists blog_system;
use blog_system;
-- 创建一个博客表
drop table if exists blog;
create table blog (
blogId int primary key auto_increment, -- 文章id
title varchar(1024), -- 文章标题
content mediumtext, -- 文章内容
userId int, -- 文章作者id
postTime datetime -- 发布时间
);
-- 创建一个用户表
drop table if exists user;
create table user (
userId int primary key auto_increment,
username varchar(128) unique,
password varchar(128)
);
-- 给 user表 插入一些数据
insert into user values(null, 'pwq', '7777');
insert into user values(null, 'hxh', '7777');
-- 给 blog表 插入一些数据方便测试
insert into blog values(null, 'Java从入门到精通', '什么是Java?', 1, now());
insert into blog values(null, 'C++到底能做什么?', 'C++是什么?', 1, now());
insert into blog values(null, 'Python为什么那么火?', '我怎么知道?', 2, now());
2.2 数据库表对应的实体类实现
User类
每个 user 对象,对应 user 表的一条记录。
public class User {
private int userId = 0;
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;
}
}
Blog类
每个 blog 对象,对应 blog 表的一条记录。
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
/**
* @author 兴趣使然黄小黄
* @version 1.0
* 每个 model.Blog 对象对应 model.Blog 表中的一条记录
*/
@SuppressWarnings({"all"})
public class Blog {
private int blogId;
private String title;
private String content;
private int userId;
private Timestamp postTime;
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 int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
// public Timestamp getPostTime() {
// return postTime;
// }
public String getPostTime(){
// 这里对原先的 getPostTime() 进行修改, 将时间戳转化成格式化字符串并返回
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(postTime);
}
public void setPostTime(Timestamp postTime) {
this.postTime = postTime;
}
}
针对时间戳在后端进行格式化字符串处理,便于前端的展示。
2.3 JDBC 工具类实现
DBUtil 封装了用于获取数据库连接和关闭数据库连接资源的方法,便于 各个实体的Dao 使用,降低代码冗余度。
DBUtil.java
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;
/**
* @author 兴趣使然黄小黄
* @version 1.0
* 用于连接数据库与关闭数据库连接的工具类
*/
@SuppressWarnings({"all"})
public class DBUtil {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/blog_system?characterEncoding=utf8&useSSL=false";
private static final String USERNAME = "root";
private static final String PASSWORD = "xxxxxx";
private static volatile DataSource dataSource = null;
private static DataSource getDataSource(){
if (dataSource == null){
synchronized (DBUtil.class){
if (dataSource == null){
dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL(URL);
((MysqlDataSource)dataSource).setUser(USERNAME);
((MysqlDataSource)dataSource).setPassword(PASSWORD);
}
}
}
return dataSource;
}
// 获取数据库连接
public static Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
// 关闭数据库连接资源
public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){
if (resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
2.4 UserDao 的实现
该类封装了对于 User 表的操作,包括根据用户名或者用户ID返回 user对象。主要用于登录功能与在博客详情页和列表页展示用户信息。具体代码如下:
UserDao.java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author 兴趣使然黄小黄
* @version 1.0
* 封装 User表 相关操作
*/
@SuppressWarnings({"all"})
public class UserDao {
/**
* 登录功能实现,根据用户名来查找用户信息
* @param username 用户名
* @return 返回用户对象
*/
public User selectByName(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();
// user 表中 username 使用 unique 约束,所以结果要么只有一条,要么没有
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 e) {
e.printStackTrace();
} finally {
DBUtil.close(connection, statement, resultSet);
}
return null;
}
/**
* 主要用于博客详情页,根据用户 id 来查找用户信息, 将作者的名字显示出来
* @param userId 用户 id
* @return 返回用户对象
*/
public User selectById(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();
// user 表中 userId 使用 primary key 约束,所以结果要么只有一条,要么没有
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 e) {
e.printStackTrace();
} finally {
DBUtil.close(connection, statement, resultSet);
}
return null;
}
}
2.5 BlogDao 的实现
该类封装了有关 blog 表的操作。包括插入博客,返回博客列表,返回单一一条博客以及删除博客功能。具体见代码与注释:
BlogDao.java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* @author 兴趣使然黄小黄
* @version 1.0
* 封装 Blog表 相关操作
*/
@SuppressWarnings({"all"})
public class BlogDao {
/**
* 插入博客
* @param blog
*/
public void insert(Blog blog){
Connection connection = null;
PreparedStatement statement = null;
try {
connection = DBUtil.getConnection();
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());
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection, statement, null);
}
}
/**
* 能够获取博客表中所有博客的信息(用于博客列表页展示摘要)
* @return 返回博客列表
*/
public List<Blog> selectAll(){
List<Blog> blogs = new ArrayList<>();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select * from blog order by postTime desc";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
Blog blog = new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("title"));
// 如果文章太长, 这里需要对其进行截取, 博客列表页只展示博客摘要信息
String content = resultSet.getString("content");
if (content.length() > 50){
content = content.substring(0, 50) + "...";
}
blog.setContent(resultSet.getString("content"));
blog.setUserId(resultSet.getInt("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
blogs.add(blog);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection, preparedStatement, resultSet);
}
return blogs;
}
/**
* 能够根据 博客id 返回一篇具体的博客
* @param blogId 博客id
* @return 返回具体的博客
*/
public Blog selectOne(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();
if (resultSet.next()){
// 因为是根据主键查询, 所以结果只有一个, 使用 if 即可
Blog blog = new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("title"));
blog.setContent(resultSet.getString("content"));
blog.setUserId(resultSet.getInt("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
return blog;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection, statement, resultSet);
}
return null;
}
/**
* 根据博客 id 从博客表中删除博客
* @param blogId 博客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 e) {
e.printStackTrace();
} finally {
DBUtil.close(connection, statement, null);
}
}
}
3 controller 层实现
无论是博客详情页、博客列表页还是登录功能诸如此类,其核心逻辑都是一样的,具体分为如下几个步骤:
- 约定前后端交互的接口;
- 实现服务器代码,分别为controller层的servlet实现的api,以及model层使用jdbc来操作数据库;
- 实现客户端代码:form / ajax / a标签跳转等。
3.1 博客列表页
该页面用于展示数据库中的博客列表。约定请求:GET/blog,响应为 json 格式。
核心代码如下:
private ObjectMapper objectMapper = new ObjectMapper();
/**
* 用于获取数据库中的博客列表、博客详情
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset=utf8");
BlogDao blogDao = new BlogDao();
// 尝试获取请求中的 blogId, 根据结果区分访问详情页还是博客列表
String param = req.getParameter("blogId");
if (param == null){
// 访问博客列表
// 从数据库中直接查询博客列表, 然后转成 JSON格式 返回即可
List<Blog> blogs = blogDao.selectAll();
// 把 blogs 对象转成 JSON 格式
String respJson = objectMapper.writeValueAsString(blogs);
resp.getWriter().write(respJson);
} else {
int blogId = Integer.parseInt(param);
Blog blog = blogDao.selectOne(blogId);
// System.out.println(blog.getContent());
String respJson = objectMapper.writeValueAsString(blog);
resp.getWriter().write(respJson);
}
}
编写客户端代码:
在页面加载的时候,通过ajax获取数据,并构造标签,挂在dom树上,显示出来。
核心代码如下:
<script>
// 在页面加载的时候, 通过 ajax 给服务器发送数据, 获取到博客列表信息, 并且显示在界面上.
function getBlogList() {
$.ajax({
type: 'get',
url: 'blog',
success: function(body) {
// 获取到的 body 就是一个 js 对象数组, 每个元素就是一个 js 对象, 根据这个对象构造 div
// 1. 先把 .right 里原有的内容给清空
let rightDiv = document.querySelector('.right');
rightDiv.innerHTML = '';
// 2. 遍历 body, 构造出一个个的 blogDiv
for (let blog of body) {
let blogDiv = document.createElement('div');
blogDiv.className = 'blog';
// 构造标题
let titleDiv = document.createElement('div');
titleDiv.className = 'title';
titleDiv.innerHTML = blog.title;
blogDiv.appendChild(titleDiv);
// 构造发布时间
let dateDiv = document.createElement('div');
dateDiv.className = 'date';
dateDiv.innerHTML = blog.postTime;
blogDiv.appendChild(dateDiv);
// 构造博客的摘要
let descDiv = document.createElement('div');
descDiv.className = 'desc';
descDiv.innerHTML = blog.content;
blogDiv.appendChild(descDiv);
// 构造 查看全文
let a = document.createElement('a');
a.innerHTML = '查看全文 >>';
// 此处希望点击之后能够跳转到 博客详情页 !!
// 这个跳转过程需要告知服务器要访问的是哪个博客的详情页.
a.href = 'blog_detail.html?blogId=' + blog.blogId;
blogDiv.appendChild(a);
// 把 blogDiv 挂到 dom 树上!
rightDiv.appendChild(blogDiv);
}
},
error: function() {
alert("获取博客列表失败!");
}
});
}
效果图如下:
3.2 博客详情页
与博客列表页相同,只不过这里只需要返回一个 blog 即可。我们依然约定请求:GET/blog,响应为 json 格式。
客户端核心代码如下:
function getBlogDetail(){
$.ajax({
type: 'get',
// location.search 拿到了形如 '?blogId=5' 这样的一段内容
url: 'blog' + location.search,
success: function(body){
// 根据 body 中的内容来构造页面
// 1. 构造博客页标题
let h3 = document.querySelector(".blog-content>h3");
h3.innerHTML = body.title;
// 2. 构造博客发布时间
let dateDiv = document.querySelector(".date");
dateDiv.innerHTML = body.postTime;
// 3. 构造博客正文
// 如果直接把 content 放入 innerHTML 则还是 markdown 格式
// 需要使用 editor.md 进行渲染
editormd.markdownToHTML('content', {
markdown: body.content
});
}
});
}
在博客列表页,可以通过a标签将blogId传到博客详情页,服务端就可以通过get请求获取到blogId并返回响应。
服务端核心代码如下:
在刚刚写好的 doGet 处理中,只需要进行简单的区分即可。
3.3 登录功能
登录功能就相对简单了,约定前后端接口,请求Post/login,响应为 Http/1.1 302,当用户登录成功后跳转到博客列表页。登录失败,则提示相应的信息。
需要注意的是,当用户登录成功后需要创建一个会话 session,用于保存必要的用户信息,便于其他页面使用。例如:验证获取登录状态,展示用户信息等。
核心代码如下:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf8"); // 让 servlet 程序也使用 utf8 的方式解析请求, 避免解析中文时出错
// 1. 获取请求中的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
if (username == null || "".equals(username) || password == null || "".equals(password)){
// 处理用户名和密码为空的情况
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前用户名或密码为空!");
return;
}
// 2. 与数据库中的账号密码进行比对
UserDao userDao = new UserDao();
User user = userDao.selectByName(username);
if (user == null || !user.getPassword().equals(password)){
// 用户名不存在或者密码错误
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("用户名不存在或密码错误!");
return;
}
// 3. 如果比对通过则创建会话
HttpSession session = req.getSession(true);
// 把刚才的用户信息存储到会话中, 这里存储 user 对象
session.setAttribute("user", user);
// 4. 返回一个重定向报文, 跳转到博客列表页
resp.sendRedirect("blog_list.html");
}
客户端核心代码如下:
这里为了方便起见,使用了 form 表单的形式。
效果图如下:
3.4 检查用户的登录状态
当用户处于博客列表页和详情页的时候,需要检查用户的登录状态。如果用户未登录,则给用户弹窗提示,并返回到登录页面。 定义前后端交互接口:
- 请求:Get/login
- 响应:HTTP/1.1 200 OK Json格式的body
如果登录了,就将 user 对象返回给客户端;如果未登录,则构造一个 user 对象,使其 userId = 1,表明该对象无效。方便客户端区分是否登录。
核心代码:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset=utf8");
HttpSession session = req.getSession(false);
if (session == null) {
// 检测会话是否存在, 如果为 null 则未登录
User user = new User();
resp.getWriter().write(objectMapper.writeValueAsString(user));
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
// 如果有会话但是 user 为null, 也视为未登录
user = new User();
resp.getWriter().write(objectMapper.writeValueAsString(user));
return;
}
// 此时已经是登录的状态, 但是需要注意, 不要把密码返回给前端
user.setPassword("");
resp.getWriter().write(objectMapper.writeValueAsString(user));
}
客户端核心代码:
// 通过 GET / login 这个接口来获取当下的登录状态
function getUserInfo(pageName){
$.ajax({
type: 'get',
url: 'login',
success: function(body){
// 判断此处的 body 是不是一个有效的 user 对象(userId = 0无效)
if (body.userId && body.userId > 0) {
// 登录成功
console.log("当前用户登录成功! 用户名:" + body.username);
if (pageName == 'blog_list.html') {
// 更新用户名
changeUsername(body.username);
}
} else {
// 登录失败 提示信息 并跳转到 login.html
alert("当前未登录, 请登录后重试!");
location.assign('blog_login.html');
}
},
error: function(){
alert("当前未登录, 请登录后重试!");
location.assign('blog_login.html');
}
});
}
效果展示:
3.5 显示用户信息
对于博客列表页,我们希望展示的是当前登录用户的信息;对于博客详情页,我们希望展示的是文章的作者信息。约定前后端交互接口,请求:get/authorInfo,响应依然为json格式。
因此,我们对登录状态检查的函数进行改动,添加一个参数,用于区分当前页是博客列表页还是博客详情页。
- 对于博客列表页,我们正常展示用户信息即可;
- 对于博客详情页,我们只需要判断当前用户和作者是否为同一人,如果不是,则更改当前页用户信息为作者信息即可。
核心代码:
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;
/**
* @author 兴趣使然黄小黄
* @version 1.0
* @date 2023/3/14 15:57
*/
@WebServlet("/authorInfo")
@SuppressWarnings({"all"})
public class AuthorServlet extends HttpServlet {
ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset=utf8");
// 该方法用于处理博客详情页的用户信息
String param = req.getParameter("blogId"); // 先获取文章的的 blogId
if (param == null || "".equals(param)) {
// 缺少参数
resp.getWriter().write("{\"ok\": false, \"reason\": \"参数缺失\"}");
return;
}
// 先在数据库中查找对应的 blog 对象, 在根据对应的 blog对象 的 userId 查找 user
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(Integer.parseInt(param));
if (blog == null) {
// 不存在对应的博文
resp.getWriter().write("{\"ok\": false, \"reason\": \"要查询的博客不存在\"}");
return;
}
UserDao userDao = new UserDao();
User author = userDao.selectById(blog.getUserId());
if (author == null) {
// 不存在对应的作者
resp.getWriter().write("{\"ok\": false, \"reason\": \"要查询的作者不存在\"}");
return;
}
// 将用户对象以 json 的形式返回响应
// 避免将密码传到前端
author.setPassword("");
resp.getWriter().write(objectMapper.writeValueAsString(author));
}
}
客户端核心代码:
// 从服务器获取当前作者的用户信息, 并显示在页面上
function getAuthorInfo(user){
$.ajax({
type: 'get',
url: 'authorInfo' + location.search,
success: function(body){
// 此处的 body 就是服务器返回的 user 对象
if (body.username) {
// 如果响应中的 username 存在, 则直接更新到页面上
changeUsername(body.username);
if (user.username == body.username) {
// 如果登录的用户和发布文章的用户为同一个人
// 在详情页的导航栏添加一个删除按钮
let navDiv = document.querySelector(".nav");
let a = document.createElement("a");
a.innerHTML = "删除";
a.href = "blogDelete" + location.search;
navDiv.appendChild(a);
}
} else {
console.log("获取作者信息失败! " + body.reason);
}
}
});
}
3.6 注销功能
注销功能非常简单,定义前后端交互接口,请求:get/logout,响应302跳转到登录页面。请求只需要在用户登录的时候添加一个 a 标签到导航栏即可。
怎样才算登录呢?
- session 会话存在;
- session 中包含 user 属性。
因此,实现注销,只需要将 user 属性从 session 中删除即可。
注销逻辑核心代码如下:
@WebServlet("/logout")
@SuppressWarnings({"all"})
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;
}
// 实现注销
session.removeAttribute("user");
resp.sendRedirect("blog_login.html");
}
}
3.7 发布博客功能
同样,先约定前后端交互接口。请求:POST/blog,以form表单的形式将title和content正文传给服务器。响应:HTTP/1.1 302 发布成功后跳转到博客详情页。
客户端核心代码:
同时需要在 editor.md 的配置部分,加上如下的代码:
服务端核心代码如下:
protected void doPost(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;
}
// utf8 的编码形式解析请求
req.setCharacterEncoding("utf8");
// 发布博客
Blog blog = new Blog();
String title = req.getParameter("title"); // 博客标题
String content = req.getParameter("content");// 博客正文
// 参数不全的情况
if (title == null || "".equals(title) || content == null || "".equals(content)) {
// 反馈客户端参数不全
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("参数不全, 无法发布博客");
return;
}
blog.setTitle(title);
blog.setContent(content);
blog.setUserId(((User) session.getAttribute("user")).getUserId()); // 作者信息
new BlogDao().insert(blog); // 发布
// 跳转
resp.sendRedirect("blog_list.html");
}
效果如下:
3.8 删除博客功能
需要明确的是,只有作者本人才能删除博客,因此,在进入博客详情页的时候,进行判断。如果是作者本人,则添加删除按钮:
服务端删除博客的逻辑如下:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 检验用户是否登录
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;
}
// 2. 获取要删除的博客的 blogId
String blogId = req.getParameter("blogId");
if (blogId == null || "".equals(blogId)) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("参数不正确!");
return;
}
// 3. 获取要删除的博客信息
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
if (blog == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前博客不存在");
return;
}
// 4. 再次校验用户是否为博客的作者
if (blog.getUserId() != user.getUserId()) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("不是您的博客, 无法删除!");
return;
}
// 5. 删除并跳转
blogDao.delete(Integer.parseInt(blogId));
resp.sendRedirect("blog_list.html");
}
写在最后
本文完整代码已上传 gitee,如有需要请自取:https://gitee.com/hxh2001/blog-system
以上便是本文的全部内容啦!创作不易,如果你有任何问题,欢迎私信,感谢您的支持!