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的相关依赖,如下所示:

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">

4.0.0

org.example

blog_system

1.0-SNAPSHOT

8

8

UTF-8

javax.servlet

javax.servlet-api

3.1.0

provided

mysql

mysql-connector-java

5.1.47

com.fasterxml.jackson.core

jackson-databind

2.15.0

war

java109_blog_system

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 getBlogs() {

List 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:关于博客系统就到这里了,如果感兴趣的话就请一键三连哦!!! 

 

相关文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: