建网站带app多少投资,永灿网站建设公司,北京网智易通科技有限公司,微信公众平台 网站开发#x1f389;#x1f389;#x1f389;点进来你就是我的人了博主主页#xff1a;#x1f648;#x1f648;#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔#x1f93a;#x1f93a;#x1f93a; 目录
一. 项目简介
1. 项目背景
2. 项目用到的技… 点进来你就是我的人了博主主页戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔 目录
一. 项目简介
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协议前端学完htmlcssjs后端学完Servlet开发后做一个博客系统巩固一下所学知识并将所学知识运用到实际当中以此来进一步提升对学习编程的兴趣
2. 项目用到的技术
前端使用到htmlcssjs使用ajax技术发送http请求请求body的格式为json格式后端使用Servlet进行开发使用Mysql数据库保存数据除此还引入了editor.mdeditor.md是一个开源的页面markdown编辑器组件采用Maven构建工具搭建项目开发环境
3. 项目功能简单介绍
登陆页面输入用户及密码点击提交如果用户或密码错误会提示用户或密码错误账号及密码正确则登陆成功成功后跳转到博客列表页面博客列表页面博客列表页面展示所有发布过的文章文章显示最多显示50字如果想查看全文则需要点击文章下的显示全文博客详情页面点击某篇文章的显示全文按钮则会展示文章的全部内容博客编辑页面点击博客列表的写博客会跳转到博客编辑页面输入文章题目及文章内容点击发布文章文章即可发布成功发布成功后会跳转到博客列表页面可以查看发布过的文章 博客注销按钮点击博客注销按钮则会跳转到博客登陆页面
二. 博客系统页面设计
这里附上静态页面设计的码云地址可以点击查看本篇文章只展示后端代码与前端代码交互的部分想要查看博客系统页面设计代码请点击博客系统页面设计
页面功能展示:
项目链接:博客登录页
三. 项目准备工作 创建Maven项目在pom.xml中添加项目依赖 后端采用Servlet开发数据库使用Mysqljackson框架可以进行序列化和反序列化将java对象和json字符串相互转化
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdorg.example/groupIdartifactId20230528/artifactIdversion1.0-SNAPSHOT/versionpropertiesmaven.compiler.source8/maven.compiler.sourcemaven.compiler.target8/maven.compiler.target/propertiesdependencies!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --dependencygroupIdjavax.servlet/groupIdartifactIdjavax.servlet-api/artifactIdversion3.1.0/versionscopeprovided/scope/dependency!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --dependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.14.1/version/dependency!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.49/version/dependency/dependencies
/project 创建与开发相关的包和类 引入前端资源 将前端资源都放在main/webapp目录下前端资源从上面码云地址中获取web.xml存放在main/webapp/WEB-INF目录注意我这里的html页面全都存放在一个html文件夹下面了,意味我在HTML页面中直接不能使用WebServle注解里面的路径来访问Servlet. 在构造请求去访问Servlet时路径是根据WebServlet注解或web.xml配置文件中定义的路径而不是Servlet类文件在服务器上的实际位置。路径是相对于整个Web应用的上下文路径通常是打包后的war包所在的路径而不是HTML页面所在的位置。 让我们再次以一个例子进行说明假设你的Web应用(war包解压)的结构如下 /myApp Web应用的上下文路径,war包名/WEB-INF/classesMyServlet.class 访问路径为/myServlet/subdirindex.html在这种情况下无论你的HTML页面位于哪里例如在/myApp/subdir/index.html你都可以通过以下方式来访问MyServlet 使用相对路径假设你正在创建一个从index.html页面到MyServlet的链接你可以使用相对于当前页面的路径即../myServlet来指向Servlet。 使用绝对路径无论你在哪里你都可以使用相对于上下文路径即/myApp的绝对路径/myServlet来访问Servlet。完整的URL将是http://example.com/myApp/myServlet。 在URL中example.com代表了网站的域名也就是服务器的网络地址。所以访问Servlet的路径并不依赖于HTML页面所在的位置而是依赖于Servlet的配置和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标题发布时间文章内容关联用户的外键一个用户可以发布多篇文章所以用户与文章对应关系为1m用户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. 构造 SQLString 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( ));//执行 sqlstatement.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 ListBlog selectAll() {ListBlog 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();Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String blogId req.getParameter(blogId);if(blogId null) {resp.setContentType(text/html;charsetutf8);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;charsetutf8);resp.getWriter().write(没有找到指定博客: blogId blogId);return;}//根据 blog 中的 userId 找到对应的用户信息UserDao userDao new UserDao();User author userDao.selectById(blog.getUserId());//设置响应正文的格式为 json格式的字符串resp.setContentType(application/json; charsetutf8);//将 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();Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 尝试获取一下 queryString 中的 blogId 字段.String blogId req.getParameter(blogId);BlogDao blogDao new BlogDao();if(blogId null) {//queryString 不存在,说明这次请求是获取博客列表页ListBlog blogs blogDao.selectAll();//设置body(响应正文)的格式resp.setContentType(application/json; charsetutf8);//直接把 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; charsetutf8);//直接把 blog 转换成符合要求的 json 格式字符串,同时写入http响应的body中objectMapper.writeValue(resp.getWriter(),blog);}}Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//发布博客//读取请求,构造 Blog 对象,插入数据库即可!!HttpSession httpSession req.getSession(false);if(httpSession null) {resp.setContentType(text/http;charsetutf8);resp.getWriter().write(当前未登录, 无法发布博客);return;}User user (User) httpSession.getAttribute(user);if(user null) {resp.setContentType(text/http;charsetutf8);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;charsetutf8);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 {Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HttpSession httpSession req.getSession(false);if(httpSession null) {//未登录状态, 就直接提示出错.resp.setContentType(text/html;charsetutf8);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();Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//设置请求的编码格式, 告诉 servlet 按照啥格式来理解请求req.setCharacterEncoding(utf8);//设置响应的编码格式, 告诉 servlet 按照啥格式来理解响应//resp.setCharacterEncoding(utf8);resp.setContentType(text/html;charsetutf8);//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);}Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType(application/json;charsetutf8);//使用这个方法获取到用户的登录状态//如果未登录,这里的会话就拿不到!!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 classlogin-container!-- 使用from包裹下列内容,便于后续给服务器提交数据 --form action../login methodpost!-- 登录对话框 --div classlogin-dialogh3登录/h3div classrowspan用户名/spaninput typetext idusername placeholder手机号/邮箱 nameusername/divdiv classrowspan密码/spaninput typepassword idpassword namepassword/divdiv classrowinput typesubmit idsubmit 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 srchttps://code.jquery.com/jquery-3.7.0.min.js/scriptscript// 在页面加载时, 向服务器发起请求, 获取博客列表数据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 代码// 构造整个博客 divlet 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 查看全文 gt;gt;;// 期望点击之后能跳转到博客详情页. 为了让博客详情页知道是点了哪个博客, 把 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 srchttps://code.jquery.com/jquery-3.7.0.min.js/script!-- 要保证这几个 js 的加载在jquery之后,因为editor.md 依赖了 jquery --script src../editor.md/lib/marked.min.js/scriptscript src../editor.md/lib/prettify.min.js/scriptscript src../editor.md/editormd.js/scriptscript$.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,输出到#conteneditormd.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元素这个元素被设置为不显示styledisplay: 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 classblog-edit-container!-- --form action../blog methodpost!-- 标题编辑区 --div classtitleinput typetext placeholder在此处输入标题 nametitleinput typesubmit idsubmit value发布文章/div!-- 博客编辑器 --!-- 把md编辑器放到这个div中 --!-- 博客编辑器, 这里用 id 是为了和 markdown 编辑器对接 --div ideditortextarea namecontent styledisplay: none;/textarea/div/form/divscript src../js/jquery.min.js/scriptscript src../editor.md/lib/marked.min.js/scriptscript src../editor.md/lib/prettify.min.js/scriptscript src../editor.md/editormd.js/scriptscript// 初始化编辑器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删除删除后跳转到用户登陆页面 后端收到请求开始执行注销操作: 七. 博客系统设计源码
在做前后端逻辑处理的时候前端代码有些稍微的改动本文没有提及到请点击查看源码查看改动的细节以及所有后端的设计实现博客系统前后端设计源码
后续还有很多扩展功能补充,大家敬请期待,我们下期再见 ! ! !