基于 Servlet 的博客系统

news2025/1/17 23:14:18

基于 Servlet 的博客系统

  • 一、准备工作
    • 1、创建项目
    • 2、创建包
    • 3、导入前端静态页面
  • 二、数据库设计
    • 1、blog(博客表)
    • 2、user(用户表)
    • 3、建库建表的 SQL 语句
  • 三、封装数据库操作
    • 1、为什么要封装数据库?
    • 2、封装数据库的连接/关闭操作
    • 3、创建实体类
    • 4、封装必要的增删改查操作
  • 四、前后端业务逻辑实现
    • 1、登录功能
      • (1)约定前后端交互接口
      • (2)编写后端代码
      • (3)编写前端代码
    • 2、检查用户登录
      • (1)约定前后端交互接口
      • (2)编写后端代码
      • (3)编写前端代码
    • 3、博客列表
      • (1)约定前后端交互接口
      • (2)编写后端代码
      • (3)编写前端代码
    • 4、博客列表页用户信息
      • (1)约定前后端交互接口
      • (2)编写后端代码
      • (3)编写前端代码
    • 5、博客详情页
      • (1)约定前后端交互接口
      • (2)编写后端代码
      • (3)编写前端代码
    • 6、博客详情页用户信息
      • (1)约定前后端交互接口
      • (2)编写后端代码
      • (3)编写前端代码
    • 7、发布博客
      • (1)约定前后端交互接口
      • (2)编写后端代码
      • (3)编写前端代码
    • 8、注销功能
      • (1)约定前后端交互接口
      • (2)编写后端代码
      • (3)编写前端代码
  • 五、总结


一、准备工作

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

注意:一般对于建表的 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 又是什么?我们下篇文章见分晓!敬请期待吧…

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最高频的五个面试题

目录 1.JavaSE阶段&#xff1a;谈谈啥是多态 2.数据结构阶段&#xff1a;谈谈哈希表 3.数据库阶段&#xff1a;谈谈事务 4.操作系统阶段&#xff1a;谈谈进程和线程的区别联系 5.网络阶段&#xff1a;TCP三次握手和四次挥手 1.JavaSE阶段&#xff1a;谈谈啥是多态 多态&a…

解决方案-LBS用户位置Redis-GEO附近人/店铺

附近人 windows安装附近人列表功能mysqlredis GEO CNNVD-201511-230 未授权访问python 多线程 redis大端模式与小端模式IP地址的不同表现形式1.字符串表现形式2. 整数表现形式3.大小端模式下的IP地址 0x01 进入python正题Python的socket库1.socket.socket(family,type)2.socket…

【前端学习】—多种方式实现数组拍平(十一)

【前端学习】—多种方式实现数组拍平&#xff08;十一&#xff09; 一、数组拍平 数组拍平也叫数组扁平化、数组拉平、数组降维&#xff0c;指的是把多维数组转化为一维数组。 二、使用场景 复杂场景下的数据处理&#xff08;echarts做大屏数据展示&#xff09; 三、如何实…

华硕U盘盘重装Win10系统步骤图解

重装操作系统是在电脑系统遇到问题或者需要清除所有数据时的一种常见解决方法。但是&#xff0c;很多使用华硕电脑的新手用户&#xff0c;不清楚具体的操作步骤&#xff0c;接下来小编就给介绍关于利用U盘给华硕电脑重装Win10系统的方法&#xff0c;帮助用户们更快地完成系统的…

Shader Graph25-UV移动旋转缩放(自定义函数)

我们将UV操作放入函数内&#xff0c;该函数的内容来自我之前的文章 Shader Graph24-摇晃树叶-CSDN博客 一、UE在Material中右键&#xff0c;新建Material Function。 增加输入 二、新建Material&#xff0c;命名为DemoUVRotationUseFunction Offset为偏移值&#xff0c;Rotat…

VMware虚拟机安装Linux系统的介绍

许多新手连 Windows 的安装都不太熟悉&#xff0c;更别提 Linux 的安装了&#xff1b;即使安装成功了&#xff0c;也有可能破坏现有的 Windows 系统&#xff0c;比如导致硬盘数据丢失、Windows 无法开机等。所以一直以来&#xff0c;安装 Linux 系统都是初学者的噩梦。 然而&a…

填充颜色游戏

无语死了这题。 题目描述 小明最近迷上下面一款游戏。游戏开始时&#xff0c; 系统将随机生成一个 N N 的 正方形棋盘&#xff0c; 棋盘的每个格子都由六种颜色中的一种绘制。在每个步骤中&#xff0c; 玩家选择一种颜色&#xff0c; 并将与左上角连接的所有网格更改为该特…

MSQL系列(四) Mysql实战-索引 Explain实战

Mysql实战-索引 Explain实战 前面我们讲解了索引的存储结构&#xff0c;我们知道了BTree的索引结构&#xff0c;也了解了索引最左侧匹配原则&#xff0c;到底最左侧匹配原则在我们的项目中有什么用&#xff1f;或者说有什么影响&#xff1f;今天我们来实战操作一下&#xff0c…

Yakit工具篇:子域名收集的配置和使用

简介(来自官方文档) 子域名收集是指通过各种技术手段&#xff0c;收集某个主域名下所有的子域名列表。子域名是指在主域名前面添加一级或多级名称的域名。例如&#xff0c;对于主域名example.com&#xff0c;其子域名可以是www.example.com、mail.example.com、blog.example.c…

MIT6.5830 Lab0-Go tutorial实验记录(二)

MIT6.5830 Lab0-Go tutorial实验记录&#xff08;二&#xff09; – WhiteNights Site 标签&#xff1a;Golang, 数据库 在将数据库的数据转换为图表前&#xff0c;我们需要先测试是否能正常访问数据库文件。 写者注 为什么要怎么做&#xff1f;因为这块 非常容易出问题。在h…

计算机基础知识33

进程基础(操作系统中的概念) 进程它是操作系统总最重要的概念&#xff0c;线程也是 进程和线程都是有操作系统来调度使用的&#xff0c;我们程序员是不能控制的 # 进程和程序是两码事、完全不一样 程序&#xff1a;其实一个死的东西、一堆代码就是程序&#xff0c;它也没有生命…

【5】c++11新特性(稳定性和兼容性)—>override关键字

override关键字很简单&#xff0c;就是起到一个检查的作用&#xff0c;父类中有一个虚函数&#xff0c;子类要去重写这个虚函数&#xff0c;那么在子类重写时&#xff0c;函数后面加上override&#xff0c;就会检查子类中重写的这个函数和父类中这个虚函数名是否一样&#xff0…

FISCO过程中存在的的问题

问题1&#xff1a;端口被占 报错&#xff1a;Exceed waiting time. Please try again to start node0 initConfig for P2PInitializer failed,check Port30303,EINFOThrow location unknown (consider using BOOST_THROW_EXCEPTION) Dynamic exception type: boost::exception_…

80026-044-06-R 数字伺服驱动器提供了更好的配置和性能

80026-044-06-R 数字伺服驱动器提供了更好的配置和性能 由于它们的精密性能&#xff0c;伺服驱动器被用于机器人&#xff0c;自动化&#xff0c;数控加工&#xff0c;甚至在过程中制造半导体. 一些运动控制应用使用模拟伺服驱动器&#xff0c;这是一项久经考验的技术。虽然它…

数据结构之顺序表的模拟实现

&#x1f495;"世事犹如书籍&#xff0c;一页页被翻过去。人要向前看&#xff0c;少翻历史旧账。"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;数据结构之顺序表的模拟实现 /*** Created with IntelliJ IDEA.* Description:* User: 绿字* Date:…

【EI会议征稿通知】第四届电网系统与绿色能源国际学术会议(PGSGE 2024)

JPCS独立出版-第四届电网系统与绿色能源国际学术会议&#xff08;PGSGE 2024&#xff09; 2024 4th International Conference on Power Grid Systems and Green Energy 2024年第四届电网系统与绿色能源国际学术会议(PGSGE 2024) 将于2024年01月05-07日在中国厦门召开。本次会…

【计算机网络】第一章——概述

个人主页直达&#xff1a;小白不是程序媛 系列专栏&#xff1a;计算机网络基础 目录 前言 计算机网络概述 概念 功能 组成 分类 标准化工作 性能指标 速率 带宽 吞吐量 时延 时延带宽积 往返时延RTT 利用率 分层 为什么要分层&#xff1f; 分层的基本原则&am…

【Java实战】创建第一个Springboot项目Hello world

没有旗舰版的Idea授权&#xff0c;所以安装了社区版的idea。不知道从何时开始&#xff0c;社区版IDEA的插件不好用了&#xff0c;所以就换了个方法生成Springboot项目。 一 在线生成 选择好对应的选项后&#xff0c;点击生成就可以下载到一个完整的springboot项目了。 二 使用…

倍福tnzip,tszip,tpzip文件的打开方式

文章目录 一. tnzip的打开方式二. tszip打开方法三. tpzip打开方法 一. tnzip的打开方式 打开项目&#xff1a;选择菜单栏 FILE&#xff0c;点击 Open Solution from Archive…&#xff0c;在弹出的 对话框中选择保存好的文件&#xff0c;单击打开。选择展开此项目的路径&…

处理sass-loader安装失败

Vue项目中安装node-sass跟sass-loader 我们在开发中,经常会使用sass语法来编写css&#xff0c;在安装node-sass和sass-loader时&#xff0c;经常会出现错误&#xff08;通常是依赖冲突&#xff09;导致安装失败。因为官方发布的版本号并不是连续的&#xff0c;有些版本与版本之…