🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!欢迎志同道合的朋友一起加油喔🤺🤺🤺
目录
一. 项目简介
1. 项目背景
2. 项目用到的技术
3. 项目功能简单介绍
二. 博客系统页面设计
三. 项目准备工作
前后端交互约定内容的分析
1. 接口路径
2. 请求方法
3. 请求参数
4. 响应数据
前后端交互 构思
四.博客系统中与数据库进行交互的类
设计数据库表
1.DBUtil类(数据库工具类)
2.Blog 类 (博客条目类)
3.User 类 (用户类)
4.BlogDao 类 (访问博客数据的对象)
5.UserDao 类 (访问用户数据的对象)
五. 博客系统的后端接口
1.AuthorServlet 类 (处理关于作者的请求)
2.BlogServlet 类 (处理关于的博客请求)
3.LogoutServlet 类 (处理注销的请求)
4. LoginServlet 类 (处理登录的请求)
六. 前端代码构造请求
1. 登陆页面功能设计
2. 博客列表页面功能设计
3. 博客详情页面功能设计
4. 博客编辑页面功能设计
5. 用户注销功能设计
七. 博客系统设计源码
一. 项目简介
1. 项目背景
在网络学完HTTP协议,前端学完html,css,js,后端学完Servlet开发后,做一个博客系统,巩固一下所学知识,并将所学知识运用到实际当中,以此来进一步提升对学习编程的兴趣
2. 项目用到的技术
- 前端使用到html,css,js,使用ajax技术发送http请求,请求body的格式为json格式
- 后端使用Servlet进行开发
- 使用Mysql数据库保存数据
- 除此还引入了editor.md,editor.md是一个开源的页面markdown编辑器组件
- 采用Maven构建工具搭建项目开发环境
3. 项目功能简单介绍
- 登陆页面:输入用户及密码,点击提交,如果用户或密码错误,会提示用户或密码错误,账号及密码正确则登陆成功,成功后跳转到博客列表页面
- 博客列表页面:博客列表页面展示所有发布过的文章,文章显示最多显示50字,如果想查看全文,则需要点击文章下的显示全文
- 博客详情页面:点击某篇文章的显示全文按钮,则会展示文章的全部内容
- 博客编辑页面:点击博客列表的写博客,会跳转到博客编辑页面,输入文章题目及文章内容点击发布文章,文章即可发布成功,发布成功后会跳转到博客列表页面,可以查看发布过的文章
- 博客注销按钮:点击博客注销按钮,则会跳转到博客登陆页面
二. 博客系统页面设计
这里附上静态页面设计的码云地址,可以点击查看,本篇文章只展示后端代码与前端代码交互的部分,想要查看博客系统页面设计代码,请点击:博客系统页面设计
页面功能展示:
三. 项目准备工作
创建Maven项目在pom.xml中添加项目依赖
- 后端采用Servlet开发
- 数据库使用Mysql
- jackson框架可以进行序列化和反序列化,将java对象和json字符串相互转化
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>20230528</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
</dependencies>
</project>
创建与开发相关的包和类
引入前端资源
- 将前端资源都放在main/webapp目录下,前端资源从上面码云地址中获取,web.xml存放在main/webapp/WEB-INF目录
- 注意我这里的html页面全都存放在一个html文件夹下面了,意味我在HTML页面中直接不能使用@WebServle注解里面的路径来访问Servlet.
例如,如果你的Web应用被部署在http://localhost:8080/blog_system2/上,那么一个带有@WebServlet("/blog")注解的Servlet将会被映射到http://localhost:8080/blog_system2/blog这个URL;但是我的Web应用是被部署在http://localhost:8080/blog_system2/html上了,那么一个带有@WebServlet("/blog")注解的Servlet将会被映射http://localhost:8080/blog_system2/html/blog这个URL,所以在前端代码构造请求时,url填相对路径就是../blog,也可以直接填绝对路径(此时web应用部署在这个项目的哪个文件夹下都不会受影响)
前后端交互约定内容的分析
1. 接口路径
- 前端请求登录:
/login
- 前端请求博客列表:
/blog
- 前端请求某篇博客详情:
/blog + location.search
- 前端请求作者信息:
/author + location.search
- 前端请求注销:
/logout
2. 请求方法
- 登录请求使用POST方法:
form
表单提交登录信息。 - 获取博客列表使用GET方法:通过AJAX发送GET请求。
- 获取博客详情和作者信息使用GET方法:通过AJAX发送GET请求。
- 注销请求使用GET方法:通过超链接发送GET请求。
3. 请求参数
- 登录请求:在登录表单中,通过
name
属性定义的字段名username
和password
,将用户输入的用户名和密码作为参数发送到后端。 - 获取博客列表、博客详情和作者信息:通过URL的查询参数,例如
blogId
用于获取博客详情,username
用于获取作者信息。
4. 响应数据
- 后端返回的响应数据是以JSON格式返回的,前端使用AJAX进行处理。
前后端交互 构思
1. login.html (博客登录页)
- 前端实现步骤:
- 当用户点击登录按钮时,触发表单的提交操作。
- 使用
$.ajax
发送 POST 请求,将用户名和密码作为请求参数发送到后端验证登录。 - 在请求成功的回调函数中,根据后端返回的结果进行判断,如果登录成功,则执行相应的操作(例如跳转到博客列表页),如果登录失败,则进行相应的提示。
- 后端实现步骤:
- 接收前端发送的 POST 请求,获取请求中的用户名和密码参数。
- 在数据库中查询是否存在对应的用户信息,进行用户名和密码的验证。
- 根据验证结果,返回相应的结果给前端,表示登录成功或登录失败。
2. blog_list.html (博客列表页)
- 前端实现步骤:
- 在页面加载时,使用
$.ajax
发送 GET 请求,向后端获取博客列表数据。 - 在请求成功的回调函数中,遍历返回的博客数据,动态构造博客内容并添加到页面中。
- 后端实现步骤:
- 接收前端发送的 GET 请求,无需参数。
- 查询数据库中的博客数据,获取博客列表。
- 将查询结果封装为 JSON 格式,并返回给前端。
3. blog_edit.html (博客编辑页)
- 前端实现步骤:
- 当用户在编辑框中填写完博客标题和内容后,点击发布文章按钮,触发表单的提交操作。
- 使用
$.ajax
发送 POST 请求,将博客标题和内容等数据发送到后端创建新的博客。 - 在请求成功的回调函数中,根据后端返回的结果进行判断,如果博客创建成功,则执行相应的操作(例如跳转到博客详情页),如果创建失败,则进行相应的提示。
- 后端实现步骤:
- 接收前端发送的 POST 请求,获取请求中的博客标题和内容等参数。
- 将获取到的博客信息插入到数据库中,创建新的博客。
- 返回相应的结果给前端,表示博客创建成功或创建失败。
4. blog_detail.html (博客详情页)
- 前端实现步骤:
- 在页面加载时,使用
$.ajax
发送 GET 请求,向后端获取指定博客的详细信息。 - 在请求成功的回调函数中,根据返回的博客数据更新页面上的标题、日期和博客正文内容。
- 后端实现步骤:
- 接收前端发送的 GET 请求,从请求参数中获取博客ID或其他标识符。
- 根据博客ID查询数据库,获取指定博客的详细信息。
- 将查询结果封装为 JSON 格式,并返回给前端。
四.博客系统中与数据库进行交互的类
DBUtil
类:提供了数据库连接和资源释放的工具方法,通过 JDBC 技术与数据库建立连接并执行 SQL 语句。
Blog
类:表示博客对象,包含博客的标题、内容、发布时间等属性,以及相应的 getter 和 setter 方法。
BlogDao
类:封装了对博客表的基本操作方法,包括新增博客、根据博客ID查询博客、查询所有博客列表、删除指定博客等。
User
类:表示用户对象,包含用户的ID、用户名和密码等属性,以及相应的 getter 和 setter 方法。
UserDao
类:封装了对用户表的基本操作方法,包括根据用户ID查询用户信息、根据用户名查询用户信息等。这些类通过在后端实现了与数据库的交互,可以方便地进行用户登录验证、博客发布和查询等操作。
设计数据库表
- 有用户登陆,所以有一张用户表,观察博客列表有显示用户昵称,所以用户表设计有四个字段:用户id,用户名,密码,昵称
- 有文章展示,所以有一张文章表,文章有文章id,标题,发布时间,文章内容,关联用户的外键
- 一个用户可以发布多篇文章,所以用户与文章对应关系为1:m,用户id作为文章表的外键
创建表的时候可以插入一些数据便于后续的测试
--这个文件主要用来写建库建表语句
--一般建议大家,在建表的时候把建表sql保留下来,以备后续部署其他机器的时候就方便了.
create database if not exists java_blog_system;
use java_blog_system;
--删除旧表,重新创建新表,删除旧表是为了防止之前的残留数据对后续的程序有负面影响
drop table if exists user;
drop table if exists blog;
--真正创建表
create table blog (
blogId int primary key auto_increment,
title varchar(128),
content varchar(4096),
postTime datetime,
userId int
);
create table user(
userId int primary key auto_increment,
username varchar(20) unique, --要求用户名和别人不重复
password varchar(20)
);
--构造测试数据
insert into blog values (1,'这是我的第一篇博客','从今天开始我要认真敲代码',now(),1);
insert into blog values (2,'这是我的第二篇博客','从昨天开始我要认真敲代码',now(),1);
insert into blog values (3,'这是我的第三篇博客','从前天开始我要认真敲代码',now(),1);
--构造测试数据
insert into user values(1,"jack","123");
insert into user values(2,"Alice","123");
1.DBUtil类(数据库工具类)
这个类是数据库工具类,主要提供数据库连接和关闭资源的方法。
-
getConnection()
:这个方法是获取数据库连接。它通过DataSource
获取连接,并返回这个连接。如果发生异常,它将打印错误信息并返回 null。 -
close(ResultSet rs, PreparedStatement ps, Connection conn)
:这个方法是关闭资源。它会检查传入的ResultSet
、PreparedStatement
和Connection
是否为 null,如果不为 null,那么就关闭它。如果在关闭资源时发生异常,它将打印错误信息。
//针对用户表提供的基本操作
//由于此处没有写注册的功能, 也就不必 add
//也没有用户删号功能, 也就不必 delete
public class UserDao {
//1.根据 userId 来查询用户信息
public User selectById(int userId) {
//获取连接数据库的对象
Connection connection = null;
//获取执行预编译SQL语句的对象
PreparedStatement statement = null;
//获取遍历结果集合的对象
ResultSet resultSet = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造SQL语句
String sql = "select * from user where userId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,userId);
//3.执行SQL语句
resultSet = statement.executeQuery();
//4.遍历结果结果
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 {
//5.释放必要的资源
DBUtil.close(connection,statement,resultSet);
}
return null;
}
//2. 根据username来查询用户信息(登录的时候)
public User selectByUsername(String username) {
//获取连接数据库的对象
Connection connection = null;
//获取执行预编译SQL语句的对象
PreparedStatement statement = null;
//获取遍历结果集合的对象
ResultSet resultSet = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造SQL语句
String sql = "select * from user where username = ?";
statement = connection.prepareStatement(sql);
statement.setString(1,username);
//3.执行SQL语句
resultSet = statement.executeQuery();
//4.遍历结果结果
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 {
//5.释放必要的资源
DBUtil.close(connection,statement,resultSet);
}
return null;
}
}
2.Blog
类 (博客条目类)
该类代表一个博客条目, 这个类中有五个成员变量:
- blogId:每篇博客的唯一标识,它是数据库表中的主键。
- title:博客的标题。
- content:博客的内容。
- postTime:博客的发表时间,这是一个Timestamp类型的数据。
- userId:发表这篇博客的用户的id。
然后有这些成员变量的 get 和 set 方法,用来获取和设置这些变量的值。
其中, getPostTime
方法返回一个格式化的字符串表示的时间,格式是 "yyyy-MM-dd hh:mm:ss"。
public class Blog {
private int blogId;
private String title;
private String content;
private Timestamp postTime;
private int 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 getPostTimestamp() {
return postTime;
}
public String getPostTime() {
//把时间戳转化成格式化时间
//格式化字符串一定不要背!!!不同的语言不同的库,都有格式化时间的操作不同库的格式化时间字符串设定不同!!!
//SimpleDateFormat M表示月份,N表示分钟
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
return simpleDateFormat.format(postTime);
}
public void setPostTime(Timestamp postTime) {
this.postTime = postTime;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
}
3.User
类 (用户类)
此类代表一个用户,这个类中有三个成员变量:
- userId:每个用户的唯一标识,它是数据库表中的主键。
- username:用户的用户名。
- password:用户的密码。
同样地,这个类有这些成员变量的 get 和 set 方法,用来获取和设置这些变量的值。
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;
}
}
4.BlogDao
类 (访问博客数据的对象)
这是一个数据访问对象, 执行与博客相关的数据库操作, 这个类中有四个方法:
-
add(Blog blog)
:这个方法是添加一篇新的博客。首先获取数据库连接,然后构造SQL语句,通过PreparedStatement
设置参数,最后执行SQL。 -
selectById(int blogId)
:这个方法是根据博客id查询一篇博客。流程和 add 方法类似,只是执行的是查询操作,查询到结果后需要创建 Blog 对象并赋值。 -
selectAll()
:这个方法是查询所有的博客。同样是执行查询操作,不过这个方法需要遍历所有的查询结果,并创建 Blog 对象。 -
delete(int blogId)
:这个方法是删除一篇博客。首先获取数据库连接,然后构造SQL语句,通过PreparedStatement
设置参数,最后执行SQL。
//通过这个类封装对 博客表的基本操作
//此处暂时不涉及到修改博客~~ (修改博客也可以通过 删除/新增 )
public class BlogDao {
//1.新增一个博客
public void add(Blog blog) {
Connection connection = null;
PreparedStatement statement = null;
try {
//1. 数据库建立连接
connection = DBUtil.getConnection();
//2. 构造 SQL
String sql = "insert into blog values(null,?,?,?,?)";
statement = connection.prepareStatement(sql);
statement.setString(1, blog.getTitle());
statement.setString(2,blog.getContent());
statement.setTimestamp(3,blog.getPostTimestamp());
statement.setInt(4,blog.getUserId( ));
//执行 sql
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection,statement,null);
}
}
//2.根据博客 id 来指定查询博客(博客详情页中)
public Blog selectById(int blogId) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造 sql 语句
String sql = "select * from blog where blogId = ?";
//prepareStatement(sql)这个方法见sql语句发送给数据库进行预编译
//返回了一个PreparedStatement对象用于执行预编译的SQL语句
statement = connection.prepareStatement(sql);
statement.setInt(1,blogId);
//3.执行SQL语句
resultSet = statement.executeQuery();
//4.遍历结果集合, 由于 blogID 在 blog 表中是唯一的(它是主键)
// 此时的查询结果, 要么是没有查到任何数据, 要么只有一条记录!!
//此处可以不使用while,直接使用if即可
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 {
//5.释放必要的资源
DBUtil.close(connection,statement,resultSet);
}
return null;
}
//3.直接查出数据库中所有的博客列表(用于博客列表页)
public List<Blog> selectAll() {
List<Blog> blogs = new ArrayList<>();
//连接数据库的对象
Connection connection = null;
//执行预编译SQL语句的对象
PreparedStatement statement = null;
//遍历结果结合的对象
ResultSet resultSet = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造SQL语句 在SQL中加入order by 让 postTime 按降序排序
String sql = "select * from blog order by postTime desc";
//获取执行预编译SQL语句的对象
statement = connection.prepareStatement(sql);
//3. 执行SQL语句
resultSet = statement.executeQuery();
//4.遍历结果结合
while (resultSet.next()) {
Blog blog = new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("title"));
//注意这里的正文!!! 在博客列表页中, 我们不需要把整个正文内容显示出来!!
String content = resultSet.getString("content");
if(content.length() >= 100) {
content = content.substring(0,100) + "...";
}
blog.setContent(content);
blog.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
blogs.add(blog);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//5.释放必要的资源
DBUtil.close(connection,statement,resultSet);
}
return blogs;
}
//4.删除指定博客
public void delete(int blogId) {
Connection connection = null;
PreparedStatement statement = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造SQL语句
String sql = "delete from blog where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, blogId);
//3.执行SQL语句
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//4.释放必要的资源
DBUtil.close(connection, statement, null);
}
}
}
5.UserDao
类 (访问用户数据的对象)
这也是一个数据访问对象, 执行与用户相关的数据库操作, 这个类有两个方法:
-
selectById(int userId)
:这个方法是根据用户id查询用户。流程和上面的方法类似,只是查询的是用户信息。 -
selectByUsername(String username)
:这个方法是根据用户名查询用户。这个方法主要用于用户登录的时候验证用户身份。
//针对用户表提供的基本操作
//由于此处没有写注册的功能, 也就不必 add
//也没有用户删号功能, 也就不必 delete
public class UserDao {
//1.根据 userId 来查询用户信息
public User selectById(int userId) {
//获取连接数据库的对象
Connection connection = null;
//获取执行预编译SQL语句的对象
PreparedStatement statement = null;
//获取遍历结果集合的对象
ResultSet resultSet = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造SQL语句
String sql = "select * from user where userId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,userId);
//3.执行SQL语句
resultSet = statement.executeQuery();
//4.遍历结果结果
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 {
//5.释放必要的资源
DBUtil.close(connection,statement,resultSet);
}
return null;
}
//2. 根据username来查询用户信息(登录的时候)
public User selectByUsername(String username) {
//获取连接数据库的对象
Connection connection = null;
//获取执行预编译SQL语句的对象
PreparedStatement statement = null;
//获取遍历结果集合的对象
ResultSet resultSet = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造SQL语句
String sql = "select * from user where username = ?";
statement = connection.prepareStatement(sql);
statement.setString(1,username);
//3.执行SQL语句
resultSet = statement.executeQuery();
//4.遍历结果结果
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 {
//5.释放必要的资源
DBUtil.close(connection,statement,resultSet);
}
return null;
}
}
这些类的注意事项:
-
在
User
类和Blog
类中,你应该确保每个属性都有相应的 getter 和 setter 方法。这些方法的实现通常是直接返回或者设置对应的属性。 -
在
BlogDao
类和UserDao
类中,你应该确保每个数据库操作都在 try-catch 块中进行,以便在出现异常时能够捕获到并进行处理。此外,不要忘记在使用完PreparedStatement
和ResultSet
后关闭它们,否则可能会导致资源泄漏。 -
在
DBUtil
类中,你应该确保在获取连接时检查连接是否为 null,如果为 null,则说明连接获取失败,应该进行相应的处理。在关闭资源时,也应该检查资源是否为 null,如果不为 null,则需要关闭它。
五. 博客系统的后端接口
AuthorServlet:根据博客ID获取博客作者的信息。
BlogServlet:处理博客相关的请求,包括获取博客列表和获取指定博客的详细信息,以及发布新的博客。
LogoutServlet:处理用户注销的请求,将用户从当前会话中移除。
LoginServlet:处理用户登录的请求,验证用户名和密码,并在验证通过后创建会话以保存用户信息。
这些类通过使用不同的 URL 映射到相应的 Servlet,并根据请求的类型(GET 或 POST)执行相应的操作。它们与模型(
User
、Blog
)和数据访问对象(UserDao
、BlogDao
)一起工作,从数据库中读取和写入数据。这个博客系统的 API 提供了用户登录、注销、发布博客、获取博客列表和获取博客作者等基本功能,可以作为一个简单的博客系统的后端接口。
1.AuthorServlet
类 (处理关于作者的请求)
这个类是一个服务器端Servlet,其功能是处理关于作者信息的请求。具体来说,它从HTTP请求中获取blogId参数,然后基于这个blogId,首先从BlogDao中获取对应的Blog对象,再通过该Blog对象中的userId字段从UserDao中获取对应的User对象(即博客作者)。如果获取的信息合法,它会将User对象以JSON格式返回给客户端。
实现细节与步骤:
- 在
doGet
方法中,首先通过HttpServletRequest
对象的getParameter
方法获取请求参数 "blogId"。 - 如果 "blogId" 参数不存在或者无效,那么直接返回错误信息,结束处理。
- 如果 "blogId" 参数有效,那么通过
BlogDao
的selectById
方法获取对应的Blog
对象,再通过Blog
对象的getUserId
方法获取对应的用户ID,然后通过UserDao
的selectById
方法获取对应的User
对象。 - 如果这个
User
对象存在,那么将其转换为JSON格式,并写入到响应的正文中。
@WebServlet("/author")
public class AuthorServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String blogId = req.getParameter("blogId");
if(blogId == null) {
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("参数非法, 缺少 blogId");
return;
}
//根据 blogId 查询 Blog 对象
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectById(Integer.parseInt(blogId));
if(blog == null) {
//博客不存在
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("没有找到指定博客: blogId = " + blogId);
return;
}
//根据 blog 中的 userId 找到对应的用户信息
UserDao userDao = new UserDao();
User author = userDao.selectById(blog.getUserId());
//设置响应正文的格式为 json格式的字符串
resp.setContentType("application/json; charset=utf8");
//将 author对象 转换成 json 格式的字符串写入到响应的 body 中
objectMapper.writeValue(resp.getWriter(),author);
}
}
2.BlogServlet
类 (处理关于的博客请求)
这个类是另一个服务器端Servlet,其功能主要是处理关于博客的请求,包括获取博客列表和发布新博客两部分功能。具体来说,它在处理GET请求时,会根据请求参数 "blogId" 来决定是返回所有博客的列表还是返回特定ID的博客;而在处理POST请求时,会从请求中读取 "title" 和 "content" 参数,然后创建一个新的Blog对象并将其添加到数据库中。
实现细节与步骤:
- 在
doGet
方法中,同样是首先尝试获取请求参数 "blogId"。如果 "blogId" 参数不存在,那么返回所有博客的列表;如果 "blogId" 参数存在,那么返回对应ID的博客。 - 在
doPost
方法中,首先检查用户是否已经登录,然后从请求中读取 "title" 和 "content" 参数,并根据这些信息创建一个新的Blog
对象,然后将这个Blog
对象添加到数据库中。
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 尝试获取一下 queryString 中的 blogId 字段.
String blogId =req.getParameter("blogId");
BlogDao blogDao = new BlogDao();
if(blogId == null) {
//queryString 不存在,说明这次请求是获取博客列表页
List<Blog> blogs = blogDao.selectAll();
//设置body(响应正文)的格式
resp.setContentType("application/json; charset=utf8");
//直接把 blogs 转换成符合要求的 json 格式字符串,同时写入http响应的body中 写法一
objectMapper.writeValue(resp.getWriter(),blogs);
//
/*//将blog先转换成json字符串 写法二
String respJson = objectMapper.writeValueAsString(blogs);
//然后写入http响应
resp.getWriter().write(respJson);*/
}else {
//queryString存在,说明本次请求获取的是指定 id 的博客
Blog blog = blogDao.selectById(Integer.parseInt(blogId));
if(blog == null) {
System.out.println("当前blogId= " + blogId + "对应的博客不存在!");
}
//设置body(响应正文)的格式
resp.setContentType("application/json; charset=utf8");
//直接把 blog 转换成符合要求的 json 格式字符串,同时写入http响应的body中
objectMapper.writeValue(resp.getWriter(),blog);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//发布博客
//读取请求,构造 Blog 对象,插入数据库即可!!
HttpSession httpSession = req.getSession(false);
if(httpSession == null) {
resp.setContentType("text/http;charset=utf8");
resp.getWriter().write("当前未登录, 无法发布博客");
return;
}
User user = (User) httpSession.getAttribute("user");
if(user == null) {
resp.setContentType("text/http;charset=utf8");
resp.getWriter().write("当前未登录, 无法发布博客!");
return;
}
//确保登录之后, 就可以把作者拿到了
//获取博客标题和正文
req.setCharacterEncoding("utf8");
String title = req.getParameter("title");
String content = req.getParameter("content");
if(title == null || "".equals(title) || content == null || "".equals(content)) {
resp.setContentType("text/http;charset=utf8");
resp.getWriter().write("当前提交数据有误! 标题或者正文为空!");
return;
}
//构造 Blog 对象
Blog blog = new Blog();
blog.setTitle(title);
blog.setContent(content);
blog.setUserId(user.getUserId());
//发布时间,在java中生成/在数据库中生成都行
blog.setPostTime(new Timestamp(System.currentTimeMillis()));
//插入数据库
BlogDao blogDao = new BlogDao();
blogDao.add(blog);
//跳转到博客博客列表页
resp.sendRedirect("html/blog_list.html");
}
}
3.LogoutServlet
类 (处理注销的请求)
这个类的功能很简单,就是处理用户的登出请求。在处理GET请求时,它会从当前的会话中移除 "user" 属性,然后重定向到登录页面。
实现细节与步骤:
- 在
doGet
方法中,首先尝试获取当前的会话,然后从会话中移除 "user" 属性,最后通过HttpServletResponse
对象的sendRedirect
方法重定向到登录页面。
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession httpSession = req.getSession(false);
if(httpSession == null) {
//未登录状态, 就直接提示出错.
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前为登录!");
return;
}
httpSession.removeAttribute("user");
resp.sendRedirect("html/login.html");
}
}
4. LoginServlet
类 (处理登录的请求)
这个类主要负责处理用户登录的请求。在处理POST请求时,会从请求中获取 "username" 和 "password" 参数,并通过 UserDao
的 selectByUsername
方法获取对应的 User
对象,然后比较用户输入的密码和数据库中存储的密码是否一致,如果一致,说明登录验证通过,会在当前会话中设置 "user" 属性为对应的 User
对象,最后重定向到博客列表页。此外,这个类还提供了一个处理GET请求的方法,用于获取当前已登录的用户信息。
实现细节与步骤:
- 在
doPost
方法中,首先从请求中获取 "username" 和 "password" 参数,然后通过UserDao
的selectByUsername
方法获取对应的User
对象。 - 然后比较用户输入的密码和数据库中存储的密码是否一致。如果一致,那么在当前会话中设置 "user" 属性为对应的
User
对象,然后通过HttpServletResponse
对象的sendRedirect
方法重定向到博客列表页。 - 如果用户名不存在或密码不一致,那么返回错误信息,结束处理。
- 在
doGet
方法中,首先尝试获取当前的会话,然后从会话中获取 "user" 属性。如果这个User
对象存在,那么将其转换为JSON格式,并写入到响应的正文中。
@WebServlet("/login")
public class LonginServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置请求的编码格式, 告诉 servlet 按照啥格式来理解请求
req.setCharacterEncoding("utf8");
//设置响应的编码格式, 告诉 servlet 按照啥格式来理解响应
//resp.setCharacterEncoding("utf8");
resp.setContentType("text/html;charset=utf8");
//1.读取参数中的用户名和密码
//注意!! 如果用户名和密码包括中文,此处可能会乱码
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username == null || "".equals(username) || password == null || "".equals(password)) {
//登录失败
String html = "<h3> 登录失败! 缺少username 或者 password 字段 </h3>";
resp.getWriter().write(html);
return;
}
//2.读数据库,看看用户名是否存在,并且密码是否匹配
UserDao userDao = new UserDao();
User user = userDao.selectByUsername(username);
if(user == null) {
//用户不存在
String html = "<h3> 登录失败! 用户名或者密码错误 </h3>";
resp.getWriter().write(html);
return;
}
if(!password.equals(user.getPassword())) {
//密码不对
String html = "<h3> 登录失败! 用户名或者密码错误 </h3>";
resp.getWriter().write(html);
return;
}
//3.用户名和密码验证成功, 登录成功,接下来就会创建会话, 使用该会话保存用户的信息
HttpSession session = req.getSession();
session.setAttribute("user",user);
//4.进行重定向,跳转到博客列表页
resp.sendRedirect("html/blog_list.html");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf8");
//使用这个方法获取到用户的登录状态
//如果未登录,这里的会话就拿不到!!
HttpSession session = req.getSession(false);
if(session == null) {
//未登录 , 返回一个空user对象
User user = new User();
//将user对象转换成json格式的字符串写入响应的body中
objectMapper.writeValue(resp.getWriter(),user);
return;
}
User user = (User)session.getAttribute("user");
if(user == null) {
user = new User();
//将user对象转换成json格式的字符串写入响应的body中
objectMapper.writeValue(resp.getWriter(),user);
return;
}
//确实成功取出了对象,直接返回即可
//将user对象转换成json格式的字符串写入响应的body中
objectMapper.writeValue(resp.getWriter(),user);
}
}
六. 前端代码构造请求
1. 登陆页面功能设计
-
表单(Form):整个登录页面被包含在一个HTML表单中。表单被设计用于收集用户输入的数据,这里的数据就是用户名和密码。表单被发送到
../login
这个路径,使用的是POST方法,意味着提交的数据将在请求体中发送。 -
用户名和密码输入框:这两个输入框分别用于接收用户的用户名和密码信息。用户名输入框的占位符提示用户可以输入手机号或邮箱作为用户名。密码输入框类型为
password
,所以输入的密码信息不会被显示出来,以保护用户的安全。 -
提交按钮:用户在输入完用户名和密码后,可以点击这个按钮提交表单。提交后,用户名和密码信息将被发送到服务器进行验证。
<!-- 登录页面的版心 -->
<div class="login-container">
<!-- 使用from包裹下列内容,便于后续给服务器提交数据 -->
<form action="../login" method="post">
<!-- 登录对话框 -->
<div class="login-dialog">
<h3>登录</h3>
<div class="row">
<span>用户名</span>
<input type="text" id="username" placeholder="手机号/邮箱" name="username">
</div>
<div class="row">
<span>密码</span>
<input type="password" id="password" name="password">
</div>
<div class="row">
<input type="submit" id="submit" value="登录">
</div>
</div>
</form>
</div>
2. 博客列表页面功能设计
-
获取并展示博客列表:通过调用
getBlogs
函数,在页面加载时向服务器发起GET请求获取博客列表数据。这个请求的响应体被期望为一个包含多个博客信息的JSON数组。每一个博客对象包含了博客的标题、发布时间和内容等信息。之后,代码通过DOM操作将这些博客信息显示在页面上。每一篇博客在页面上以一个div元素的形式存在,包括标题、发布时间、内容摘要和一个查看全文的链接。点击查看全文的链接会跳转到博客详情页,并且通过查询字符串参数把博客的ID传递给详情页。
详细步骤如下:
-
请求博客列表:页面加载时,
getBlogs
函数被调用。在这个函数中,jQuery的$.ajax
方法用于向服务器发送一个GET请求,目标URL为'../blog'。 -
处理响应:服务器将以一个包含多个博客对象的JSON数组形式返回博客列表。这个JSON数组已经被jQuery自动解析成JavaScript的对象数组。
-
遍历博客对象:每一个博客对象都包含了博客的标题(title)、发布时间(postTime)、内容(content)等信息。
getBlogs
函数中的for循环对数组进行遍历,每次遍历都会处理一个博客对象。 -
构建博客元素:每个博客对象都会被转化为HTML元素以显示在页面上。为此,
getBlogs
函数创建了包含标题、发布时间、内容摘要和查看全文链接的div元素。其中,查看全文的链接会带有一个查询字符串参数,这个参数包含了当前博客的ID,可以用于在详情页识别出具体的博客。 -
添加到页面:构建完的博客div元素会被添加到页面的'.container-right'元素中,以在页面上显示出博客的信息。
- 检查登录状态:
checkLogin
函数通过向服务器发起GET请求到../login
,检查用户的登录状态。如果响应体中包含有用户ID,并且用户ID大于0,那么就认为用户已经登录,然后在页面上显示出用户的用户名。如果没有用户ID,或者用户ID不大于0,那么就认为用户未登录,代码会强制跳转到登录页面。
整个过程是异步的,即浏览器不会等待博客列表的请求和处理过程完成就继续执行其他的JavaScript代码。相反,当请求的响应到达时,jQuery会自动调用预定义的成功回调函数来处理响应数据。
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script>
// 在页面加载时, 向服务器发起请求, 获取博客列表数据
function getBlogs() {
$.ajax({
type: 'get',
url: '../blog',
success: function(body) {
// 响应的正文 是一个 json 字符串, 此处已经被 jquery 自动解析成 js 对象数组了.
// 直接 for 循环遍历即可.
console.log(body);
let containerRight = document.querySelector('.container-right');
for (let blog of body) {
// 构造页面内容, 参考之前写好的 html 代码
// 构造整个博客 div
let blogDiv = document.createElement('div');
blogDiv.className = 'blog';
// 构造标题
let titleDiv = document.createElement('div');
titleDiv.className = 'title';
titleDiv.innerHTML = blog.title;
blogDiv.appendChild(titleDiv);
// 构造发布时间
let dateDiv = document.createElement('div');
dateDiv.className = 'date';
dateDiv.innerHTML = blog.postTime;
blogDiv.appendChild(dateDiv);
// 构造 博客 摘要
let descDiv = document.createElement('div');
descDiv.className = 'desc';
descDiv.innerHTML = blog.content;
blogDiv.appendChild(descDiv);
// 构造查看全文按钮
let a = document.createElement('a');
a.innerHTML = '查看全文 >>';
// 期望点击之后能跳转到博客详情页. 为了让博客详情页知道是点了哪个博客, 把 blogId 给传过去
a.href = 'blog_detail.html?blogId=' + blog.blogId;
blogDiv.appendChild(a);
// 把 blogDiv 加到父元素中
containerRight.appendChild(blogDiv);
}
}
});
}
// 要记得调用
getBlogs();
function checkLogin() {
$.ajax({
type: 'get',
url: '../login',
success: function(body) {
if(body.userId && body.userId >0) {
//登录成功
console.log("当前用户已经登录!");
//加上这个功能,把当前用户的名字显示到界面上
let h3 = document.querySelector('.container-left .card h3');
h3.innerHTML = body.username;
}else {
//当前未登录
//强制跳转到登录页
location.assign('login.html');
}
}
});
}
checkLogin();
</script>
3. 博客详情页面功能设计
-
获取博客详情:在页面加载后,执行一个ajax请求(即异步HTTP请求),以获取博客的具体信息。此请求使用GET方法,URL是
'../blog' + location.search
。这里的location.search
包含当前URL的查询部分,用于指示服务器我们想要获取哪篇博客的信息。一旦请求成功,服务器将返回一个包含博客信息的JSON对象。 -
处理和展示博客详情:在ajax请求成功后,会触发success回调函数。在这个函数中,我们获取JSON对象中的博客信息,然后把这些信息插入到页面对应的元素中。标题和日期信息直接添加到对应的div元素中。而博客的内容,因为通常是Markdown格式,需要先转换成HTML。这个转换过程由editormd库提供的
markdownToHTML
函数完成。 -
检查用户登录状态:
checkLogin
函数通过发送一个GET请求到'../login'
URL,来获取当前登录用户的信息。如果请求成功,success回调函数会被调用。在这个函数中,我们检查服务器返回的JSON对象中的userId属性。如果这个属性存在且大于0,就认为用户已经登录;否则,我们认为用户尚未登录,然后将页面重定向到登录页。 -
获取作者信息:
getAuthor
函数通过向'../author' + location.search
URL发送GET请求,来获取博客作者的信息。如果请求成功,success回调函数会被调用。在这个函数中,我们从服务器返回的JSON对象中获取作者的用户名,然后将这个用户名显示到页面上。
这些函数在页面加载后会自动调用,从而实现上述功能。在调用这些函数之前,需要先加载所依赖的库(即jQuery和editormd)。这是通过 <script src="..."></script>
标签完成的。在这些标签中,src属性指向库文件的URL。
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<!-- 要保证这几个 js 的加载在jquery之后,因为editor.md 依赖了 jquery -->
<script src="../editor.md/lib/marked.min.js"></script>
<script src="../editor.md/lib/prettify.min.js"></script>
<script src="../editor.md/editormd.js"></script>
<script>
$.ajax({
type: 'get',
url: '../blog' + location.search,
success: function(body) {
//处理响应结果,此处的body就是表示一个博客的js对象
//1.更新标题
let titleDiv = document.querySelector(".container-right .title");
titleDiv.innerHTML = body.title;
//2.更新日期
let dateDiv = document.querySelector('.date');
dateDiv.innerHTML = body.postTime;
//3.更新博客正文
//此处不应该把博客正文添加到这个标签里面
//editormd里面有一个api,能将Markdown格式的字符串转换成HLML,输出到#conten
editormd.markdownToHTML('content',{ markdown: body.content });
}
});
function checkLogin() {
$.ajax({
type: 'get',
url: '../login',
success: function(body) {
if(body.userId && body.userId >0) {
//登录成功
console.log("当前用户已经登录!");
}else {
//当前未登录
//强制跳转到登录页
location.assign('login.html');
}
}
});
}
checkLogin();
//这是函数定义
function getAuthor() {
$.ajax({
type: 'get',
url: '../author' + location.search,
success: function(body) {
//把 username 设置到界面上
let h3 = document.querySelector('.container-left .card h3');
h3.innerHTML = body.username;
}
});
}
//这里函数调用该函数才会执行
getAuthor();
</script>
4. 博客编辑页面功能设计
-
HTML表单设计:这部分的代码定义了用户用于输入和提交博客内容的表单。表单内包含一个用于输入标题的文本框以及一个用于提交文章的按钮。除此之外,还有一个占位的
<textarea>
元素,这个元素被设置为不显示(style="display: none;"
),是为了与Markdown编辑器对接,让编辑器的内容可以被提交到服务器。 -
Markdown编辑器初始化:通过调用
editormd()
函数,对Markdown编辑器进行初始化。编辑器的尺寸设置为全宽(width: "100%")和高度为父元素高度减去50像素(height: "calc(100% - 50px)"),50像素是预留给标题的空间。编辑器的初始内容被设置为"# 在这里写下一篇博客",这是一个Markdown格式的标题。editormd()
函数中的path
参数设置了Markdown编辑器库依赖插件的路径。 -
用户登录状态的检查:在
checkLogin()
函数中,使用jQuery的$.ajax()方法向服务器发送一个GET请求,目标URL为'../login'。如果请求成功,会获取到服务器返回的JSON对象,检查其中的userId
属性。如果userId
存在且大于0,说明用户已经登录,将在控制台打印"当前用户已经登录!"。如果userId
不存在或者不大于0,说明用户未登录,那么会强制将页面重定向到登录页面。
通过这三个步骤,实现了博客编辑页的基本功能设计:提供了一个用户友好的Markdown编辑器界面供用户编写博客,同时确保只有登录的用户才能访问编辑页面和发布博客。
<!-- 博客编辑页的版心 -->
<div class="blog-edit-container">
<!-- -->
<form action="../blog" method="post">
<!-- 标题编辑区 -->
<div class="title">
<input type="text" placeholder="在此处输入标题" name="title">
<input type="submit" id="submit" value="发布文章">
</div>
<!-- 博客编辑器 -->
<!-- 把md编辑器放到这个div中 -->
<!-- 博客编辑器, 这里用 id 是为了和 markdown 编辑器对接 -->
<div id="editor">
<textarea name="content" style="display: none;"></textarea>
</div>
</form>
</div>
<script src="../js/jquery.min.js"></script>
<script src="../editor.md/lib/marked.min.js"></script>
<script src="../editor.md/lib/prettify.min.js"></script>
<script src="../editor.md/editormd.js"></script>
<script>
// 初始化编辑器
let editor = editormd("editor", {
// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
width: "100%",
// 设定编辑器高度
height: "calc(100% - 50px)", /* 减 titile 的高度 */
// 编辑器中的初始内容
markdown: "# 在这里写下一篇博客",
// 指定 editor.md 依赖的插件路径
path: "../editor.md/lib/"
});
function checkLogin() {
$.ajax({
type: 'get',
url: '../login',
success: function(body) {
if(body.userId && body.userId >0) {
//登录成功
console.log("当前用户已经登录!");
}else {
//当前未登录
//强制跳转到登录页
location.assign('login.html');
}
}
});
}
checkLogin();
</script>
5. 用户注销功能设计
- 通过a标跳转去构造请求,后端接口接收到请求去帮我们完成注销的逻辑
- 获取到session
- 如果session不为空,将session中保存的user删除
- 删除后跳转到用户登陆页面
后端收到请求开始执行注销操作:
七. 博客系统设计源码
在做前后端逻辑处理的时候,前端代码有些稍微的改动,本文没有提及到,请点击查看源码,查看改动的细节以及所有后端的设计实现:博客系统前后端设计源码
后续还有很多扩展功能补充,大家敬请期待,我们下期再见 ! ! !