基于 Servlet 的博客系统

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 基于 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(博客表)

列名 数据类型 描述
blogId INT 博客ID(主键)
title VARCHAR 博客标题
content VARCHAR 博客内容
userId INT 用户ID(外键)
postTime DATETIME 发布时间

2、user(用户表)

列名 数据类型 描述
userId INT 用户ID(主键)
username VARCHAR 用户名
password VARCHAR 密码

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);

注意:一般对于建表的 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();
            }
        }
    }
}

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;
    }
}

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;
    }
}

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;
    }
}

(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、登录功能

(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("<h3>缺少用户名或密码!<h3>");
            return;
        }
        // 2.读取数据库,检查用户名或密码是否存在
        UserDao userDao = new UserDao();
        User user = userDao.selectUserByName(username);
        if (user == null) {
            // 用户不存在
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("<h3>用户名或密码错误!<h3>");
            return;
        }
        if (!user.getPassword().equals(password)) {
            // 密码错误
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("<h3>用户名或密码错误!<h3>");
            return;
        }
        // 3.用户登录成功,设置会话
        HttpSession session = req.getSession(true);
        // 把用户对象存储到 session 中了. 下次用户访问其他页面, 
        // 就可以直接拿到会话, 进一步拿到之前的 user 对象
        session.setAttribute("user",user);
        // 4. 返回一个重定向响应, 跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }
}

(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>


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);
}

(3)编写前端代码

function getLoginStatus() {
    $.ajax({
        type:"get",
        url:"login",
        success:function (body) {
            // 返回 200 时,直接打印日志即可
            console.log("用户已登录!");
        },
        error:function(body) {
            // 返回 403 时,跳转到登录页
            location.assign("login.html");
        }
    })
}

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);
    }
}

(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 = "查看全文 &gt;&gt;";

               // 组合标签
               blogDiv.appendChild(title);
               blogDiv.appendChild(dateDiv);
               blogDiv.appendChild(descDiv);
               blogDiv.appendChild(a);
               containerRight.appendChild(blogDiv);

           }
       }
   })
}
// 调用方法
getBlogs();

注意事项:

(1)时间格式化

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

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

(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);
}

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

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

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);

    }

(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");
        }
    })
}

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);
        }
    }
}

(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();

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);
    }
}

(3)编写前端代码

function getAuthor () {
    $.ajax({
        type:"get",
        url:"user"+ location.search,
        success:function(body) {
            let userName = document.querySelector(".card h3");
            userName.innerHTML = body.username;
        }
    })

}
// 调用方法
getAuthor();

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 = "<h3>title 或者 content 为空! 新增博客失败!</h3>";
            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");
    }

(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标签,用于输入内容 -->
        <textarea name="content" style="display: none;"></textarea>
    </div>
</form>

调整 editormd

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

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");
    }
}

(3)编写前端代码

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

<a href="logout">注销</a>

五、总结

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


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


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
7月前
|
Java
学院管理系统【JSP+Servlet+JavaBean】(Java课设)
学院管理系统【JSP+Servlet+JavaBean】(Java课设)
64 3
学院管理系统【JSP+Servlet+JavaBean】(Java课设)
|
7月前
|
Java
人事管理系统【JSP+Servlet+JavaBean】(Java课设)
人事管理系统【JSP+Servlet+JavaBean】(Java课设)
67 0
|
7月前
银行营业网点管理系统——Servlet包(CityAreaServlet )
银行营业网点管理系统——Servlet包(CityAreaServlet )
|
7月前
|
Java
排课系统【JSP+Servlet+JavaBean】(Java课设)
排课系统【JSP+Servlet+JavaBean】(Java课设)
75 5
|
7月前
|
Java
学校教师管理系统【JSP+Servlet+JavaBean】(Java课设)
学校教师管理系统【JSP+Servlet+JavaBean】(Java课设)
66 2
|
4月前
|
供应链 前端开发 Java
JSP+servlet+mybatis+layui服装库存管理系统(大三上学期课程设计)
这篇文章通过一个服装库存管理系统的实例,展示了在Spring Boot项目中使用Ajax、JSON、layui、MVC架构和iframe等技术,涵盖了注册登录、权限管理、用户管理、库存管理等功能,并提供了系统运行环境和技术要求的详细说明。
JSP+servlet+mybatis+layui服装库存管理系统(大三上学期课程设计)
|
7月前
银行营业网点管理系统——Servlet包(updateServlet )
银行营业网点管理系统——Servlet包(updateServlet )
|
4月前
|
监控 前端开发 Java
揭秘Web开发神器:Servlet、过滤器、拦截器、监听器如何联手打造无敌博客系统,让你的用户欲罢不能!
【8月更文挑战第24天】在Java Web开发中,Servlet、过滤器(Filter)、拦截器(Interceptor,特指Spring MVC中的)及监听器(Listener)协同工作,实现复杂应用逻辑。以博客系统为例,Servlet处理文章详情请求,过滤器(如LoginFilter)检查登录状态并重定向,Spring MVC拦截器(如LoggingInterceptor)提供细粒度控制(如日志记录),监听器(如SessionListener)监控会话生命周期事件。这些组件共同构建出高效、有序的Web应用程序。
42 0
|
7月前
|
Java
新闻发布系统【JSP+Servlet+JavaBean】(Java课设)
新闻发布系统【JSP+Servlet+JavaBean】(Java课设)
58 2
|
7月前
|
Java
学校人员管理系统【JSP+Servlet+JavaBean】(Java课设)
学校人员管理系统【JSP+Servlet+JavaBean】(Java课设)
58 2