【JavaEE】前后端分离实现博客系统(后端实现)

写在前面

 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 层实现

  无论是博客详情页、博客列表页还是登录功能诸如此类,其核心逻辑都是一样的,具体分为如下几个步骤:

  1. 约定前后端交互的接口;
  2. 实现服务器代码,分别为controller层的servlet实现的api,以及model层使用jdbc来操作数据库;
  3. 实现客户端代码: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 = '查看全文 &gt;&gt;';
                        // 此处希望点击之后能够跳转到 博客详情页 !!
                        // 这个跳转过程需要告知服务器要访问的是哪个博客的详情页. 
                        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 标签到导航栏即可。

怎样才算登录呢?

  1. session 会话存在;
  2. 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
 以上便是本文的全部内容啦!创作不易,如果你有任何问题,欢迎私信,感谢您的支持!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/736.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

响应式编程详解,带你熟悉Reactor响应式编程

文章目录一、什么是响应式编程1、Java的流和响应式流2、Java中响应式的使用3、Reactor中响应式流的基本接口4、Reactor中响应式接口的基本使用二、初始Reactor1、Flux和Mono的基本介绍2、引入Reactor依赖3、响应式类型的创建4、响应式类型的组合&#xff08;1&#xff09;使用m…

【C语言蓝桥杯每日一题】——数字三角形

【C语言蓝桥杯每日一题】—— 数字三角形&#x1f60e;前言&#x1f64c;数字三角形&#x1f64c;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&#xff1a;全神贯注的上吧&#xff01;&#xff01;&#xff01; &#x1f60a…

QEMU启动ARM32 Linux内核

目录前言前置知识ARM Versatile Express开发板简介ARM处理器家族简介安装qemu-system-arm安装交叉编译工具交叉编译ARM32 Linux内核交叉编译ARM32 Busybox使用busybox制作initramfs使用QEMU启动ARM32 Linux内核模拟vexpress-a9开发板模拟vexpress-a15开发板参考前言 本文介绍采…

编译原理

文章目录绪论第1章 绪论1.什么是编译2.编译系统的结构3.词法分析第2章 语言及其文法字母表 ∑\sum∑概念终结符非终结符产生式文法Chomsky文法分类体系0型文法 &#xff08;Type-0 Grammar&#xff09;1型文法&#xff08;Type-1 Grammar&#xff09;2型文法&#xff08;Type-2…

JAVA开发与JAVA(一文学会使用ElasticSearch)

在web网站的架设中特别是数据量大的网站或者APP小程序需要搜索或者全文检索的场景&#xff0c;几乎都需要借助ElasticSearch来作为全文检索引擎&#xff0c;以提高网站的搜索效率和性能。 这一节&#xff0c;我们通过一篇文章介绍&#xff0c;使大家通过一文就学会使用Elastic…

python 函数:定义、调用、参数、返回值、嵌套、变量的作用域(局部变量、全局变量)、global、匿名函数lambda

函数可以将我们的程序分解成最小的模块&#xff0c;避免重复使用。函数内部的代码&#xff0c;只有被调用的时候才会执行。 函数的定义&#xff08;def就是define&#xff09;&#xff1a; 格式&#xff1a;def 函数名(): 函数封装的代码 函数的调用&#xff1a; 格式&…

大学生考研的意义?

当我拿起笔头&#xff0c;准备写这个话题时&#xff0c;心里是非常难受的&#xff0c;因为看到太多的学生在最好的年华&#xff0c;在自由的大学本应该开拓知识&#xff0c;提升认知&#xff0c;动手实践&#xff0c;不断尝试和试错&#xff0c;不断历练自己跳出学生思维圈&…

15000 字的 SQL 语句大全 第一部分

一、基础 1、说明&#xff1a;创建数据库CREATE DATABASE database-name 2、说明&#xff1a;删除数据库drop database dbname 3、说明&#xff1a;备份sql server--- 创建 备份数据的 device USE master EXEC sp_addumpdevice disk, testBack, c:\mssql7backup\MyNwind_1.dat …

数据结构--二叉树

目录1.树概念及结构1.1数的概念1.2数的表示2.二叉树概念及结构2.1二叉树的概念2.2数据结构中的二叉树2.3特殊的二叉树2.4二叉树的存储结构2.4.1顺序存储2.4.2链式存储2.5二叉树的性质3.堆的概念及结构3.1堆的实现3.1.1堆的创建3.1.2堆的插入3.1.3堆顶的删除3.1.4堆的代码实现3.…

蓝桥杯刷题冲刺 | 倒计时26天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录1.路径2.特别数的和3.MP3储存4.求和1.路径 题目 链接&#xff1a; 路径 - 蓝桥云课 (lanqiao.cn…

算法学习之二分查找

&#x1f383;个人主页&#x1f383;&#xff1a;勇敢的小牛儿 &#x1f9e8;推荐专栏&#x1f9e8;&#xff1a;C语言知识点 ✨座右铭✨&#xff1a;敢于尝试才有机会 ⚠️今日鸡汤⚠️&#xff1a;Is the true wisdom fortitude ambition. -- Napoleon 真正的才智是刚毅的志向…

【云原生·Docker】常用命令

目录 &#x1f341;1、管理命令 &#x1f341;2、帮助命令 &#x1f341;3、镜像命令 &#x1f341;4、容器命令 &#x1f342;4.1.查看容器 &#x1f342;4.2.创建容器 &#x1f342;4.3.删除容器 &#x1f342;4.4.拷贝文件 &#x1f342;4.5.查看容器IP &#x1f341;5、部署…

LSTM从入门到精通(形象的图解,详细的代码和注释,完美的数学推导过程)

先附上这篇文章的一个思维导图什么是RNN按照八股文来说&#xff1a;RNN实际上就是一个带有记忆的时间序列的预测模型RNN的细胞结构图如下&#xff1a;softmax激活函数只是我举的一个例子&#xff0c;实际上得到y<t>也可以通过其他的激活函数得到其中a<t-1>代表t-1时…

C语言/动态通讯录

本文使用了malloc、realloc、calloc等和内存开辟有关的函数。 文章目录 前言 二、头文件 三、主界面 四、通讯录功能函数 1.全代码 2.增加联系人 3.删除联系人 4.查找联系人 5.修改联系人 6.展示联系人 7.清空联系人 8.退出通讯录 总结 前言 为了使用通讯录时&#xff0c;可以…

Opencv项目实战:22 物体颜色识别并框选

目录 0、项目介绍 1、效果展示 2、项目搭建 3、项目代码展示与部分讲解 Color_trackbar.py bgr_detector.py test.py 4、项目资源 5、项目总结 0、项目介绍 本次项目要完成的是对物体颜色的识别并框选&#xff0c;有如下功能&#xff1a; &#xff08;1&#xff09;…

线程池的使用:如何写出高效的多线程程序?

目录1.线程池的使用2.编写高效的多线程程序Java提供了Executor框架来支持线程池的实现&#xff0c;通过Executor框架&#xff0c;可以快速地创建和管理线程池&#xff0c;从而更加方便地编写多线程程序。 1.线程池的使用 在使用线程池时&#xff0c;需要注意以下几点&#xff…

GDAL python教程基础篇(7)OGR空间计算

1.空间计算 地理数据处理&#xff08;geoprocessing&#xff09;计算函数&#xff1a; 多边形&#xff08;Polygon&#xff09;&#xff1a; 1、交&#xff1a;poly3.Intersection(poly2) 2、并&#xff1a;poly3.Union(poly2) 3、差&#xff1a;poly3.Difference(poly2) 4、补…

python打包成apk界面设计,python打包成安装文件

大家好&#xff0c;给大家分享一下如何将python程序打包成apk文件&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 1、如何用python制作十分秒加减的apk 如何用python制作十分秒加减的apk&#xff1f;用法:. apk包放入apk文件目录,然后输入…

Linux基础命令大全(下)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…

走进哈希心房

目录 哈希的概念 哈希函数 哈希冲突和解决方法 闭散列 插入 查找 删除 开散列 插入 查找 删除 哈希表&#xff08;开散列&#xff09;整体代码 位图 位图模拟实现思路分析&#xff1a; 位图应用 布隆过滤器 本文介绍unordered系列的关联式容器&#xff0c;unor…