• 基于 Servlet 的博客系统



    一、准备工作

    1、创建项目

    这里需要创建一个Maven项目,在 pom.xml 中引入项目的依赖文件(Servlet、Mysql、Jackson),并创建必要的目录结构:

    2、创建包

    为了使代码层次更加清晰,这里采用经典的Web项目设计结构——MVC:

    M:model,表示和数据相关的部分。
    V:view,表示和界面相关的部分。
    C:controller,表示数据和界面之间的业务逻辑。

    因此在后端业务逻辑方面,在Java目录下创建两个包,分别是 model,存放和数据相关的逻辑代码;controller,存放前后端交互的业务逻辑。对于博客的前端页面部分,可以在导入时,直接放到 webapp 目录下。

    3、导入前端静态页面

    在这里插入图片描述

    二、数据库设计

    对于当前博客系统的数据库设计相对比较简单,主要涉及到两个实体,分别是 博客用户,它们之间的 ER 关系图如下以及各自的表结构如下所示:

    1、blog(博客表)

    列名数据类型描述
    blogIdINT博客ID(主键)
    titleVARCHAR博客标题
    contentVARCHAR博客内容
    userIdINT用户ID(外键)
    postTimeDATETIME发布时间

    2、user(用户表)

    列名数据类型描述
    userIdINT用户ID(主键)
    usernameVARCHAR用户名
    passwordVARCHAR密码

    3、建库建表的 SQL 语句

    create database if not exists blog_system charset utf8;;
    
    use blog_system;
    
    drop table if exists users;
    create table users (
        userId int primary key auto_increment,
        username varchar(50) unique,
        password varchar(50)
    );
    
    drop table if exists blogs;
    create table blogs (
        blogId int primary key auto_increment,
        title varchar(32),
        content varchar(4096),
        postTime datetime,
        userId int,
        foreign key(userId) references users(userId)
    );
    
    -- 为了方便后续调试,这里插入一些初始内容
    insert into users values(null,"张三","123"),(null,"李四","456");
    
    insert into blogs values(null,"我的第一篇博客","编程之路,道阻且长。","2022-4-26 14:22:00",1);
    insert into blogs values(null,"我的第一篇博客","C生万物,编程之本。","2022-5-26 14:22:00",1);
    insert into blogs values(null,"我的第一篇博客","Java 面向对象。","2022-6-26 14:22:00",1);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    注意:一般对于建表的 sql 都会单独搞个 .sql 文件来保存。因为后续程序可能需要在不同的主机上部署,部署的时候就需要在对应的主机上把数据库也给创建好。把建表 sql 保存好,方便后续在不同的机器上进行建库建表。

    三、封装数据库操作

    1、为什么要封装数据库?

    在解答这个问题之前,我们先假设在项目中不封装数据库操作的情形:倘若我们对数据库不做任何的封装,试想一下当我们在后续的业务逻辑中,比如想要查询数据库的数据、或是想要向数据库中插入一些数据,无论我们针对数据库进行任何操作,都需要进行 JDBC 的五个步骤:

    1. 创建并初始化一个数据源
    2. 和数据库服务器建立连接
    3. 构造SQL语句
    4. 执行SQL语句
    5. 释放必要的资源

    在业务逻辑中,像这样和数据库的交互操作可能有很多,如果每个操作我们都按部就班地进行 JDBC,在代码层面将会是非常冗余,并且代码的可读性将会大大降低,更糟糕的是这样做还会使得我们在开发中不能专注于业务逻辑,大大降低开发效率。

    恰恰相反,我们对数据库相关逻辑进行封装,对外提供接口方法,不仅使代码更加简洁、增加代码的可读性,而且可以使我们专注于业务逻辑的开发,提升开发效率。

    2、封装数据库的连接/关闭操作

    这里创建一个 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;
    
    // 封装数据库的 连接/关闭 操作
    public class DBUtil {
    
        // 1.创建并初始化一个数据源
        // 这个类中需要提供 DataSource,而 DataSource 对于一个项目来说,存在一个就行,因此需要使用单例。
        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("jdbc:mysql://127.0.0.1:3306/blog_system?characterEncoding=utf8&useSSL=false");
                        ((MysqlDataSource)dataSource).setUser("root");
                        ((MysqlDataSource)dataSource).setPassword("123456");
                    }
                }
            }
            return dataSource;
        }
    
        // 2.和数据库服务器建立连接
        public static Connection getConnection() throws SQLException {
            return getDataSource().getConnection();
        }
    
        // 3.释放必要的资源
        public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    3、创建实体类

    实体类是为了将数据库中的表结构映射到代码中,方便后续进行数据的操作和管理。对于实体类的每一行数据都是,对应数据库表中的一行记录。由于我们的数据库中有两张表:users、blogs,因此我们根据表结构分别创建 User 实体类、Blog 实体类:

    User 实体类

    package model;
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    Blog 实体类

    package model;
    import java.sql.Timestamp;
    public class Blog {
        private int blogId;
        private String title;
        private String content;
        private Timestamp postTime;
        private int userId;
    
        public int getUserId() {
            return userId;
        }
    
        public void setUserId(int userId) {
            this.userId = 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 Timestamp getPostTime() {
            return postTime;
        }
    
        public void setPostTime(Timestamp postTime) {
            this.postTime = postTime;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    4、封装必要的增删改查操作

    (1)封装 users 表的查询操作
    由于当期博客系统不涉及用户的注册、销户,因此仅封装必要的查询操作即可:

    package model;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    public class UserDao {
        // 1.根据用户 id 查询用户
        public User selectUserById(int userId) {
            Connection connection = null;
            PreparedStatement statement = null;
            ResultSet resultSet = null;
            try {
                connection = DBUtil.getConnection();
                String sql = "select * from users 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 e) {
                e.printStackTrace();
            } finally {
                DBUtil.close(connection,statement,resultSet);
            }
            return null;
        }
    
        // 2.根据用户名查询哟用户
        public User selectUserByName(String username) {
            Connection connection = null;
            PreparedStatement statement = null;
            ResultSet resultSet = null;
            try {
                connection = DBUtil.getConnection();
                String sql = "select * from users 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 e) {
                e.printStackTrace();
            } finally {
                DBUtil.close(connection,statement,resultSet);
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    (2)封装 blogs 表的增删改查操作

    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;
    public class BlogDao {
        // 1.把一个 Blog 对象插入到数据库中.
        public void insert(Blog blog) {
            Connection connection = null;
            PreparedStatement statement = null;
            try {
                connection = DBUtil.getConnection();
                String sql = "insert into blogs values(null,?,?,?,?)";
                statement = connection.prepareStatement(sql);
                statement.setString(1, blog.getTitle());
                statement.setString(2, blog.getContent());
                statement.setTimestamp(3, blog.getPostTime());
                statement.setInt(4,blog.getUserId());
    
                // 执行sql
                statement.executeUpdate();
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                DBUtil.close(connection,statement,null);
            }
        }
    
        // 2.查询 blog 表中所有的博客数据.
        public List<Blog> selectAll() {
            List<Blog> lists = new ArrayList<>();
            Connection connection = null;
            PreparedStatement statement = null;
            ResultSet resultSet = null;
            try {
                connection = DBUtil.getConnection();
                String sql = "select * from blogs order by postTime desc";
                statement = connection.prepareStatement(sql);
    
                // 执行sql
                resultSet = statement.executeQuery();
    
                while (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"));
                    lists.add(blog);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                DBUtil.close(connection,statement,resultSet);
            }
            return lists;
        }
    
        // 3.指定一个博客id 来查询对应的博客
        public Blog selectBlogById(int blogId) {
            Connection connection = null;
            PreparedStatement statement = null;
            ResultSet resultSet = null;
            try {
                connection = DBUtil.getConnection();
                String sql = "select * from blogs where blogId = ?";
                statement = connection.prepareStatement(sql);
                statement.setInt(1,blogId);
    
                // 执行sql
                resultSet = statement.executeQuery();
    
                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 e) {
                e.printStackTrace();
            } finally {
                DBUtil.close(connection,statement,resultSet);
            }
            return null;
        }
    
        // 4.指定 博客id 来删除博客
        public void deleteBlogById(int blogId) {
            Connection connection = null;
            PreparedStatement statement = null;
            try {
                connection = DBUtil.getConnection();
                String sql = "delete from blogs where blogId = ?";
                statement = connection.prepareStatement(sql);
                statement.setInt(1,blogId);
    
                // 执行sql
                statement.executeUpdate();
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                DBUtil.close(connection,statement,null);
            }
        }
    
        // 5.指定博客 id 来修改博客内容
        public void updateBlog(int blogId,String newContent) {
            Connection connection = null;
            PreparedStatement statement = null;
            try {
                connection = DBUtil.getConnection();
                String sql = "update blogs set content = ? where id = ?";
                statement = connection.prepareStatement(sql);
                statement.setString(1,newContent);
                statement.setInt(2,blogId);
    
                // 执行sql
                statement.executeUpdate();
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                DBUtil.close(connection,statement,null);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    四、前后端业务逻辑实现

    1、登录功能

    (1)约定前后端交互接口

    我们约定:通过 form 表单发送一个 post 请求,服务端根据请求内容验证用户登录。

    (2)编写后端代码

    @WebServlet("/login")
    public class LoginServlet extends HttpServlet {
        @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.equals("") || 
            password == null || password.equals("")) {
                resp.setContentType("text/html;charset=utf8");
                resp.getWriter().write("

    缺少用户名或密码!

    "); return; } // 2.读取数据库,检查用户名或密码是否存在 UserDao userDao = new UserDao(); User user = userDao.selectUserByName(username); if (user == null) { // 用户不存在 resp.setContentType("text/html;charset=utf8"); resp.getWriter().write("

    用户名或密码错误!

    "); return; } if (!user.getPassword().equals(password)) { // 密码错误 resp.setContentType("text/html;charset=utf8"); resp.getWriter().write("

    用户名或密码错误!

    "); return; } // 3.用户登录成功,设置会话 HttpSession session = req.getSession(true); // 把用户对象存储到 session 中了. 下次用户访问其他页面, // 就可以直接拿到会话, 进一步拿到之前的 user 对象 session.setAttribute("user",user); // 4. 返回一个重定向响应, 跳转到博客列表页 resp.sendRedirect("blog_list.html"); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    (3)编写前端代码

    前端代码这里稍作修改即可:将 action 路径补充完整,添加 input 输入框 name 属性。

    <form action="login" method="post">
         <table>
             <tr>
                 <th colspan="2">登 录th>
             tr>
             <tr>
                 <td class="t1">用户名td>
                 <td><input type="text" id="username" name = "username">td>
             tr>
             <tr>
                 <td class="t1">密码td>
                 <td><input type="password" id="password" name = "password">td>
             tr>
             <tr>
                 <td colspan="2"><input type="submit" value="提交" id="submit">td>
             tr>
         table>
     form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2、检查用户登录

    对于一个网站来说,访问网站中的任何页面通常需要是登录状态,如果是未登录则跳转到登录页,要求用户强制登录。

    (1)约定前后端交互接口

    我们约定:当访问博客列表页、详情页、编辑页的时候、使用 AJAX 发送一个 get 请求,服务端根据会话返回一个状态码,在 ajax 的回调函数中,判定响应状态码是否为 403,如果是则使用 location.assign 进行页面跳转。

    (2)编写后端代码

    由于规定的关联路径不变,我们只需要在 LoginServlet 下增加一个 doGet 方法即可:

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
        HttpSession session = req.getSession(false);
        if (session == null) {
            // 会话不存在(未登录)
            resp.setStatus(403);
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            // 会话存在但是用户对象不存在(未登录)
            resp.setStatus(403);
            return;
        }
        // 已登录状态
        resp.setStatus(200);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (3)编写前端代码

    function getLoginStatus() {
        $.ajax({
            type:"get",
            url:"login",
            success:function (body) {
                // 返回 200 时,直接打印日志即可
                console.log("用户已登录!");
            },
            error:function(body) {
                // 返回 403 时,跳转到登录页
                location.assign("login.html");
            }
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3、博客列表

    (1)约定前后端交互接口

    我们约定:浏览器给服务器发送一个 GET /blog 这样的 HTTP 请求时,服务器给浏览器返回了一个 JSON 格式的数据。

    (2)编写后端代码

    @WebServlet("/blog")
    public class BlogServlet extends HttpServlet {
        private ObjectMapper objectMapper = new ObjectMapper();
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
            BlogDao blogDao = new BlogDao();
            List<Blog> blogs = blogDao.selectAll();
            String respString = objectMapper.writeValueAsString(blogs);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (3)编写前端代码

    function getBlogs() {
       $.ajax({
           type:"get",
           url:"blog",
           success:function(body) {
               let containerRight = document.querySelector(".container .right");
               for(let blog of body) {
                   // 构造标签
                   let blogDiv = document.createElement("div");
                   blogDiv.className = "blog";
                   let title = document.createElement("h3");
                   title.innerHTML = blog.title;
                   let dateDiv = document.createElement("div");
                   dateDiv.className = "date";
                   dateDiv.innerHTML = blog.postTime;
                   let descDiv = document.createElement("div");
                   descDiv.className = "desc";
                   descDiv.innerHTML = blog.content;
                   let a = document.createElement("a");
                   a.href = "blog_detail.html?blogId="+blog.blogId;
                   a.innerHTML = "查看全文 >>";
    
                   // 组合标签
                   blogDiv.appendChild(title);
                   blogDiv.appendChild(dateDiv);
                   blogDiv.appendChild(descDiv);
                   blogDiv.appendChild(a);
                   containerRight.appendChild(blogDiv);
    
               }
           }
       })
    }
    // 调用方法
    getBlogs();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    注意事项:

    (1)时间格式化
    在使用 getPostTime() 获取博客发布时间时,我们很可能得到一个时间戳,因此需要,对getPostTime 进行必要的格式化处理:

    public String getPostTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(postTime);
    }
    
    • 1
    • 2
    • 3
    • 4

    (2)提取文章摘要
    文章的摘要通常是文章内容的一部分,由于我们使用的博客编辑方式是 Markdown,如果直接提取文章部分内容,可能出现一些语法符号,为此我们可以使用第三方库 commonmark-java,将 Markdown 文本转换为纯文本:

    import org.commonmark.node.Node;
    import org.commonmark.parser.Parser;
    import org.commonmark.renderer.text.TextContentRenderer;
    // markdown 纯文本转换方法
    public static String convert(String markdown) {
        if (markdown == null || markdown.isEmpty()) {
            return "";
        }
    
        // 创建 Markdown 解析器
        Parser parser = Parser.builder().build();
    
        // 解析 Markdown 内容并生成 AST(抽象语法树)
        Node document = parser.parse(markdown);
    
        // 创建纯文本渲染器,并禁用生成的纯文本中的空行
        TextContentRenderer textRenderer = TextContentRenderer.builder()
                .stripNewlines(true)
                .build();
    
        // 渲染 AST 并以纯文本格式输出
        return textRenderer.render(document);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    由于摘要只在博客列表页(blog_list.html)展示,并且每次调用selectAll()方法,所以我们可以在方法内部增加一些逻辑实现文章摘要的转换和提取操作:

    	 // ...这里省略上文
         String content = resultSet.getString("content");
         // 这里简单设置一下文章的摘要,将markdown文本转换为纯文本,并摘取前200字符
         content = convert(content);
         if (content.length() > 200) {
             content = content.substring(0,200) + ". . .";
         }
         // ...这里省略下文
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4、博客列表页用户信息

    博客列表页展示的登录用户的信息。

    (1)约定前后端交互接口

    我们约定:当访问博客列表页使用 AJAX 发送一个 get 请求,服务端将用户信息以 Json 格式返回。

    (2)编写后端代码

    这里在 LoginServlet 的基础上做出修改,如果用户是登录状态,则将用户信息以 Json 格式一起返回。

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
            HttpSession session = req.getSession(false);
            if (session == null) {
                // 会话不存在(未登录)
                resp.setStatus(403);
                return;
            }
            User user = (User) session.getAttribute("user");
            if (user == null) {
                // 会话存在但是用户对象不存在(未登录)
                resp.setStatus(403);
                return;
            }
            // 已登录状态
            resp.setStatus(200);
            // 密码置空,防止泄漏
            user.setPassword("");
            String respString = objectMapper.writeValueAsString(user);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    (3)编写前端代码

    这里可以复用 getLoginStatus 方法,登录成功后,将用户信息显示在页面上。

    function getLoginStatus() {
        $.ajax({
            type:"get",
            url:"login",
            success:function (body) {
                // 返回 200 时,将用户信息显示到页面上
                console.log("用户已登录!");
                let userName = document.querySelector(".card h3");
                userName.innerHTML = body.username;
            },
            error:function(body) {
                // 返回 403 时,跳转到登录页
                location.assign("login.html");
            }
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    5、博客详情页

    (1)约定前后端交互接口

    我们约定:浏览器给服务器发送一个 GET /blog?blogId=xxx 这样的 HTTP 请求时,服务器给浏览器返回了一个 JSON 格式的数据。

    (2)编写后端代码

    在获取每一篇博客的时候,由于约定请求地址形如:blog?blogId=xxx,相比于访问博客列表页多了一个 string query,但他们关联的路径都是 blog,因此我们只需要在 BlogServlet 稍作调整即可:

    @WebServlet("/blog")
    public class BlogServlet extends HttpServlet {
        private ObjectMapper objectMapper = new ObjectMapper();
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
            BlogDao blogDao = new BlogDao();
            // 获取 query string
            String blogId = req.getParameter("blogId");
            if (blogId == null) {
                List<Blog> blogs = blogDao.selectAll();
                String respString = objectMapper.writeValueAsString(blogs);
                resp.setContentType("application/json;charset=utf8");
                resp.getWriter().write(respString);
            } else {
                Blog blog = blogDao.selectBlogById(Integer.parseInt(blogId));
                String respString = objectMapper.writeValueAsString(blog);
                resp.setContentType("application/json;charset=utf8");
                resp.getWriter().write(respString);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    (3)编写前端代码

    function getBlog() {
         $.ajax({
             type:"get",
             // location.search是用于获取当前页面 URL 的查询字符串部分
             url:"blog"+ location.search,
             success: function(body) {
                 // 设置博客的标题
                 let h3 = document.querySelector('.right h3');
                 h3.innerHTML = body.title;
                 // 设置发布时间
                 let dateDiv = document.querySelector('.right .date');
                 dateDiv.innerHTML = body.postTime;
                 // 设置正文. 正文内容应该是 markdown 格式的数据. 
                 // 此处要显示的应该是渲染过的 markdown 的内容, 而不是 markdown 的原始字符串. 
                 // 第一个参数, 是一个 html 元素的 id, 接下来渲染的结果会放到对应的 元素中. 
                 editormd.markdownToHTML('content', {markdown: body.content});
             }
    
         })
     }
     // 调用方法
     getBlog();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    6、博客详情页用户信息

    博客详情页展示的当前文章的作者信息。

    (1)约定前后端交互接口

    我们约定:当访问博客列表页使用 AJAX 发送一个 get 请求,服务端将用户信息以 Json 格式返回。

    (2)编写后端代码

    @WebServlet("/user")
    public class UserServlet extends HttpServlet {
        private ObjectMapper objectMapper = new ObjectMapper();
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
            // 获取 blogId
            String blogId = req.getParameter("blogId");
            if (blogId == null || blogId.equals("")) {
                // 直接返回一个 userId 为 0 的对象,因为最终返回的是一个 json 数据.
                // 如果返回一个 html,前端处理就要麻烦
                String respJson = objectMapper.writeValueAsString(new User());
                resp.setContentType("application/json; charset=utf8");
                resp.getWriter().write(respJson);
                System.out.println("参数给定的 blogId 为空!");
                return;
            }
            // 2. 查询数据库, 查询对应的 Blog 对象
            BlogDao blogDao = new BlogDao();
            Blog blog = blogDao.selectBlogById(Integer.parseInt(blogId));
            if (blog == null) {
                // 同上
                String respJson = objectMapper.writeValueAsString(new User());
                resp.setContentType("application/json; charset=utf8");
                resp.getWriter().write(respJson);
                System.out.println("参数给定的 blogId 不存在!");
                return;
            }
            // 3. 根据 blog 中的 userId, 查询作者信息.
            int userId = blog.getUserId();
            UserDao userDao = new UserDao();
            User user = userDao.selectUserById(userId);
            if (user == null) {
                // 同上
                String respJson = objectMapper.writeValueAsString(new User());
                resp.setContentType("application/json; charset=utf8");
                resp.getWriter().write(respJson);
                System.out.println("该博客对应的作者不存在!");
                return;
            }
            // 4. 把 user 对象返回给页面
            user.setPassword("");
            String respString = objectMapper.writeValueAsString(user);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    (3)编写前端代码

    function getAuthor () {
        $.ajax({
            type:"get",
            url:"user"+ location.search,
            success:function(body) {
                let userName = document.querySelector(".card h3");
                userName.innerHTML = body.username;
            }
        })
    
    }
    // 调用方法
    getAuthor();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    7、发布博客

    在博客编辑页,点击发布按钮,用户编写的博客标题、正文、系统时间就可以保存到数据库中,后续就可以在博客列表页和博客详情页中进行访问了。

    (1)约定前后端交互接口

    我们约定:我们通过 form 表单发送一个 post 请求,服务端将请求中的内容保存到数据库中。

    (2)编写后端代码

    @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
            req.setCharacterEncoding("utf8");
            // 1.从请求中拿到标题和正文
            String title = req.getParameter("title");
            String content = req.getParameter("content");
            if (title == null || title.equals("") || content == null || content.equals("")) {
                String html = "

    title 或者 content 为空! 新增博客失败!

    "
    ; resp.setContentType("text/html; charset=utf8"); resp.getWriter().write(html); return; } // 2.从会话中拿到作者 id HttpSession session = req.getSession(false); // 因为只有登录了才能提交博客,因此此时session一定不为空 User user = (User) session.getAttribute("user"); int userId = user.getUserId(); // 3.构造 blog 对象 Blog blog = new Blog(); blog.setUserId(userId); blog.setTitle(title); blog.setContent(content); blog.setPostTime(new Timestamp(System.currentTimeMillis())); // 4.将 blog 插入到数据库中 BlogDao blogDao = new BlogDao(); blogDao.insert(blog); // 5.跳转到博客列表页 resp.sendRedirect("blog_list.html"); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    (3)编写前端代码

    完善 form 表单

    <form action="blog" method="post">
        <div class="title">
            <input type="text" id="title_input" placeholder="在这里写下文章标题" name="title">
            <input type="submit" id="submit">
        div>
        <div id="editor">
            
            <textarea name="content" style="display: none;">textarea>
        div>
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    调整 editormd

     var editor = editormd("editor", {
                // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. 
                width: "100%",
                // 设定编辑器高度
                height: "calc(100% - 50px)",
                // 编辑器中的初始内容
                markdown: "# 在这里写下一篇博客",
                // 指定 editor.md 依赖的插件路径
                path: "editor.md/lib/",
                saveHtmlToTextarea:true
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    8、注销功能

    (1)约定前后端交互接口

    我们约定:浏览器通过 a 标签给服务器发送一个 GET /logout 这样的 HTTP 请求时,服务端删除会话并将页面跳转到登录页。

    (2)编写后端代码

    我们这里主要通过删除 session 对象中的 user 来实现“注销”的目的(在检查登录状态的逻辑中对 user 做出了判定)。

    @WebServlet("/logout")
    public class LogoutServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
            // 1.删除session中的会话信息:user
            HttpSession session = req.getSession(false);
            session.removeAttribute("user");
            // 2.跳转到登录页面
            resp.sendRedirect("login.html");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (3)编写前端代码

    这里只需要填写一下 href 即可。

    <a href="logout">注销a>
    
    • 1

    五、总结

    本篇文章到这里就结束了,为了大家更容易理解,文章中展示了代码的的具体实现,这也就导致整体内容有点长。最后回顾一下本篇内容,本篇主要介绍了【基于Servlet的博客系统】,带着大家从前期准备工作开始,一步步实现了整个项目的构建,希望有需要的小伙伴看完能有所收获。

    最后大家需要明白,当前的项目是基于 Servlet 实现的,有很多地方在实现上还不够“优雅”,还存在着优化和拓展的空间。那么如何让项目更“优雅”呢?答案就是将项目改造为SpringBoot。那么什么又是SpringBoot?Spring 又是什么?我们下篇文章见分晓!敬请期待吧…

  • 相关阅读:
    【flask入门系列】Flask-SQLAlchemy的安装与配置
    Google刚刚推出了图神经网络Tensorflow-GNN
    LIME Low light Image Enhancement via Illumination Map Estimation
    【21天python打卡】第13天 网络爬虫(4)
    【无标题】
    VB、C#、VC使用条件编译自动选择结构体对齐方式
    OpenAI最新回应:没有人会为了尽快取得成功而“偷工减料”
    阿里云CPaaS,上榜Gartner全球代表服务商
    烤瓷牙修复牙齿的种类都有哪些?
    tensorrt部署深度学习模型
  • 原文地址:https://blog.csdn.net/LEE180501/article/details/132463552