博客项目(前后端分离)(servlet实战演练)

作者简介:大家好,我是未央;

博客首页:未央.303

系列专栏:实战项目

每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!!!!

文章目录

前言

项目介绍

一、MVC模式简介

1.1  MVC 模式含义

1.2 MVC 的工作流程

二、项目概述

2.1 项目的几个页面

2.2 功能大概流程

2.3 M层、V层和C层

三、项目实战

3.1 创建项目

3.2 引入依赖

pom.xml代码

3.3 创建必要目录

 3.4 编写代码

3.5 打包和部署:配置Smart Tomcat

四、模型层

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

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

4.3 🌰DButil类:连上数据库

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

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

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

5.1 🍑前后端交互接口举例说明

5.2 🍑BlogServlet代码

5.3 🍑DeleteBlogServlet代码

5.4 🍑UserServlet代码

5.5 🍑LoginServlet代码

5.6 🍑LoginOutServlet代码

5.7 🍑RegServlet代码

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

6.1 🏀博客登录页

6.2 🏀博客注册页

6.3 🏀博客列表页

6.4 🏀博客详情页

6.5 🏀博客编辑页

6.6 🏀视图层对应的css代码

总结



前言

项目介绍

目前现在更主流的开发方式是 "前后端分离" 的方式.
这种方式下服务器端不关注页面的内容, 而只是给 网页端提供数据;
网页端通过 ajax 的方式和服务器之间交互数据, 网页拿到数据之后再根据数据的内容渲染到页面上.从而通过前后端分离的方式来实现博客系统的编写和各种博客系统功能的实现。

开发一个博客系统,实现博客用户的注册、登录,并且编写博客列表页、博客详情页、博客编辑页等,能够实现博客的编写、查看、保存等功能。


图示过程说明:


一、MVC模式简介

1.1  MVC 模式含义

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

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

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


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

​ 


1.2 MVC 的工作流程

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

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


二、项目概述

2.1 项目的几个页面

总的来说,我们当前的项目有这样几个页面:

博客登录页、博客注册页、博客列表页、博客详情页、博客编辑页。

博客登录页


博客注册页 


博客列表页 


博客详情页 


博客编辑页


2.2 功能大概流程

首先登录注册不用多说

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

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

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

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


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


2.3 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)渲染并展示给用户。


三、项目实战

3.1 创建项目

对于Maven来说,我们不用下载,因为idea中内置了线程的Maven,我们之间拿来用就好。

下面我们主要介绍在Maven在idea中的使用:

1、新建一个Maven项目


 2、Maven的使用

首先我们先了解刚刚我们新创建的Maven项目的组成部分

下面我们以mysql驱动包的引入来说明Maven引入依赖的流程 

项目图示示例:


3.2 引入依赖

依赖引入的方法:

因为Servlet这个API部署JDK内置的,而是第三方(Tomcat)提供的,所以我们要想使用,就需要额外的引入Servlet依赖 。

我们借助Maven来引入Servlet依赖——这里我们所导入的第三方库都是从Maven中央仓库中获取

中央仓库地址:

链接:Maven Repository: Search/Browse/Explore (mvnrepository.com)


项目代码图示:

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>blog_system</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency><!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.4</version></dependency></dependencies> </project>

3.3 创建必要目录

虽然当下 Maven 帮我们创建了一些目录,但是还不够。

当前这个目录还不足以支撑我们写一个 Servlet 项目,我们这个项目是依赖Tomca服务器,为了符合Tomcat的格式,我们就需要手动创建一些目录和文件。

常见目录和文件:


 3.4 编写代码

详细代码编写流程见下面模型层;Controller控制层;视图层;


3.5 打包和部署:配置Smart Tomcat

详细步骤跳转至博客:http://t.csdn.cn/gruKuicon-default.png?t=N7T8http://t.csdn.cn/gruKu



四、模型层

两部分:

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

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


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

代码:

package model;
//这个类的对象表示一个用户;
//属于实体类(对应我们数据库表里面的一条记录)
public class User {private int userId;// 当前登录的用户idprivate String username;// 用户名字private String password;// 用户密码// 但注意我们这个属性并没有放在数据库中,因为这个是随时都会改变的(随着你当前所查看博客的 不同、当前登录用户的不同)private int 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;}
}

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

代码:

package mode;import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.SimpleTimeZone;// 实体类是我们进行增删改查的基本单位
//这个类的对象表示一篇博客,属于实体类(对应我们数据库表里面的一条记录)
//这里的属性一般就跟着表走;
public class Blog {private int blogId;  // 博客idprivate String title;private String content;private int userId; // 这篇博客的作者的idprivate 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;}
}

4.3 🌰DButil类:连上数据库

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

代码示例:

package model;
import com.mysql.jdbc.Connection;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;//这个类主要是用来进行封装数据库建立连接/断开连接的操作;
// 这里我们采用了懒汉模式,会有线程安全问题,要注意加锁;
//原因:因为servlet程序天然就是运行在多线程环境的,每一个请求都可能对应着一个线程(Tomcat 是通过多线程的方式来处理很多请求)
public class DBUtil {private volatile static DataSource dataSource = null;//此处先创建一个数据源datasource;//登录数据库,并获取数据源;即对数据源进行初始化的操作;private static DataSource getDataSource(){if(dataSource == null){          // 锁内外判断,如果已经创建了数据源,就不进行加锁操作了,典型的双重效验锁;synchronized (DBUtil.class) {    // 对类对象加锁if (dataSource == null) {dataSource = new MysqlDataSource(); //创建datasource实例;//这里需要给datasource设置一些属性;((MysqlDataSource) dataSource).setURL("jdbc:mysql://127.0.0.1:3306/BlogSystem?characterEncoding=utf8&useSSL=false");//创建一个url属性;((MysqlDataSource) dataSource).setUser("root");//设置用户名;((MysqlDataSource) dataSource).setPassword("136982");// 当我们要把代码部署到云服务器上,我们这里设置的密码是云服务器上的mysql的密码;}}}return  dataSource;}// 与数据源(数据库)建立连接;public static Connection getconnection() throws SQLException {return (Connection) getDataSource().getConnection();}// 关闭连接,释放资源;public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){//然后依次按照反着的操作进行关闭;//resultSetif(resultSet != null){try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}//statement;if(statement != null){try {statement.close();} catch (SQLException e) {e.printStackTrace();}}//connection;if(connection != null){try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}}

代码示例图:




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

package model;import com.mysql.jdbc.Connection;import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;//针对博客要实现的功能
//1.新增博客(博客编辑页)
//2.查询出博客列表(博客列表页)
//3.查询出指定博客的详细(博客详细页)
//4.删除指定的博客(可以在博客详细页中加入此功能)
public class BlogDao {//注意:下列代码都是JDBC操作,代码相似性很好!//1.新增博客功能public void insert(Blog blog){Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {//1.先和数据库建立链接connection = DBUtil.getconnection();//2.构造SQL语句String sql = "insert into blog values(null, ?, ?, ?, now())";preparedStatement = connection.prepareStatement(sql);preparedStatement.setString(1, blog.getTitle());preparedStatement.setString(2, blog.getContent());preparedStatement.setInt(3, blog.getUserId());//3.执行SQL语句int ret = preparedStatement.executeUpdate();if (ret == 1) {System.out.println("插入成功!!!");}else{System.out.println("插入失败!!!");}} catch (SQLException e) {e.printStackTrace();}finally {DBUtil.close(connection, preparedStatement, resultSet);}}//2.查询出博客列表//当前这个方法是给博客列表页使用的//而博客列表页里面,不需要显示博客的完整正文,只需要有一小部分即可(作为一个摘要)(71,72)public List<Blog> selectAll(){Connection connection = null;//定义来为第1步和数据库建立连接PreparedStatement Statement = null;//定义来构造第2步的SQL语句ResultSet resultSet = null;//定义来执行第3步的SQL语句List<Blog> blogs = new ArrayList<>();//创建一个数组blogs用于存放blog对象的属性(65)try {//1.先和数据库建立链接connection = DBUtil.getconnection();//2.构造SQL语句String sql = "select * from blog";Statement = connection.prepareStatement(sql);//3.执行SQL语句resultSet = Statement.executeQuery();//4.遍历结果集合while (resultSet.next()){   //依次遍历resultSetBlog blog = new Blog();//创建一个blog对象访问集合//然后我们给blog对象设置内容,要对应db.sql表中的属性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);//substring是用来截取字符blog.setUserId(resultSet.getInt("userId"));blog.setPostTime(resultSet.getTimestamp("postTime"));blogs.add(blog);}} catch (SQLException e) {e.printStackTrace();}//5.最后不要忘记了关闭数据库finally {DBUtil.close(connection,Statement,resultSet);}return  blogs;//最后直接返回blogs即可}//3.通过blogId查询出指定的博客的详细public Blog selectOne(int blogId){Connection connection = null;//定义来为第1步和数据库建立连接PreparedStatement Statement = null;//定义来构造第2步的SQL语句ResultSet resultSet = null;//定义来执行第3步的SQL语句try{//1.先和数据库建立链接connection = DBUtil.getconnection();//2.构造SQL语句String sql = "select * from blog where blogId = ?";//此处的?表示的是我们参数传入的博客IdStatement = connection.prepareStatement(sql);Statement.setInt(1,blogId);//3.执行SQL语句resultSet = Statement.executeQuery();//4.遍历结果集合//由于是按照blogId来查询,blogId是自增主键,不能够重复//所以此处的查询结果不可能是多条记录,只能是1条或者0条记录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,Statement,resultSet);}return null;}//4.通过blogId删除指定的博客public Blog delect(int blogId){Connection connection = null;PreparedStatement Statement = null;try {connection = DBUtil.getconnection();String sql = "delect from blog where blogId = ?";Statement = connection.prepareStatement(sql);Statement.setInt(1,blogId);int ret = Statement.executeUpdate();if (ret == 1){System.out.println("打印成功!!");}else {System.out.println("打印失败!!");}} catch (SQLException e) {e.printStackTrace();}finally {DBUtil.close(connection,Statement,null);}return null;}}

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

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

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

1、约定前后端交互接口

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

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

接下来,我们大致介绍一下我们这个项目中所用到的交互接口

5.1 🍑前后端交互接口举例说明

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


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


5.2 🍑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 {@Overrideprotected 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和BlogDao)在上面


下面我们列出对应视图层的代码:

相关联的视图层代码:博客列表页,博客详情页。

博客列表页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设置或获取网页地址跟在问号后面的部分,即queryStringDeleteA.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>

5.3 🍑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@Overrideprotected 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设置或获取网页地址跟在问号后面的部分,即queryStringDeleteA.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>


5.4 🍑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;@Overrideprotected 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设置或获取网页地址跟在问号后面的部分,即queryStringDeleteA.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>


5.5 🍑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,是否存了sessionIdHttpSession 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_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设置或获取网页地址跟在问号后面的部分,即queryStringDeleteA.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>

博客详情页(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设置或获取网页地址跟在问号后面的部分,即queryStringDeleteA.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>


5.6 🍑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方法@Overrideprotected 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");}
}


5.7 🍑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 {@Overrideprotected 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)) { // 注意因为这里是字符所以我们,要用equalsresp.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)总代码展示

6.1 🏀博客登录页

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


6.2 🏀博客注册页

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


6.3 🏀博客列表页

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


6.4 🏀博客详情页

<!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设置或获取网页地址跟在问号后面的部分,即queryStringDeleteA.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>

6.5 🏀博客编辑页

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

6.6 🏀视图层对应的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代码:

.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:

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

总结

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue 网络处理 - axios 异步请求的使用,请求响应拦截器

目录 一、axiox 1.1、axios 简介 1.2、axios 基本使用 1.2.1、下载核心 js 文件. 1.2.2、发送 GET 异步请求 1.2.3、发送 POST 异步请求 1.2.4、发送 GET、POST 请求最佳实践 1.3、请求响应拦截器 1.3.1、拦截器解释 1.3.2、请求拦截器的使用 1.3.3、响应拦截器的使用…

Umi3实战教程

一、框架介绍 umi是蚂蚁金服的前端开发框架&#xff0c;它内置了路由、web/移动端UI库、数据流、权限控制、常用hooks库、构建、部署、测试、等等一些工具&#xff0c;几乎涵盖了正常前端开发要用到的所有工具。 二、环境准备 pnpm 相比npm、yarn&#xff0c;pnpm更小更快扁平…

虚幻引擎:如何实现骨骼重定向

前言&#xff1a; 为什么需要做骨骼重定向&#xff0c;因为当前角色素材没有对应的动画&#xff0c;这时候我们可以找个身高体型差不多的带有动画素材的另一个角色来做重定向&#xff0c;这样我们就可以得到我们需要的动画素材了。 1.首先创建两个骨骼的IK绑定 2.然后给两个骨骼…

【算法|前缀和系列No.2】牛客网 DP35 【模板】二维前缀和

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【牛客网刷题】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希…

发面试题:(四)synchronized和lock区别

synchronized 关键字 synchronized关键字解决的是多个线程之间访问资源的同步性&#xff0c;synchronized关键字可以保证被它 修饰的方法或者代码块在任意时刻只能有一个线程执行。 另外&#xff0c;在 Java 早期版本中&#xff0c; synchronized属于重量级锁&#xff0c;效率…

vue3学习源码笔记(小白入门系列)------KeepAlive 原理

目录 说明组件是如何被缓存的&#xff0c;什么时候被激活对于KeepAlive 中组件 如何完成激活的对于KeepAlive 中组件 如何完成休眠的 总结 说明 Vue 内置了 KeepAlive 组件&#xff0c;实现缓存多个组件实例切换时&#xff0c;完成对卸载组件实例的缓存&#xff0c;从而使得组…

竞赛 深度学习+python+opencv实现动物识别 - 图像识别

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络3.1卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 inception_v3网络5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; *…

DHorse v1.4.2 发布,基于 k8s 的发布平台

版本说明 优化特性 在集群列表增加集群版本&#xff1b;修改Jvm的GC指标名&#xff1b; 解决问题 解决shell脚本换行符的问题&#xff1b;解决部署历史列表页&#xff0c;环境名展示错误的问题&#xff1b;解决指标收集功能的异常&#xff1b; 升级指南 升级指南 DHorse…

零宽空格引发的问题

有人跟我反馈说有bug。 我说&#xff1a;啥bug&#xff1f; 对方说&#xff1a;刚申请的内部用户的账号登录不上去。 我说&#xff1a;还有这种事&#xff0c;报啥错&#xff1f; 登录的时候报了这个错&#xff1a; 我一看还好还好&#xff0c;跟上一次不一样的错&#xff…

“探寻服务器的无限潜能:从创意项目到在线社区,你会做什么?”

文章目录 每日一句正能量前言什么是服务器&#xff1f;服务器能做什么&#xff1f;服务器怎么用&#xff1f;部署创意项目&#xff0c;还是在线社区亦或做其他的&#xff1f;后记 每日一句正能量 未知的下一秒&#xff0c;千万不要轻言放弃。 前言 在数字化时代&#xff0c;服…

SpringBoot面试题7:SpringBoot支持什么前端模板?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:SpringBoot支持什么前端模板? Spring Boot支持多种前端模板,其中包括以下几种常用的: Thymeleaf:Thymeleaf是一种服务器端Java模板引擎,能够…

基于马尔可夫随机场的图像去噪算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、马尔可夫随机场的基本原理 4.2、基于马尔可夫随机场的图像去噪算法 5.算法完整程序工程 1.算法运行效果图预览 原图&#xff1a; 加入噪声的图像&#xff1a; 滤波后的图像 迭代过程…

2-k8s-控制器介绍

文章目录 一、控制器类型二、Deployment控制器三、SatefulSet控制器四、Daemonset控制器五、Job控制器六、CronJob 控制器 一、控制器类型 Deployment&#xff1a;适合无状态的服务部署StatefullSet&#xff1a;适合有状态的服务部署DaemonSet&#xff1a;一次部署&#xff0c…

关于ts的keyof

type props_type {name: string,age: number }const props: props_type {name: tjq,age: 18 }for (const key in props) { //props[key]出现红色波浪线const value props[key]; }why&#xff1f; 经过我查阅多方资料&#xff0c;在网上看到一个比较合适的例子 地址&#xf…

OpenRemote: Java 开源 IoT 物联网开发平台,匹配智慧城市、智能家居、能源管理

OpenRemote 是一个直观、用户友好的基于Java语言的开源 IoT 物联网设备管理平台&#xff0c;它包括从连接设备到构建应用程序和特定领域的智能应用程序的所有功能和特性。通过OpenRemote物联网平台&#xff0c;用户可以收集和处理来自不同设备的传感器数据&#xff0c;适用于智…

Gson反序列化原理

前言 序列化和反序列化是日常工作中经常使用的工具&#xff0c;一般用于对象的持久化保存或者对象的网络传输&#xff0c;一般有两种情况&#xff0c;一种是对象本身实现了Serializable接口&#xff0c;这种情况下可以利用jdk自带的功能或者Kryo等这种封装好的序列化反序列化工…

Elasticsearch:什么是大语言模型 (LLMs)?

假设你想参加流行的游戏节目 Jeopardy&#xff08;这是一个美国电视游戏节目&#xff0c;参赛者将获得答案并必须猜测问题&#xff09;。 要参加演出&#xff0c;你需要了解任何事情的一切。 所以你决定在接下来的三年里每天都花时间阅读互联网上的所有内容。 你很快就会意识到…

关于 Invalid bound statement (not found): 错误的解决

关于 Invalid bound statement not found: 错误的解决 前言错误原因解决方法1. 检查SQL映射文件2. 检查MyBatis配置3. 检查SQL语句4. 检查命名约定5. 清除缓存6. 启用日志记录 重点注意 结语 我是将军我一直都在&#xff0c;。&#xff01; 前言 当开发Java Spring Boot应用程…

挚文集团:股票回购速度、收入指引均不及预期,令投资者失望

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 挚文集团未来将不再公布MAU数据 今年6月初&#xff0c;挚文集团(MOMO)在公布2023年第一季度业绩时透露&#xff0c;“陌陌应用的月活跃用户(MAU)”已经从去年3月的1.109亿下降到了今年3月的1.065亿&#xff0c;同比下降了-…

2023,简历石沉大海?软件测试岗位真的已经饱和了....

各大互联网公司的接连裁员&#xff0c;政策限制的行业接连消失&#xff0c;让今年的求职雪上加霜&#xff0c;想躺平却没有资本&#xff0c;还有人说软件测试岗位饱和了&#xff0c;对此很多求职者深信不疑&#xff0c;因为投出去的简历回复的越来越少了。 另一面企业招人真的…