irpas技术客

小小项目-系统 - 服务器版本 - javaEE初阶 - 细节狂魔_Dark And Grey

irpas 7337

文章目录 前言博客系统准备工作1、创建项目2、引入依赖3、创建必要目录4、编写代码5 和 6、打包和部署:配置Smart Tomcat7、验证程序 下面,开始正式编写服务器代码了MVC 中的 V(view)【页面的显示】MVC 中的 M(Model)【操作数据存取的逻辑】1、创建数据库/表结构 【数据库设计】mysql代码就在这里,不想写就在这里拷贝 2、 封装数据库操作2.1、先创建DButil 类 来封装 数据库连接操作2.2、创建实体类2.3、封装针对数据的增删改查BlogDao:这个类用于封装博客表的基本操作(增删查改)BlogDao 总程序UserDao:这个类用于封装用户表的基本操作(增删查改)UserDao 总程序 小结 MVC 中的 C (Controller)【处理请求之后的关键逻辑】在完成 controller 代码之前,我们需要 前后端的交互接口。先从 博客列表页 / 主页 入手针对获取博客列表这件事情,我们就可以约定前后端的交互接口了。博客列表页 - 服务器代码 && 客户端之间的代码服务器代码部分服务器总代码客户端代码部分客户端总代码效果图 - 附带解决博客显示问题 博客博客详情页下面我们就可以约定前后端的交互接口了。博客博客详情页 - 服务器代码 && 客户端之间的代码服务器部分服务器总代码 - blog客户端部分客户端总代吗效果图 博客登录页下俩我们就来约定前后端交互接口这回我们先来写客户端,把登录页面进行调整客户端总代码后端部分后端总代码 - login效果图 代码整改 - 完善页面跳转逻辑约定一下,前后端交互接口下面我们来书写服务器代码服务器总代码 - login前端部分博客列表页部分效果图博客详情页博客详情页 && 博客列表页 - 前端检查登录逻辑代码效果图 代码整改2 - 完善页面逻辑约定前后端接口 && 前后端程序1、针对博客列表页2、博客详情页 - 约定 - 服务器代码部分2、博客详情页 - 服务器总代吗 - authorInfo2、博客详情页 - 前端部分前端代码部分效果图 阶段性总结 实现 “注销”功能约定前后端交互接口后端部分后端总代码 - logout客户端部分效果图 博客列表页 - 发布博客约定前后端交互接口后端总代码 - blog前部部分前端总代码效果图 博客列表页 - 删除博客 【最后一项功能】界面上 / 前端 的处理:前端总代码服务器处理:效果图 整个项目各部部分的代码model :数据存储相关代码数据库访问代码:DButil实体类UserBlog 提供 操作实体类方法 的类BlogDaoUserDao View:前端页面显示 与 构造请求部分代码CSS 公共样式代码博客登录页网页框架代码 与 动态显示代码 部分页面样式(CSS 代码) 博客列表页(主页)前端框架代码 与 JS代码CSS 样式代码部分 博客详情页前端框架代码 与 JS代码CSS 样式代码部分 博客编辑页前端框架代码 与 JS代码CSS 样式代码部分 Controller : 处理请求逻辑代码补码博客登录页/博客列表页/博客详情页/博客编辑页 - 服务器代码博客列表页 / 博客编辑页 - 服务器代码博客详情页 - 服务器代码获取作者信息删除博客操作 总结

前言

没有看过这篇博客系统的页面设计,,建议先去看这篇文章。 因为你接下来的内容,我不会在去讲页面是怎么去设计的,默认你是懂得其中的逻辑。 而这篇文章是在博客系统的页面设计的成果上,进行 改进 / 升级 的结果。 ? 另外,这篇Servlet - JavaEE初阶博文中,实战案例“表白墙” 需要看一看!!!

因为这个 表白墙的实现,就是基于 前后端分离的方式来开发的。 前后端分离: 让后端只是给前端返回数据,数据往往就是以 json 格式来组织的。 后端这边彻底不必关心页面的结构和样式了。 只要说开始的时候,前端和后端各自约定好前后端交互接口,然后再分别开发就行了。 前后端分离带来的好处: 除了能够各自独立的进行开发,还能各自独立的进行测试。 前端写完了代码,可以自己创建一个 假的服务器(mock server),来“吐出”一些构造好的假数据,来验证前端写的内容的正确性。 后端写完代码了,也可以自己构造一些 HTTP 请求,来验证服务器“吐出”的数据对不对。 这也就是postman做的事情。

我们再来稍微回顾一下前面讲的表白墙的实现步骤:

1、把前端页面给做出来 2、约定前后端交互接口 3、实现服务器代码来处理请求,返回 json 格式的数据。 4、实现客户端代码来构造发送请求,处理响应。 其实这短短4个步骤,就涉及到了前后端分离的手段。 其实我们仔细想一下:我们最开始写前端代码的时候,完全没有依赖服务器是吧。 比如:写表白墙页面的时候,就完全没有考虑到后端的问题。 看过我前面写的博客的读友们,我来帮你们回忆一下,是那篇博客。 前端三剑客之 JavaScript的最后一个案例。 而在我讲解 servlet的时候,我给它加入了 服务器元素。使其可以通过 IP地址来访问。(仅限=与主机所在的局域网内部设备) 当然要想做到全网都能访问,我们还需要买一个云服务器来获取外网 IP,这样才可以做到全网都能访问我们制作的页面。 ? 如果看过上篇博文的朋友,就知道:我们在完成服务器代码之后,并没有关注前端是怎么去写的,直接使用postman取构造一个HTTP请求,来观察我们服务器代码返回数据,是否达到我们的预期。 也就是说我们写后端代码的时候,并不关注前端代码是什么样子的。 ? 最后我们把服务器和客户端的代码都写完之后,再放在一起进行调试,看看有什么问题存在。 ? 这种 前后端分离 的开发方式,在日常开发中是属于主流的开发方式。


?

博客系统

在前面已经完成了博客系统的设计。 博客:前端 - 博客系统(页面设计)

而且我们在设计博客系统页面的时候,也没有关心服务器的问题。 直接就把页面显示的内容给写死了。 此时,再把它领出来,就是为了给它加入服务器。 使其成为一个 动态的页面,显示的内容随着用户的输入(操作)而改变。 另一个原因是 前端知识 和 后端服务器知识,我们都有了。 我们就具有实现一个简单网站的能力了。 当然,就需要来实践了。


?

准备工作

还是那熟悉配方:七个步骤

1、创建项目

打开你们的idea,跟着我开始搞事情!


?

2、引入依赖

既然涉及到服务器,那么 servlet 肯定是要引入的,另外,有涉及到前后端交互,那么Jackson 也是必要的。 因为数据的组织方式需要使用到 JSON 形式。 最后文章是需要存储的,那么 数据库MySQL 是必不可少的! 中央仓库地址:https://mvnrepository.com/


?

3、创建必要目录 <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name> Archetype Created Web Application </display-name> </web-app>


?

4、编写代码

先随便写一个代码


?

5 和 6、打包和部署:配置Smart Tomcat


?

7、验证程序


?

下面,开始正式编写服务器代码了 MVC 中的 V(view)【页面的显示】

因为 MVC 中的 V 已经完成了。

没完成的,看看这博客博客系统(页面设计)先把 页面设计出来。

下面我们就把前面写的博客页面给拷贝到当前项目的 webapp 路径下面 记住!千万不要放错位置了!!! 一定要是 webapp 目录底下!!!!

另外,这些文件都可以理解成静态的资源。 后续打包部署的时候,这些静态资源也会被一并“打包部署”,后续也就可以在浏览器中访问到了。


?

MVC 中的 M(Model)【操作数据存取的逻辑】

也就是实现数据库相关的代码。

此处的代码都是我之前讲过的 MySQL最后一讲:JDBC 编程。 虽然代码写起来没有难度,但是比较繁琐。 先使用这种最朴素的写法,找找感觉。 后面有机会的话,我会给大家带来更简单的方法。


?

1、创建数据库/表结构 【数据库设计】

设计数据库,需要根据当前的需求来进行设计。

首先,我们来回忆一下:我们的博客系统中有几个页面 1、博客列表页:显示博客的列表 2、博客详情页:点击博客列表页上面 列出的博客条目,跳转到的页面,这个页面显示博客的完整内容。 3、博客登录页 4、博客编辑页:基于 markdown的插件 editor.md 文件夹,搞了一个markdown编译器。 然后我们就可以基于这个页面来发布博客了。 ? 下面,我们就可以基于上述的页面,提出一些需求! 1、存储博客: 当我们点击发布按钮的时候,博客被发布到 服务器上,就要被存储起来。【MySQL的作用就在于此】 ? 2、获取博客:在博客列表页 和 详情页中,能够拿到博客的内容。 ? 3、还能进行登录校验:用户名,密码等是否在服务器存储数据中存在。

现在我们需要基于上面的需求来思考:需要设计那些表呢?

其实在设计表的时候,我们需要抓住需求中“实体”。 “实体”:可参考这篇文章 对数据表进行“增删查改”的进阶操作 实体:关键性的名词

在main目录底下,创建一个sql文件,用于编写 建库建表的 SQL。

MySQL中的数据类型,可参考这篇文章MySQL第二讲 - 数据表简单操作 与 “增删查改的开头部分- 增” ? 先来创建 博客表 下面我们再来创建 用户表

为了后面测试方便,我们来给用户表插入几条数据。

下面我们来打开MySQL来看一下效果

mysql代码就在这里,不想写就在这里拷贝 -- 双减号是注释符 -- 通过这文件来编写建库建表的 sql -- 如果 java_blog 数据库不存在,则创建把它创建出来;反之不创建。 create database if not exists java_blog; -- 选中数据库 use java_blog; -- 此时我们已经进入 数据库java_blog内部了 -- 下面我们来创一个博客表 -- 在创建之前,先判断一下数据库中是否已经存在 blog 数据表 -- 如果存在,删除它 drop table if exists blog; create table blog( -- 将博客序号设置为一个自增主键 blogId int primary key auto_increment, -- 博客标题 title varchar(1024), -- 博客正文:注意博客内容是非常长的,因此varchar是不够的,故使用 mediumtext. -- mediumtext 可以表示几个G 的数据。大概16兆 content mediumtext, -- 博客作者ID userId int, -- 文章的发布时间 postTime datetime ); -- 用户表 drop table if exists user ; create table user( userId int primary key auto_increment, -- 去重 username varchar(128) unique, password varchar(128) ); -- 因为 用户id 为主键,直接置为null,默认从1开始分配编号 -- userId=1 insert into user values(null,"zhangsan","123"); -- userId=2 insert into user values(null,"lisi","456");

?

2、 封装数据库操作 2.1、先创建DButil 类 来封装 数据库连接操作

就是上一篇博客Servlet - JavaEE初阶中实现 服务器版本的表白墙中创建的DButil类,用来简化我们代码量。 这里代码与上篇博文中 DButi类l 中的代码,是一样的! 这里就不再赘述了。 关于多线程,你可以参考多线程基础篇

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 { private static final String URL = "jdbc:mysql://127.0.0.1:3306/java_blog?characterEncoding=utf8&useSSL=false"; private static final String User = "root"; private static final String Password = "123456"; // 加上 volatile 防止编译器对其进行优化。 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(URL); ((MysqlDataSource)dataSource).setUser(User); ((MysqlDataSource)dataSource).setPassword(Password); } } } return dataSource; } // 获取练剑 public static Connection getConnection() throws SQLException { return getDataSource().getConnection(); } //资源回收 public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){ if (resultSet != null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (preparedStatement != null){ try { preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

?

2.2、创建实体类

进一步来说:就是使用实体类来表示数据库中的一条记录


?

2.3、封装针对数据的增删改查

此时,我们要真正开始写 JDBC 代码了!

我们把提供了增删改查这样的类,称为 DAO - 数据访问对象(Data Access Object) 下面我们就来创建两个这样的类 来提供 对应内容的增删查改的功能。


?

BlogDao:这个类用于封装博客表的基本操作(增删查改)


?

BlogDao 总程序 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、往博客表里,插入一条记录(一篇博客) public static void insert(Blog blog){ Connection connection = null; PreparedStatement preparedStatement = null; // JDBC 基本的代码 try { //1、和数据库建立连接 connection = DButil.getConnection(); //2、构造sql语句 // 这里的 now() 表示取得是当前系统的时间 String sql = "insert into blog values(null,?,?,?,now())"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1,blog.getTitle()); preparedStatement.setString(2, blog.getContent()); preparedStatement.setInt(3,blog.getUserId()); //3、执行SQL语句 preparedStatement.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); }finally { //4、关闭连接,回收资源 DButil.close(connection,preparedStatement,null); } } // 2、能够获取到博客表中的所有博客的信息【用于博客列表页显示博客有多少】 // 另外,我们的博客列表,会从每篇博文中截取一段内容,因此截取的内容不一定是截取到完整的博客内容。 // 因为截取到完整的博客的内容,占用的空间会很大!两三行还行,多了就不利于显示更多的博文信息 public static List<Blog> selectAll(){ List<Blog> blogs = new ArrayList<>(); Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; // JDBC 基本的代码 try { //1、和数据库建立连接 connection = DButil.getConnection(); //2、构造sql语句 String sql = "select * from blog"; preparedStatement = connection.prepareStatement(sql); //3、执行SQL语句 resultSet = preparedStatement.executeQuery(); // 4、 遍历结果集 while(resultSet.next()){ Blog blog = new Blog(); blog.setBlogId(resultSet.getInt("blogId")); blog.setTitle(resultSet.getString("title")); blog.setContent(resultSet.getString("content")); blog.setUserId(resultSet.getInt("userId")); blog.setTimestamp(resultSet.getTimestamp("postTime")); blogs.add(blog); } } catch (SQLException e) { e.printStackTrace(); }finally { //5、关闭连接,回收资源 DButil.close(connection,preparedStatement,resultSet); } return blogs; } // 3、能够根据 博客id 读取到指定的博客内容【用于博客详情页显示一篇博客的详情内容】 public static Blog selectOne(int blogId){ Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; // JDBC 基本的代码 try { //1、和数据库建立连接 connection = DButil.getConnection(); //2、构造sql语句 String sql = "select * from blog where blogId = ?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1,blogId); //3、执行SQL语句 resultSet = preparedStatement.executeQuery(); // 4、 遍历结果集:此处我们是使用主键 作为查询条件 // 因此查询的结果要么是一条,要么是零条。 if(resultSet.next()){ Blog blog = new Blog(); blog.setBlogId(resultSet.getInt("blogId")); blog.setTitle(resultSet.getString("title")); blog.setContent(resultSet.getString("content")); blog.setUserId(resultSet.getInt("userId")); blog.setTimestamp(resultSet.getTimestamp("postTime")); return blog; } } catch (SQLException e) { e.printStackTrace(); }finally { //5、关闭连接,回收资源 DButil.close(connection,preparedStatement,resultSet); } return null; } // 4、从博客列表中,根据博客Id 来删除博客 public static void delete(int blogId){ Connection connection = null; PreparedStatement preparedStatement = null; // JDBC 基本的代码 try { //1、和数据库建立连接 connection = DButil.getConnection(); //2、构造sql语句 String sql = "delete from blog where blogId = ?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1,blogId); //3、执行SQL语句 preparedStatement.executeUpdate(); // 4、 遍历结果集:此处我们是使用主键 作为查询条件 // 因此查询的结果要么是一条,要么是零条。 } catch (SQLException e) { e.printStackTrace(); }finally { //5、关闭连接,回收资源 DButil.close(connection,preparedStatement,null); } } // 注意上述的4个操作只涉及到增删查,不涉及到 改。 // 改,这个操作可以被实现,但是不在这里实现。 // 换句话来说:实现该这个操作 和 前面的增删查,并没有什么区别。 // 所以在这里只是显示几个经典的功能,如果你们想要实现 改,加油! // 我偷个懒。 }

?

UserDao:这个类用于封装用户表的基本操作(增删查改)


?

UserDao 总程序 import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; // 这个类用于封装用户表的基本操作(增删查改) public class UserDao { //针对这个类来说,我们就简化的写就行了 // 注册/注销 的功能,我们就不实现了 // 主要实现: // 1、根据 用户名 来查询用户信息 // 这个操作会在登录逻辑中使用。 public static User selectByName(String username){ Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; try { connection = DButil.getConnection(); String sql = "select * from user 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、根据 用户id 来查询用户信息 // 博客详情页,就可以根据用户id 来查询作者的名字,将其显示出来 public static User selectById(int userId){ Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; try { connection = DButil.getConnection(); String sql = "select * from user 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; } }
小结

经过上述的逻辑,终于把数据库操作都给准备好了。 接下来,就可以实现服务器中的后续代码了

换句话来说: Model 就搞定了,下面要去写 controller 了。


?

MVC 中的 C (Controller)【处理请求之后的关键逻辑】 在完成 controller 代码之前,我们需要 前后端的交互接口。

究竟前端给后端发送什么样的请求,后端给前端返回什么样的响应。 必须把这件事确定好了,我们才可以进行后续的操作。

需要注意的是:我们在约定前后端接口的时候,会涉及到好几个页面。 我们一个一个页面来写: 针对这里的4个页面,分别去约定前后端交互接口,编写服务器代码,编写客户端代码。


?

先从 博客列表页 / 主页 入手

这个页面需要能够展示出数据库中的博客列表

我们先看一下博客列表页长什么样子,不知道看过博客页面设计博文的读友们是否还有印象?


?

针对获取博客列表这件事情,我们就可以约定前后端的交互接口了。

约定好了这件事之后,我们就可以按照这样的约定来编写服务器代码。


?

博客列表页 - 服务器代码 && 客户端之间的代码 服务器代码部分

下面我们再来看下执行效果。 通过postman来测试服务器代码,返回的数据是否正确。 但是还没完!还有一个小细节。

那就是发布时间的表现形式。


?

服务器总代码 package controller; import com.fasterxml.jackson.databind.ObjectMapper; import model.Blog; import model.BlogDao; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; @WebServlet("/blog") public class blogServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); // 这个方法用来获取到数据库中的博客列表 @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 从数据数据中查询到博客列表,将格式转化成json格式,然后直接返回即可 List<Blog> blogs = BlogDao.selectAll(); // 把 blogs 转化成json数组的形式 String respJson = objectMapper.writeValueAsString(blogs); resp.setContentType("application/json; charset=utf8"); resp.getWriter().write(respJson); } }

?

客户端代码部分

我们需要在页面加载的时候,让页面通过 ajax 访问到数据库中的博客数据,并且填入页面中。


?

客户端总代码

不要老想着拷贝,上面讲解代码的时候还涉及到 blogDao 和 Blog,两个类的改动。

另外你没有实现 博客的页面设计,也是不行的。 我这里只是提供 前端发送请求,与处理服务器返回的响应 的 逻辑代码。 具体的 博客页面设计,还是需要你看 博客页面设计的文章。 而且接下来的所有代码都是这样给你们的。 不要想着偷油,你就能看得懂这里的逻辑

<!-- 首先,要想使用 ajax,就需要引入依赖 jQuery --> <script src="js/jquery.min.js"></script> <script> // 在页面加载的时候,通过 ajax 给服务器发送数据,获取博客列表信息,并且显示在页面上 // 这里我们通过函数来使用。 function getBlogList(){ $.ajax({ type: "get", url: "blog",// Servlet Path success: function(body){ // 获取到的body 已经是一个 json 数组,里面每个元素就是一个js对象 // 根据这个对象构造一个div // 1、 先把 .right里面的内容给清空 // 换成我们从服务器中得到的“新鲜数据” let rightDiv = document.querySelector('.right'); rightDiv.innerHTML="";// 清空 //2、遍历 body,构造出一个个的 blogDiv for(let blog of body){ let blogDiv = document.createElement('div'); blogDiv.className = "blog";// 引入CSS属性 // 构造标题 let titleDiv = document.createElement('div'); titleDiv.innerHTML = blog.title;//获取标题 titleDiv.className = "title";//引入CSS属性 blogDiv.appendChild(titleDiv);//接回页面中 // 构造发布时间 let dateDiv = document.createElement('div'); dateDiv.innerHTML = blog.postTime; dateDiv.className = "date"; 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 的作用就体现出来了,blogId就是一篇博客的身份Id // 我们可以通过 URL中的查询字符串来表明博客的身份 a.href = "blogdetail.html?blogId=" + blog.blogId; // 未来我们点击这个 a 标签 //发送的HTTP请求里面就会包含要访问的页面,以及对应博客身份 // 从而根据这个ID 找到 具体是那个博客,显示其内容 blogDiv.appendChild(a);// 记入页面 // 最后 把 blogDiv 接回到右侧区域 // 也就是blogDiv 挂到 DOM 树上。 rightDiv.appendChild(blogDiv); } }, error: function(){ alert("获取博客列表失败!") } }); } getBlogList();// 调用函数 </script>

?

效果图 - 附带解决博客显示问题

1、博客列表中 博客显示顺序问题

2、页面刷新时,即重新加载页面时,会涉及到清空右侧区域内容,填充新的内容,在此之间,会存在会卡顿一下的情况。 其中的原因就是: 加载原先写死的数据,并清空原先数据的时候,需要时间来执行。


?

博客博客详情页

我们来看一下博客详情页长什么样子。 看到上面我划线标出的 查询字符串 了吗?它将起到至关重要的作用!!!

整个链接(URL)就是一个湖区博客详情页的请求。 此处,我们希望:点击一篇博客的 查看全文的链接之后,所得到的页面,能显示出当前这个博客的正文内容。 怎么去获取对应的正文内容呢?就是我在 URL 中 划出的 blogId 键值对,来查询并获取到对应的内容。 ? 至于方法,还是通过 ajax 来进行获取。 具体来说: 在 blog_detail 页面加载的时候,触发 ajax 请求来访问服务器,获取到博客内容。 再次填充到博客详情页里面!


?

下面我们就可以约定前后端的交互接口了。


?

博客博客详情页 - 服务器代码 && 客户端之间的代码 服务器部分

约定的请求里面,不是带有 blog 嘛? 还是使用 原来的 BlogServlet 类,来实现对 博客详情页请求 的处理。

那么问题来了: 博客详情页 和 博客列表页 的 请求,方法都是 GET. 此时的doGet方法中,已经用于实现了 博客列表页 的逻辑。 因此,我们要想把 详情页请求的处理,填入其中。 那么,代码上又如何区别 那个部分是 详情页,那个是列表页 的 代码呢? 还是那个详情页 URL 中 划出 的 那个 blogId 的键值对,它是区分两者请求的关键。 此时我们就只用了一个服务器代码完成了两个页面的逻辑。


服务器总代码 - blog package controller; import com.fasterxml.jackson.databind.ObjectMapper; import model.Blog; import model.BlogDao; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; @WebServlet("/blog") public class blogServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); // 这个方法用来获取到数据库中的博客列表 @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("application/json; charset=utf8"); // 先尝试获取到 req 中的 blogId 参数 // 如果参数存在,说明接下来要处理的详情页的逻辑;反之,接下来就要处理列表页的逻辑 String str = req.getParameter("blogId"); if(str == null){ // 如果 str 为 null,说明:接下来就要处理列表页的逻辑 // 从数据数据中查询到博客列表,将格式转化成json格式,然后直接返回即可 List<Blog> blogs = BlogDao.selectAll(); // 把 blogs 转化成json数组的形式 String respJson = objectMapper.writeValueAsString(blogs); // resp.setContentType("application/json; charset=utf8"); resp.getWriter().write(respJson); }else{ // 反之,接下来就要处理列表页的逻辑 // 将获取到的 blogId 转换成 整形 int blogId = Integer.parseInt(str); // 调用我们之前书写 selectOne 方法,来查询指定的博客信息 Blog blog = BlogDao.selectOne(blogId); // 将 blog对垒 转换成 json格式的字符串 String respJson = objectMapper.writeValueAsString(blog); // resp.setContentType("application/json; charset=utf8"); resp.getWriter().write(respJson); } } }

下面我们来进行阶段性测试,看看服务器代码 是否将两者的逻辑区分开来。


?

客户端部分


?

客户端总代吗

记住!渲染显示的内容,需要你们引入 markdown的第三方库。 具体配置的步骤,还是需要你看 博客页面设计的文章。

<!-- 第一步还是先引入 jQuery 第三方库 --> <!-- 因为使用 ajax 需要依赖于 jQuery --> <script> // 使用函数来来进行前端代码的逻辑 function getBlogDetail(){ $.ajax({ type: "get", url: "blog" + location.search, success: function(body){ // 根据 body 中的内容来构造页面 // 构造 博客标题 let h3 = document.querySelector('.blog-content>h3'); h3.innerHTML = body.title; // 构造 博客发布时间 let dateDiv = document.querySelector(".date"); dateDiv.innerHTML = body.postTime; // 构造博客正文 // 如果直接把 content 设为innerHTML // 此时显示在界面上的内容是原始的 markdown字符串 // let content = document.querySelector('#content'); // content.innerHTML = body.content; //这里我们使用 editor.md 自带的方法,对内容进行渲染 // 第一个参数对应 id=content 的 html 标签:渲染后得到的HTML片段,就会被放到这个标签下 // 第二参数:需要渲染的数据对象 editormd.markdownToHTML('content',{ markdown: body.content }); } }); } getBlogDetail(); </script>

?

效果图

为了突出 markdown的渲染的效果,我们再来插入一组数据。


?

博客登录页

目的:就是实现用户登录逻辑。

能够说登录用户咋输入正确的用户名和密码之后,页面登录成功。


?

下俩我们就来约定前后端交互接口


?

这回我们先来写客户端,把登录页面进行调整


?

客户端总代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>博客登录页</title> <link rel="stylesheet" href="./css/common.css"> <link rel="stylesheet" href="./css/bloglogin.css"> </head> <body> <!-- 导航栏 --> <!-- 为了能够在后续过程中,添加样式等操作,建议都引入一个类属性 --> <div class="navigation"> <img src="./image/2.jpg" alt=""> <span>我的博客系统</span> <!-- 空白元素,用于站位置 --> <div class="spacer"></div> <a href="bloghome.html">主页</a> <a href="writingblog.html">写博客</a> <!-- <a href="#">注销</a> --> </div> <!-- 登录页面内容区域 --> <div class="login-container"> <!-- 加上一个对话框 --> <form action="login" method="post"> <div class="login-dialog"> <h3>登陆</h3> <div class="row"> <span>用户名</span> <input type="text" id="username" 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> </body> </html>

?

后端部分


?

后端总代码 - login package controller; import model.User; import model.UserDao; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/login") public class loginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf8");// 设置字符集 resp.setCharacterEncoding("utf8"); //1、获取到请求中的参数 String username = req.getParameter("username"); String password = req.getParameter("password"); //2、和数据库的内容进行比较 if(username == null || "".equals(username) || password == null || "".equals(password)){ resp.setContentType("text/html; charset=utf8");//告诉浏览器当前返回一个 静态页面,读取数据形式是utf8 resp.getWriter().write("当前用户名或密码不能为空!"); }else{ // 用户名 和 密码 不为空的情况 User user = UserDao.selectByName(username); if(user == null || !user.getPassword().equals(password)){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("当前用户名,或者密码不正确!"); }else{ //3、如果比较通过,就创建会话 HttpSession session = req.getSession(true); // 把刚才的用户信息,存储在会话中 session.setAttribute("user", user); //4、返回一个重定向报文,跳转到博客列表页 resp.sendRedirect("bloghome.html"); } } } }

?

效果图

我们再来看加上汉字的情况


?

代码整改 - 完善页面跳转逻辑

当登录功能完成之后,就需要调整一下之前定的两个页面【博客列表 和 博客详情】

让这两个页面,必须在登录后,才能访问!!! 此处我们就做出了这样的限制。 而且这样限制一下,后面实现其它功能会更简单! ? 在我们进入 博客 列表页 / 详情页 的时候,先检查一下用户的登录状态。 如果用户当前已经是登录状态,才能继续使用。 反之,用户处于位登录状态,则强制跳转到 博客登录页。

那么如何实现上述功能?

可以在 博客 列表 / 详情 页,加载的时候,通过 ajax 访问一下服务器,来获取当前的登录状态。 看看能不能获取到 状态: 如果获取到了,就说明当前确实已经登录了。 此时就可以留在这个页面了。 ? 如果没有获取到,说明用户处于未登录状态。 既然是未登录,那么就不能滞留在当前页面,强制跳转到登录页面,完成登录之后,才能继续访问。


?

约定一下,前后端交互接口


?

下面我们来书写服务器代码


?

服务器总代码 - login

在当前服务器这里拿到了 session,并且拿到了里面的 user,视为登录成功!

如果登录成功的话,服务器会给客户端返回 sessionId。 浏览器就会保存这个sessionId。 并且在下次方式请求的时候就会带上这个 id。 服务器拿到了 这个Id,就可以去一个“哈希表”中查询,从而获取对应的信息。 这样就知道了 当前的 sessionId 对象是谁。

package controller; import com.fasterxml.jackson.databind.ObjectMapper; import model.User; import model.UserDao; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/login") public class loginServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf8");// 设置字符集 resp.setCharacterEncoding("utf8"); //1、获取到请求中的参数 String username = req.getParameter("username"); String password = req.getParameter("password"); //2、和数据库的内容进行比较 if(username == null || "".equals(username) || password == null || "".equals(password)){ resp.setContentType("text/html; charset=utf8");//告诉浏览器当前返回一个 静态页面,读取数据形式是utf8 resp.getWriter().write("当前用户名或密码不能为空!"); }else{ // 用户名 和 密码 不为空的情况 User user = UserDao.selectByName(username); if(user == null || !user.getPassword().equals(password)){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("当前用户名,或者密码不能正确!"); }else{ //3、如果比较通过,就创建会话 HttpSession session = req.getSession(true); // 把刚才的用户信息,存储在会话中 session.setAttribute("user", user); //4、返回一个重定向报文,跳转到博客列表页 resp.sendRedirect("bloghome.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 = new User(); resp.getWriter().write(objectMapper.writeValueAsString(user)); return; } // 如果会话不为空,就来查询会话中是否存在user 对象 // 如果存在,返回的就是 一个非空的 user 对象 // 如果不存在,返回的是一个 null User user = (User) session.getAttribute("user"); // user 不存在 if(user == null){ user = new User(); resp.getWriter().write(objectMapper.writeValueAsString(user)); return; } // user存在 / 处于已登录的状态 // 注意:此处不要把密码也给返回去了【避免密码泄露】 user.setPassword(""); resp.getWriter().write(objectMapper.writeValueAsString(user)); } }

?

前端部分


?

博客列表页部分


?

效果图

我们重启Tomcat,直接通过浏览器的地址栏输入URL,进行访问博客列表页


?

博客详情页

这里和列表页是一样的。 因此即将刚才写的前端代码拷贝过去就行了。


?

博客详情页 && 博客列表页 - 前端检查登录逻辑代码 function getUserInfo(){ $.ajax({ type: "get", url: "login", success: function(body){ // 判定此处的 body 对象,是不是一个有效的 user 对象(userId 是否 非0 ) // 判断body.userId 是否存在,并且大于0 if(body.userId && body.userId>0){ // 进入 if 语句中,说明用户:处于登录状态 console.log("当前用户登录成功! 用户名:" + body.username); }else{ // 登录失败 // 通过 前端重定向方法 location.assign 方法 来进行页面的跳转 alert("当前您尚未登录!请在登录之后,再来访问博客列表!"); location.assign('bloglogin.html'); } }, error: function(){ alert("当前您尚未登录!请在登录之后,再来访问博客列表!"); location.assign('bloglogin.html'); } }); } getUserInfo();

?

效果图

还是跟前面测试一样,重启Tomcat,直接通过浏览器的地址栏输入URL,进行访问博客列表页。

注意! 虽然前面登录过,但是会话信息是保存在服务器内存中的(HttpSession的hash表,是内存的) 因此,我才会要求你们重启Tomcat,就是为了清空会话信息。 这样你们才能更清晰的看到效果。 另外,有的朋友可能会想法:我们能不能给 会话 设置一个 “过期时间”? 就比如: 我们在登录 腾讯视频等等其它网站的时候,登录过一次。但是过一段时间就需要我们重新登录了。 答案是课可以的! 但是,服务器一旦重启,不管 你的 “过期时间” 有没有到,这个会话一样会被删除。 然后,你再次访问也还是会强制 重新登录的! ? ? 有的朋友可能会说:使用 cookie 其实 cookie,我们一直都在用。另外 cookie 也确实是存储 浏览器所在机器的硬盘上。 cookie中存储的是 sessionId,也就是 key。 但是键值对的本体,还是在服务器上的。 ? 重启服务器,这个键值对本体,还是会被删除。 举个例子:网吧购置会员卡 cookie 就相当于是一张网卡,sessionId 就是 你的会员id(key)。 key 所对应的 value 值,就是你在这张卡上存的钱。 但是网吧倒闭了,你再拿着这张卡,去上网,或者退钱。 尽管你是知道自己的会员id的,你也是上不了网,退不了钱的!!! 网吧都没了,你上哪去上网???上哪去退钱?? 人家老板都跑路了!!! 或者说:网吧换主人了。 没有那个老板,会买前任的单。 人家是不会认你这张卡的! 这都是套路!! 一波会员换个老板,懂的都懂。 ? 如果想要持久化存储,还是需要 数据库来实现。 也就是说:你要自己敲代码来实现。 ? 另外,我们 代码并没有把往cookie里存,而只是在内存中用了一下 JS 虽然也能操作cookie,但是这里就不讲了。 但是如果你不去操作,返回的值 也就是在内存中,随着你页面的 关闭 / 刷新,内存中的值也就没了。 ? 也就是说:不是说随便使用 ajax,ajax点什么,都会往cookie中存储。


?

代码整改2 - 完善页面逻辑

也就是说: 代码整改2 完善的页面逻辑:让左侧用户栏显示信息,根据实际情况来显示。 博客主页,也就博客列表页,左侧显示的是登录用户的信息。 ? 而它的右侧博客,可能是登录用户写的,也可能不是登录用户写的。 因此,点击任意任意一篇博客,左侧显示的信息,是当前博客的作者信息。 这就是我们 整改代码2 的 最终目的。


?

约定前后端接口 && 前后端程序

?

1、针对博客列表页

这里其实在前已经处理过了,在 检查登录状态的时候。 下面我们来验证一下程序的效果 有的人可能会有问题: 而且,当初设计数据库的时候,就没有考虑开辟一个专门数据表来存储这些 图片,文章数目等等。。。字段的信息。


?

2、博客详情页 - 约定 - 服务器代码部分

这个就有些特别了! 它需要创建 一个新的 服务器接口。

我们先来书写后端代码


?

2、博客详情页 - 服务器总代吗 - authorInfo package controller; import com.fasterxml.jackson.databind.ObjectMapper; import model.Blog; import model.BlogDao; import model.User; import model.UserDao; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/authorInfo") public class AuthorInfoServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("application/json; charset=utf8"); // 通过这个方法,来获取到指定的博客作者信息 String blogId = req.getParameter("blogId"); if(blogId == null || "".equals(blogId)){ // 参数绝少 resp.getWriter().write("{ \"ok\": false,\"reason\": \"参数缺失!\"}"); return; } // 根据 blogId 在数据库中进行查找,找到对应的 Blog 对象 // 然后,在进一步根据 Blog 对象 找到作者信息 Blog blog = BlogDao.selectOne(Integer.parseInt(blogId)); if(blog == null){ resp.getWriter().write("{ \"ok\": false,\"reason\": \"要查找的博客不存在!\"}"); return; } //根据 blog 对象,查询到用户对象 User author = UserDao.selectById(blog.getUserId()); if (author == null){ resp.getWriter().write("{ \"ok\": false,\"reason\": \"要查找的用户不存在!\"}"); return; } // 把 author 对象返回到浏览器这边 author.setPassword(""); String respJson = objectMapper.writeValueAsString(author); resp.getWriter().write(respJson); } }

下面我们来使用 postman来测试一下服务器代码,返回的数据是否符合我们的预期

记得重启Tomcat


?

2、博客详情页 - 前端部分


?

前端代码部分 //通过这个方法,从服务器获取当前博客的作者信息,并显示在封面上 function getAuthorInfo(){ $.ajax({ type: "get", url:'authorInfo'+ location.search, // 此处的body,就是是一个 user 对象 success: function(body){ if(body.username){ // 如果响应中的 username 存在,就把这个值设置到网页面上 changeUserName(body.username); }else{ console.log("获取作者信息失败" + body.reason); } } }); } getAuthorInfo();

?

效果图


?

阶段性总结

通过上述的开发,就能有一个感受 页面和服务器之间的交互,不一定只要一次,而且大概率是有很多次交互。

博客列表页(博客主页) 这里涉及到了两次交互: 1、从服务器拿到博客列表数据 2、从服务器拿到当前用户登录的信息 ? 博客详情页 这里涉及到了三次交互: 1、从服务器拿到博客的详细内容 2、从服务器拿到当前用户登录的信息 3、从服务器拿到当前文章的作者信息 ? 其实想咱们当前这种简单的页面,有个两三次交互,一点都不多! 有些复杂的页面,可能和服务器有 十次 或者二十次的交互,都是很正常的!


?

实现 “注销”功能

简单来说‘:就是退出用户的登录状态。 返回到登录页,来切换账号。 或者就是单纯的为了退出登录状态。 我们的导航栏中,不是有一个注销按钮吗? 我们期望:当用户点击 注销按钮 之后,就会在服务器上取消登录的状态,并且能够跳转回到登录页面。 当下 注销按钮 这里并没有赋予效果。

等于就点了之后没有反应 下面代码中的 # 号相当于是一个占位符,并没有实际意义。

我们希望在点击之后,能够给服务器发送一个 HTTP 请求,从而触发注销操作。

这个操作具体就是把会话中信息给删除就行了。 一旦会话中的消息被删除,就会触发 检查登录状态 的逻辑,从而强制回到登录页面。


?

约定前后端交互接口


?

后端部分


?

后端总代码 - logout

我们需要明白:什么样子的情况才算是登录?

用户有一个 session,同时 session 有一个 user属性。 这个属性是用户登录成功的时候,会创建一个会话,并且在会话中存储当前登录用户的信息对象 也就是说: session 和 user 对象 同时具备,才能叫登录状态。 ? 即:注销功能,只需破坏上面两者中的任意一个即可。 这里我们选择的是 破坏 第二个条件:把会话中的user属性删除。 ? 有的人可能会问:为什么不优先删除会话?这样不是更直接,删得也更干净?? 原因很简单:Servlet 没有提供 删除会话 的 API。 那么就只能删除属性了。

package controller; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/logout") public class LogoutServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 先找到当前用户的会话 HttpSession session = req.getSession(false); if (session == null){ // 如果 session 为 null,会话不存在,说明用户压根就没有登录过 // 既然没有登录,也就谈不上注销 resp.getWriter().write("当前用户尚未登录,无法注销!"); return; } // 已登录状态 // 然后把这个用户的会话中的信息给删除掉 session.removeAttribute("user"); resp.sendRedirect("bloglogin.html");// 发送一个重定向的响应 } }

?

客户端部分


?

效果图

这里我只展示一个页面的注销功能,其他的页面的小姑都一样

记得重启Tomcat!!!


?

博客列表页 - 发布博客

我们先来回顾一下博客编辑页的页面长什么样子 ? 我们预期的效果如下:


?

约定前后端交互接口


?

后端总代码 - blog

doPost 是我们处理发布博客的逻辑代码 doPost 是我们用来获取到数据库中的博客列表

package controller; import com.fasterxml.jackson.databind.ObjectMapper; import model.Blog; import model.BlogDao; import model.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.List; @WebServlet("/blog") public class blogServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); // 这个方法用来获取到数据库中的博客列表 @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("application/json; charset=utf8"); // 先尝试获取到 req 中的 blogId 参数 // 如果参数存在,说明接下来要处理的详情页的逻辑;反之,接下来就要处理列表页的逻辑 String str = req.getParameter("blogId"); if(str == null){ // 如果 str 为 null,说明:接下来就要处理列表页的逻辑 // 从数据数据中查询到博客列表,将格式转化成json格式,然后直接返回即可 List<Blog> blogs = BlogDao.selectAll(); // 把 blogs 转化成json数组的形式 String respJson = objectMapper.writeValueAsString(blogs); // resp.setContentType("application/json; charset=utf8"); resp.getWriter().write(respJson); }else{ // 反之,接下来就要处理列表页的逻辑 // 将获取到的 blogId 转换成 整形 int blogId = Integer.parseInt(str); // 调用我们之前书写 selectOne 方法,来查询指定的博客信息 Blog blog = BlogDao.selectOne(blogId); // 将 blog对垒 转换成 json格式的字符串 String respJson = objectMapper.writeValueAsString(blog); // resp.setContentType("application/json; charset=utf8"); resp.getWriter().write(respJson); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(false); if(session == null){ // 当前用户处于未登录状态,不能发布博文! resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("前用户处于未登录状态,不能发布博文!"); return; } User user = (User) session.getAttribute("user"); if(user == null){ // 当前用户处于未登录状态,不能发布博文! resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("前用户处于未登录状态,不能发布博文!"); return; } // 只要上面两个条件都满足,才能算是处于登录状态 // 也只有处于登录的用户,才能编写博客。 // 如果别人写了半天,点击发布,你提示别人未登录,这不是消遣别人嘛! // 因此 判断用户登录状态的代码 必须置前!才能执行下面的逻辑! req.setCharacterEncoding("utf8");// 一定要先指定好请求按哪种编码方式来解析 // 先从请求中,取出参数(博客的标题和正文) String title = req.getParameter("title"); String content = req.getParameter("content"); if(title == null || content == null || "".equals(title)||"".equals(content)){ // 我们认为质押出现上述情况之一,就是违法数据 // 直接告诉客户端,请求参数不对 resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("发布博客失败!缺少必要的参数!"); return; } // 如果没有出错的话,我们就构造一个 Blog 对象,把当前的数据存入数据库 Blog blog = new Blog(); // 此处我们蛀牙给 Blog 设置的属性,主题是 title,content,userId(作者信息) // postTime【now()方法】 和 blogId(自增主键) 这两个不需要手动指定,都是插入数据库的时候,自动生成的。 blog.setTitle(title); blog.setContent(content); // 作者 id 就是当前提交这个博客的用户的身份信息!!! blog.setUserId(user.getUserId()); BlogDao.insert(blog); // 新增博客之后,我们就需要跳转到博客列表页 resp.sendRedirect("bloghome.html"); } }

?

前部部分

我们就需要整一个 form表单,把一些关键信息“包裹起来”。

与登录页面所做的事情是一样的。 还没完哦!我们还需要修改 发布按钮的样式。 原先是针对 button 进行设置样式的,而现在换成input标签。 因此原先的针对button设置的样式也就不起租用了!


?

前端总代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>博客编辑页</title> <link rel="stylesheet" href="./css/common.css"> <link rel="stylesheet" href="./css/writingblog.css"> <!-- 引入 editor.md 的依赖 --> <link rel="stylesheet" href="./editor.md/css/editormd.min.css"> <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.min.js"></script> </head> <body> <!-- 导航栏 --> <!-- 为了能够在后续过程中,添加样式等操作,建议都引入一个类属性 --> <div class="navigation"> <img src="./image/2.jpg" alt=""> <span>我的博客系统</span> <!-- 空白元素,用于站位置 --> <div class="spacer"></div> <a href="bloghome.html">主页</a> <a href="writingblog.html">写博客</a> <a href="logout">注销</a> </div> <!-- 包裹整个博客编辑页内容的顶级容器 --> <div class="blog-edit-container"> <!-- 内容分为两个部分:标题区 和 编辑区 --> <form action="blog" method="post"> <!-- 标题区 --> <div class="title"> <!-- 输入框 --> <input type="text" placeholder="在这里写下文章标题" name="title"> <!-- <button>发布文章</button> --> <input type="submit" id="submit" value="发布文章"> </div> <!-- 编辑区:放置 markdown 编译器 --> <div id="editor"> <!-- 为了进行 form 的提交,此处搞一个 textare 多行编辑框 --> <!-- 借助这个编辑框来实现表单的提交 --> <!-- 可以借助 style属性给其添加一个 隐藏属性 --> <textarea name="content" style="display: none"></textarea> <!-- 通过 textare 可以设置 editor.md,让编辑器把 markdown 内容也同步的保存到这个隐藏的 textare 中 --> <!-- 从而就可以进行 form 提交 --> <!-- 不要去深究执行过程,这就是官方提供的一个做法 --> </div> </form> </div> <script> // 初始化编译器 let editor = editormd("editor",{ // 这里的尺寸必须在这里设置,设置样式会被 editormd 自动覆盖掉 width: "100%", // 设置编译器的高度 height: "calc(100% - 50px)", // 编译器中的初始内容 markdown: "# 在这里写一篇博客", // 指定 editor.md 依赖的插件路径 path: "./editor.md/lib/", // 此处要加上一个重要的选项,然后 editor.md 就会自动把用户在编辑器输入的内容同步保存到隐藏的textare中 saveHTMLToTextarea: true, }); </script> </div> </body> </html>

?

效果图

下面我们来发布一个博客,看看是否达到了我们想要的效果


?

博客列表页 - 删除博客 【最后一项功能】

删除博客,肯定不是随随便便就可以删除的。

博客,只能删除自己写的!!!! 如果每个人都能删除别人的博客,不就乱套了嘛!


?

界面上 / 前端 的处理:

在博客详情页这里,就去进行判定

判定看当前这个博客的作者,是否就是登陆用户。 如果是,就在导航栏显示一个删除键、 如果不是,导航栏里就不显示 删除按钮


?

前端总代码 // 通过 GET /login 这个接口,来获取当前的登录状态 function getUserInfo(){ $.ajax({ type: "get", url: "login", success: function(body){ // 判定此处的 body 对象,是不是一个有效的 user 对象(userId 是否 非0 ) // 判断body.userId 是否存在,并且大于0 if(body.userId && body.userId>0){ // 进入 if 语句中,说明用户:处于登录状态 console.log("当前用户登录成功! 用户名:" + body.userName); //在 getUserInfo 的回调函数中,来调用 getAuthorInfo 函数 getAuthorInfo(body);// 将获取到用户信息,作为参数发送给 getAuthorInfo 函数 }else{ // 登录失败 // 通过 前端重定向方法 location.assign 方法 来进行页面的跳转 alert("当前您尚未登录!请在登录之后,再来访问博客列表!"); location.assign('bloglogin.html'); } }, error: function(){ alert("当前您尚未登录!请在登录之后,再来访问博客列表!"); location.assign('bloglogin.html'); } }); } getUserInfo(); //通过这个方法,从服务器获取当前博客的作者信息,并显示在封面上 function getAuthorInfo(user){//此时的参数user 就是刚才从服务器拿到的当前用户登录的信息 $.ajax({ type: "get", url:'authorInfo'+ location.search, // 此处的body,就是是一个 user 对象【这是文章作者的信息】 success: function(body){ if(body.username){ // 如果响应中的 username 存在,就把这个值设置到网页面上 changeUserName(body.username); // 然后,我们就可以开始进行判断了 if(body.username == user.username){ // 如果相同,说明作者 和 登录用户 就是同一个人。 // 此时就可以显示出 删除按钮 // 获取导航栏对象 let navDIV = document.querySelector('.navigation'); // 创建一个新的 a 标签 let a = document.createElement('a'); a.innerHTML= "删除"; //期望点击删除按钮,构造一个形如 blogDelete?blogId=6 这样的请求 a.href = "blogDelete"+ location.search; navDIV.appendChild(a);// 江删除按钮接入导航栏中 } }else{ console.log("获取作者信息失败" + body.reason); } } }); } // getAuthorInfo(); function changeUserName(username){ let h3 = document.querySelector('.card>h3'); h3.innerHTML = username; }

细心的人就会发现:当前这个代码,就是使用了套娃操作。

一个ajax中调用另外一个ajax。 当前我们只是两个ajax,感觉还好。即使是在回调里嵌套调用 ajax,也勉强能够接受。 但如果我们要进行的操作有 十个八个的。 而且一直都是前一个 ajax 回调 下一个 ajax 的时候,代码写起来就会非常之丑陋! 这种情况,在JS中被称为 “回调地狱” 那么,我们该怎么避免这个问题呢? 在 JS 的 ES6 版本中 引入了 Promise,就能解决这个问题。 Promise:能有效的解决 “回调地狱” 的问题。 它其实也是能够让 这种异步执行的代码,去按照异步的规则来执行。 但是它不再是通过回调的方式来执行科,而是通过一些 “语法糖” 把我们已有的回调函数进行封装。 以至于代码写起来很简便,代码看起来很优雅。 ? 但是后来 人们又觉得 Promise 还是差点意思。 在 ES7 版本中,又引入 async 和 await,能够更加优雅的解决上述问题了。 这些 Promise,async 和 await等等。。。都是 语法糖。 通过这些语法,能够简化代码,让代码看起来没有那么复杂,那么丑陋。 执行的效果任然是一样的。 慌个说法:通过调用一些简洁的方法,来完成复杂执行效果。 既降低了代码的复杂度,又达到了预期的效果。

下面我们测试一下:博客作者 和 登录用户 是同一个人的情况下。 导航栏中是否存在 删除按钮。


?

服务器处理:

用户点击删除按钮,触发一个 HTTP 请求,HTTP请求就会让服务器删除指定的博客。

服务器收到这个请求之后,就会把这个博客从数据库中删除 约定一下接口:在讲前端的时候,已经制定了 Servlet Path 和 queryString。

package controller; import model.Blog; import model.BlogDao; import model.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/blogDelete") public class BlogDeleteServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1、检查当前用户是否登录 HttpSession session = req.getSession(false); if (session == null){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("当前您处于未登录状态,无法进行删除操作!"); return; } User user = (User) session.getAttribute("user"); if(user == null){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("当前您处于未登录状态,无法进行删除操作!"); return; } // 2、获取到参数中的 blogId String blogId = req.getParameter("blogId"); if (blogId == null || "".equals(blogId)){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("参数残缺,无法进行删除操作!"); return; } // 3、获取需要删除的博客信息 Blog blog = BlogDao.selectOne(Integer.parseInt(blogId)); if (blog == null){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("您要删除的博客不存在,无法进行删除操作!"); return; } // 4、再次校验:当前的用户是否是博客的作者 if(user.getUserId() != blog.getUserId()){ // 虽然在前端中处理过这个问题,但是再稳一波,twin check 更保险! resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("非法删除操作!"); return; } // 5、删除指定博客 BlogDao.delete(Integer.parseInt(blogId)); // 6、返回一个重定向响应 resp.sendRedirect("bloghome.html"); } }

?

效果图


?

整个项目各部部分的代码

?

model :数据存储相关代码 数据库访问代码: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 { private static final String URL = "jdbc:mysql://127.0.0.1:3306/java_blog?characterEncoding=utf8&useSSL=false"; private static final String User = "root"; private static final String Password = "123456"; // 加上 volatile 防止编译器对其进行优化。 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(URL); ((MysqlDataSource)dataSource).setUser(User); ((MysqlDataSource)dataSource).setPassword(Password); } } } return dataSource; } // 获取练剑 public static Connection getConnection() throws SQLException { return getDataSource().getConnection(); } //资源回收 public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){ if (resultSet != null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (preparedStatement != null){ try { preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

?

实体类 User package model; // 每个User 对象,对应 model.User 数据表 里的 一条记录 public class User { private int userId = 0; 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; import java.text.SimpleDateFormat; // 每个 blog 对象,对应 blog数据表 里的 一条记录 public class Blog { private int blogId; private String title; private String content; private int userId; private Timestamp postTime; // 下面我们就来给他们设置 getter 和 setter 方法 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 int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } // public Timestamp getPostTime() { // return postTime; // } // 这里不再是返回一个时间戳对象,而是返回一个 String // 这个 String 就是格式化好了的时间 public String getPostTime() { // 在Java中有一个类,叫做 SimpleDataFormat 类,中文意思就是 日期格式 // SimpleDateFormat 类 在转换时间格式的时候,需要在构造方法中指定转换的时间格式 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 具体转换的实现是通过 SimpleDateFormat 类中 format 方法来实现的。 // 将 postTime 作为 format 方法的参数即可 return simpleDateFormat.format(postTime);//返回的时间格式,就是我们所指定的时间格式 } public void setPostTime(Timestamp postTime) { this.postTime = postTime; } }

?

提供 操作实体类方法 的类

?

BlogDao 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、往博客表里,插入一条记录(一篇博客) public static void insert(Blog blog){ Connection connection = null; PreparedStatement preparedStatement = null; // JDBC 基本的代码 try { //1、和数据库建立连接 connection = DButil.getConnection(); //2、构造sql语句 // 这里的 now() 表示取得是当前系统的时间 String sql = "insert into blog values(null,?,?,?,now())"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1,blog.getTitle()); preparedStatement.setString(2, blog.getContent()); preparedStatement.setInt(3,blog.getUserId()); //3、执行SQL语句 preparedStatement.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); }finally { //4、关闭连接,回收资源 DButil.close(connection,preparedStatement,null); } } // 2、能够获取到博客表中的所有博客的信息【用于博客列表页显示博客有多少】 // 另外,我们的博客列表,会从每篇博文中截取一段内容,因此截取的内容不一定是截取到完整的博客内容。 // 因为截取到完整的博客的内容,占用的空间会很大!两三行还行,多了就不利于显示更多的博文信息 public static List<Blog> selectAll(){ List<Blog> blogs = new ArrayList<>(); Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; // JDBC 基本的代码 try { //1、和数据库建立连接 connection = DButil.getConnection(); //2、构造sql语句 String sql = "select * from blog order by postTime desc"; preparedStatement = connection.prepareStatement(sql); //3、执行SQL语句 resultSet = preparedStatement.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()>50){ // 如果代码超过了50个字,进行截取 // 这里为什么是50,因为我愿意! content = content.substring(0,50) + "......."; } blog.setContent(content); blog.setUserId(resultSet.getInt("userId")); blog.setPostTime(resultSet.getTimestamp("postTime")); blogs.add(blog); } } catch (SQLException e) { e.printStackTrace(); }finally { //5、关闭连接,回收资源 DButil.close(connection,preparedStatement,resultSet); } return blogs; } // 3、能够根据 博客id 读取到指定的博客内容【用于博客详情页显示一篇博客的详情内容】 public static Blog selectOne(int blogId){ Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; // JDBC 基本的代码 try { //1、和数据库建立连接 connection = DButil.getConnection(); //2、构造sql语句 String sql = "select * from blog where blogId = ?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1,blogId); //3、执行SQL语句 resultSet = preparedStatement.executeQuery(); // 4、 遍历结果集:此处我们是使用主键 作为查询条件 // 因此查询的结果要么是一条,要么是零条。 if(resultSet.next()){ Blog blog = new Blog(); blog.setBlogId(resultSet.getInt("blogId")); blog.setTitle(resultSet.getString("title")); blog.setContent(resultSet.getString("content")); blog.setUserId(resultSet.getInt("userId")); blog.setPostTime(resultSet.getTimestamp("postTime")); return blog; } } catch (SQLException e) { e.printStackTrace(); }finally { //5、关闭连接,回收资源 DButil.close(connection,preparedStatement,resultSet); } return null; } // 4、从博客列表中,根据博客Id 来删除博客 public static void delete(int blogId){ Connection connection = null; PreparedStatement preparedStatement = null; // JDBC 基本的代码 try { //1、和数据库建立连接 connection = DButil.getConnection(); //2、构造sql语句 String sql = "delete from blog where blogId = ?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1,blogId); //3、执行SQL语句 preparedStatement.executeUpdate(); // 4、 遍历结果集:此处我们是使用主键 作为查询条件 // 因此查询的结果要么是一条,要么是零条。 } catch (SQLException e) { e.printStackTrace(); }finally { //5、关闭连接,回收资源 DButil.close(connection,preparedStatement,null); } } // 注意上述的4个操作只涉及到增删查,不涉及到 改。 // 改,这个操作可以被实现,但是不在这里实现。 // 换句话来说:实现该这个操作 和 前面的增删查,并没有什么区别。 // 所以在这里只是显示几个经典的功能,如果你们想要实现 改,加油! // 我偷个懒。 }

?

UserDao package model; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; // 这个类用于封装用户表的基本操作(增删查改) public class UserDao { //针对这个类来说,我们就简化的写就行了 // 注册/注销 的功能,我们就不实现了 // 主要实现: // 1、根据 用户名 来查询用户信息 // 这个操作会在登录逻辑中使用。 public static User selectByName(String username){ Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; try { connection = DButil.getConnection(); String sql = "select * from user 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、根据 用户id 来查询用户信息 // 博客详情页,就可以根据用户id 来查询作者的名字,将其显示出来 public static User selectById(int userId){ Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; try { connection = DButil.getConnection(); String sql = "select * from user 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; } }

?

View:前端页面显示 与 构造请求部分代码

引入 jQuery 和 引入 第三方库markdown,需要你们自己配置,反正我都讲了。 在博客页面设计的博文中

?

CSS 公共样式代码 /* 放置一些各个页面都会用到的公共样式 */ /* 导航栏就是属于每个页面都会用到的公共样式 */ /* 首先,我们需要去掉浏览器样式 */ *{ margin: 0; padding: 0; box-sizing: border-box; } /* 给整个页面加上背景 */ html,body{ /* html 的父元素就是浏览器窗口 */ /* 此处的100%,意思是 html 元素的高度 和 浏览器一样高*/ /* body 的元素是 html */ /* 其意思不言而喻,就是 “继承父亲的财产” */ height: 100%; /* 加上背景图 */ /* .. 是指 当前 common 文件的父级目录 */ /* 我存放的图片的地方,就与它的父级目录是同一级 */ background-image: url(../image/preview.jpg); /* 拒绝平铺 */ background-repeat: no-repeat; /* 图片覆盖整个页面 */ background-size: cover; /* 图片处于剧痛位置 */ background-position: center; } /* 导航栏样式 */ .navigation{ width: 100%; /* 一般这里的尺寸都是设计稿规定好了的 */ /* 但是这里并没有,所以这里的尺寸,自己决定 */ height: 50px; /* 由于导航栏的背景颜色属于一种半透明的状态 */ /* 所以我们要使用 rgba 的方式来进行处理 */ /* 颜色你们自己发挥 */ /* 需要注意的 透明度alpha 是一个 0 - 1 之间数字*/ background-color: rgba(55,20,11, 0.6); /* 由于里面的logo,标题,连接之类的都是水平一行来进行排列的 */ /* 所以,这里我们就需要用到弹性布局 */ display: flex; /* 实现元素 垂直居中的效果。 */ align-items: center; color: orange; } .navigation img{ /* 将图片缩小一段 */ /* 上期上下左右都留一点空位 */ width: 40px; height: 40px; /* 将图片设置为原型 */ border-radius: 50%; /* 设置 内/外边距 */ margin-left: 30px; margin-right: 10px; } .navigation .spacer{ /* 相对于父元素的宽度,占比百分之70 */ width: 80%; } .navigation a{ /* 去掉下划线 */ text-decoration: none; /* 设置标签之间的间距 */ /* 上下内边距0px,左右10px */ padding: 0 10px; color: orange; } /* 接下来是 版心相关的样式 */ .container{ /* 注意!既然要留有空白,那么宽度就不能是100% */ width: 1000px; /* 版心的理想高度:页面高度 减去 导航栏的高度 */ height: calc(100% - 50px); /* 水平居中 */ /* 上下外边距为 0,左右边距由浏览器自动调整 */ margin: 0 auto; display: flex; justify-content: space-between; } /* 版心的左侧部分 */ .container .left{ height: 100%; width: 200px; /* background-color: red; */ } /* 版心的右侧部分 */ .container .right{ height: 100%; /* 父类container 的宽度为1000px */ /* 左侧栏占了200px */ width: 795px; /* background-color: blue; */ /* 背景颜色 */ background-color: rgba(255, 255, 255,0.75); /* 边框圆角 */ border-radius: 20px; /* 处理溢出问题 */ /* 溢出了就滚动内容,没溢出就不滚动 */ overflow: auto; } /* 接下来实现 card 部分的样式 */ .card{ /* 背景颜色 */ background-color: rgba(255,255,255, 0.75); /* 背景区域圆角 */ border-radius: 20px; /* 通过这里的内边距,就可以让图片水平居中 */ /* 这里设置的30像素,意思是指4个方向,都是30px */ /* 因为我们的图片长宽都是140px,而card的宽为200px */ /* 200 -140 == 60px,两边一平摊刚好 30 px */ /* 刚好能水平居中,而且上下空出30px */ padding: 30px; } .card img{ /* card 的宽度为 200px 【默认与父类 left 宽度相同】*/ /* 先把图片的尺寸速效 */ width: 140px; height: 140px; /* 将图片变成圆形形 */ border-radius: 50%; } .card h3{ text-align: center; padding: 10px; } .card a{ /* 首先,先把 a 标签转化成 块级元素 */ /* 因为 a 默认是行内元素,行内元素的很多边距是没有效果的 */ display: block; /* 文本居中 */ text-align: center; /* 去掉下划线 */ text-decoration: none; /* 字体颜色 */ color: rgb(64, 55, 55); /* 间距 */ padding: 10px; } .card .counter{ /* 弹性布局,目的:为了更好的水平排列 */ display: flex; /* 通过 justify-content: aroumd */ /* 来使用它们左右进行分离排列 */ justify-content: space-around; padding: 5px; }

?

博客登录页 网页框架代码 与 动态显示代码 部分 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>博客登录页</title> <link rel="stylesheet" href="./css/common.css"> <link rel="stylesheet" href="./css/bloglogin.css"> </head> <body> <!-- 导航栏 --> <!-- 为了能够在后续过程中,添加样式等操作,建议都引入一个类属性 --> <div class="navigation"> <img src="./image/2.jpg" alt=""> <span>我的博客系统</span> <!-- 空白元素,用于站位置 --> <div class="spacer"></div> <a href="bloghome.html">主页</a> <a href="writingblog.html">写博客</a> <!-- <a href="#">注销</a> --> </div> <!-- 登录页面内容区域 --> <div class="login-container"> <!-- 加上一个对话框 --> <form action="login" method="post"> <div class="login-dialog"> <h3>登陆</h3> <div class="row"> <span>用户名</span> <input type="text" id="username" 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> </body> </html>

?

页面样式(CSS 代码) /* 登录页面的专用样式文件 */ .login-container{ /* 页面宽度 */ width: 100%; /* 减去导航的高度 */ height: calc(100% - 50px); /* 需要让里面的子元素,垂直水平居中,就会用到 flex */ display: flex; /* 垂直居中 */ align-items: center; /* 水平居中 */ justify-content: center; }

?

博客列表页(主页)

?

前端框架代码 与 JS代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>博客主页</title> <link rel="stylesheet" href="./css/common.css"> <link rel="stylesheet" href="./css/bologlist.css"> </head> <body> <!-- 导航栏 --> <!-- 为了能够在后续过程中,添加样式等操作,建议都引入一个类属性 --> <div class="navigation"> <img src="./image/2.jpg" alt=""> <span>我的博客系统</span> <!-- 空白元素,用于站位置 --> <div class="spacer"></div> <a href="bloghome.html">主页</a> <a href="writingblog.html">写博客</a> <a href="logout">注销</a> </div> <!-- 这里的 .container 作为页面的版心 --> <div class="container"> <!-- 左侧用户信息区 --> <div class="left"> <!-- 表示整个用户信息区域 --> <div class="card"> <img src="./image/1.jpg" alt=""> <!-- &nbsp; 空格符 --> <h3> </h3> <a href="#">github 地址</a> <div class="counter"> <span>文章</span> <span>分类</span> </div> <div class="counter"> <span>2</span> <span>1</span> </div> </div> </div> <!-- 右侧内容区 --> <div class="right"> <!-- 一个 blog 对应一篇博客 --> <!-- <div class="blog"> --> <!-- <div class="title"> 我的第一篇博客 </div> --> <!-- 博客发布时间 --> <!-- <div class="date"> 2022-05-10 17:48:00 </div> --> <!-- 博客内容摘要 --> <!-- <div class="desc"> --> <!-- 输入 lorem 可以UI及生成一句话 --> <!-- 从今天起,争取卷死在读的各位,Lorem ipsum dolor sit, amet consectetur adipisicing elit. Id numquam vitae voluptate, veniam ipsa placeat! Consectetur eligendi quia quibusdam, mollitia eos totam quod repellendus veritatis, iste natus odio nihil ea. --> <!-- </div> --> <!-- 注意!这里不能直接写 > 号 --> <!-- 因为会被 HTML认为是标签,而导致无法识别 --> <!-- 这里就需要用转义字符:大于号的转义字符 就是 &gt; --> <!-- <a href="./blogdetail.html">查看全文 &gt;&gt; </a> --> <!-- </div> --> <!-- </div> --> </div> </div> <!-- 首先,要想使用 ajax,就需要引入依赖 jQuery --> <script src="js/jquery.min.js"></script> <script> // 在页面加载的时候,通过 ajax 给服务器发送数据,获取博客列表信息,并且显示在页面上 // 这里我们通过函数来使用。 function getBlogList(){ $.ajax({ type: "get", url: "blog",// Servlet Path success: function(body){ // 获取到的body 已经是一个 json 数组,里面每个元素就是一个js对象 // 根据这个对象构造一个div // 1、 先把 .right里面的内容给清空 // 换成我们从服务器中得到的“新鲜数据” let rightDiv = document.querySelector('.right'); rightDiv.innerHTML="";// 清空 //2、遍历 body,构造出一个个的 blogDiv for(let blog of body){ let blogDiv = document.createElement('div'); blogDiv.className = "blog";// 引入CSS属性 // 构造标题 let titleDiv = document.createElement('div'); titleDiv.innerHTML = blog.title;//获取标题 titleDiv.className = "title";//引入CSS属性 blogDiv.appendChild(titleDiv);//接回页面中 // 构造发布时间 let dateDiv = document.createElement('div'); dateDiv.innerHTML = blog.postTime; dateDiv.className = "date"; 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 的作用就体现出来了,blogId就是一篇博客的身份Id // 我们可以通过 URL中的查询字符串来表明博客的身份 a.href = "blogdetail.html?blogId=" + blog.blogId; // 未来我们点击这个 a 标签 //发送的HTTP请求里面就会包含要访问的页面,以及对应博客身份 // 从而根据这个ID 找到 具体是那个博客,显示其内容 blogDiv.appendChild(a);// 记入页面 // 最后 把 blogDiv 接回到右侧区域 // 也就是blogDiv 挂到 DOM 树上。 rightDiv.appendChild(blogDiv); } }, error: function(){ alert("获取博客列表失败!") } }); } getBlogList();// 调用函数 // 在这里加上一个逻辑 // 通过 GET /login 这个接口,来获取当前的登录状态 function getUserInfo(){ $.ajax({ type: "get", url: "login", success: function(body){ // 判定此处的 body 对象,是不是一个有效的 user 对象(userId 是否 非0 ) // 判断body.userId 是否存在,并且大于0 if(body.userId && body.userId>0){ // 进入 if 语句中,说明用户:处于登录状态 console.log("当前用户登录成功! 用户名:" + body.username); // 根据当前用户登录情况,把当前用户名设置到界面上。 changeUserName(body.username); }else{ // 登录失败 // 通过 前端重定向方法 location.assign 方法 来进行页面的跳转 alert("当前您尚未登录!请在登录之后,再来访问博客列表!"); location.assign('bloglogin.html'); } }, error: function(){ alert("当前您尚未登录!请在登录之后,再来访问博客列表!"); location.assign('bloglogin.html'); } }); } getUserInfo(); function changeUserName(username){ let h3 = document.querySelector('.card>h3'); h3.innerHTML = username; } </script> </body> </html>

?

CSS 样式代码部分 /* 这个文件中专门写和博客列表页相关的样式 */ .blog{ /* blog的宽度 和 父元素right是一样的 */ width: 100%; /* 高度如果不设置,就取决于内容高度的总和 */ /* 所以,我们这里不去设置 */ /* 设置 每一篇博客的间距,通过 设置内边距 */ padding: 20px; } /* 标题 */ .blog .title{ /* 居中 */ text-align: center; /* 字体大小 */ font-size: 22px; /* 字体粗细 */ font-weight: bold; /* 设置边距 */ /* 上边边距 10px,左右0px(因为blog里面已经设置了啦20px) */ padding: 10px 0; } /* 日期 */ .blog .date{ /* 文本居中 */ text-align: center; /* 字体颜色 */ color: rgb(204, 33, 204); /* 边距 */ padding: 10px; } /* 自然段 */ .blog .desc{ text-indent: 2em; } /* 查看按钮 */ .blog a{ /* 将 a标签 设置为 块级元素,方便设置尺寸 和 边距 */ display: block; /* 尺寸 */ width: 140px; height: 40px; /* 去掉下划线 */ text-decoration: none; /* 字体颜色 */ color: black; /* 文本内容居中 */ text-align: center; /* 文本框垂直居中 */ line-height: 40px; /* 文本框水平居中位置 */ margin: 10px auto; /* 边框: 边框线条粗细2px,颜色:黑色,边框线条:实线 */ border: 2px black solid; /* 如果想让变化的过程变得柔和一些,就可以加上过渡效果 */ transition: all 0.5s; } /* 伪类选择器 */ .blog a:hover{ background-color: orange; }

?

博客详情页

?

前端框架代码 与 JS代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>博客详情页</title> <link rel="stylesheet" href="./css/common.css"> <link rel="stylesheet" href="./css/blogdetail.css"> <!-- 引入 editor.md 的依赖 --> <link rel="stylesheet" href="./editor.md/css/editormd.min.css"> <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.min.js"></script> </head> <body> <!-- 导航栏 --> <!-- 为了能够在后续过程中,添加样式等操作,建议都引入一个类属性 --> <div class="navigation"> <img src="./image/2.jpg" alt=""> <span>我的博客系统</span> <!-- 空白元素,用于站位置 --> <div class="spacer"></div> <a href="bloghome.html">主页</a> <a href="writingblog.html">写博客</a> <a href="logout">注销</a> </div> <!-- 这里的 .container 作为页面的版心 --> <div class="container"> <!-- 左侧用户信息区 --> <div class="left"> <!-- 表示整个用户信息区域 --> <div class="card"> <img src="./image/1.jpg" alt=""> <!-- &nbsp; 空格符 --> <h3> </h3> <a href="#">github 地址</a> <div class="counter"> <span>文章</span> <span>分类</span> </div> <div class="counter"> <span>2</span> <span>1</span> </div> </div> </div> <!-- 右侧内容区 --> <div class="right"> <!-- 使用一个 div 来包裹整个博客的内容详情 --> <div class="blog-content"> <!-- 博客标题 --> <h3> </h3> <!-- 发布时间 --> <div class="date"> </div> <div id="content" style="opacity: 60%"> </div> </div> </div> </div> <!-- 第一步还是先引入 jQuery 第三方库 --> <!-- 因为使用 ajax 需要依赖于 jQuery --> <script> // 使用函数来来进行前端代码的逻辑 function getBlogDetail(){ $.ajax({ type: "get", url: 'blog'+ location.search, success: function(body){ // 根据 body 中的内容来构造页面 // 构造 博客标题 let h3 = document.querySelector(".blog-content>h3"); h3.innerHTML = body.title; // 构造 博客发布时间 let dateDiv = document.querySelector(".date"); dateDiv.innerHTML = body.postTime; // 构造博客正文 // 如果直接把 content 设为innerHTML // 此时显示在界面上的内容是原始的 markdown字符串 // let content = document.querySelector('#content'); // content.innerHTML = body.content; //这里我们使用 editor.md 自带的方法,对内容进行渲染 // 第一个参数对应 id=content 的 html 标签:渲染后得到的HTML片段,就会被放到这个标签下 // 第二参数:需要渲染的数据对象 editormd.markdownToHTML('content',{ markdown: body.content }); } }); } getBlogDetail(); // 通过 GET /login 这个接口,来获取当前的登录状态 function getUserInfo(){ $.ajax({ type: "get", url: "login", success: function(body){ // 判定此处的 body 对象,是不是一个有效的 user 对象(userId 是否 非0 ) // 判断body.userId 是否存在,并且大于0 if(body.userId && body.userId>0){ // 进入 if 语句中,说明用户:处于登录状态 console.log("当前用户登录成功! 用户名:" + body.userName); //在 getUserInfo 的回调函数中,来调用 getAuthorInfo 函数 getAuthorInfo(body);// 将获取到用户信息,作为参数发送给 getAuthorInfo 函数 }else{ // 登录失败 // 通过 前端重定向方法 location.assign 方法 来进行页面的跳转 alert("当前您尚未登录!请在登录之后,再来访问博客列表!"); location.assign('bloglogin.html'); } }, error: function(){ alert("当前您尚未登录!请在登录之后,再来访问博客列表!"); location.assign('bloglogin.html'); } }); } getUserInfo(); //通过这个方法,从服务器获取当前博客的作者信息,并显示在封面上 function getAuthorInfo(user){//此时的参数user 就是刚才从服务器拿到的当前用户登录的信息 $.ajax({ type: "get", url:'authorInfo'+ location.search, // 此处的body,就是是一个 user 对象【这是文章作者的信息】 success: function(body){ if(body.username){ // 如果响应中的 username 存在,就把这个值设置到网页面上 changeUserName(body.username); // 然后,我们就可以开始进行判断了 if(body.username == user.username){ // 如果相同,说明作者 和 登录用户 就是同一个人。 // 此时就可以显示出 删除按钮 // 获取导航栏对象 let navDIV = document.querySelector('.navigation'); // 创建一个新的 a 标签 let a = document.createElement('a'); a.innerHTML= "删除"; //期望点击删除按钮,构造一个形如 blogDelete?blogId=6 这样的请求 a.href = "blogDelete"+ location.search; navDIV.appendChild(a);// 江删除按钮接入导航栏中 } }else{ console.log("获取作者信息失败" + body.reason); } } }); } // getAuthorInfo(); function changeUserName(username){ let h3 = document.querySelector('.card>h3'); h3.innerHTML = username; } </script> </body> </html>

?

CSS 样式代码部分 /* 给博客详情页使用的样式文件 */ /* 首先,我们需要给 blog-content 设置内边距*/ /* 因为你们仔细看看模板,就会知道内容与边框是有一定的距离的 */ .blog-content{ padding: 30px; } /* 标题 */ .blog-content h3{ /* 标题居中 */ text-align: center; /* 设置边距,因为标题与下面的时间是有距离的 */ /* .blog-content设置的边距 和这个没有关系!!! */ /* .blog-content 是针对整体,.blog-content h3 是针对内容中的标题 */ /* 上下 20 px,左右0px */ padding: 20px 0; } /* 日期 */ .blog-content .date{ /* 文本居中 */ text-align: center; /* 字体颜色 */ color: rgb(204, 33, 204); /* 针对日期设置间距 */ padding: 20px; } /* 自然段 */ .blog-content p{ /* 每个自然段首行缩进 2个字符 */ text-indent: 2em; /* 给每个自然段设置边距,使其每个自然段隔开 */ padding: 10px 0; }

?

博客编辑页

?

前端框架代码 与 JS代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>博客编辑页</title> <link rel="stylesheet" href="./css/common.css"> <link rel="stylesheet" href="./css/writingblog.css"> <!-- 引入 editor.md 的依赖 --> <link rel="stylesheet" href="./editor.md/css/editormd.min.css"> <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.min.js"></script> </head> <body> <!-- 导航栏 --> <!-- 为了能够在后续过程中,添加样式等操作,建议都引入一个类属性 --> <div class="navigation"> <img src="./image/2.jpg" alt=""> <span>我的博客系统</span> <!-- 空白元素,用于站位置 --> <div class="spacer"></div> <a href="bloghome.html">主页</a> <a href="writingblog.html">写博客</a> <a href="logout">注销</a> </div> <!-- 包裹整个博客编辑页内容的顶级容器 --> <div class="blog-edit-container"> <!-- 内容分为两个部分:标题区 和 编辑区 --> <form action="blog" method="post" style="height: 100%"> <!-- 标题区 --> <div class="title"> <!-- 输入框 --> <input type="text" placeholder="在这里写下文章标题" name="title" id="title"> <!-- <button>发布文章</button> --> <input type="submit" id="submit" value="发布文章"> </div> <!-- 编辑区:放置 markdown 编译器 --> <div id="editor"> <!-- 为了进行 form 的提交,此处搞一个 textare 多行编辑框 --> <!-- 借助这个编辑框来实现表单的提交 --> <!-- 可以借助 style属性给其添加一个 隐藏属性 --> <textarea name="content" style="display: none"></textarea> <!-- 通过 textare 可以设置 editor.md,让编辑器把 markdown 内容也同步的保存到这个隐藏的 textare 中 --> <!-- 从而就可以进行 form 提交 --> <!-- 不要去深究执行过程,这就是官方提供的一个做法 --> </div> </form> </div> <script> // 初始化编译器 let editor = editormd("editor",{ // 这里的尺寸必须在这里设置,设置样式会被 editormd 自动覆盖掉 width: "100%", // 设置编译器的高度 height: "calc(100% - 50px)", // 编译器中的初始内容 markdown: "# 在这里写一篇博客", // 指定 editor.md 依赖的插件路径 path: "./editor.md/lib/", // 此处要加上一个重要的选项,然后 editor.md 就会自动把用户在编辑器输入的内容同步保存到隐藏的textare中 saveHTMLToTextarea: true, }); </script> </div> </body> </html>

?

CSS 样式代码部分 /* 这是博客编辑页专用的样式文件 */ /* 容器 */ .blog-edit-container { /* 尺寸 */ width: 1000px; /* 页面高度 - 导航栏高度 */ height: calc(100% - 50px); /* 水平居中 */ margin: 0 auto; } /* 先针对标题区 的 div 下手 */ .blog-edit-container .title{ /* 与分类元素 .blog-edit-container 一样:1000px */ width: 100%; /* 标题区域高度 */ height: 50px; /* 为了方便调节尺寸,我们使用 弹性布局 */ display: flex; /* 垂直居中 */ align-items: center; /* 使子元素 输入框 和 按钮 靠两边放置,中间留个缝 */ justify-content: space-between; } .blog-edit-container .title #title{ /* 尺寸 */ /* 宽度我们要考虑一下 */ /* 总长度是1000px,我打算给按钮设置 100px 的宽度*/ /* 那么 输入的宽度就应该是 900px */ /* 但是模板中,两者是不能紧挨着的 */ /* 所以我给 890px */ width: 890px; height: 40px; /* 边框圆角 */ border-radius: 15px; /* 去掉边框线 */ border: none; /* 去掉轮廓线 */ outline: none; /* 背景颜色 和 透明度 */ background-color: rgba(255, 255, 255, 0.75); font-size: 20px; padding-left: 10px; } .blog-edit-container .title #submit{ width: 100px; height: 40px; /* 背景颜色 */ background-color: rgb(240, 163, 21); /* 字体颜色 */ color: white; /* 去掉边框线 */ border: none; /* 去掉轮廓线 */ outline: none; /* 边框圆角 */ border-radius: 15px; } /* 点击时,按钮背景颜色为黑色 */ .blog-edit-container .title #submit:active{ background-color: black; } #editor{ border-radius: 15px; /* background-color 只是针对当前元素进行设置,不会影响到子元素 */ /* background-color: rgba(255, 255, 255, 0.75); */ /* opacity 会影响到子元素 */ /* 给最外面的父元素设置了透明,里面的元素也会一起半透明 */ opacity: 75%; }

?

Controller : 处理请求逻辑代码补码

?

博客登录页/博客列表页/博客详情页/博客编辑页 - 服务器代码

doPost :用户登录逻辑 doGet:验证登录状态逻辑

package controller; import com.fasterxml.jackson.databind.ObjectMapper; import model.User; import model.UserDao; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/login") public class loginServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf8");// 设置字符集 resp.setCharacterEncoding("utf8"); //1、获取到请求中的参数 String username = req.getParameter("username"); String password = req.getParameter("password"); //2、和数据库的内容进行比较 if(username == null || "".equals(username) || password == null || "".equals(password)){ resp.setContentType("text/html; charset=utf8");//告诉浏览器当前返回一个 静态页面,读取数据形式是utf8 resp.getWriter().write("当前用户名或密码不能为空!"); }else{ // 用户名 和 密码 不为空的情况 User user = UserDao.selectByName(username); if(user == null || !user.getPassword().equals(password)){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("当前用户名,或者密码不能正确!"); }else{ //3、如果比较通过,就创建会话 HttpSession session = req.getSession(true); // 把刚才的用户信息,存储在会话中 session.setAttribute("user", user); //4、返回一个重定向报文,跳转到博客列表页 resp.sendRedirect("bloghome.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 = new User(); resp.getWriter().write(objectMapper.writeValueAsString(user)); return; } // 如果会话不为空,就来查询会话中是否存在user 对象 // 如果存在,返回的就是 一个非空的 user 对象 // 如果不存在,返回的是一个 null User user = (User) session.getAttribute("user"); // user 不存在 if(user == null){ user = new User(); resp.getWriter().write(objectMapper.writeValueAsString(user)); return; } // user存在 / 处于已登录的状态 // 注意:此处不要把密码也给返回去了【避免密码泄露】 user.setPassword(""); resp.getWriter().write(objectMapper.writeValueAsString(user)); } }

?

博客列表页 / 博客编辑页 - 服务器代码

doGet:获取数据库存储的博客列表信息 doPost:发布博客

package controller; import com.fasterxml.jackson.databind.ObjectMapper; import model.Blog; import model.BlogDao; import model.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.List; @WebServlet("/blog") public class blogServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); // 这个方法用来获取到数据库中的博客列表 @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("application/json; charset=utf8"); // 先尝试获取到 req 中的 blogId 参数 // 如果参数存在,说明接下来要处理的详情页的逻辑;反之,接下来就要处理列表页的逻辑 String str = req.getParameter("blogId"); if(str == null){ // 如果 str 为 null,说明:接下来就要处理列表页的逻辑 // 从数据数据中查询到博客列表,将格式转化成json格式,然后直接返回即可 List<Blog> blogs = BlogDao.selectAll(); // 把 blogs 转化成json数组的形式 String respJson = objectMapper.writeValueAsString(blogs); // resp.setContentType("application/json; charset=utf8"); resp.getWriter().write(respJson); }else{ // 反之,接下来就要处理详情页的逻辑 // 将获取到的 blogId 转换成 整形 int blogId = Integer.parseInt(str); // 调用我们之前书写 selectOne 方法,来查询指定的博客信息 Blog blog = BlogDao.selectOne(blogId); // 将 blog对垒 转换成 json格式的字符串 String respJson = objectMapper.writeValueAsString(blog); // resp.setContentType("application/json; charset=utf8"); resp.getWriter().write(respJson); } } // 发布博客 @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(false); if(session == null){ // 当前用户处于未登录状态,不能发布博文! resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("前用户处于未登录状态,不能发布博文!"); return; } User user = (User) session.getAttribute("user"); if(user == null){ // 当前用户处于未登录状态,不能发布博文! resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("前用户处于未登录状态,不能发布博文!"); return; } // 只要上面两个条件都满足,才能算是处于登录状态 // 也只有处于登录的用户,才能编写博客。 // 如果别人写了半天,点击发布,你提示别人未登录,这不是消遣别人嘛! // 因此 判断用户登录状态的代码 必须置前!才能执行下面的逻辑! req.setCharacterEncoding("utf8");// 一定要先指定好请求按哪种编码方式来解析 // 先从请求中,取出参数(博客的标题和正文) String title = req.getParameter("title"); String content = req.getParameter("content"); if(title == null || content == null || "".equals(title)||"".equals(content)){ // 我们认为质押出现上述情况之一,就是违法数据 // 直接告诉客户端,请求参数不对 resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("发布博客失败!缺少必要的参数!"); return; } // 如果没有出错的话,我们就构造一个 Blog 对象,把当前的数据存入数据库 Blog blog = new Blog(); // 此处我们蛀牙给 Blog 设置的属性,主题是 title,content,userId(作者信息) // postTime【now()方法】 和 blogId(自增主键) 这两个不需要手动指定,都是插入数据库的时候,自动生成的。 blog.setTitle(title); blog.setContent(content); // 作者 id 就是当前提交这个博客的用户的身份信息!!! blog.setUserId(user.getUserId()); BlogDao.insert(blog); // 新增博客之后,我们就需要跳转到博客列表页 resp.sendRedirect("bloghome.html"); } }

?

博客详情页 - 服务器代码 获取作者信息 package controller; import com.fasterxml.jackson.databind.ObjectMapper; import model.Blog; import model.BlogDao; import model.User; import model.UserDao; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/authorInfo") public class AuthorInfoServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("application/json; charset=utf8"); // 通过这个方法,来获取到指定的博客作者信息 String blogId = req.getParameter("blogId"); if(blogId == null || "".equals(blogId)){ // 参数绝少 resp.getWriter().write("{ \"ok\": false,\"reason\": \"参数缺失!\"}"); return; } // 根据 blogId 在数据库中进行查找,找到对应的 Blog 对象 // 然后,在进一步根据 Blog 对象 找到作者信息 Blog blog = BlogDao.selectOne(Integer.parseInt(blogId)); if(blog == null){ resp.getWriter().write("{ \"ok\": false,\"reason\": \"要查找的博客不存在!\"}"); return; } //根据 blog 对象,查询到用户对象 User author = UserDao.selectById(blog.getUserId()); if (author == null){ resp.getWriter().write("{ \"ok\": false,\"reason\": \"要查找的用户不存在!\"}"); return; } // 把 author 对象返回到浏览器这边 author.setPassword(""); String respJson = objectMapper.writeValueAsString(author); resp.getWriter().write(respJson); } }

?

删除博客操作 package controller; import model.Blog; import model.BlogDao; import model.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/blogDelete") public class BlogDeleteServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1、检查当前用户是否登录 HttpSession session = req.getSession(false); if (session == null){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("当前您处于未登录状态,无法进行删除操作!"); return; } User user = (User) session.getAttribute("user"); if(user == null){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("当前您处于未登录状态,无法进行删除操作!"); return; } // 2、获取到参数中的 blogId String blogId = req.getParameter("blogId"); if (blogId == null || "".equals(blogId)){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("参数残缺,无法进行删除操作!"); return; } // 3、获取需要删除的博客信息 Blog blog = BlogDao.selectOne(Integer.parseInt(blogId)); if (blog == null){ resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("您要删除的博客不存在,无法进行删除操作!"); return; } // 4、再次校验:当前的用户是否是博客的作者 if(user.getUserId() != blog.getUserId()){ // 虽然在前端中处理过这个问题,但是再稳一波,twin check 更保险! resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("非法删除操作!"); return; } // 5、删除指定博客 BlogDao.delete(Integer.parseInt(blogId)); // 6、返回一个重定向响应 resp.sendRedirect("bloghome.html"); } }

?

总结

学到了这里,就就是一种检验,检验前面博客讲的知识是不是学扎实了。 至少你得能看懂代码。

当前博客系统,可以认为是一个比较简单的程序。 像这样的程序,你写道简历上,当做一个小小项目也行。 其作用 聊胜于无,不然也不会说是小小项目。 但是别看简单,要动手!知道嘛!


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #小小项目博客系统 #服务器版本 #javaEE初阶 #细节狂魔