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

相关推荐
SuperherRo43 分钟前
蓝队技能-应急响应篇&Web内存马查杀&JVM分析&Class提取&诊断反编译&日志定性
servlet·filter·蓝队·内存马
Dola_Pan3 小时前
Linux文件IO(二)-文件操作使用详解
java·linux·服务器
wang_book3 小时前
Gitlab学习(007 gitlab项目操作)
java·运维·git·学习·spring·gitlab
蜗牛^^O^4 小时前
Docker和K8S
java·docker·kubernetes
从心归零4 小时前
sshj使用代理连接服务器
java·服务器·sshj
IT毕设梦工厂5 小时前
计算机毕业设计选题推荐-在线拍卖系统-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
Ylucius6 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
七夜zippoe6 小时前
分布式系统实战经验
java·分布式
是梦终空6 小时前
JAVA毕业设计176—基于Java+Springboot+vue3的交通旅游订票管理系统(源代码+数据库)
java·spring boot·vue·毕业设计·课程设计·源代码·交通订票
落落落sss7 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle