【Java EE初阶二十八】简单的博客系统

1. 博客系统的基本情况

1.1 四个页面

1.博客列表页:显示出当前网站上都有哪些博客

2.博客详情页:点击列表上的某个博客,就能进入对应详情页,(显示出博客的具体内容)

3.博客编辑页:让用户编写博客内容,并且发送到服务器

  1. 博客系统登录页

总所周知,我们的博客是markdown编辑器,markdown(md)是程序员常用的一种用来写文档的语言,主流的博客系统, 都是支持 md;我们所编写的博客系统使用到的md编辑器不是我们所写的,而是我们引第三方库editor.md,这是一个开源的项目,从 github 上下载好,放到这个目录里了,如下图所示;

1.2 项目编写逻辑

当下要完成的任务,是基于上述页面,来进行编写服务器/前后端交互代码

1)、实现博客列表页.

让页面从服务器拿到博客数据 (数据库)

2)、实现博客详情页,

点击博客详情的时候,可以从服务器拿到博客的完整数据

  1. 、实现登录功能

4)、实现强制要求登录

(当前处于未登录的状态下,其他的页面,博客列表,博客详情,博客编辑...就会强制跳转到登录页),要求用户登录之后才能进入博客系统

5)、实现显示用户信息

从服务器获取到下面两种信息:

博客列表页拿到的是当前登录的用户的信息;

博客详情页拿到的是文章作者的信息.

6)、实现退出登录

7)、发布博客

博客编辑页,输入文章标题和内容之后,点击发布,就能把这个数据上传到服务器上并保存

1.3 准备工作

  1. 、创建项目,引入依赖,把当前的这些前端页面也导入到项目中.

分别引入javax.servlet、mysql、jackson-databind的相关依赖,如下所示:

java 复制代码
<?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 表)

数据库语言来创建用户表和博客表:

sql 复制代码
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的代码:

java 复制代码
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);
            }
        }
    }
}

之后就是创建实体类,每个表都需要搞一个专门的类来表示,表里的一条数据,就会对应到这个类的一个对象,把数据库中的数据和代码联系起来了,如下所示:

这是博客类:

java 复制代码
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类:

java 复制代码
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:

java 复制代码
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:

java 复制代码
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的方法如下所示:

java 复制代码
public String getPostTime() {
        // Java 标准库提供了一个 SimpleDateFormat 类, 完成时间戳到格式化时间的转换.
        // 这个类的使用, 千万不要背!!! 都要去查一下!! 背大概率会背错!!!
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return simpleDateFormat.format(postTime);
    }

存在的问题二:希望我们写好的博客,能够按照发布时间就近从上往下排序,而不是乱序;

博客列表里面的数据都是从数据库里拿出来的,所以我们需要展示blog表的数据的时候按照有序展示即可,代码如下所示:

sql 复制代码
String sql = "select * from blog order by postTime desc";

2.2 博客详情页

点不同的博客,跳转过去之后,都会带有不同的 blogld 的query string,后续在博客详情页中,就可以也给服务器发起 ajax 请求,根据这里的 bogld查询数据库中该博客的具体内容,并且再返回给博客详情页,即前端还是把得到的数据给构造到页面上.

1)、约定前后端交互接口

这个请求是在进入博客详情页的时候,通过ajax 发给服务器.

请求和相应格式如下所示:

  1. 、让前端代码,通过 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)、让服务器处理请求,并返回响应

java 复制代码
 @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)、让服务器处理上述请求.

java 复制代码
 @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),即将下面一部分重新创建类,然后后续在需要这个过程的时候只要通过类名进行调用即可;

html 复制代码
// 定义新的函数, 获取登录状态
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类:用户信息服务器

java 复制代码
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);
    }
}

博客作者信息服务器:

java 复制代码
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)、编写后端代码,处理这个请求,完成退出登录的操作.

java 复制代码
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)、编写服务器代码, 处理刚才的请求

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

相关推荐
醉颜凉12 分钟前
【NOIP提高组】潜伏者
java·c语言·开发语言·c++·算法
阿维的博客日记16 分钟前
java八股-jvm入门-程序计数器,堆,元空间,虚拟机栈,本地方法栈,类加载器,双亲委派,类加载执行过程
java·jvm
qiyi.sky17 分钟前
JavaWeb——Web入门(8/9)- Tomcat:基本使用(下载与安装、目录结构介绍、启动与关闭、可能出现的问题及解决方案、总结)
java·前端·笔记·学习·tomcat
lapiii35821 分钟前
图论-代码随想录刷题记录[JAVA]
java·数据结构·算法·图论
RainbowSea23 分钟前
4. Spring Cloud Ribbon 实现“负载均衡”的详细配置说明
java·spring·spring cloud
程序员小明z24 分钟前
基于Java的药店管理系统
java·开发语言·spring boot·毕业设计·毕设
爱敲代码的小冰43 分钟前
spring boot 请求
java·spring boot·后端
Lyqfor1 小时前
云原生学习
java·分布式·学习·阿里云·云原生
程序猿麦小七1 小时前
今天给在家介绍一篇基于jsp的旅游网站设计与实现
java·源码·旅游·景区·酒店
张某布响丸辣2 小时前
SQL中的时间类型:深入解析与应用
java·数据库·sql·mysql·oracle