小小博客项目(servlet实战演练)

news2024/11/17 10:39:43

目录

MVC模式简介

项目概述

🍑Model(模型层)

🍑View(视图层)

🍑Controller(控制器层)

项目实战

 上面pom.xml代码参考

一、模型层

🌰User代码:对应数据库中的User表

🌰Blog类:对应数据库中的Blog表

🌰DButil类:连上数据库

🌰UserDao类:对数据库中User表的操作:

🌰BlogDao类:对数据库中的Blog表的操作:

二、Controller控制层:用来处理我们前端发来的请求。

🏀前后端交互接口举例说明

🏀BlogServlet代码

相关联的视图层代码

博客列表页blog_home.html:

博客详情页blog_detail代码

🏀DeleteBlogServlet代码

相关联的视图层代码

博客详情页(blog_detail.html)]

 🏀UserServlet代码

与之相关的视图层的代码

博客列表页(blog_home.html)

博客详情页(blog_detail.html)

🏀LoginServlet代码

相关联的视图层的代码

博客登录页(blog_login.html)

博客列表页(blog_home.html)

博客详情页(blog_detail.html)

🏀LoginOutServlet代码

🏀RegServlet代码

三、视图层(view)总代码展示

🍑博客登录页

🍑博客注册页

🍑博客列表页

🍑博客详情页

🍑博客编辑页

🔔视图层对应的css代码


MVC模式简介

在开始介绍这个项目之前,首先我们要知道什么是MVC模式

MVC 模式,全称为 Model-View-Controller(模型-视图-控制器)模式,它是一种软件架构模式,其目标是将软件的用户界面(即前台页面)和业务逻辑分离,使代码具有更高的可扩展性、可复用性、可维护性以及灵活性。

 通常情况下,一个完整的 Java Web 应用程序,其结构如下图所示

 MVC 模式将应用程序划分成模型(Model)、视图(View)、控制器(Controller)等三层,如下图所示。

分层描述
Model(模型)它是应用程序的主体部分,主要由以下 2 部分组成:
  • 实体类 Bean:专门用来存储业务数据的对象,它们通常与数据库中的某个表对应,例如 User、Student 等。
  • 业务处理 Bean:指 Service 或 Dao 的对象,专门用于处理业务逻辑、数据库访问。

一个模型可以为多个视图(View)提供数据,一套模型(Model)的代码只需写一次就可以被多个视图重用,有效地减少了代码的重复性,增加了代码的可复用性。
View(视图)指在应用程序中专门用来与浏览器进行交互,展示数据的资源。在 Web 应用中,View 就是我们常说的前台页面,通常由 HTML、JSP、CSS、JavaScript 等组成。
Controller(控制器)通常指的是,应用程序的 Servlet。它负责将用户的请求交给模型(Model)层进行处理,并将 Model 层处理完成的数据,返回给视图(View)渲染并展示给用户。

在这个过程中,Controller 层不会做任何业务处理,它只是 View(视图)层和 Model (模型)层连接的枢纽,负责调度 View 层和 Model 层,将用户界面和业务逻辑合理的组织在一起,起粘合剂的效果。

MVC 的工作流程如下:

  1. 用户发送请求到服务器;
  2. 在服务器中,请求被控制层(Controller)接收;
  3. Controller 调用相应的 Model 层处理请求;
  4. Model 层处理完毕将结果返回到 Controller;
  5. Controller 再根据 Model 返回的请求处理结果,找到相应的 View 视图;
  6. View 视图渲染数据后最终响应给浏览器。

可以说,Controller就是View视图层和Model层之间的桥梁,我们接下来的这个小小项目就用到了MVC思想。


项目概述

总的来说,我们当前的项目有这样几个页面:登录页、注册页、博客列表页、博客详情页、博客编辑页。

登录页

 注册页

博客列表页

 博客详情页

博客编辑页

 功能已经大概流程大概如下:

首先登录注册不用多说

🌰在博客列表页,看到的左侧的用户名必须是我们当前登录的用户名,右侧了博客列表显示的所有用户所写的博客简介。

🌰在博客详情页,我们看到的左侧的用户名是我们当前所查看博客的作者,此外如果这篇博客的作者是当前登录的用户的话,该用户还有权限在博客详情页中删除该博客(删除后就跳转到博客列表页,此时博客列表页已经没有了刚才删除的博客简介),反之则没有权限。

🌰在博客编辑页,当你新写了一篇博客,发布成功后就会跳转到博客列表页,同时在博客列表页的开头就显示出了你刚刚所写的那篇博客的博客简介)

另外,我们要在博客列表页、博客详情页和博客编辑页,检查用户当前的登录状态,如果是为未登录,页面就强制跳转到登录页。

既然我们是按MVC的模式来写的,那么我们就来看看在这个项目中M层、V层和C层都分别做了什么吧!

🍑Model(模型层)

首先对于M层,即我们的模型层。他需要接收C(控制层)对他的调控,完成对数据的操作。

再来看看这张图

我们这个项目需要用到两张表:User用户表和Blog博客表,M层应该有分别对应这两张表的实体类User类和Blog类。另外还应该有对这两张表中数据分别处理的UserDao类、BlogDao类,用来对完成数据的处理(新增、删除、查询等)

🍑View(视图层)

从上图也可以看出,view负责我们用户界面的展示,其实就是前端(HTML、CSS、JS的相关代码)前端把用提交的数据或或者说操作以HTTP请求的方式通过服务器发给我们的Controller(控制器层),然后控制器再对请求做对应的处理,再把处理的结果以相应的形式返回给前端。

🍑Controller(控制器层)

控制器是视图层和模型层之间的桥梁,按前面所说:好像都是控制器层来和视图层(前端)来做交互。那么模型层干什么了呢?

你可不要误会了模型层,他才是处理数据的幕后之人,我们的控制器层只是把视图层(用户)发来的请求,交给模型层来干,同时把模型层辛辛苦苦处理的数据结果返回视图(view)渲染并展示给用户。

在这个过程中,Controller 层不会做任何业务数据上处理,它只是 View(视图)层和 Model (模型)层连接的枢纽,负责调度 View 层和 Model 层,将用户界面和业务逻辑合理的组织在一起,起粘合剂的效果。

光说他们三个之间的关系可能还不清楚,让我们来举例说明

这个我们博客系统实际这三个层所处的位置关系,比如我们现在在进入了博客列表页, 前端(我们的视图层)发来了一个请求要我们返回当前数据库里所有的数据。于是我们的控制层(BlogServlet)代码接收了这个请求,并调用了我们的模型层(BlogDao)让我们的模型层处理了这个数据(在数据库中进行了相关的增删查改)。之后呢,控制层就把处理结果返回给视图(view)渲染并展示给用户。

项目实战

我们是通过maven来创建项目的(项目创建的具体流程,以及要引入的包和依赖参看我的上一篇文章servlet实现表白墙

1、创建项目 

2、引入依赖

3、创建必要目录

4、编写代码

5和6、打包和部署:配置Smart Tomcat

7、测试验证

Model(模型层)


先看看一下项目实现后的目录结构

 

 注意我们上面wepapp/WEB-INF/web.xml这个目录和文件是需要我们自己建的。web.xml里面的内容可以直接复制下面的代码

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>Archetype Created Web Application</display-name>
</web-app>

 上面pom.xml代码参考

<?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>BlogSystem</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <!--引入servlet依赖-->
        <!-- 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>

        <!--引入jackson来操作-->
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.4</version>
        </dependency>
        <!--引入mysql依赖-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>

    </dependencies>
    <!--指定压缩包的类型-->
    <packaging>war</packaging>
    <!--指定压缩包的名称-->
    <build>
        <finalName>BlogSystem</finalName>
    </build>
</project>

一、模型层

两部分:

实体类——对应我们数据库中的表

业务处理操作类——对应我们对数据库的操作

🌰User代码:对应数据库中的User表

package mode;
// 实体类,每个实体类对应数据库中一张表

public class User {
    private int userId; // 当前登录的用户id
    private String username; // 用户名字
    private String password;



    // 但注意我们这个属性并没有放在数据库中,因为这个是随时都会改变的(随着你当前所查看博客的不同、当前登录用户的不同)
    private int isYourBlog;  // 通过这个属性,在博客详情页,我们判断是否能够删除该博客

    public int getIsYourBlog() {
        return isYourBlog;
    }

    public void setIsYourBlog(int isYourBlog) {
        this.isYourBlog = isYourBlog;
    }

    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;
    }
}

🌰Blog类:对应数据库中的Blog表

package mode;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.SimpleTimeZone;

// 实体类是我们进行增删改查的基本单位
// 这个对象的实例就表示一篇博客
public class Blog {
    private int blogId;  // 博客id
    private String title;
    private String content;
    private int userId; // 这篇博客的作者的id
    private Timestamp postTime;

    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 int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

//   // 这里返回的不再是一个时间戳,而是格式化好的String
//    public String getDatetime() {
//        // return datetime; 如果我们不改这里返回的是一个时间戳,而不是格式化好的时间
//        // Java中SimpleDateFormat类就可以 进行时间格式的转换,但需要我们在构造方法中指定转换的时间格式
//        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//        return simpleDateFormat.format(this.postTime);
//    }


    // 把这个方法魔改一下!! 在方法里面把时间戳构造成格式化时间. 以 String 的方式来返回.
    public String getPostTime() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return simpleDateFormat.format(this.postTime);
    }





    public void setPostTime(Timestamp postTime) {
        this.postTime = postTime;
    }
}

既然表创建好了,我们是不是就应该对数据库中的表进行操作了呀!

别慌,我们还要连上数据库

🌰DButil类:连上数据库

用来连接上数据库(主要因为我们这个项目是要部署到云服务器上的,所以我们连接的也是云服务上的数据库)

package mode;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
// 如果找不到包而爆红,尝试着刷新一些pom.xml依赖文件
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

// 通过这个类来实现对数据库的连接
public class DBUtil {
    Connection connection = null;
    private static DataSource dataSource = null;

    // 这里我们采用了懒汉模式,会有线程安全问题,要注意加锁
    // 登录数据库,并获取数据源
    public 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/BlogSystem?characterEncoding=utf8&useSSL=false");
                    ((MysqlDataSource)dataSource).setUser("root");
                    ((MysqlDataSource)dataSource).setPassword(""); //  当我们要把代码部署到云服务器上,我们这里设置的密码的云服务器上的mysql的密码
                }
            }
        }
        return dataSource;
    }
    // 与数据源(数据库)建立连接
    public static Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }
    // 关闭连接,释放资源
    public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) { // 用try catch比较合理一点
                e.printStackTrace();
            }
        }
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

🌰UserDao类:对数据库中User表的操作:

package mode;

import javax.servlet.annotation.WebServlet;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

// 这个类我们是来操作用户表,进行用户表的增删改查
public class UserDao {
    // 新增用户
    public static int insertUser(User user) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            // 1、与数据库建立连接
            connection = DBUtil.getConnection();
            // 2、构造sql语句
            String sql = "insert into user values(null, ?, ?)";

            statement = connection.prepareStatement(sql);
            statement.setString(1, user.getUsername());
            statement.setString(2, user.getPassword());


            // 执行语句
            // 但执行新增用户前,我们要检查当前用户名,是否已经被注册过
            String check = "select * from user where username <=> ?";
            PreparedStatement statementTest = connection.prepareStatement(check);
            statementTest.setString(1, user.getUsername());
            ResultSet test = statementTest.executeQuery();

            // 如果test.next(),可以进入说明test不为空,我们在遍历博客列表时,也是通过test.next来遍历查询到的博客列表的
            if (test.next()) { // 因为这个test是ResultSet,我们不能用他是否为null判断查询结果为空
                // 说明数据库中已经有了该用户,不能重复注册
                System.out.println("@@@@@@@@@@@@@");
                return 1;  // 我们用返回值来代表插入的成功与失败,1失败,0成功
            }
            int ret = statement.executeUpdate();
            if (ret == 1) {
                System.out.println("新增用户成功!");
            }
            else {
                System.out.println("新增失败!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return 0; // return 0 说明插入成功:注册用户成功
    }

    // 根据用户名,查看用户的详细信息
    public static User selectByName(String name) {
        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, name);
            // 执行语句
            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 e) {
            e.printStackTrace();
        }
        finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }
    // 根据用户id, 查看用户的详细信息
    public static User selectById (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 e) {
            e.printStackTrace();
        }
        finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }
}

🌰BlogDao类:对数据库中的Blog表的操作:

package mode;
// 这个类来实现对博客的增删改查
// 针对博客要实现的功能:
// 1. 新增博客 (博客编辑页)
// 2. 查询出博客列表 (博客列表页)
// 3. 查询出指定博客的详情 (博客详情页)
// 4. 删除指定的博客 (可以在博客详情页中加入)

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class BlogDao {
    // 新增博客
    public static void insertBlog(Blog blog) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            // 先和数据库建立起连接
            connection = DBUtil.getConnection();
            // 构造sql语句
            String sql = "insert into blog value(null, ?, ?, ?, now())";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, blog.getTitle());
            preparedStatement.setString(2, blog.getContent());
            preparedStatement.setInt(3, blog.getUserId());
            // 执行语句
            int ret = preparedStatement.executeUpdate();
            if (ret == 1) {
                System.out.println("插入成功!!!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        finally {
            DBUtil.close(connection, preparedStatement, resultSet);
        }
    }
    // 查询博客列表
    public static List<Blog> getList() {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        List<Blog> blogs = new ArrayList<>();
        try {
            // 与数据库建立连接
            connection = DBUtil.getConnection();
            // 构造sql语句,按时间查询排列
            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"));
                String content = resultSet.getString("content");
                // 如果内容过长,就截取内容的一部分
                if (content.length() > 100) {
                    content = content.substring(0, 100);
                }
                blog.setContent(content);
                blog.setUserId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blogs.add(blog);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        finally {
            // 与数据库断开连接,释放资源
            DBUtil.close(connection, statement, resultSet);
        }
        return blogs;
    }
    // 查看博客详情,当前前端传给我们的博客对象,根据博客Id,获取当前博客的博客详情
    public static Blog getDetail(int blogId) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            // 1、与数据库建立连接
            connection = DBUtil.getConnection();
            // 2、构造sql语句
            String sql = "select * from blog where blogID <=> ?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, blogId);
            // 3、执行语句
            ResultSet resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setUserId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                return blog;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        finally {
            DBUtil.close(connection, preparedStatement, null);
        }
        return null;
    }
    public static void deleteBlog(int blogId) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            // 1、与数据库建立连接
            connection = DBUtil.getConnection();

            // 2、执行sql语句
            String sql = "delete from blog where blogId = ?";
            // 通过PreparedStatement对象来表示要执行的一个sql语句,
            // 这个操作就是要告诉PreparedStatement对象,要执行的sql语句是什么、该语句要交给xx数据库服务器来执行,通过connection与dataSource建立连接
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogId); // 把sql语句补充完整
            int ret = statement.executeUpdate();
            // 通过上面我们也不难发现,我们执行构建sql、执行sql语句都是有PreparedStatement对象statement来执行的
            // connection的作用仅仅只是建立与数据库建立连接,把要执行的sql语句交给PreparedStatement对象
            if (ret == 1) { // ret代表执行该sql语句所改变的行数
                System.out.println("删除成功!");
            }
            else {
                System.out.println("删除失败!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        finally {
            DBUtil.close(connection, statement, resultSet);
        }
    }
}

二、Controller控制层:用来处理我们前端发来的请求。

事实上,在项目中我们如果想要实现一个功能,有以下三步

1、约定前后端交互接口

2、编写服务器端代码(其实主要编写的就是我们这里控制层的代码)

3、编写客户端代码(即视图层,我们的前端)

我们大致说以下我们这个项目中所用到的交互接口(详细的可以看我们表白墙的那一篇博客)

🏀前后端交互接口举例说明

🍑博客列表获取(博客列表页中) 

接下来我们对Controller层的这几个类做一下解析

🏀BlogServlet代码

 首先看我们的BlogServlet代码(与博客列表页、博客详情页、博客编辑页相关联)

BlogServlet代码

package controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import mode.Blog;
import mode.BlogDao;
import mode.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;
import java.util.List;
// 这个类可以获取博客列表以及指定博客的博客详情
// 同时这个类也处理新博客的发布,前端的博客编辑页通过from表单,把title和content发给我们,方法是post
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取博客列表,这个是由我们的blog_home.html发出的get请求
        // 通过jackson使得我们返回的响应的json格式的
        // 告诉服务器如何解析请求,用户输入的可能是中文
        req.setCharacterEncoding("utf8");
        ObjectMapper objectMapper = new ObjectMapper();
        String blogId = req.getParameter("blogId");
        if (blogId == null) { // 说明此时前端是要我们返回博客列表
            // 因为我们子啊BlogDao类中定义的方法是静态的,所以这里我们不用实例化对象就能调用
            List<Blog> blogs = BlogDao.getList();
            // 把blogs转成json数组的格式
            String respJson = objectMapper.writeValueAsString(blogs);
            // 告诉浏览器以json格式来读取响应数据,如果没写这行,浏览器会把他当作是一个普通的字符串来处理
            resp.setContentType("application/json; charset=utf-8");
            resp.getWriter().write(respJson);
        }
        else {  // 说明前端此时是要我们返回指定blogId的博客详情
            Blog blog = BlogDao.getDetail(Integer.parseInt(blogId));
            String respJson = objectMapper.writeValueAsString(blog);
            // 告诉浏览器以json格式来读取响应数据,如果没写这行,浏览器会把他当作是一个普通的字符串来处理
            resp.setContentType("application/json; charset=utf-8");
            resp.getWriter().write(respJson);

        }
    }

    @Override
    // 发表新的博客,新增博客
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 告诉服务器如何解析请求,用户输入的可能是中文
        req.setCharacterEncoding("utf8");
        // 接收前端提交是数据
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        // 我们还需要一个author信息,当前发布该文章的作者就当前的登录用户
        HttpSession session = req.getSession(false);
        User user = (User) session.getAttribute("user");
        int authorId = user.getUserId(); // 因为当前在博客编辑页面,用户一点是登陆过的,所以不用判断当前用户是否登录

        // 构建新的博客对象
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        blog.setUserId(authorId);
        // 在数据库中添加刚新建的博客
        BlogDao.insertBlog(blog);
        // 新增完成后,跳转到博客列表页
        resp.sendRedirect("blog_home.html");
    }
}

对应的模板层的代码代码在上面,下面我们列出对应视图层的代码

相关联的视图层代码

博客列表页blog_home.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>博客主页</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="bologlist.css">

</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>
    <a href="logout">退出登录</a>
    
  </div>
  <!-- 这里是页面的版心 -->
  <div class="container">
    <!-- 左侧用户信息区 -->
    <div class="left">
      <!-- 表示整个用户信息区域 -->
      <div class="card">
        <img src="image/博客头像男.jpg">
        <h3>是小鱼儿哈</h3>
        <a href="https://blog.csdn.net/weixin_61061381?type=blog">CSDN地址</a>
        <div class="counter">
          <span>文章</span>
          <span>分类</span>
        </div>
        <div class="counter">
          <span>67</span>
          <span>7</span>
        </div>
      </div>
    </div>
    <!-- 右侧博客列表页 -->
    <div class="right">
    
    
    </div>
   
  </div>
  <!-- 我们的js代码一般放在我们页面后面,来进行事件给构造,因为在调用回调函数时候,前提是你已经有了页面内容,然后对页面内容进行操作 -->
   <!-- 响要使用ajax构造请求,我们要有引入jquery -->
   <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
   <script>
     // 通过该函数,从后端获取到当前登录的用户是谁,并把它显示在博客列表页上
     function getUser() {
      $.ajax({
        type: 'get',
        url: 'user',
        success: function(body) {
          // 替换条原来的用户信息
          let userDiv = document.querySelector('.card h3');
          // 引入css,因为我们css就是按原来的类选择器来设置样式的
           // 但其实这一步也没什么必要,因为我们只是获取到原来的作者标签,并不是新建的标签,在获取到了后该标签所对应的className本来就是原来的名字,不用担心原来的css样式失效
          userDiv.className = '.card h3';
          userDiv.innerHTML = body.username;
        },
        error: function() {
           // 强行跳转到登录页面. 
           location.assign('blog_login.html');
        }
      });
     }
     // 你要明白一个html文件,在加载的过程中,是可以同时执行多个函数、多个ajax同时发送请求的
     // 通过这个函数, 来从服务器获取到博客列表的数据
     function getBlogList() {
         $.ajax({
             type: 'get',
             url: 'blog',  // servlet path, 在页面加载的过程中就会调用主机ip + contentPath + 这个servlet path(就@WebServlet后的地址)
             //dataType: 'json',
             success: function(body) {
                 // 根据返回的 json 数据, 来构造出页面内容, div.blog
                 // jquery ajax 会自动的把响应得到的 body 按照响应的 Content-Type 进行转换格式. 
                 // 如果响应的 Content-Type 是 json, 此时就会自动把 body 转成 js 的对象
                 let rightDiv = document.querySelector('.right');
                 for (let blog of body) {
                     // 新建博客结点
                     let blogDiv = document.createElement('div');
                     blogDiv.className = 'blog'; // 引入CSS属性
                     // 创建博客标题
                     let titleDiv = document.createElement('div');
                     titleDiv.className = 'title';
                     titleDiv.innerHTML = blog.title;
                     blogDiv.appendChild(titleDiv); // 把博客标题挂到博客结点上
                     // 创建日期
                     let dateDiv = document.createElement('div');
                     dateDiv.className = 'date';
                     dateDiv.innerHTML = blog.postTime;
                     blogDiv.appendChild(dateDiv);  // 把博客日期挂到博客结点上
                     // 创建摘要
                     let descDiv = document.createElement('div');
                     descDiv.className = 'desc';
                     descDiv.innerHTML = blog.content;
                     blogDiv.appendChild(descDiv);  // 把博客摘要挂到博客结点上
                     // 创建查看全文按钮
                     let a = document.createElement('a');
                     a.innerHTML = '查看全文 >>';
                     // a标签跳转的过程就相当于是发了一个get请求,这里我们在跳转的url地址后加上要传递的参数,即QueryString,等下我们在博客详情页也会用到这个参数
                     a.href = 'blog_detail.html?blogId=' + blog.blogId;
                     blogDiv.appendChild(a);
                     // 把 blogDiv 加入外层元素
                     rightDiv.appendChild(blogDiv);  // 把构建好的一篇博客挂到博客列表上
                 }
             },
             error: function() {
              alert("获取博客列表失败!")
             }
         });
     }
     function checkLogin() {
      $.ajax({
        type: 'get',
        url: 'login',
        success: function(body) {
          // 如果用户已经登录就什么也不做
        },
        error: function() {

          //alert("当前登录已过期,请重新登录!")

          // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('blog_login.html');
        }
      });
     }
     getBlogList(); // 不用忘了调用函数
     checkLogin();
     getUser();
    </script>
     
</body>
</html>







 

 


博客详情页blog_detail代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>博客详情页</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="blogdetail.css">

   <!-- 引入 editor.md 的依赖 -->
   <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
   <script src="js/jquery.min.js"></script>
   <script src="editor.md/lib/marked.min.js"></script>
   <script src="editor.md/lib/prettify.min.js"></script>
   <script src="editor.md/editormd.js"></script>
</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>
    <a href="logout">退出登录</a>
    <!-- 如果这篇博客是用户自己写的,这里会出现一个删除该博客的按钮 -->
    
  </div>
  <!-- 这里是页面的版心 -->
  <div class="container">
    <!-- 左侧用户信息区 -->
    <div class="left">
      <!-- 表示整个用户信息区域 -->
      <div class="card">
        <img src="image/博客头像男.jpg">
        <h3>是小鱼儿哈</h3>
        <a href="https://blog.csdn.net/weixin_61061381?type=blog">CSDN地址</a>
        <div class="counter">
          <span>文章</span>
          <span>分类</span>
        </div>
        <div class="counter">
          <span>67</span>
          <span>7</span>
        </div>
      </div>
      
    </div>
    <!-- 右侧内容区 -->
    <div class="right">
      <!-- 标题 -->
      <h2></h2>
      <!-- 日期 -->
      <div class="date">
        
      </div> 

       <!-- 博客正文,注意我们这里是id名为content,而不是类名,id选择器#开头 -->
       <div id="content" style="opacity: 80%">  
                    
      </div>

    </div>  
  </div>
    <!-- 引入jquery -->
    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script>
      // 通过这个函数,借助blogId,来从后端获取到这篇博客的作者,并把他显示在博客详情页上
      function getAuthor() {
        $.ajax({
          type: 'get',
          url: 'user' + location.search, //JS中可以通过location.search设置或获取网页地址跟在问号后面的部
          
          // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
          success: function(body) {
            let authorDiv = document.querySelector('.card h3');
            // 但其实这一步也没什么必要,因为我们只是获取到原来的作者标签,并不是新建的标签,在获取到了后该标签所对应的className本来就是原来的名字,不用担心原来的css样式失效
            authorDiv.className = '.card h3';
            authorDiv.innerHTML = body.username;

            // 接下来我们根据后端返回的body,来确定是否在导航栏中显示删除该博客这个按键(只有当前博客的作者和登录的用户名一样才能删,只能删自己的)
            // 所以我们就要在对应的后端代码中判断当前要删除的博客是否是自己的博客,在user/locatin.search所对应的后端servlet代码写相应的逻辑

            if (body.isYourBlog == 1) {
              // 新增一个a标签
              // alert("进入添加删除博客按钮的那个if语句了呀!"); 可以用了测试是否进入了该条件语句
              let DeleteA = document.createElement('a');
              DeleteA.href = "delete" + location.search; //JS中可以通过location.search设置或获取网页地址跟在问号后面的部分,即queryString
              DeleteA.innerHTML = "删除该博客";

              // 注意,在JS中,单引号和双引号是不同的,下面的代码如果你用双引号,你就获取不到该组件
              let navDiv = document.querySelector('.navigation'); 
              // 此时navDiv就代码的类名是class=avigation这个组件,这次赋值后,该组件的名字并没有变
              // 要想改变该组件的名字,还要通过 navDiv.className 来修改,让他被新的css选择器来修饰
             
              navDiv.appendChild(DeleteA);  // 把我们新增的a标签添加到导航栏中


              // // 在导航栏中加个按钮, 用来删除文章. 
              // let deleteA = document.createElement('a');
              // // location.search 就是当前页面 url 的 query string, 也就是
              // // ?blogId=1 这样的结果. 
              // deleteA.href = 'blogDelete' + location.search;
              // deleteA.innerHTML = '删除';

              // let navDiv = document.querySelector('.nav');
              // navDiv.appendChild(deleteA);

            }
          },
          error: function() {
            // 强行跳转到登录页面
            location.assign('blog_login.html');
          }
        });
      }


      // 通过这个函数, 来获取到博客详情
      function getBlogDetail() {
        $.ajax({
          type: 'get',
          url: 'blog' + location.search, // servlet path,和获取博客列表一个地址,但获取博客列表不带blogId,显示博客详情带blogId
          
          // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
          success: function(body) {
          
            // 根据返回的数据来构造页面
            // rightDiv是我们博客详情,我们具体的博客内容要挂在rightDiv这个结点上
            let rightDiv = document.querySelector('.right'); // 获取我们上面的右侧页面对象,接下来我们就可以对右侧页面对象进行操作来给我们页面添加元素
            rightDiv.className = 'right'; // 引入css
            // 博客标题构建与添加
            // let titleDiv = document.createComment('div');
            // titleDiv.className = 'h2';
            // titleDiv.innerHTML = body.title;
            // rightDiv.appendChild(titleDiv); // 把标题结点挂到博客详情上

            // // 创建该篇博客的发布日期,并把日期添加到博客详情rightDiv上
            // let dateDiv = document.createComment('div');
            // dateDiv.className = 'date';
            // dateDiv.innerHTML = body.dateTime;
            // rightDiv.appendChild(dateDiv);
            // 博客标题
            let titleDiv = document.querySelector('.right h2');
            titleDiv.innerHTML = body.title;
            // 发布日期
            let dateDiv = document.querySelector('.right .date');
            dateDiv.innerHTML = body.postTime;
            // 创建内容
            
           
            // 刚才这里是直接把正文内容设置到 innerHTML 中了, 没有渲染的过程的. 
            // let divContent = document.querySelector('#content');
            // divContent.innerHTML = body.content;
            // 靠谱的做法, 应该是先使用 editor.md 进行渲染. 
            // [重要] 这个方法就是 editor.md 提供的一个方法把 markdown 字符串转成格式化的效果. 
            // 第一个参数是一个 div 的 id, 表示要把渲染结果放到哪个 div 中. 
            // 第二个参数是一个 js 对象, 把正文内容传入即可. (还支持很多别的参数属性, 此处暂时不涉及)
            editormd.markdownToHTML('content', {
              markdown: body.content
            });            
          },
          error: function() {
            alert("获取博客详情失败!");
          }

        });
      }

      function checkLogin() {
        $.ajax({
          type: 'get',
          url: 'login',
          success: function(body) {
            // 如果用户已经登录就什么也不做
          },
          error: function() {

          //alert("当前登录已过期,请重新登录!")

          // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('blog_login.html');
          }
        });
      }
      getBlogDetail();
      checkLogin();
      getAuthor();
      
     
    </script>
</body>
</html>


🏀DeleteBlogServlet代码

对应了删除操作,处理了前端的博客详情页(blog_detail.html中)发来的get请求。

当然了在这个类中也少不了对模型层中代码的使用(Blog和BlogDao)

package controller;

import mode.Blog;
import mode.BlogDao;
import mode.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("/delete")
public class DeleteBlogServlet extends HttpServlet {
    // 因为我们是通过a标签来跳转到这里的,发的请求是get
    // 前端在给我们传HTTP的get请求时,带上了当前博客详情页中,当前所访问的blogId

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 告诉浏览器读取响应数据的格式
        resp.setContentType("text/html; charset=utf8");
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setStatus(403);
            System.out.println("当前你还未登录,无删除权限!");
            resp.getWriter().write("当前你还未登录,无删除权限");
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            resp.setStatus(403);
            System.out.println("当前你还未登录,无删除权限!");
            resp.getWriter().write("当前你还未登录,无删除权限");
            return;
        }

        int blogId = Integer.parseInt(req.getParameter("blogId"));
        Blog blog = BlogDao.getDetail(blogId);
        if (blog == null) {
            resp.setStatus(403);
            System.out.println("没有你要删除的博客,删除失败!");
            resp.getWriter().write("没有你要删除的博客,删除失败");
        }
        else {
            // 其实我们这里判断是否能删除没必要,因为只有在能删除的情况下,前端才会显示删除按键,跳到我们这里
            if (blog.getUserId() == user.getUserId()) {
                // 此时说明通过blogId而获取的当前的博客作者id和当前登录用户的id相同,可以删除
                BlogDao.deleteBlog(blogId);
                // 删除成功后,自动跳转到博客列表页
                resp.sendRedirect("blog_home.html");
            }
            resp.setStatus(403);
            System.out.println("你要删除的博客不是你自己所写的博客删除失败!");
            resp.getWriter().write("你要删除的博客不是你自己所写的博客, 删除失败");
        }



    }
}

相关联的视图层代码

博客详情页(blog_detail.html)]

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>博客详情页</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="blogdetail.css">

   <!-- 引入 editor.md 的依赖 -->
   <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
   <script src="js/jquery.min.js"></script>
   <script src="editor.md/lib/marked.min.js"></script>
   <script src="editor.md/lib/prettify.min.js"></script>
   <script src="editor.md/editormd.js"></script>
</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>
    <a href="logout">退出登录</a>
    <!-- 如果这篇博客是用户自己写的,这里会出现一个删除该博客的按钮 -->
    
  </div>
  <!-- 这里是页面的版心 -->
  <div class="container">
    <!-- 左侧用户信息区 -->
    <div class="left">
      <!-- 表示整个用户信息区域 -->
      <div class="card">
        <img src="image/博客头像男.jpg">
        <h3>是小鱼儿哈</h3>
        <a href="https://blog.csdn.net/weixin_61061381?type=blog">CSDN地址</a>
        <div class="counter">
          <span>文章</span>
          <span>分类</span>
        </div>
        <div class="counter">
          <span>67</span>
          <span>7</span>
        </div>
      </div>
      
    </div>
    <!-- 右侧内容区 -->
    <div class="right">
      <!-- 标题 -->
      <h2></h2>
      <!-- 日期 -->
      <div class="date">
        
      </div> 

       <!-- 博客正文,注意我们这里是id名为content,而不是类名,id选择器#开头 -->
       <div id="content" style="opacity: 80%">  
                    
      </div>

    </div>  
  </div>
    <!-- 引入jquery -->
    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script>
      // 通过这个函数,借助blogId,来从后端获取到这篇博客的作者,并把他显示在博客详情页上
      function getAuthor() {
        $.ajax({
          type: 'get',
          url: 'user' + location.search, //JS中可以通过location.search设置或获取网页地址跟在问号后面的部
          
          // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
          success: function(body) {
            let authorDiv = document.querySelector('.card h3');
            // 但其实这一步也没什么必要,因为我们只是获取到原来的作者标签,并不是新建的标签,在获取到了后该标签所对应的className本来就是原来的名字,不用担心原来的css样式失效
            authorDiv.className = '.card h3';
            authorDiv.innerHTML = body.username;

            // 接下来我们根据后端返回的body,来确定是否在导航栏中显示删除该博客这个按键(只有当前博客的作者和登录的用户名一样才能删,只能删自己的)
            // 所以我们就要在对应的后端代码中判断当前要删除的博客是否是自己的博客,在user/locatin.search所对应的后端servlet代码写相应的逻辑

            if (body.isYourBlog == 1) {
              // 新增一个a标签
              // alert("进入添加删除博客按钮的那个if语句了呀!"); 可以用了测试是否进入了该条件语句
              let DeleteA = document.createElement('a');
              DeleteA.href = "delete" + location.search; //JS中可以通过location.search设置或获取网页地址跟在问号后面的部分,即queryString
              DeleteA.innerHTML = "删除该博客";

              // 注意,在JS中,单引号和双引号是不同的,下面的代码如果你用双引号,你就获取不到该组件
              let navDiv = document.querySelector('.navigation'); 
              // 此时navDiv就代码的类名是class=avigation这个组件,这次赋值后,该组件的名字并没有变
              // 要想改变该组件的名字,还要通过 navDiv.className 来修改,让他被新的css选择器来修饰
             
              navDiv.appendChild(DeleteA);  // 把我们新增的a标签添加到导航栏中


              // // 在导航栏中加个按钮, 用来删除文章. 
              // let deleteA = document.createElement('a');
              // // location.search 就是当前页面 url 的 query string, 也就是
              // // ?blogId=1 这样的结果. 
              // deleteA.href = 'blogDelete' + location.search;
              // deleteA.innerHTML = '删除';

              // let navDiv = document.querySelector('.nav');
              // navDiv.appendChild(deleteA);

            }
          },
          error: function() {
            // 强行跳转到登录页面
            location.assign('blog_login.html');
          }
        });
      }


      // 通过这个函数, 来获取到博客详情
      function getBlogDetail() {
        $.ajax({
          type: 'get',
          url: 'blog' + location.search, // servlet path,和获取博客列表一个地址,但获取博客列表不带blogId,显示博客详情带blogId
          
          // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
          success: function(body) {
          
            // 根据返回的数据来构造页面
            // rightDiv是我们博客详情,我们具体的博客内容要挂在rightDiv这个结点上
            let rightDiv = document.querySelector('.right'); // 获取我们上面的右侧页面对象,接下来我们就可以对右侧页面对象进行操作来给我们页面添加元素
            rightDiv.className = 'right'; // 引入css
            // 博客标题构建与添加
            // let titleDiv = document.createComment('div');
            // titleDiv.className = 'h2';
            // titleDiv.innerHTML = body.title;
            // rightDiv.appendChild(titleDiv); // 把标题结点挂到博客详情上

            // // 创建该篇博客的发布日期,并把日期添加到博客详情rightDiv上
            // let dateDiv = document.createComment('div');
            // dateDiv.className = 'date';
            // dateDiv.innerHTML = body.dateTime;
            // rightDiv.appendChild(dateDiv);
            // 博客标题
            let titleDiv = document.querySelector('.right h2');
            titleDiv.innerHTML = body.title;
            // 发布日期
            let dateDiv = document.querySelector('.right .date');
            dateDiv.innerHTML = body.postTime;
            // 创建内容
            
           
            // 刚才这里是直接把正文内容设置到 innerHTML 中了, 没有渲染的过程的. 
            // let divContent = document.querySelector('#content');
            // divContent.innerHTML = body.content;
            // 靠谱的做法, 应该是先使用 editor.md 进行渲染. 
            // [重要] 这个方法就是 editor.md 提供的一个方法把 markdown 字符串转成格式化的效果. 
            // 第一个参数是一个 div 的 id, 表示要把渲染结果放到哪个 div 中. 
            // 第二个参数是一个 js 对象, 把正文内容传入即可. (还支持很多别的参数属性, 此处暂时不涉及)
            editormd.markdownToHTML('content', {
              markdown: body.content
            });            
          },
          error: function() {
            alert("获取博客详情失败!");
          }

        });
      }

      function checkLogin() {
        $.ajax({
          type: 'get',
          url: 'login',
          success: function(body) {
            // 如果用户已经登录就什么也不做
          },
          error: function() {

          //alert("当前登录已过期,请重新登录!")

          // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('blog_login.html');
          }
        });
      }
      getBlogDetail();
      checkLogin();
      getAuthor();
      
     
    </script>
</body>
</html>

 🏀UserServlet代码

 

UserServlet代码

package controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import mode.Blog;
import mode.BlogDao;
import mode.User;
import mode.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 javax.servlet.http.HttpSession;
import java.io.IOException;
import java.sql.Struct;

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    public static boolean isYourBlog = false;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 返回当前的用户
        // String user
        // 如果前端带的请求中没有带blogId,说明是列表页的
        String blogId = req.getParameter("blogId");
        // 处理博客列表页
        if (blogId == null) {
            HttpSession session = req.getSession(false);
            if (session == null) {
                resp.setContentType("text/html; charset=utf-8");
                //resp.getWriter().write("当前用户未登录请重新登录");
                resp.setStatus(403); // 返回给前端403,让前端在error回调函数中强制跳转到登录页面
                return;
            }

            User user = (User) session.getAttribute("user");
            // 这里的判断是否是多次一举呢? 我们再登录的servlet代码中,再创建会话的时候,已经把当前登录的user对象给存在session的value中的键值对里了
            // 因为我们接下来的退出登录操作其实就是把session中的user的对象给删除了,但session本身并没有删除,因为req中没有直接提供删session的方法
            // 这样的话,就是即使session存在,但用户还未登录,此时的user为空
            if (user == null) {
                resp.setContentType("text/html; charset=utf-8");
                resp.setStatus(403);  // 表示服务器拒绝你的访问,意思的为登录的用户禁止访问该界面

            }
            // 为了能用ObjectMapper,我们要在web.xml中引用jackson这个第三方库
            ObjectMapper objectMapper = new ObjectMapper();
            resp.setContentType("application/json; charset=utf-8");
            // 把我们后端要返回的数据转换json格式
            String retJson = objectMapper.writeValueAsString(user);
            resp.getWriter().write(retJson);
        }

        // 处理博客详情页,获取到了blogId,根据他获取到当前的博客作者,并返回数据给前端
        else {
            // 告诉浏览器以json格式来读取响应数据,如果没写这行,浏览器会把他当作是一个普通的字符串来处理
            resp.setContentType("application/json; charset=utf-8");
            // 获取该blogId,所对应的博客详情
            Blog blog = BlogDao.getDetail(Integer.parseInt(blogId));
            if (blog == null) {
                resp.setStatus(403); // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
                resp.setContentType("text/html; charset=utf-8");
                resp.getWriter().write("当前的blogId有误!!");
            }
            else {

                int userId = blog.getUserId();  // 这个userId是我们当前这篇博客的作者Id
                // 这个userId不仅在博客表里有,在用户表里也有,所以我们就可以借助这个userId,找到该篇博客所对应的作者名,也就是你通过userId在user表中找到用户名
                User author = UserDao.selectById(userId);
                if (author == null) {
                    resp.setStatus(403);
                }
                HttpSession session = req.getSession(false);
                if (session == null) {
                    resp.setStatus(403);
                }
                User user = (User) session.getAttribute("user");
                if (user == null) {
                    resp.setStatus(403);
                }
                // 当前user.getUserId()的值,就是当前登录用户的id值,如果这两个值一样就说明当前我们查看博客详情的这篇博客的作者id和当前登录用户的id
                // 是一个人,就可以进行删除操作,所以我们给这个类添加一个属性,来表示可以删除
                if (author.getUserId() == user.getUserId()) {
                    author.setIsYourBlog(1);  // 接下来我们前端还要依赖这个属性来操作——是否显示删除按钮
                }


                ObjectMapper objectMapper = new ObjectMapper();

                // 把我们后端要返回的数据转换json格式
                String retJson = objectMapper.writeValueAsString(author);
                System.out.println(retJson);
                resp.getWriter().write(retJson);
            }

        }
    }

}

与之相关的视图层的代码

博客列表页(blog_home.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>博客主页</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="bologlist.css">

</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>
    <a href="logout">退出登录</a>
    
  </div>
  <!-- 这里是页面的版心 -->
  <div class="container">
    <!-- 左侧用户信息区 -->
    <div class="left">
      <!-- 表示整个用户信息区域 -->
      <div class="card">
        <img src="image/博客头像男.jpg">
        <h3>是小鱼儿哈</h3>
        <a href="https://blog.csdn.net/weixin_61061381?type=blog">CSDN地址</a>
        <div class="counter">
          <span>文章</span>
          <span>分类</span>
        </div>
        <div class="counter">
          <span>67</span>
          <span>7</span>
        </div>
      </div>
    </div>
    <!-- 右侧博客列表页 -->
    <div class="right">
    
    
    </div>
   
  </div>
  <!-- 我们的js代码一般放在我们页面后面,来进行事件给构造,因为在调用回调函数时候,前提是你已经有了页面内容,然后对页面内容进行操作 -->
   <!-- 响要使用ajax构造请求,我们要有引入jquery -->
   <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
   <script>
     // 通过该函数,从后端获取到当前登录的用户是谁,并把它显示在博客列表页上
     function getUser() {
      $.ajax({
        type: 'get',
        url: 'user',
        success: function(body) {
          // 替换条原来的用户信息
          let userDiv = document.querySelector('.card h3');
          // 引入css,因为我们css就是按原来的类选择器来设置样式的
           // 但其实这一步也没什么必要,因为我们只是获取到原来的作者标签,并不是新建的标签,在获取到了后该标签所对应的className本来就是原来的名字,不用担心原来的css样式失效
          userDiv.className = '.card h3';
          userDiv.innerHTML = body.username;
        },
        error: function() {
           // 强行跳转到登录页面. 
           location.assign('blog_login.html');
        }
      });
     }
     // 你要明白一个html文件,在加载的过程中,是可以同时执行多个函数、多个ajax同时发送请求的
     // 通过这个函数, 来从服务器获取到博客列表的数据
     function getBlogList() {
         $.ajax({
             type: 'get',
             url: 'blog',  // servlet path, 在页面加载的过程中就会调用主机ip + contentPath + 这个servlet path(就@WebServlet后的地址)
             //dataType: 'json',
             success: function(body) {
                 // 根据返回的 json 数据, 来构造出页面内容, div.blog
                 // jquery ajax 会自动的把响应得到的 body 按照响应的 Content-Type 进行转换格式. 
                 // 如果响应的 Content-Type 是 json, 此时就会自动把 body 转成 js 的对象
                 let rightDiv = document.querySelector('.right');
                 for (let blog of body) {
                     // 新建博客结点
                     let blogDiv = document.createElement('div');
                     blogDiv.className = 'blog'; // 引入CSS属性
                     // 创建博客标题
                     let titleDiv = document.createElement('div');
                     titleDiv.className = 'title';
                     titleDiv.innerHTML = blog.title;
                     blogDiv.appendChild(titleDiv); // 把博客标题挂到博客结点上
                     // 创建日期
                     let dateDiv = document.createElement('div');
                     dateDiv.className = 'date';
                     dateDiv.innerHTML = blog.postTime;
                     blogDiv.appendChild(dateDiv);  // 把博客日期挂到博客结点上
                     // 创建摘要
                     let descDiv = document.createElement('div');
                     descDiv.className = 'desc';
                     descDiv.innerHTML = blog.content;
                     blogDiv.appendChild(descDiv);  // 把博客摘要挂到博客结点上
                     // 创建查看全文按钮
                     let a = document.createElement('a');
                     a.innerHTML = '查看全文 >>';
                     // a标签跳转的过程就相当于是发了一个get请求,这里我们在跳转的url地址后加上要传递的参数,即QueryString,等下我们在博客详情页也会用到这个参数
                     a.href = 'blog_detail.html?blogId=' + blog.blogId;
                     blogDiv.appendChild(a);
                     // 把 blogDiv 加入外层元素
                     rightDiv.appendChild(blogDiv);  // 把构建好的一篇博客挂到博客列表上
                 }
             },
             error: function() {
              alert("获取博客列表失败!")
             }
         });
     }
     function checkLogin() {
      $.ajax({
        type: 'get',
        url: 'login',
        success: function(body) {
          // 如果用户已经登录就什么也不做
        },
        error: function() {

          //alert("当前登录已过期,请重新登录!")

          // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('blog_login.html');
        }
      });
     }
     getBlogList(); // 不用忘了调用函数
     checkLogin();
     getUser();
    </script>
     
</body>
</html>

博客详情页(blog_detail.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>博客详情页</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="blogdetail.css">

   <!-- 引入 editor.md 的依赖 -->
   <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
   <script src="js/jquery.min.js"></script>
   <script src="editor.md/lib/marked.min.js"></script>
   <script src="editor.md/lib/prettify.min.js"></script>
   <script src="editor.md/editormd.js"></script>
</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>
    <a href="logout">退出登录</a>
    <!-- 如果这篇博客是用户自己写的,这里会出现一个删除该博客的按钮 -->
    
  </div>
  <!-- 这里是页面的版心 -->
  <div class="container">
    <!-- 左侧用户信息区 -->
    <div class="left">
      <!-- 表示整个用户信息区域 -->
      <div class="card">
        <img src="image/博客头像男.jpg">
        <h3>是小鱼儿哈</h3>
        <a href="https://blog.csdn.net/weixin_61061381?type=blog">CSDN地址</a>
        <div class="counter">
          <span>文章</span>
          <span>分类</span>
        </div>
        <div class="counter">
          <span>67</span>
          <span>7</span>
        </div>
      </div>
      
    </div>
    <!-- 右侧内容区 -->
    <div class="right">
      <!-- 标题 -->
      <h2></h2>
      <!-- 日期 -->
      <div class="date">
        
      </div> 

       <!-- 博客正文,注意我们这里是id名为content,而不是类名,id选择器#开头 -->
       <div id="content" style="opacity: 80%">  
                    
      </div>

    </div>  
  </div>
    <!-- 引入jquery -->
    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script>
      // 通过这个函数,借助blogId,来从后端获取到这篇博客的作者,并把他显示在博客详情页上
      function getAuthor() {
        $.ajax({
          type: 'get',
          url: 'user' + location.search, //JS中可以通过location.search设置或获取网页地址跟在问号后面的部
          
          // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
          success: function(body) {
            let authorDiv = document.querySelector('.card h3');
            // 但其实这一步也没什么必要,因为我们只是获取到原来的作者标签,并不是新建的标签,在获取到了后该标签所对应的className本来就是原来的名字,不用担心原来的css样式失效
            authorDiv.className = '.card h3';
            authorDiv.innerHTML = body.username;

            // 接下来我们根据后端返回的body,来确定是否在导航栏中显示删除该博客这个按键(只有当前博客的作者和登录的用户名一样才能删,只能删自己的)
            // 所以我们就要在对应的后端代码中判断当前要删除的博客是否是自己的博客,在user/locatin.search所对应的后端servlet代码写相应的逻辑

            if (body.isYourBlog == 1) {
              // 新增一个a标签
              // alert("进入添加删除博客按钮的那个if语句了呀!"); 可以用了测试是否进入了该条件语句
              let DeleteA = document.createElement('a');
              DeleteA.href = "delete" + location.search; //JS中可以通过location.search设置或获取网页地址跟在问号后面的部分,即queryString
              DeleteA.innerHTML = "删除该博客";

              // 注意,在JS中,单引号和双引号是不同的,下面的代码如果你用双引号,你就获取不到该组件
              let navDiv = document.querySelector('.navigation'); 
              // 此时navDiv就代码的类名是class=avigation这个组件,这次赋值后,该组件的名字并没有变
              // 要想改变该组件的名字,还要通过 navDiv.className 来修改,让他被新的css选择器来修饰
             
              navDiv.appendChild(DeleteA);  // 把我们新增的a标签添加到导航栏中


              // // 在导航栏中加个按钮, 用来删除文章. 
              // let deleteA = document.createElement('a');
              // // location.search 就是当前页面 url 的 query string, 也就是
              // // ?blogId=1 这样的结果. 
              // deleteA.href = 'blogDelete' + location.search;
              // deleteA.innerHTML = '删除';

              // let navDiv = document.querySelector('.nav');
              // navDiv.appendChild(deleteA);

            }
          },
          error: function() {
            // 强行跳转到登录页面
            location.assign('blog_login.html');
          }
        });
      }


      // 通过这个函数, 来获取到博客详情
      function getBlogDetail() {
        $.ajax({
          type: 'get',
          url: 'blog' + location.search, // servlet path,和获取博客列表一个地址,但获取博客列表不带blogId,显示博客详情带blogId
          
          // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
          success: function(body) {
          
            // 根据返回的数据来构造页面
            // rightDiv是我们博客详情,我们具体的博客内容要挂在rightDiv这个结点上
            let rightDiv = document.querySelector('.right'); // 获取我们上面的右侧页面对象,接下来我们就可以对右侧页面对象进行操作来给我们页面添加元素
            rightDiv.className = 'right'; // 引入css
            // 博客标题构建与添加
            // let titleDiv = document.createComment('div');
            // titleDiv.className = 'h2';
            // titleDiv.innerHTML = body.title;
            // rightDiv.appendChild(titleDiv); // 把标题结点挂到博客详情上

            // // 创建该篇博客的发布日期,并把日期添加到博客详情rightDiv上
            // let dateDiv = document.createComment('div');
            // dateDiv.className = 'date';
            // dateDiv.innerHTML = body.dateTime;
            // rightDiv.appendChild(dateDiv);
            // 博客标题
            let titleDiv = document.querySelector('.right h2');
            titleDiv.innerHTML = body.title;
            // 发布日期
            let dateDiv = document.querySelector('.right .date');
            dateDiv.innerHTML = body.postTime;
            // 创建内容
            
           
            // 刚才这里是直接把正文内容设置到 innerHTML 中了, 没有渲染的过程的. 
            // let divContent = document.querySelector('#content');
            // divContent.innerHTML = body.content;
            // 靠谱的做法, 应该是先使用 editor.md 进行渲染. 
            // [重要] 这个方法就是 editor.md 提供的一个方法把 markdown 字符串转成格式化的效果. 
            // 第一个参数是一个 div 的 id, 表示要把渲染结果放到哪个 div 中. 
            // 第二个参数是一个 js 对象, 把正文内容传入即可. (还支持很多别的参数属性, 此处暂时不涉及)
            editormd.markdownToHTML('content', {
              markdown: body.content
            });            
          },
          error: function() {
            alert("获取博客详情失败!");
          }

        });
      }

      function checkLogin() {
        $.ajax({
          type: 'get',
          url: 'login',
          success: function(body) {
            // 如果用户已经登录就什么也不做
          },
          error: function() {

          //alert("当前登录已过期,请重新登录!")

          // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('blog_login.html');
          }
        });
      }
      getBlogDetail();
      checkLogin();
      getAuthor();
      
     
    </script>
</body>
</html>


🏀LoginServlet代码

用来处理前端登录页(blog_login.html)发来的发来的post请求

此外这个类还额外处理了一个验证当前用户是否已经登录的get请求

(前端的博客列表页blog.home、博客详情页blog_detail.html和博客编辑页都发送一个这样的验证用户是否登录的get请求)

用到了模型层的User、UserDao 

package controller;

import mode.User;
import mode.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 javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    // 处理用户提交登录登陆信息,进行302跳转,跳转到博客列表页,即博客首页
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        // 告诉服务器如何解析请求,用户输入的密码可能是中文
        req.setCharacterEncoding("utf8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null || password == null) {
            resp.getWriter().write("你输入的账号或密码错误");
            return;
        }
        User user = UserDao.selectByName(username);
        if (user == null || !user.getPassword().equals(password)) {
            System.out.println("这是因为浏览器默认按GDK来解读我们HTTP请求发过来的参数吗");
            resp.getWriter().write("你输入的账号或密码错误");
        }
        else {
            // tomcat把会话存到 内存中,服务器一重启,会话就消失了
            // 登录之后,构造会话
            HttpSession session = req.getSession(true); // 参数为true说明如果当前没有会话,就新建立一个会话
            // 把刚才获取到的user对象给存到session里,方便后续使用
            session.setAttribute("user", user);
            // 返回一个重定向报文,跳转到博客列表页
            resp.sendRedirect("blog_home.html");
        }

    }

    @Override
    // 检查用户是否登录,没登陆强转返回登录页面
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 看能不能从请求中取到session,也接是看看请求中有没有cookie,是否存了sessionId
        HttpSession session = req.getSession(false);
        if (session == null) {
            // resp.sendRedirect("blog_login.html"); 为啥在后端就直接跳转不了,的确302了,但就是跳转不了
            // 当前用户还为登录,可以在这里跳转,也可以设置设置一个状态值,返回给前端,让前端跳转
            resp.setStatus(403);

            //resp.sendRedirect("blog_login.html"); // 强转跳转到登录页面
        }

        else {
            // 不做任何处理

            // resp.setStatus(200);
        }
    }
}

相关联的视图层的代码

博客登录页(blog_login.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>用户登录</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="bloglogin.css">
</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>

  </div>

   <!-- 这个container_login就作为我们登录的版心 -->
  <div class="container_login">
    <form action="login" method="post">
      <div class="dialog">
        <h3 >登录</h3>
        <div class="row"> 
          <span>用户名</span>
          <!-- 必须要用name属性,我们后端就是通过这些name属性拿到用户提交的信息的 -->
          <!-- 如果没有name属性,前端给后端发的请求中没没有用户信息  -->
          <input type="text" id="username" name="username"> 
        </div>
        <div class="row">
          <span>密码</span>
          <input type="password" id="password" name="password">
        </div>
        <div class="row">
        <input type="submit" id="submit" value="登录">
        </div>
        
        <div class="row">
          <div id="reg">
            <a href="blog_reg.html">注册</a>
          </div>
         
        </div>
      </div>
    </form>

   

    
  </div>
</body>
</html>

博客列表页(blog_home.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>博客主页</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="bologlist.css">

</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>
    <a href="logout">退出登录</a>
    
  </div>
  <!-- 这里是页面的版心 -->
  <div class="container">
    <!-- 左侧用户信息区 -->
    <div class="left">
      <!-- 表示整个用户信息区域 -->
      <div class="card">
        <img src="image/博客头像男.jpg">
        <h3>是小鱼儿哈</h3>
        <a href="https://blog.csdn.net/weixin_61061381?type=blog">CSDN地址</a>
        <div class="counter">
          <span>文章</span>
          <span>分类</span>
        </div>
        <div class="counter">
          <span>67</span>
          <span>7</span>
        </div>
      </div>
    </div>
    <!-- 右侧博客列表页 -->
    <div class="right">
    
    
    </div>
   
  </div>
  <!-- 我们的js代码一般放在我们页面后面,来进行事件给构造,因为在调用回调函数时候,前提是你已经有了页面内容,然后对页面内容进行操作 -->
   <!-- 响要使用ajax构造请求,我们要有引入jquery -->
   <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
   <script>
     // 通过该函数,从后端获取到当前登录的用户是谁,并把它显示在博客列表页上
     function getUser() {
      $.ajax({
        type: 'get',
        url: 'user',
        success: function(body) {
          // 替换条原来的用户信息
          let userDiv = document.querySelector('.card h3');
          // 引入css,因为我们css就是按原来的类选择器来设置样式的
           // 但其实这一步也没什么必要,因为我们只是获取到原来的作者标签,并不是新建的标签,在获取到了后该标签所对应的className本来就是原来的名字,不用担心原来的css样式失效
          userDiv.className = '.card h3';
          userDiv.innerHTML = body.username;
        },
        error: function() {
           // 强行跳转到登录页面. 
           location.assign('blog_login.html');
        }
      });
     }
     // 你要明白一个html文件,在加载的过程中,是可以同时执行多个函数、多个ajax同时发送请求的
     // 通过这个函数, 来从服务器获取到博客列表的数据
     function getBlogList() {
         $.ajax({
             type: 'get',
             url: 'blog',  // servlet path, 在页面加载的过程中就会调用主机ip + contentPath + 这个servlet path(就@WebServlet后的地址)
             //dataType: 'json',
             success: function(body) {
                 // 根据返回的 json 数据, 来构造出页面内容, div.blog
                 // jquery ajax 会自动的把响应得到的 body 按照响应的 Content-Type 进行转换格式. 
                 // 如果响应的 Content-Type 是 json, 此时就会自动把 body 转成 js 的对象
                 let rightDiv = document.querySelector('.right');
                 for (let blog of body) {
                     // 新建博客结点
                     let blogDiv = document.createElement('div');
                     blogDiv.className = 'blog'; // 引入CSS属性
                     // 创建博客标题
                     let titleDiv = document.createElement('div');
                     titleDiv.className = 'title';
                     titleDiv.innerHTML = blog.title;
                     blogDiv.appendChild(titleDiv); // 把博客标题挂到博客结点上
                     // 创建日期
                     let dateDiv = document.createElement('div');
                     dateDiv.className = 'date';
                     dateDiv.innerHTML = blog.postTime;
                     blogDiv.appendChild(dateDiv);  // 把博客日期挂到博客结点上
                     // 创建摘要
                     let descDiv = document.createElement('div');
                     descDiv.className = 'desc';
                     descDiv.innerHTML = blog.content;
                     blogDiv.appendChild(descDiv);  // 把博客摘要挂到博客结点上
                     // 创建查看全文按钮
                     let a = document.createElement('a');
                     a.innerHTML = '查看全文 >>';
                     // a标签跳转的过程就相当于是发了一个get请求,这里我们在跳转的url地址后加上要传递的参数,即QueryString,等下我们在博客详情页也会用到这个参数
                     a.href = 'blog_detail.html?blogId=' + blog.blogId;
                     blogDiv.appendChild(a);
                     // 把 blogDiv 加入外层元素
                     rightDiv.appendChild(blogDiv);  // 把构建好的一篇博客挂到博客列表上
                 }
             },
             error: function() {
              alert("获取博客列表失败!")
             }
         });
     }
     function checkLogin() {
      $.ajax({
        type: 'get',
        url: 'login',
        success: function(body) {
          // 如果用户已经登录就什么也不做
        },
        error: function() {

          //alert("当前登录已过期,请重新登录!")

          // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('blog_login.html');
        }
      });
     }
     getBlogList(); // 不用忘了调用函数
     checkLogin();
     getUser();
    </script>
     
</body>
</html>







 

 


博客详情页(blog_detail.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>博客详情页</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="blogdetail.css">

   <!-- 引入 editor.md 的依赖 -->
   <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
   <script src="js/jquery.min.js"></script>
   <script src="editor.md/lib/marked.min.js"></script>
   <script src="editor.md/lib/prettify.min.js"></script>
   <script src="editor.md/editormd.js"></script>
</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>
    <a href="logout">退出登录</a>
    <!-- 如果这篇博客是用户自己写的,这里会出现一个删除该博客的按钮 -->
    
  </div>
  <!-- 这里是页面的版心 -->
  <div class="container">
    <!-- 左侧用户信息区 -->
    <div class="left">
      <!-- 表示整个用户信息区域 -->
      <div class="card">
        <img src="image/博客头像男.jpg">
        <h3>是小鱼儿哈</h3>
        <a href="https://blog.csdn.net/weixin_61061381?type=blog">CSDN地址</a>
        <div class="counter">
          <span>文章</span>
          <span>分类</span>
        </div>
        <div class="counter">
          <span>67</span>
          <span>7</span>
        </div>
      </div>
      
    </div>
    <!-- 右侧内容区 -->
    <div class="right">
      <!-- 标题 -->
      <h2></h2>
      <!-- 日期 -->
      <div class="date">
        
      </div> 

       <!-- 博客正文,注意我们这里是id名为content,而不是类名,id选择器#开头 -->
       <div id="content" style="opacity: 80%">  
                    
      </div>

    </div>  
  </div>
    <!-- 引入jquery -->
    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script>
      // 通过这个函数,借助blogId,来从后端获取到这篇博客的作者,并把他显示在博客详情页上
      function getAuthor() {
        $.ajax({
          type: 'get',
          url: 'user' + location.search, //JS中可以通过location.search设置或获取网页地址跟在问号后面的部
          
          // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
          success: function(body) {
            let authorDiv = document.querySelector('.card h3');
            // 但其实这一步也没什么必要,因为我们只是获取到原来的作者标签,并不是新建的标签,在获取到了后该标签所对应的className本来就是原来的名字,不用担心原来的css样式失效
            authorDiv.className = '.card h3';
            authorDiv.innerHTML = body.username;

            // 接下来我们根据后端返回的body,来确定是否在导航栏中显示删除该博客这个按键(只有当前博客的作者和登录的用户名一样才能删,只能删自己的)
            // 所以我们就要在对应的后端代码中判断当前要删除的博客是否是自己的博客,在user/locatin.search所对应的后端servlet代码写相应的逻辑

            if (body.isYourBlog == 1) {
              // 新增一个a标签
              // alert("进入添加删除博客按钮的那个if语句了呀!"); 可以用了测试是否进入了该条件语句
              let DeleteA = document.createElement('a');
              DeleteA.href = "delete" + location.search; //JS中可以通过location.search设置或获取网页地址跟在问号后面的部分,即queryString
              DeleteA.innerHTML = "删除该博客";

              // 注意,在JS中,单引号和双引号是不同的,下面的代码如果你用双引号,你就获取不到该组件
              let navDiv = document.querySelector('.navigation'); 
              // 此时navDiv就代码的类名是class=avigation这个组件,这次赋值后,该组件的名字并没有变
              // 要想改变该组件的名字,还要通过 navDiv.className 来修改,让他被新的css选择器来修饰
             
              navDiv.appendChild(DeleteA);  // 把我们新增的a标签添加到导航栏中


              // // 在导航栏中加个按钮, 用来删除文章. 
              // let deleteA = document.createElement('a');
              // // location.search 就是当前页面 url 的 query string, 也就是
              // // ?blogId=1 这样的结果. 
              // deleteA.href = 'blogDelete' + location.search;
              // deleteA.innerHTML = '删除';

              // let navDiv = document.querySelector('.nav');
              // navDiv.appendChild(deleteA);

            }
          },
          error: function() {
            // 强行跳转到登录页面
            location.assign('blog_login.html');
          }
        });
      }


      // 通过这个函数, 来获取到博客详情
      function getBlogDetail() {
        $.ajax({
          type: 'get',
          url: 'blog' + location.search, // servlet path,和获取博客列表一个地址,但获取博客列表不带blogId,显示博客详情带blogId
          
          // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
          success: function(body) {
          
            // 根据返回的数据来构造页面
            // rightDiv是我们博客详情,我们具体的博客内容要挂在rightDiv这个结点上
            let rightDiv = document.querySelector('.right'); // 获取我们上面的右侧页面对象,接下来我们就可以对右侧页面对象进行操作来给我们页面添加元素
            rightDiv.className = 'right'; // 引入css
            // 博客标题构建与添加
            // let titleDiv = document.createComment('div');
            // titleDiv.className = 'h2';
            // titleDiv.innerHTML = body.title;
            // rightDiv.appendChild(titleDiv); // 把标题结点挂到博客详情上

            // // 创建该篇博客的发布日期,并把日期添加到博客详情rightDiv上
            // let dateDiv = document.createComment('div');
            // dateDiv.className = 'date';
            // dateDiv.innerHTML = body.dateTime;
            // rightDiv.appendChild(dateDiv);
            // 博客标题
            let titleDiv = document.querySelector('.right h2');
            titleDiv.innerHTML = body.title;
            // 发布日期
            let dateDiv = document.querySelector('.right .date');
            dateDiv.innerHTML = body.postTime;
            // 创建内容
            
           
            // 刚才这里是直接把正文内容设置到 innerHTML 中了, 没有渲染的过程的. 
            // let divContent = document.querySelector('#content');
            // divContent.innerHTML = body.content;
            // 靠谱的做法, 应该是先使用 editor.md 进行渲染. 
            // [重要] 这个方法就是 editor.md 提供的一个方法把 markdown 字符串转成格式化的效果. 
            // 第一个参数是一个 div 的 id, 表示要把渲染结果放到哪个 div 中. 
            // 第二个参数是一个 js 对象, 把正文内容传入即可. (还支持很多别的参数属性, 此处暂时不涉及)
            editormd.markdownToHTML('content', {
              markdown: body.content
            });            
          },
          error: function() {
            alert("获取博客详情失败!");
          }

        });
      }

      function checkLogin() {
        $.ajax({
          type: 'get',
          url: 'login',
          success: function(body) {
            // 如果用户已经登录就什么也不做
          },
          error: function() {

          //alert("当前登录已过期,请重新登录!")

          // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('blog_login.html');
          }
        });
      }
      getBlogDetail();
      checkLogin();
      getAuthor();
      
     
    </script>
</body>
</html>

🏀LoginOutServlet代码

用来应对视图层发来的注销登录的请求,当然也用到了模型层(User、UserDao)

package controller;

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 LoginOutServlet extends HttpServlet {
    // 我们是通过a标签跳转到这里来的,所以这里是get方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取当前的session,因为只有在用户以及登录的情况下,才会出现这个退出登录的按键,所以这里我们不考虑session为空
        // 因为在req类中没有直接提供删除session的操作,所以为了简便我们这里就不删session
        // 我们这里的退出登录只是把session里的user键值对中的值给删除了,但session还在,sessionId还在,也就是请求中的cookie还在
        // 这也是为啥我们在判断当前用户是否登录是还要专门判断session在的user的值还在不在(user是否为空),如果不再说明我们已经通过这种方式,退出了登录
        HttpSession session = req.getSession(false);
        session.setAttribute("user", null);
        // 重定向报文,强制跳转到登录页面,既然以及退出登录了,肯定要重新登录呀!!
        resp.sendRedirect("blog_login.html");
    }
}


 

🏀RegServlet代码

用来注册用户,当然和视图层(注册页)和模型层(User、UserDao)也有联系

package controller;

import mode.User;
import mode.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("/reg")
public class RegServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        req.setCharacterEncoding("utf-8");


        String username = req.getParameter("username");
        String password1 = req.getParameter("password1");
        String password2 = req.getParameter("password2");

        if (username == null | password1 == null | password2 == null) {
            resp.setStatus(403);
            resp.setContentType("text/html; charset=utf-8");
            resp.getWriter().write("你的输入不能为空!请重新输入!!");
            // resp.sendRedirect("blog_reg.html");
            // 因为我们是用from表单提交的,前端跳转后,我们无法给用户提示信息
            // 在有跳转的情况下,我们的提示信息显示不出来,所以我们干脆就不跳转,让用户手动跳转,重新注册

        }
        // 密码不一致也不行
        else if (!password1.equals(password2)) { // 注意因为这里是字符所以我们,要用equals
            resp.setStatus(403);
            resp.setContentType("text/html; charset=utf-8");
            // 下面是返回了一个js弹窗
            //resp.getWriter().write("<script language='javascript'>alert('两次输入的密码不一致!请重新输入!!')</script>");

            resp.getWriter().write("两次输入的密码不一致!请重新输入!!");
            //resp.sendRedirect("blog_reg.html");

        } else {
            // 如果程序走到这里说明,说明用户的提交没什么问题,我们可以开始给用户注册了
            User user = new User();
            user.setUsername(username);
            user.setPassword(password1);
            int ret = UserDao.insertUser(user);
            if (ret == 0) {
                // 注册成功,跳转到登录页面
                System.out.println("注册成功!");
                resp.sendRedirect("blog_login.html");
            }
            else {
                // 注册失败,新增用户失败,当前用户名已经被注册过了
                resp.setStatus(403);
                System.out.println("注册失败!");
                resp.setContentType("text/html; charset=utf-8");
                resp.getWriter().write("当前用户已存在,请重新输入!!");
                //resp.sendRedirect("blog_reg.html");
            }

        }
    }
}

三、视图层(view)总代码展示

🍑博客登录页

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>用户登录</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="bloglogin.css">
</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>

  </div>

   <!-- 这个container_login就作为我们登录的版心 -->
  <div class="container_login">
    <form action="login" method="post">
      <div class="dialog">
        <h3 >登录</h3>
        <div class="row"> 
          <span>用户名</span>
          <!-- 必须要用name属性,我们后端就是通过这些name属性拿到用户提交的信息的 -->
          <!-- 如果没有name属性,前端给后端发的请求中没没有用户信息  -->
          <input type="text" id="username" name="username"> 
        </div>
        <div class="row">
          <span>密码</span>
          <input type="password" id="password" name="password">
        </div>
        <div class="row">
        <input type="submit" id="submit" value="登录">
        </div>
        
        <div class="row">
          <div id="reg">
            <a href="blog_reg.html">注册</a>
          </div>
         
        </div>
      </div>
    </form>

   

    
  </div>
</body>
</html>







 

 


🍑博客注册页

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>用户注册</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="blogreg.css">
</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>
    <!-- 既然是注册,还是不要显示这两个了 -->
    <a href="blog_login.html">登录</a>
    <!-- <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a> -->

  </div>

   <!-- 这个container_login就作为我们登录的版心 -->
  <div class="container_reg">
    <form action="reg" method="post">
      <div class="dialog">
        <h3 >注册</h3>
        <div class="row"> 
          <span>用户名</span>
          <!-- 必须要用name属性,我们后端就是通过这些name属性拿到用户提交的信息的 -->
          <!-- 如果没有name属性,前端给后端发的请求中没没有用户信息  -->
          <input type="text" id="username" name="username"> 
        </div>
        <div class="row">
          <span>密码</span>
          <input type="password" id="password1" name="password1">
        </div>
        <div class="row">
            <span>确认密码</span>
            <input type="password" id="password2" name="password2">
          </div>

        <div class="row">
        <input type="submit" id="submit" value="注册">
        </div>
        
      
      </div>
    </form>

   

    
  </div>
</body>
</html>







 

 


🍑博客列表页

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>博客主页</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="bologlist.css">

</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>
    <a href="logout">退出登录</a>
    
  </div>
  <!-- 这里是页面的版心 -->
  <div class="container">
    <!-- 左侧用户信息区 -->
    <div class="left">
      <!-- 表示整个用户信息区域 -->
      <div class="card">
        <img src="image/博客头像男.jpg">
        <h3>是小鱼儿哈</h3>
        <a href="https://blog.csdn.net/weixin_61061381?type=blog">CSDN地址</a>
        <div class="counter">
          <span>文章</span>
          <span>分类</span>
        </div>
        <div class="counter">
          <span>67</span>
          <span>7</span>
        </div>
      </div>
    </div>
    <!-- 右侧博客列表页 -->
    <div class="right">
    
    
    </div>
   
  </div>
  <!-- 我们的js代码一般放在我们页面后面,来进行事件给构造,因为在调用回调函数时候,前提是你已经有了页面内容,然后对页面内容进行操作 -->
   <!-- 响要使用ajax构造请求,我们要有引入jquery -->
   <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
   <script>
     // 通过该函数,从后端获取到当前登录的用户是谁,并把它显示在博客列表页上
     function getUser() {
      $.ajax({
        type: 'get',
        url: 'user',
        success: function(body) {
          // 替换条原来的用户信息
          let userDiv = document.querySelector('.card h3');
          // 引入css,因为我们css就是按原来的类选择器来设置样式的
           // 但其实这一步也没什么必要,因为我们只是获取到原来的作者标签,并不是新建的标签,在获取到了后该标签所对应的className本来就是原来的名字,不用担心原来的css样式失效
          userDiv.className = '.card h3';
          userDiv.innerHTML = body.username;
        },
        error: function() {
           // 强行跳转到登录页面. 
           location.assign('blog_login.html');
        }
      });
     }
     // 你要明白一个html文件,在加载的过程中,是可以同时执行多个函数、多个ajax同时发送请求的
     // 通过这个函数, 来从服务器获取到博客列表的数据
     function getBlogList() {
         $.ajax({
             type: 'get',
             url: 'blog',  // servlet path, 在页面加载的过程中就会调用主机ip + contentPath + 这个servlet path(就@WebServlet后的地址)
             //dataType: 'json',
             success: function(body) {
                 // 根据返回的 json 数据, 来构造出页面内容, div.blog
                 // jquery ajax 会自动的把响应得到的 body 按照响应的 Content-Type 进行转换格式. 
                 // 如果响应的 Content-Type 是 json, 此时就会自动把 body 转成 js 的对象
                 let rightDiv = document.querySelector('.right');
                 for (let blog of body) {
                     // 新建博客结点
                     let blogDiv = document.createElement('div');
                     blogDiv.className = 'blog'; // 引入CSS属性
                     // 创建博客标题
                     let titleDiv = document.createElement('div');
                     titleDiv.className = 'title';
                     titleDiv.innerHTML = blog.title;
                     blogDiv.appendChild(titleDiv); // 把博客标题挂到博客结点上
                     // 创建日期
                     let dateDiv = document.createElement('div');
                     dateDiv.className = 'date';
                     dateDiv.innerHTML = blog.postTime;
                     blogDiv.appendChild(dateDiv);  // 把博客日期挂到博客结点上
                     // 创建摘要
                     let descDiv = document.createElement('div');
                     descDiv.className = 'desc';
                     descDiv.innerHTML = blog.content;
                     blogDiv.appendChild(descDiv);  // 把博客摘要挂到博客结点上
                     // 创建查看全文按钮
                     let a = document.createElement('a');
                     a.innerHTML = '查看全文 >>';
                     // a标签跳转的过程就相当于是发了一个get请求,这里我们在跳转的url地址后加上要传递的参数,即QueryString,等下我们在博客详情页也会用到这个参数
                     a.href = 'blog_detail.html?blogId=' + blog.blogId;
                     blogDiv.appendChild(a);
                     // 把 blogDiv 加入外层元素
                     rightDiv.appendChild(blogDiv);  // 把构建好的一篇博客挂到博客列表上
                 }
             },
             error: function() {
              alert("获取博客列表失败!")
             }
         });
     }
     function checkLogin() {
      $.ajax({
        type: 'get',
        url: 'login',
        success: function(body) {
          // 如果用户已经登录就什么也不做
        },
        error: function() {

          //alert("当前登录已过期,请重新登录!")

          // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('blog_login.html');
        }
      });
     }
     getBlogList(); // 不用忘了调用函数
     checkLogin();
     getUser();
    </script>
     
</body>
</html>







 

 


🍑博客详情页

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>博客详情页</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="blogdetail.css">

   <!-- 引入 editor.md 的依赖 -->
   <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
   <script src="js/jquery.min.js"></script>
   <script src="editor.md/lib/marked.min.js"></script>
   <script src="editor.md/lib/prettify.min.js"></script>
   <script src="editor.md/editormd.js"></script>
</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>
    <a href="logout">退出登录</a>
    <!-- 如果这篇博客是用户自己写的,这里会出现一个删除该博客的按钮 -->
    
  </div>
  <!-- 这里是页面的版心 -->
  <div class="container">
    <!-- 左侧用户信息区 -->
    <div class="left">
      <!-- 表示整个用户信息区域 -->
      <div class="card">
        <img src="image/博客头像男.jpg">
        <h3>是小鱼儿哈</h3>
        <a href="https://blog.csdn.net/weixin_61061381?type=blog">CSDN地址</a>
        <div class="counter">
          <span>文章</span>
          <span>分类</span>
        </div>
        <div class="counter">
          <span>67</span>
          <span>7</span>
        </div>
      </div>
      
    </div>
    <!-- 右侧内容区 -->
    <div class="right">
      <!-- 标题 -->
      <h2></h2>
      <!-- 日期 -->
      <div class="date">
        
      </div> 

       <!-- 博客正文,注意我们这里是id名为content,而不是类名,id选择器#开头 -->
       <div id="content" style="opacity: 80%">  
                    
      </div>

    </div>  
  </div>
    <!-- 引入jquery -->
    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script>
      // 通过这个函数,借助blogId,来从后端获取到这篇博客的作者,并把他显示在博客详情页上
      function getAuthor() {
        $.ajax({
          type: 'get',
          url: 'user' + location.search, //JS中可以通过location.search设置或获取网页地址跟在问号后面的部
          
          // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
          success: function(body) {
            let authorDiv = document.querySelector('.card h3');
            // 但其实这一步也没什么必要,因为我们只是获取到原来的作者标签,并不是新建的标签,在获取到了后该标签所对应的className本来就是原来的名字,不用担心原来的css样式失效
            authorDiv.className = '.card h3';
            authorDiv.innerHTML = body.username;

            // 接下来我们根据后端返回的body,来确定是否在导航栏中显示删除该博客这个按键(只有当前博客的作者和登录的用户名一样才能删,只能删自己的)
            // 所以我们就要在对应的后端代码中判断当前要删除的博客是否是自己的博客,在user/locatin.search所对应的后端servlet代码写相应的逻辑

            if (body.isYourBlog == 1) {
              // 新增一个a标签
              // alert("进入添加删除博客按钮的那个if语句了呀!"); 可以用了测试是否进入了该条件语句
              let DeleteA = document.createElement('a');
              DeleteA.href = "delete" + location.search; //JS中可以通过location.search设置或获取网页地址跟在问号后面的部分,即queryString
              DeleteA.innerHTML = "删除该博客";

              // 注意,在JS中,单引号和双引号是不同的,下面的代码如果你用双引号,你就获取不到该组件
              let navDiv = document.querySelector('.navigation'); 
              // 此时navDiv就代码的类名是class=avigation这个组件,这次赋值后,该组件的名字并没有变
              // 要想改变该组件的名字,还要通过 navDiv.className 来修改,让他被新的css选择器来修饰
             
              navDiv.appendChild(DeleteA);  // 把我们新增的a标签添加到导航栏中


              // // 在导航栏中加个按钮, 用来删除文章. 
              // let deleteA = document.createElement('a');
              // // location.search 就是当前页面 url 的 query string, 也就是
              // // ?blogId=1 这样的结果. 
              // deleteA.href = 'blogDelete' + location.search;
              // deleteA.innerHTML = '删除';

              // let navDiv = document.querySelector('.nav');
              // navDiv.appendChild(deleteA);

            }
          },
          error: function() {
            // 强行跳转到登录页面
            location.assign('blog_login.html');
          }
        });
      }


      // 通过这个函数, 来获取到博客详情
      function getBlogDetail() {
        $.ajax({
          type: 'get',
          url: 'blog' + location.search, // servlet path,和获取博客列表一个地址,但获取博客列表不带blogId,显示博客详情带blogId
          
          // 后端在查询失败的时候不要返回200,避免前端触发ajax中的sucess回调函数
          success: function(body) {
          
            // 根据返回的数据来构造页面
            // rightDiv是我们博客详情,我们具体的博客内容要挂在rightDiv这个结点上
            let rightDiv = document.querySelector('.right'); // 获取我们上面的右侧页面对象,接下来我们就可以对右侧页面对象进行操作来给我们页面添加元素
            rightDiv.className = 'right'; // 引入css
            // 博客标题构建与添加
            // let titleDiv = document.createComment('div');
            // titleDiv.className = 'h2';
            // titleDiv.innerHTML = body.title;
            // rightDiv.appendChild(titleDiv); // 把标题结点挂到博客详情上

            // // 创建该篇博客的发布日期,并把日期添加到博客详情rightDiv上
            // let dateDiv = document.createComment('div');
            // dateDiv.className = 'date';
            // dateDiv.innerHTML = body.dateTime;
            // rightDiv.appendChild(dateDiv);
            // 博客标题
            let titleDiv = document.querySelector('.right h2');
            titleDiv.innerHTML = body.title;
            // 发布日期
            let dateDiv = document.querySelector('.right .date');
            dateDiv.innerHTML = body.postTime;
            // 创建内容
            
           
            // 刚才这里是直接把正文内容设置到 innerHTML 中了, 没有渲染的过程的. 
            // let divContent = document.querySelector('#content');
            // divContent.innerHTML = body.content;
            // 靠谱的做法, 应该是先使用 editor.md 进行渲染. 
            // [重要] 这个方法就是 editor.md 提供的一个方法把 markdown 字符串转成格式化的效果. 
            // 第一个参数是一个 div 的 id, 表示要把渲染结果放到哪个 div 中. 
            // 第二个参数是一个 js 对象, 把正文内容传入即可. (还支持很多别的参数属性, 此处暂时不涉及)
            editormd.markdownToHTML('content', {
              markdown: body.content
            });            
          },
          error: function() {
            alert("获取博客详情失败!");
          }

        });
      }

      function checkLogin() {
        $.ajax({
          type: 'get',
          url: 'login',
          success: function(body) {
            // 如果用户已经登录就什么也不做
          },
          error: function() {

          //alert("当前登录已过期,请重新登录!")

          // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('blog_login.html');
          }
        });
      }
      getBlogDetail();
      checkLogin();
      getAuthor();
      
     
    </script>
</body>
</html>

🍑博客编辑页

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>开始书写吧</title>
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="writeblog.css">
   <!-- 引入 editor.md 的依赖 -->
   <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
   <script src="js/jquery.min.js"></script>
   <script src="editor.md/lib/marked.min.js"></script>
   <script src="editor.md/lib/prettify.min.js"></script>
   <script src="editor.md/editormd.js"></script>
</head>
<body>
  <!-- 导航栏 -->
  <div class="navigation">
    <img src="image/大头贴.png">
    <span>我的博客系统</span>
    <!-- 占位置用的 -->
    <span class="space"></span>

    <a href="blog_home.html">主页</a>
    <a href="write_blog.html">写博客</a>
    <a href="logout">退出登录</a>
  </div>
  <div class="blog-edit-container"> 
    <!-- 内容分为两个部分,标题区和编辑区 -->
    <form action="blog" method="post" style="height: 100%">
       <!-- 标题区 -->
      <div class="title">
      <!-- 这里我们用from表单提交数据,后端要想获得前端提交的数据,from表单中要加上name,后端就是通过这个key来获取到value的 -->
        <input type="text" id="title" placeholder="请输入文章标题", name="title">
        <input type="submit" id="submit" value="发布文章">
      </div>
    
      <!-- 编辑区 -->
      <div id="editor">
        <!-- 需要在这里加上一个隐藏的 textarea -->
        <!-- 属于editor.md 这个库的要求 -->
        <textarea name="content" style="display: none;"></textarea>
      </div>
    </form>

    <script>
      // 初始化编辑器
      var editor = editormd("editor", {
          // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. 
          width: "100%",
          // 设定编辑器高度,这个100%是相对于父元素的高度,此时editor的父元素的from, 但form没高度,所以我们要把from的高度设置一下
          height: "calc(100% - 50px)",
          // 编辑器中的初始内容
          markdown: "# 在这里写下一篇博客",
          // 指定 editor.md 依赖的插件路径
          path: "editor.md/lib/",
          // 加上这个属性,效果就是把编辑器里的内容给自动保存到textarea里
          saveHTMLToTextarea:true,
      });

      // 检查当前用户是否登录,如果未登录就强制跳转到登录界面
      function checkLogin() {
        $.ajax({
        type: 'get',
        url: 'login',
        success: function(body) {
          // 如果用户已经登录就什么也不做
        },
        error: function() {

          //alert("当前登录已过期,请重新登录!")

          // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('blog_login.html');
        }
        });
      }
      // 别忘了函数调用
      checkLogin() // 这样函数在页面加载的时候就会调用
    </script>
  </div>
  
  
</body>
</html>

🔔视图层对应的css代码

还有他们各自对应的css代码,注意css代码和html代码在同一个目录下

共同的样式

common.css

/* 导航栏的样式 */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
html, body {
  height: 100%;
  background-image: url(image/未来风\(1\).jpg);
  background-repeat: no-repeat;
  background-position: center center;
  background-size: cover;
}
.navigation {
  width: 100%;
  height: 50px;
  background-color: rgba(50, 50, 50, 0.4);
  color: rgb(255, 255, 255);
  display: flex;
  /* 实现元素垂直居中 */
  align-items: center;
}
.navigation img {
  height: 40px;
  width: 40px;
  border-radius: 50%;
  margin-left: 30px;
  margin-right: 10px;
}
.navigation .space {
  width: 68%;
}
.navigation a {
  /* 去掉下划线 */
  text-decoration: none;
  color: rgb(255, 255, 255);
  justify-content: space-between;
  padding: 0 10px;
  display: flex;
  
}
.container {
  width: 1000px;
  height: calc(100% - 50px);
  /* 要弄成弹性布局,要不然右块内容就另起一行了 */
  display: flex;
  /* 水平居中 */
  margin: 0 auto;
  /* 左右两块中间留出间距 */
  justify-content: space-between;
  /* 想一下为啥justify-content: center不能让两块居中; */
  
}
.container .left {
  height: 100%;
  width: 200px;
  
}
.container .right {
  height: 100%;
  width: 795px;
  background-color: rgba(255, 255, 255, 0.7);
  border-radius: 10px;
  /* 处理异常问题 */
  overflow:auto;
}
.card {
  /* card的宽度默认是200px,与父类相同 */
  background-color: rgba(255, 255, 255, 0.7);
  /* 注意棱角 */
  border-radius: 10px;
  /* 通过内边距使得头像水平居中,200 = 140 + 2 * 30 */
  padding: 30px;
}

/* 用户头像 */
.card img {
  width: 140px;
  height: 140px;
  border-radius: 70px;
}
.card h3 {
  text-align: center;
  margin: 5px;
}
.card a{
  /* 行级元素变成块级元素,因为行级元素无法指定大小 */
  display: block;
  /* 去掉下划线 */
  text-decoration: none;
  /* 文本居中 */
  text-align: center;
  color: rgba(50, 50, 50, 0.4);
  padding: 5px;
}
.card .counter {
  display: flex;
  /* 使得他们左右进行分离排列 */
  justify-content: space-around;
  margin: 5px;
}


博客登录页所对应的css代码

/* .dialog {
  width: 400px;
  height: 400px;
  display: flex;
  justify-content: center;
  align-items: center;
  注意不能之间用弹性布局,我们弹性布局用在父子之间,写在父元素中
} */
.container_login {
  width: 100%;
  /* 要注意减去导航栏的高度 */
  height: calc(100% - 50px); 
  display: flex;
 
  align-items: center;
  justify-content: center;
}
.container_login .dialog {
  width: 400px;
  height: 400px;
  background-color: rgba(255, 255, 255, 0.6);
  /* 边框圆角 */
  border-radius: 10px;
}
.dialog h3 {
  margin: 30px;
  text-align: center;
}

.dialog .row {
  width: 100%;
  height: 50px;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 20px;
}
.dialog .row span {
  display: block;

  width: 100px;
  height: 40px;
  font-weight: 700;
  /* 文本水平居中 */
  text-align: center;
  padding-top: 10px;
  padding-bottom: 10px;

}
/* 输入框格式设置 */
.dialog .row #username, #password {
  display: block;
  width: 200px;
  height: 40px;
  border-radius: 10px;

  /* 设置左内边距,使得输入的数据与边框隔开 */
  padding-left: 10px;
  /* 去掉边框线,和轮廓线 */
  border: none;
  outline: none;
  /* 设置字体大小,和输入时字体的位置 */
  font-size: 22px;
  /* 文本垂直居中 */
  line-height: 40px;
}
/*登录提交框*/
.row #submit {
  width: 300px;
  height: 40px;
  margin-left: 50px;
  margin-right: 30px;
  background-color: rgb(0, 189, 189);
  font-size: large;
  /*设置边框中的文本位置*/
  text-align: center; 
  padding-top: 5px;
  padding-bottom: 5px;

}

/*注册框*/
.row #reg {
  width: 300px;
  height: 40px;
  margin-left: 50px;
  margin-right: 30px;
  background-color: rgb(0, 189, 126);
  font-size: large;
  /*设置边框中的文本位置*/
  text-align: center; 
  padding-top: 5px;
  padding-bottom: 5px;

}
/*去掉下划线
.navigation a {
 
  text-decoration: none;
  color: rgb(255, 255, 255);
  justify-content: space-between;
  padding: 0 10px;
  display: flex;
  
} * /

博客注册页对应的css

.container_reg {
  width: 100%;
  /* 要注意减去导航栏的高度 */
  height: calc(100% - 50px);
  display: flex;

  align-items: center;
  justify-content: center;
}
.container_reg .dialog {
  width: 400px;
  height: 400px;
  background-color: rgba(255, 255, 255, 0.6);
  /* 边框圆角 */
  border-radius: 10px;
}
.dialog h3 {
  margin: 30px;
  text-align: center;
}

.dialog .row {
  width: 100%;
  height: 50px;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 20px;
}
.dialog .row span {
  display: block;

  width: 100px;
  height: 40px;
  font-weight: 700;
  /* 文本水平居中 */
  text-align: center;
  padding-top: 10px;
  padding-bottom: 10px;

}
.dialog .row #username, #password1, #password2 {
  display: block;
  width: 200px;
  height: 40px;
  border-radius: 10px;

  /* 设置左内边距,使得输入的数据与边框隔开 */
  padding-left: 10px;
  /* 去掉边框线,和轮廓线 */
  border: none;
  outline: none;
  /* 设置字体大小,和输入时字体的位置 */
  font-size: 22px;
  /* 文本垂直居中 */
  line-height: 40px;
}
/*注册提交框*/
.dialog .row #submit {
  width: 300px;
  height: 40px;
  margin-left: 50px;
  margin-right: 30px;
  background-color: rgb(0, 189, 189);
  font-size: large;
  /*设置边框中的文本位置*/
  text-align: center;
  padding-top: 5px;
  padding-bottom: 5px;

}

博客列表页对应的css

/* 博客主页中博客概述、博客列表中的页面个数 */
.blog {
  width: 100%;
  /* blog的宽度和父元素right是一样的,
  而高度如果不设定的话,就取决于内容高度的总和
  所以我们在这里不设置高度 */
  padding: 10px;
  /* 当我们要注意设置每一篇博客的间距 */
}
.blog .title {
  /* 设置字体大小和粗细 */
  font-size: 18px;
  font-weight: 600;
  /* 居中 */
  text-align: center;
  padding-top: 10px;
  padding-bottom: 5px;
}
.blog .date {
  padding: 5px;
  color: darkgoldenrod;
  text-align: center;
}
.blog .desc, p{
  /* 首行缩进, 注意在博客详情页的每一段落也要首行缩进*/
  text-indent: 2em;
}
.blog a {
  width: 140px;
  height: 40px;
  text-decoration: none;
  color: #000;
 
  /* 行级元素无法改变高度和宽度 */
  display: block;
  font-size: 15px;
  font-weight: 300px;
  /* 文本内容水平居中 */
  text-align: center;
  /* 文本框垂直居中 */
  line-height: 40px;

  /* 边框的上下边距5px,水平居中 */
  margin: 5px auto;
  display: flex;

  /* 边框线条粗细2px,颜色黑色,边框线条:实线 */
  border: 2px black solid;
  /* 当用户点击文本框:查看全文时,产生渐变效果 */
  transition: all 1.5s;
}
.blog a:hover {
  /* 设置渐变效果的颜色 */
  background-color: orange;
}

博客详情页对应的css

/* 博客详情页的格式 */

p {
  text-indent: 2em;
  /* 给自然段和自然段之间设置间距 */
  padding: 10px;
}
.right h2 {
  padding: 15px;
  text-align: center;
}
.right .date {
  text-align: center;
  color: orange;
  /* 水平居中 */
  padding-top: 0x;
  padding-bottom: 15px;
}

博客编辑页对应的css

.blog-edit-container {
  width: 1000px;
  height: calc(100% - 50px);
  /* 水平居中 */
  margin: 0 auto;
}
.blog-edit-container .title {
  width: 100%;
  height: 50px;
  /* 垂直居中 */
  align-self: center;
  display: flex;
  /* 使子元素输入框和按钮靠两边放置,中间留个缝 */
  justify-content: space-between;


}
.title #title {
  width: 790px;
  height: 40px;
  border-radius: 10px;
  display: block;
  /* 去掉边框和轮廓线条 */
  border: none;
  outline: none;
  padding-left: 10px;
  font-size: large;
  background-color: rgba(255, 255, 255, 0.75);
  align-items: center;
}
.title #submit {
  width: 200px;
  height: 40px;
  background-color: rgba(255, 165, 0, 0.7);
  border-radius: 10px;
  font-size: large;
  font-weight: 600;
  display: block;
  /* 去掉边框和轮廓线条 */
  border: none;
  outline: none;
}

.blog-edit-container #editor {
  border-radius: 10px;
  background-color: rgba(255, 255, 255, 0.7);
  /* 设置半透明 */
  opacity: 85%;
}

项目gitee连接icon-default.png?t=M85Bhttps://gitee.com/shen-xiao-yu/java104/tree/master/BlogSystem

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是NFS?NFS挂载

文章目录1、NFS服务2、RPC服务与NFS3、NFS的优缺点4、NFS服务端的搭建与配置5、小结1、NFS服务 NFS&#xff0c;全称Network File System&#xff0c;即网络文件系统。最大的功能是通过网络&#xff0c;让不同的机器、不同的操作系统可以共享彼此的文件。 &#x1f609; 更直白…

Go语言开发k8s-05-ConfigMap操作

文章目录1. 结构体1.1 ConfigMapList1.2 ConfigMap1.3 TypeMeta1.4 ObjectMeta1.7 对照yml文件示例1.5 Immutable1.6 Data1.7 BinaryData2. Create configMap语法完整示例3. Get ConfigMapList语法完整示例4. Get ConfigMap语法完整示例5. Update ConfigMap语法完整示例6. Dele…

【网络篇】第二篇——IP协议与MAC地址详解

IP协议 理解源IP地址和目的IP地址 网段划分 IP地址的数量限制 私有IP地址和公网IP地址 路由 NAT(网络地址转换) ​编辑NAT IP转换过程 NAPT MAC地址 理解源MAC地址和目的MAC地址 对比理解MAC地址和IP地址 只有一个MAC地址&#xff0c;可以传输数据嘛? 为什么有了…

网络原理——网络层与数据链路层

JavaEE传送门JavaEE 网络原理——No.3 传输层_TCP的滑动窗口, 流量控制与拥塞控制 网络原理——No.4 传输层_TCP协议中的延迟应答, 捎带应答, 面向字节流与TCP的异常处理 目录网络层IP 协议IP 地址路由选择数据链路层以太网网络层 网络层做的工作, 就是两点之间, 规划出一个合…

python基于PHP+MySQL的健身房管理系统

随着时代的发展人们人们对身体不健康越来越重视。身体是革命的本钱,所以只有有一个好的身体才能够积极地参加到工作和学习中去。当前社会生活节奏较快人们往往没有时间进行锻炼于是造成了很多富贵病以及办公室职业病的发生。这是一种极其不健康的生活方式,我为了能够让这些亚健…

坚持记账的5大好处,你知道吗?

记账可以理清生活开支&#xff0c;有效节流通过记账&#xff0c;每时每刻都能知道自己的财务状况&#xff0c;花钱的时候会更有目的与计划&#xff0c;不像以前手忙脚乱一团糟&#xff0c;也不再寅吃卯粮&#xff0c;随意透支。对于很多不知道钱花到哪里去的人来说&#xff0c;…

SpringBoot(自定义注解)

目录 1.注解的概念 1.1 什么是注解 1.2 java注解分类 1.3 JDK基本注解 1.4 JDK元注解 2.自定义注解 2.1 自定义注解的组成 2.2 如何自定义注解 2.3 案例 2.3.1 简单了解基本属性 2.3.2 获取类与方法上的注解值 2.3.3 获取类属性上的注解属性值 2.3.4 获取参数修饰注解对应的属…

MQTT X v1.8.3 正式发布

近日&#xff0c;MQTT X 发布了最新的 1.8.3 版本&#xff0c;主要对功能使用进行了优化&#xff0c;并修复了使用过程中所出现的各类问题。例如&#xff0c;优化了 MQTT 5.0 Clean Start 的使用方式&#xff0c;为会话过期间隔添加默认值&#xff1b;优化 MQTT X CLI 的默认输…

数据结构与算法分析之并查集

1. 并查集 并查集是一种树型的数据结构&#xff0c;并查集可以高效的进行如下操作&#xff1a; 查询元素p和元素q是否属于同一组合并元素p和元素q所在的组 1.1 并查集结构 并查集是一种树型结构&#xff0c;但这棵树和我们之前学的二叉树、红黑树、B树等都不一样&#xff…

WindowsServer2016配置故障转移群集图文教程

准备工作 首先准备好两台以上的服务器&#xff0c;并记录好IP和主机名。如下 172.31.210.203 WIN-S8LC9RKL4BB 172.31.215.54 WIN-76A6N72MRTD 之后需要配置到host文件中。 正文 基础配置 首先打开host文件&#xff0c;把所有的IP及主机添加进去。host文件地址如下。 C:\Wi…

10.30-11.3|浙大报考点硕士研究生2023年网上确认系统操作流程

一、登陆《2023年全国硕士研究生招生考试网上确认系统》https://yz.chsi.com.cn/wsqr/stu/index.html。填写考生所在地&#xff0c;点击“确定”。二、点击“开始进行网上确认”。 三、阅读网报公告之后&#xff0c;点击“我已经阅读完毕”。 四、阅读考生诚信考试承诺书&#…

QT5串口编程——编写简单的上位机

下面开始介绍串口类的使用。 首先&#xff0c;QT5是自带QSerialPort这个类的&#xff0c;使用时需要在pro文件里面添加一行&#xff1a; ​然后直接引用头文件就可以了。 ​在QT5中&#xff0c;串口通信是借助一个QSerialPort的对象来实现的&#xff0c;在设置QSerialPort对象…

课堂笔记| 第七章:多态

本节课要点&#xff1a; 继承特性多态虚函数目录 一、多继承 二、继承的前提&#xff1a;正确的分类 三、多态 1. 虚函数 2. 确保覆盖和终止覆盖 3. 虚函数的实现原理 4. 虚析构函数 四、纯虚函数和抽象类 1. 纯虚函数 2. 抽象类 一、多继承 在之前的课程中&a…

SpringCloud Alibaba Sentinel实现熔断与限流

目录 一、简介 1.官网 & 介绍 2.下载地址 3.作用 4.如何使用 ⭐解决服务使用中的各种问题 5.Sentinel与Hystrix的区别 二、安装sentinel控制台 1.sentinel组件由2部分构成 2.安装步骤 ①地址 ②运行命令 ③访问sentinel界面 三、初始化演示工程 1.启动naco…

MyBatis 环境搭建配置全过程【IDEA】

文章目录一、MyBatis 介绍二、MyBatis 环境搭建1.MyBatis 下载2.配置 jdk 版本3.创建 Maven 工程4.IDEA 连接数据库5.项目文件构架6.引入相关依赖7.命令行创建数据库8.数据库配置文件9.核心配置文件三、入门测试程序1.创建表准备数据2.创建 POJO 实体3.创建映射文件4.修改核心配…

如何增加 KVM 虚拟机的磁盘大小

KVM 是一种集成到 Linux 内核中的虚拟化技术。您可以使用virsh、virt-manager和GNOME Boxes等工具创建虚拟机并与 KVM 交互。 磁盘空间不足是最常见的 VM 来宾问题之一。在测试新 VM 时,您可能会故意使用较小的磁盘。随着时间的推移,您会累积文件,直到虚拟磁盘几乎已满。以…

C语言笔记

fabs用来求double类型的绝对值&#xff0c;小数点后保留6位#include<math.h> double fabs(double ) labs用来求长整型long整型的绝对值&#xff0c; long cabs(long n); abs用来求整数的绝对值&#xff0c;labs求long long的绝对值#include<stdlib.h> double ret …

初识C++ (二)

初识C 二 上节课输入输出问题的一些补充一. 缺省参数1.1 半缺省参数1.2 全缺省参数二. 函数重载2.1 重载是什么意思&#xff1f;2.2 如何区分重载函数参数类型不同参数个数不同参数顺序不同附加题1附加题22.3 c支持函数重载的原理预处理编译汇编连接总结要以一种很认真的态度去…

深度优先搜索(dfs)和广度优先搜索(bfs)

目录 一、前言 二、关于dfs和bfs有意思的小故事 三、深搜题例 1、小猫爬山链接 2、基本思路 3、代码 &#xff08;1&#xff09;python代码 四、广搜题例 1、武士风度的牛链接 2、基本思路 3、代码 &#xff08;1&#xff09;C代码 &#xff08;3&#xff09;pyth…

现在的编程语言越来越多,为什么 C 和 C++ 还没有被现在的时代淘汰呢?

C/C会不会被时代淘汰&#xff1f;这个问题跳过了一步&#xff0c;关键是这个问题&#xff1a; C/C有哪些其它语言难以代替的特殊之处&#xff1f; 1、对实现细节的控制粒度 一般我们常说&#xff1a;C/C具有较高的执行效率。其实这句话不是特别准确&#xff0c;有时候它们并…