1. 博客系统的基本情况
1.1 四个页面
1.博客列表页:显示出当前网站上都有哪些博客
2.博客详情页:点击列表上的某个博客,就能进入对应详情页,(显示出博客的具体内容)
3.博客编辑页:让用户编写博客内容,并且发送到服务器
4. 博客系统登录页总所周知,我们的博客是markdown编辑器,markdown(md)是程序员常用的一种用来写文档的语言,主流的博客系统, 都是支持 md;我们所编写的博客系统使用到的md编辑器不是我们所写的,而是我们引第三方库editor.md,这是一个开源的项目,从 github 上下载好,放到这个目录里了,如下图所示;
1.2 项目编写逻辑
当下要完成的任务,是基于上述页面,来进行编写服务器/前后端交互代码
1)、实现博客列表页.
让页面从服务器拿到博客数据 (数据库)
2)、实现博客详情页,
点击博客详情的时候,可以从服务器拿到博客的完整数据3) 、实现登录功能
4)、实现强制要求登录
(当前处于未登录的状态下,其他的页面,博客列表,博客详情,博客编辑...就会强制跳转到登录页),要求用户登录之后才能进入博客系统
5)、实现显示用户信息
从服务器获取到下面两种信息:博客列表页拿到的是当前登录的用户的信息;
博客详情页拿到的是文章作者的信息.
6)、实现退出登录
7)、发布博客
博客编辑页,输入文章标题和内容之后,点击发布,就能把这个数据上传到服务器上并保存
1.3 准备工作
1) 、创建项目,引入依赖,把当前的这些前端页面也导入到项目中.
分别引入javax.servlet、mysql、jackson-databind的相关依赖,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>blog_system</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
</dependencies>
<packaging>war</packaging>
<build>
<finalName>java109_blog_system</finalName>
</build>
</project>
2)、数据库设计
设计好对应的表结构,并且把数据库相关代码,也进行封装 ,步骤如下:
a)、找到实体
博客 (blog 表)
用户 (user 表)数据库语言来创建用户表和博客表:
create database if not exists java109_blog_system charset utf8; use java109_blog_system; drop table if exists blog; drop table if exists user; create table blog ( blogId int primary key auto_increment, title varchar(1024), content varchar(4096), postTime datetime, userId int ); create table user ( userId int primary key auto_increment, username varchar(50) unique, -- 用户名也要求是不能重复的. password varchar(50) ); -- 插入一些测试数据, 方便后续进行测试工作 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, 'smallye', '111'); insert into user values (2, 'shengmengyao', '222');
b)、确认实体之间的关系
一对多的关系:一个博客,只能属于一个用户
一个用户,可以发布多个博客,下面是blog和user的相关属性:blog (blogld, title, content, postTime, userld)
user (userld, username, password)3)、把数据库操作的代码进行一些封装.
进行网站开发的过程中,一种常见的代码组织结构是MVC 结构.
M mode!: 操作数据的代码
V view: 操作/构造界面的代码
C controller: 业务逻辑,处理前端请求懒汉模式是线程不安全的,当前 Servlet 本身就是在多线程环境下执行的,且Tomcat 收到多个请求的时候,就会使用多线程的方式,执行不同的 Servlet 代码,
DBUtil主要是完成对于数据库建立连接和关闭连接的实现,下面是对于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; // 通过这个类, 封装数据库建立连接的操作. // 由于接下来代码中, 有多个 Servlet 都需要使用数据库. 就需要有一个单独的地方来把 DataSource 这里的操作进行封装. // 而不能只是放到某个 Servlet 的 init 中了. // 此处可以使用 单例模式(懒汉模式更安全) 来表示 dataSource public class DBUtil { private volatile static DataSource dataSource = null; private static DataSource getDataSource() { if (dataSource == null) { synchronized (DBUtil.class) { if (dataSource == null) { dataSource = new MysqlDataSource(); ((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java109_blog_system?characterEncoding=utf8&useSSL=false"); ((MysqlDataSource) dataSource).setUser("root"); ((MysqlDataSource) dataSource).setPassword("111111"); } } } return dataSource; } public static Connection getConnection() throws SQLException { return getDataSource().getConnection(); } public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { throw new RuntimeException(e); } } if (statement != null) { try { statement.close(); } catch (SQLException e) { throw new RuntimeException(e); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { throw new RuntimeException(e); } } } }
之后就是创建实体类,每个表都需要搞一个专门的类来表示,表里的一条数据,就会对应到这个类的一个对象,把数据库中的数据和代码联系起来了,如下所示:
这是博客类:
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 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 String getPostTime() { // Java 标准库提供了一个 SimpleDateFormat 类, 完成时间戳到格式化时间的转换. // 这个类的使用, 千万不要背!!! 都要去查一下!! 背大概率会背错!!! 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; } @Override public String toString() { return "Blog{" + "blogId=" + blogId + ", title='" + title + '\'' + ", content='" + content + '\'' + ", postTime=" + postTime + ", userId=" + userId + '}'; } }
这是用户user类:
package model; // User 对象就对应到 user 表中的一条记录. 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; } @Override public String toString() { return "User{" + "userId=" + userId + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
还需要创建两个类,来完成针对博客表和用户表的增删改查操作:
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; // 通过 BlogDao 来完成针对 blog 表的操作 public class BlogDao { // 1. 新增操作 (提交博客就会用到) public void insert(Blog blog) { Connection connection = null; PreparedStatement statement = null; try { // 1. 建立连接 connection = DBUtil.getConnection(); // 2. 构造 SQL String sql = "insert into blog values (null, ?, ?, now(), ?)"; statement = connection.prepareStatement(sql); statement.setString(1, blog.getTitle()); statement.setString(2, blog.getContent()); statement.setInt(3, blog.getUserId()); // 3. 执行 SQL statement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(); } finally { DBUtil.close(connection, statement, null); } } // 2. 查询博客列表 (博客列表页) // 把数据库里所有的博客都拿到. public List<Blog> getBlogs() { List<Blog> blogList = new ArrayList<>(); Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; try { connection = DBUtil.getConnection(); String sql = "select * from blog order by postTime desc"; statement = connection.prepareStatement(sql); resultSet = statement.executeQuery(); while (resultSet.next()) { Blog blog = new Blog(); blog.setBlogId(resultSet.getInt("blogId")); blog.setTitle(resultSet.getString("title")); // 此处读到的正文是整个文章内容. 太多了. 博客列表页, 只希望显示一小部分. (摘要) // 此处需要对 content 做一个简单截断. 这个截断长度 100 这是拍脑门出来的. 具体截取多少个字好看, 大家都可以灵活调整. 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")); blogList.add(blog); } } catch (SQLException e) { throw new RuntimeException(e); } finally { DBUtil.close(connection, statement, resultSet); } return blogList; } // 3. 根据博客 id 查询指定的博客 public Blog getBlog(int blogId) { Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; try { connection = DBUtil.getConnection(); String sql = "select * from blog where blogId = ?"; statement = connection.prepareStatement(sql); statement.setInt(1, blogId); resultSet = statement.executeQuery(); // 由于此处是拿着 blogId 进行查询. blogId 作为主键, 是唯一的. // 查询结果非 0 即 1 , 不需要使用 while 来进行遍历 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 throwables) { throwables.printStackTrace(); } finally { DBUtil.close(connection, statement, resultSet); } return null; } // 4. 根据博客 id, 删除博客 public void delete(int blogId) { Connection connection = null; PreparedStatement statement = null; try { connection = DBUtil.getConnection(); String sql = "delete from blog where blogId = ?"; statement = connection.prepareStatement(sql); statement.setInt(1, blogId); statement.executeUpdate(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { DBUtil.close(connection, statement, null); } } }
UserDao:
package model; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; // 通过 UserDao 完成针对 user 表的操作 public class UserDao { // 新增暂时不需要. 不需要实现 "注册" 功能. // 删除暂时不需要. 不需要实现 "注销帐户" 功能. // 1. 根据 userId 来查到对应的用户信息 (获取用户信息) public User getUserById(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 throwables) { throwables.printStackTrace(); } finally { DBUtil.close(connection, statement, resultSet); } return null; } // 2. 根据 username 来查到对应的用户信息 (登录) public User getUserByName(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 throwables) { throwables.printStackTrace(); } finally { DBUtil.close(connection, statement, resultSet); } return null; } }
DAO =>Data Access Object 数据访问对象,通过这两个类的对象,来完成针对数据库表的操作.
2. 博客系统的编写
2.1 获取博客列表页
在博客列表页加载的时候,通过 ajax 给服务器发起请求,从服务器(数据库) 拿到博客列表数据,并且显示到页面上;
工作步骤:
1)、约定前后端交互接口
请求和格式如下图所示;
2)、让浏览器给服务器发起请求了
在前端写完请求代码之后一定要在后面进行调用该请求方法,这样才能成功向服务器发起请求;
3)、服务器处理上述请求,返回响应数据(查询数据库)
4)、让前端代码处理上述响应数据
构造成 html 片段,显示到页面上关于前端代码的转义字符的表示:
上述代码的专有名词的说明:
这里的构造页面的过程,还是之前的 api,主要如下所示:
1)、querySelector: 获取页面已有的元素
2)、createElement: 创建新的元素
3)、innerHtml: 设置元素里的内容
4)、className: 设置元素的 class 属性
5)、appendChild: 把这个元素添加到另一个元素的末尾.上述代码中a 标签在 html 中称为"超链接”,点击之后能够跳转到一个新的页面
前端代码生成页面如下所示:
此处使用的是比较朴素的方式是基于 dom api 的方式,dom api 就属于是浏览器提供的标准的 api(不属于任何的第三方框架和库);
存在的问题一:
我们期望的是格式化时间,当发布完文章之后就需要把时间戳转成格式化时间,即形如2023-12-31 22:23:59的形式;
归根到底就是要修改服务器的代码,让返回给前端的时间数据就是格式化数据,而不是简单的时间戳,如下所示:
1)、jackson 发现 blogs 是一个 List,于是就会循环遍历里面的每个元素
2)、针对每个元素(Blog 对象),通过反射的方式,获取到都有哪些属性, 属性的名字,属性的值关于格式化时间的语法:
上述代码指定一个格式化字符串,描述了当前时间日期具体的格式;
修改之后的服务器getPostTime的方法如下所示:
public String getPostTime() { // Java 标准库提供了一个 SimpleDateFormat 类, 完成时间戳到格式化时间的转换. // 这个类的使用, 千万不要背!!! 都要去查一下!! 背大概率会背错!!! SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return simpleDateFormat.format(postTime); }
存在的问题二:希望我们写好的博客,能够按照发布时间就近从上往下排序,而不是乱序;
博客列表里面的数据都是从数据库里拿出来的,所以我们需要展示blog表的数据的时候按照有序展示即可,代码如下所示:
String sql = "select * from blog order by postTime desc";
2.2 博客详情页
点不同的博客,跳转过去之后,都会带有不同的 blogld 的query string,后续在博客详情页中,就可以也给服务器发起 ajax 请求,根据这里的 bogld查询数据库中该博客的具体内容,并且再返回给博客详情页,即前端还是把得到的数据给构造到页面上.
1)、约定前后端交互接口
这个请求是在进入博客详情页的时候,通过ajax 发给服务器.
请求和相应格式如下所示:2) 、让前端代码,通过 ajax 发起请求.
此处发起 ajax 请求的时候要带有 blogld,但是blogld 当前就处于博客详情页 的 url 中.
我们就可以通过 location.search 方式拿到 详情页面 url 中的 query string;综上所述:
当前是使用一个 Servlet 处理两种请求,
博客列表页,不带 query string
博客详情页,带有 query string,所以就可以根据 query string 是否存在的情况,来区分是哪种请求并相对应的分别返回不同的数据即可.3)、让服务器处理这个请求
4)、前端拿到响应之后, 把响应数据, 构造成页面的 html 片段
写完代码之后,再点击某个博客,就可以看到有的博客里面详情页还是之前的旧的内容.
这个问题实际上是浏览器缓存引起的,浏览器加载页面的时候,是通过网络获取的.(网络速度比较慢),浏览器有时候就会把已经加载过的页面,在本地硬盘保存一份,后续再访问同一个页面,这样就不需要通过网络加载,直接加载本地硬盘的这一份,对于这种情况, 用ctrl + F5来强制刷新页面。当前博客详情页虽然能够显示出博客的正文了,为了显示正文被md 渲染后的效果,所以我们要使用第三方库(editor.md);
editor.md 官方文档上,给出了具体的例子,来完成上述操作:
2.3. 实现登录
在登录页面,在输入框中填写用户名和密码.之后点击登录,就会给服务器发起 HTTP 请求,(可以使用 ajax,也可以使用 form表单,服务器就会处理登录请求.读取用户名和密码,在数据库査询是否匹配,如果正确,就登录成功,创建会话,跳转到博客列表页 ;由于这里登录成功,,直接进行重定向跳转,这种重定向操作,就不需要浏览器额外写代码处理,直接浏览器就自动跳转了
1)、约定前后端交互接口
2)、让前端发起请求.
3)、让服务器处理请求,并返回响应
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 读取参数中的用户名和密码 req.setCharacterEncoding("utf8"); String username = req.getParameter("username"); String password = req.getParameter("password"); // 验证一下参数, 看下是否合理. if (username == null || username.length() == 0 || password == null || password.length() == 0) { resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("您输入的用户名或者密码为空!"); return; } // 2. 查询数据库, 看看这里的用户名密码是否正确. UserDao userDao = new UserDao(); User user = userDao.getUserByName(username); if (user == null) { // 用户名不存在! resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("您输入的用户名或密码不正确!"); return; } if (!password.equals(user.getPassword())) { // 密码不正确! resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("您输入的用户名或密码不正确!"); return; } // 3. 创建会话 HttpSession session = req.getSession(true); session.setAttribute("user", user); // 4. 跳转到主页了. resp.sendRedirect("blog_list.html"); }
2.4. 强制要求登录
在博客列表页,详情页,编辑页,判定当前用户是否已经登录(在这几个页面中,在每一次页面刷新都会给服务器发起ajax请求,从服务器获取当前的登录状态),如果未登录,则强制跳转到登录页.(要求用户必须登录后才能使用);
1)、约定前后端交互接口
2)、让前端代码发起这个请求
一个页面触发的 ajax 是可以有多个的,一个页面通常都会触发多个 ajax.这些 ajax 之间是“并发执行"的。js 中, 没有"多线程”这样的机制;而 ajax 是一种特殊情况, 能够起到类似于"多线程"的效果,所以当页面中发起两个或者多个 ajax 的时候,这些 ajax 请求就相当于并发的发送的,彼此之间不会相互干预.(不是串行执行,也不是执行完一个 ajax,得到响应之后再执行下一个....)
同时对于从服务器传来的响应,谁的响应先回来了,就先执行谁的回调;
3)、让服务器处理上述请求.
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 通过这个方法, 来反馈当前用户的登录状态. // 一个简单的判定方式, 直接看会话是否存在. // 此处使用一个更严格的方式. 不仅要求会话要存在, 还要求会话中要能保存 user 对象. // (之所以给出这种设定, 也是为了后面实现 "退出登录" 这样的功能来做个铺垫) HttpSession session = req.getSession(false); if (session == null) { // 会话不存在, 用户属于未登录状态. resp.setStatus(403); return; } User user = (User) session.getAttribute("user"); if (user == null) { // user 对象也不存在. 同样也属于未登录状态 resp.setStatus(403); return; } // 两个都存在, 返回 200 // 此处 200 不写也行. 默认就是 200 resp.setStatus(200); }
当前虽然登录过了,但是一旦重新启动服务器,此时仍然会被判定为未登录状态.登录状态是通过服务器这里的 session 来存储的.session是服务器内存中的类似于 hashmap 这样的结构,一旦服务器重启了,hashmap 里面原有的内容就没了.
当前只是让博客列表页,能够有强制登录.但是实际上,博客编辑页和博客详情页,也应该要有这种机制:
如上图所示,可以把一些公共的 js 代码(判断当前的登录状态,没有进行登录的话,就强制进入到登录页面),单独提取出来放到某个 js 文件中,然后通过 html 中的 script 标签,来引用这样的文件内容,此时如上图所示, 就可以在 html 中调用对应的公共代码了.(可以理解为java中的导包import),即将下面一部分重新创建类,然后后续在需要这个过程的时候只要通过类名进行调用即可;
// 定义新的函数, 获取登录状态 function getLoginStatus() { $.ajax({ type: 'get', url: 'login', success: function(body) { // 已经登录的状态. console.log("已经登录了!"); }, error: function() { // error 这里对应的回调函数, 就是在响应状态码不为 2xx 的时候会触发. // 当服务器返回 403 的时候, 就会触发当前这个 error 部分的逻辑了. // 强制要求页面跳转到博客登录页. // 为啥不在服务器直接返回一个 302 这样的重定向响应, 直接跳转到登录页呢? // 302 这种响应, 无法被 ajax 直接处理. (如果是通过提交 form, 点击 a 标签这种触发的 http 请求, 浏览器可以响应 302) // 前端页面跳转的实现方式. location.assign('login.html'); } }) }
2.5. 显示用户信息
博客列表页: 显示的是当前登录的用户的信息
博客详情页:显示的是当前文章的作者信息
在页面加载的时候,给服务器发起 ajax 请求,服务器返回对应的用户数据,根据发起请求不同的页面,服务器返回不同的信息即可;
1)、约定前后端交互接口
2)、先让前端代码,发起这样的请求.
3)、编写服务器代码,来处理上述请求
userInfo类:用户信息服务器
package servlet; import com.fasterxml.jackson.databind.ObjectMapper; 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("/userInfo") public class UserInfoServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); @Override protected void doGet(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; } // 此时把 user 对象转成 json , 并返回给浏览器. resp.setContentType("application/json; charset=utf8"); // 注意, user 中还有 password 属性呢!! 把密码再返回回去, 不太合适的. user.setPassword(""); String respJson = objectMapper.writeValueAsString(user); resp.getWriter().write(respJson); } }
博客作者信息服务器:
package servlet; 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("/getAuthorInfo") public class AuthorInfoServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 先拿到请求中的 blogId String blogId = req.getParameter("blogId"); if (blogId == null) { resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("请求中缺少 blogId!"); return; } // 2. 在 blog 表中查询到对应的 Blog 对象 BlogDao blogDao = new BlogDao(); Blog blog = blogDao.getBlog(Integer.parseInt(blogId)); if (blog == null) { resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("blogId 没有找到!"); return; } // 3. 根据 blog 对象中的 userId, 从 user 表中查到对应的作者. UserDao userDao = new UserDao(); User user = userDao.getUserById(blog.getUserId()); if (user == null) { resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("userId 没有找到!"); return; } // 4. 把这个 user 对象, 返回到浏览器这边. user.setPassword(""); String respJson = objectMapper.writeValueAsString(user); resp.setContentType("application/json; charset=utf8"); resp.getWriter().write(respJson); } }
此处是通过两步 sql分别查询的,先査的 blog 表里面的 blog 对象,再查的 user 表;
4)、在前端代码中,处理响应.
把响应中的数据,给写到刚才页面的对应位置上
2.6 退出登录
博客列表/博客详情/博客编辑 导航栏中,都有一个"注销"按钮,让用户点击"注销"的时候,就能够触发一个 HTTP 请求(GET 请求),服务器收到这个 GET 请求的时候,就把会话里的 user 这个Attribute 给删了;由于在判定用户是否是登录状态的逻辑中,需要同时验证会话存在且和这里的 user Attribute 也存在,只要破坏一个上述条件,就可以使登录状态发生改变了.
为啥不直接删除 session 本身,其主要因为servlet 没有提供删除 session 的方法.虽然有间接的方式(session 可以设置过期时间,设置一个非常短的过期时间),也可以起到删除的效果,但是实现效果其实不是特别很好;session 提供了 removeAttribute 这样的方法, 可以把 user 这个 Attribute 给删了
1)、约定前后端交互接口
2)、编写前端代码, 发送请求,
不用写 ajax, 直接就给 a 标签设置 href 属性即可;3)、编写后端代码,处理这个请求,完成退出登录的操作.
package servlet; 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) { resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("当前您尚未登录"); return; } // 再把会话中的 user 的属性给删除掉 session.removeAttribute("user"); // 跳转到博客登录页 resp.sendRedirect("login.html"); } }
2.7 发布博客
当点击提交的时候,就需要构造 http 请求,把此时的页面中的标题和正文都传输到服务器这过
服务器把这个数据存入数据库即可.此处这里的 http 请求,可以使用 ajax,也可以使用 form (这种填写输入框,提交数据的场景,使用 form 会更方便)
1)、约定前后端交互接口
2)、编写前端代码构造请求.
标题,本身就是一个属性,咱们自己写的 input,给他加上 name 属性也很容易.但是博客正文,是由 editor md 构成的一个编辑器,这里如何添加 name 属性呢:我们可以将 editor md 和 form 表单配合使用.
如上图所示,这个 div 就是 editor.md 的编辑器的容器,在这个 div 里,设置一个隐藏的 textarea 标签(多行编辑框,把 name 属性加到这个 textarea.),并且在初始化 editormd 对象的时候,加上一个对应的属性即可;
请求抓包如下所示:
3)、编写服务器代码, 处理刚才的请求
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 读取请求中的参数 req.setCharacterEncoding("utf8"); String title = req.getParameter("title"); String content = req.getParameter("content"); if (title == null || title.length() == 0 || content == null || content.length() == 0) { resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("当前传过来的标题或正文为空! 无法新增博客!"); return; } // 2. 从会话中, 拿到当前登录的用户的 userId 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; } // 3. 构造 blog 对象 Blog blog = new Blog(); blog.setTitle(title); blog.setContent(content); // 从会话中拿到 当前正在登录的用户的 userId, 设置进去即可 blog.setUserId(user.getUserId()); // 4. 插入到数据库中 BlogDao blogDao = new BlogDao(); blogDao.insert(blog); // 5. 返回一个 302 这样的重定向报文, 跳转到博客列表页. resp.sendRedirect("blog_list.html"); }
3. 项目展示
1、进入登录页面
2、博客列表页及用户信息页
3、博客详情页及作者信息页
ps:关于博客系统就到这里了,如果感兴趣的话就请一键三连哦!!!