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

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/427274.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Eclipse也可以轻松创建JSP动态Web项目 绿色、免费、神器、就是好用一大截!

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起学习和进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&a…

STM32CubeMX PID差速循迹小车

在之前TB6612驱动中&#xff0c;主要实现了固定速度下小车的循迹&#xff0c;在车身偏转使用默认的速度进行纠偏&#xff0c;使车身恢复正常状态。接线图和之前TB6612一样STM32Cubemx TB6612直流电机驱动-CSDN博客。 今天要做的是&#xff0c;两路循迹判断车身偏的情况下&…

好书推荐 《Excel函数与公式应用大全for Excel 365 Excel 2021》

一.基本介绍 1.什么是 Excel? Excel 是微软公司开发的一款电子表格软件&#xff0c;是 Microsoft Office 套件的一部分。它被广泛用于数据处理、分析、可视化和管理等方面。Excel 提供了丰富的功能&#xff0c;使用户能够创建、编辑、存储和分享各种类型的数据表格。 2.Exc…

C++——String(1)

目录 1. 为什么学习string类&#xff1f; 1.1 C语言中的字符串 1.2 相关题目 2. 标准库中的string类 2.1 string类&#xff08;了解一下&#xff09; 2.2 string类的常用接口说明 1. string类对象的常见构造 2. string类对象的容量操作 3. string类对象的访问及遍历操作…

llm llama GPU 内存/显存计算

Calculating GPU memory for serving LLMs | Substratus.AI

Deeplearning4j【基础 01】初识Java深度学习框架DL4J

初识Java深度学习框架DL4J 1.起因2.简介3.组件3.1 Deeplearning4j/ScalNet3.1.1 Deeplearning4jf&#xff08;Java&#xff09;3.1.2 ScalNet&#xff08;Scala&#xff09; 3.2 ND4J/LibND4J3.3 SameDiff3.4 DataVec3.5 Arbiter3.6 RL4J 4.总结 内容来自网络&#xff0c;基于官…

(libusb) usb口自动刷新

文章目录 libusb自动刷新程序Code目录结构Code项目文件usb包code包 效果描述重置reset热拔插使用 END libusb 在操作USB相关内容时&#xff0c;有一个比较著名的库就是libusb。 官方网址&#xff1a;libusb 下载&#xff1a; 下载源码官方编好的库github&#xff1a;Release…

招聘人才小程序源码系统:多城市招聘平台+招聘会+职场咨询 带完整的搭建教程以及安装代码包

移动互联网的飞速发展&#xff0c;线上招聘已成为企业和求职者之间的重要桥梁。为了满足多城市、多行业、多岗位的招聘需求&#xff0c;以及提供一站式的求职服务&#xff0c;小编给大家分享一款“招聘人才小程序源码系统”。该系统不仅整合了多城市的招聘平台资源&#xff0c;…

C++:函数模板整理

函数模板: 找到函数相同的实现思路&#xff0c;区别于函数的参数类型。 使用函数模板使得函数可容纳不同类型的参数实现函数功能&#xff0c;而不是当类型不同时便编译大量类型不同的函数&#xff0c;产生大量重复代码和内存占用 函数模板格式&#xff1a; template<typ…

Linxu自动化构建工具make/Makefile究竟时什么?

Linxu自动化构建工具make/Makefile究竟时什么&#xff1f; 一、简介二、makefile文件制作&#xff08;简洁版&#xff09;2.1 源文件2.2 makefile如何制作2.2.1 依赖关系、依赖方法2.2.3 伪目标&#xff08;清理文件资源&#xff09; 三、make/Makefile自动化原理3.1 伪目标为什…

Lua 篇(一)— 安装运行Hello World

目录 前言一、Lua 是什么&#xff1f;二、Lua和C#的区别三、安装 LuaLinux 系统上安装Mac OS X 系统上安装Window 系统上安装emmyluaRider 安装(推荐) 四、Lua学习资料 前言 Lua 是一种轻量级的嵌入式脚本语言&#xff0c;它可以与 C 语言无缝集成&#xff0c;提供了强大的编程…

程序员的金三银四求职宝典:如何在关键时期脱颖而出?

个人主页&#xff1a;17_Kevin-CSDN博客 随着春天的脚步渐近&#xff0c;程序员们的求职热潮也随之而来。在这个被称为“金三银四”的招聘季&#xff0c;如何从众多求职者中脱颖而出&#xff0c;成为了许多程序员关注的焦点。本文将为你提供一份全面的求职宝典&#xff0c;助你…

程序员眼中的“祖传代码”

引言 在IT界&#xff0c;特别是在Java项目中&#xff0c;“祖传代码”通常指的是那些经过长时间积累、由多位开发者共同维护、且蕴含深厚技术沉淀的代码片段或模块。这些代码可能存在于项目的核心模块&#xff0c;也可能是一些辅助性的工具类。它们承载着项目的历史&#xff0…

vulhub中Wordpress 4.6 任意命令执行漏洞复现

由于Mysql初始化需要一段时间&#xff0c;所以请等待。成功运行后&#xff0c;访问http://your-ip:8080/打开站点&#xff0c;初始化管理员用户名和密码后即可使用&#xff08;数据库等已经配置好&#xff0c;且不会自动更新&#xff09;。 发送如下数据包&#xff0c;可见/tmp…

Kyuubi之Share Level

文章目录 Kyuubi介绍Spark thriftServer的问题Kyuubi架构 共享域参数CONNECTIONUSERGROUPSERVER Kyuubi介绍 Spark thriftServer的问题 STS面临以下的问题&#xff1a; 无法适应多租户场景。STS后端引擎仅仅启动一个application提供服务&#xff0c;提交用户和队列均为固定。…

Linux:Kubernetes(k8s)基础理论笔记(1)

我笔记来源的图片以及共享至GitHub&#xff0c;本章纯理论。这是k8s中部分的基础理论 &#x1f447; KALItarro/k8spdf: 这个里面只有一个pdf文件 (github.com)https://github.com/KALItarro/k8spdf&#x1f446; 什么是kubernetes kubernetes 是一个开源的&#xff0c;用于管…

TikTok企业认证教程:提升账号可信度的必备步骤

TikTok企业认证是TikTok平台用来验证账号真实性和权威性的方式。通过企业认证之后&#xff0c;企业能在TikTok上获得官方标识&#xff0c;可以增强品牌的专业形象&#xff0c;也有利于提升用户对企业内容的信任度。而且通过TikTok企业认证还可以解锁高级功能&#xff0c;如数据…

第105讲:Mycat垂直分表实战:从规划到解决问题的完整指南

文章目录 1.垂直分表的背景2.垂直分表案例实战2.1.垂直分表规划2.2.配置Mycat实现垂直分表2.3.重启Mycat2.4.在Mycat命令行中导入数据结构2.5.查看由Mycat分表后每个分片上存储的表2.6.Mycat垂直分表后可能遇到的问题2.7.垂直分表完成 1.垂直分表的背景 我们的商城系统数据库&…

基于springboot实现乐器社区网站系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现乐器社区网站系统演示 摘要 音乐一直以来都是人们非常喜爱的一种休闲娱乐的方式&#xff0c;人们在聆听音乐时可以全身心的进行放松&#xff0c;从音乐中可以获得认同感&#xff0c;可以与音乐进行情感的交流。而音乐的组成形式也是多样的&#xff0c;现代…

AP8854 DC-DC降压恒压 12V 6A过EMI线路图 宽电压电源管理IC

产品描述 AP8854 一款宽电压范围降压型 DC-D电源管理芯片&#xff0c;内部集成使能开关控制、基准电源、误差放大器、过热保护、限流保护、短路保护等功能&#xff0c;非常适合宽电压输入降压使用。AP8854 带使能控制&#xff0c;可以大大节省外围器件&#xff0c;更加适合电池…