基于 Servlet 的博客系统

基于 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());// 执行sqlstatement.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);// 执行sqlresultSet = 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);// 执行sqlresultSet = 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);// 执行sqlstatement.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);// 执行sqlstatement.executeUpdate();} catch (SQLException e) {e.printStackTrace();} finally {DBUtil.close(connection,statement,null);}}
}

四、前后端业务逻辑实现

1、登录功能

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

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

(2)编写后端代码

@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected 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();@Overrideprotected 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 格式一起返回。

    @Overrideprotected 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();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {BlogDao blogDao = new BlogDao();// 获取 query stringString 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();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 获取 blogIdString 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)编写后端代码

@Overrideprotected 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.从会话中拿到作者 idHttpSession 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 {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 1.删除session中的会话信息:userHttpSession 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.mzph.cn/news/109201.shtml

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

相关文章

鸿蒙初体验

下载与安装DevEco Studio 在HarmonyOS应用开发学习之前&#xff0c;需要进行一些准备工作&#xff0c;首先需要完成开发工具DevEco Studio的下载与安装以及环境配置。 进入DevEco Studio下载官网&#xff0c;单击“立即下载”进入下载页面。 DevEco Studio提供了Windows版本和…

小谈设计模式(30)—Java设计模式总结

小谈设计模式&#xff08;30&#xff09;—Java设计模式总结 专栏介绍专栏地址专栏介绍 总括三个主要类别abc 创建型模式&#xff08;Creational Patterns&#xff09;常见的创建型模式单例模式&#xff08;Singleton Pattern&#xff09;工厂模式&#xff08;Factory Pattern&…

嵌入式养成计划-41----C++ auto--lambda表达式--C++中的数据类型转换--C++标准模板库(STL)--list--C++文件操作

九十九、auto 99.1 概念 C11引入了自动类型推导&#xff0c;和Python不一样&#xff0c;C中的自动类型推导&#xff0c;需要auto关键字来引导比如 &#xff1a;auto a 1.2; 会被编译器自动识别为 a 为 double 类型 99.2 作用 auto修饰变量&#xff0c;可以自动推导变量的数…

嵌入式平台的电源总结

本文引注: https://mp.weixin.qq.com/s/PuSxHDFbJjjHEReukLSvyg 1.AC的定义 Alternating Current&#xff08;交流&#xff09;的首字母缩写。AC是大小和极性&#xff08;方向&#xff09;随时间呈周期性变化的电流。电流极性在1秒内的变化次数被称为频率&#xff0c;以Hz为单位…

Elasticsearch小bug记录:term: XXX was completely eliminated by analyzer

问题&#xff1a; 下面这个报错&#xff0c;是在配置同义词的时候报的错&#xff1a;不能识别南京。 {"error": {"root_cause": [{"type": "illegal_argument_exception","reason": "failed to build synonyms"…

Conflence 空间管理

1.创建空间 功能入口&#xff1a; Conflence→空间→创建空间 功能说明&#xff1a; &#xff08;1&#xff09;选择“空白空间”&#xff0c;点击“下一步” &#xff08;2&#xff09;填写空间必要信息&#xff0c;点击“创建”按钮&#xff0c;即可完成空间创建 空间名…

力扣 095. 最长公共子序列(C语言+动态规划)

1. 题目 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字符&#xff08…

什么是云原生?零基础学云原生难吗?

伴随着云计算的浪潮&#xff0c;云原生概念也应运而生&#xff0c;而且火得一塌糊涂&#xff0c;但真正谈起“云原生”&#xff0c;大多数非 IT 从业者的认知往往仅限于将服务应用放入云端&#xff0c;在云上处理业务。实际上&#xff0c;云原生远不止于此。 现在越来越多的企…

SVN服务端客户端安装配置

SVN服务端客户端安装配置 1、服务端下载安装1.1 软件下载1.2 软件安装 2、客户端下载安装2.1 软件下载2.2 软件安装2.2.1 安装2.2.2. 汉化 3、SVN配置3.1 SVN服务器端配置3. 2 SVN客户端配置 1、服务端下载安装 1.1 软件下载 百度网盘链接&#xff1a;VisualSVN-Server-5.3.0…

SAP-QM-采购过程模式与特性检验不匹配QD244

在创建物理样本&#xff0c;维护检验计划的采样过程时报错&#xff0c;从报错看&#xff0c;这个采样过程的评估模式和检验特性不匹配&#xff0c; 评估模式为700 700是根据特征上下线&#xff0c;计算平均值进行评估&#xff0c;是定量特性&#xff0c;经过测试 是检验特性的…

Window 窗口函数 (Spark Sql)

在 Spark SQL 中&#xff0c;Window 函数是一种用于在查询结果集中执行聚合、排序和分析操作的强大工具。它允许你在查询中创建一个窗口&#xff0c;然后对窗口内的数据进行聚合计算。 import org.apache.spark.sql.expressions.Window import org.apache.spark.sql.functions…

Typora使用教程

相关介绍 Typora是一款所写即所得的Markdown编辑器&#xff0c;支持跨平台Window,Linux和Mac都可以。 相关链接 Typora安装包历史版本地址&#xff1a;官网历史版本地址 问题及知识点汇总 关于图片的问题-设置图像自动复制到自己指定文件夹下 问题描述 在写笔记的过程中&am…

【yolov8系列】yolov8的目标检测、实例分割、关节点估计的原理解析

1 YOLO时间线 这里简单列下yolo的发展时间线&#xff0c;对每个版本的提出有个时间概念。 2 yolov8 的简介 工程链接&#xff1a;https://github.com/ultralytics/ultralytics 2.1 yolov8的特点 采用了anchor free方式&#xff0c;去除了先验设置可能不佳带来的影响借鉴Genera…

MongoDB 未授权访问漏洞

简介 MongoDB是一个基于分布式文件存储的数据库&#xff0c;是一个介于关系数据库和非关系数据库之间的产品&#xff0c;它的特点是高性能、易部署、易使用&#xff0c;存储数据非常方便&#xff0c;默认情况下是没有认证的这就导致不熟悉它的研发人员部署后没有做访问控制导致…

智能化安全巡更巡查系统—提升安全管理效率

传统的巡检都是手工完成&#xff0c;记录、拍照&#xff0c;回到办公室打印表单再交给作业队伍整改&#xff0c;再去现场核实复查&#xff0c;流程繁琐&#xff0c;效率低。而且大部分工地为了减少麻烦&#xff0c;人员往往都是口头沟通&#xff0c;存在很大质量风险&#xff0…

模型量化笔记--KL散度量化

KL散度量化 前面介绍的非对称量化中&#xff0c;是将数据中的min值和max值直接映射到[-128, 127]。 同样的&#xff0c;前面介绍的对称量化是将数据的最大绝对值 ∣ m a x ∣ |max| ∣max∣直接映射到127。 上面两种直接映射的方法比较粗暴&#xff0c;而TensorRT中的int8量化…

家中种绿植有什么风水讲究?

现在越来越多的人&#xff0c;都居住在小区高楼里&#xff0c;与绿植的接触也越来越少&#xff0c; 因此&#xff0c;很多人会选择在自己家中种上几株绿植。在家里种植植物&#xff0c;不仅美观&#xff0c;陶冶情操&#xff0c;还能净化空气&#xff0c;为家中增添好的风水。 …

凉鞋的 Unity 笔记 109. 专题一 小结

109. 专题一 小结 在这一篇&#xff0c;我们来对第一个专题做一个小的总结。 到目前为止&#xff0c;大家应该能够感受到此教程的基调。 内容的难度非常简单&#xff0c;接近于零基础的程度&#xff0c;不过通过这些零基础内容所介绍的通识内容其实是笔者好多年的时间一点点…

下拉选择器的树状结构图

类似&#xff1a;【Vue-Treeselect 和 vue3-treeselect】树形下拉框 一&#xff1a;图 二&#xff1a;如果有多层级的数据结构&#xff0c;可以用treeselect插件实现 1、安装&#xff1a; npm install --save riophae/vue-treeselect 2、实现&#xff1a; <el-form ref&qu…

树叶识别系统python+Django网页界面+TensorFlow+算法模型+数据集+图像识别分类

一、介绍 树叶识别系统。使用Python作为主要编程语言开发&#xff0c;通过收集常见的6中树叶&#xff08;‘广玉兰’, ‘杜鹃’, ‘梧桐’, ‘樟叶’, ‘芭蕉’, ‘银杏’&#xff09;图片作为数据集&#xff0c;然后使用TensorFlow搭建ResNet50算法网络模型&#xff0c;通过对…