目录
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 部分组成:
一个模型可以为多个视图(View)提供数据,一套模型(Model)的代码只需写一次就可以被多个视图重用,有效地减少了代码的重复性,增加了代码的可复用性。 |
View(视图) | 指在应用程序中专门用来与浏览器进行交互,展示数据的资源。在 Web 应用中,View 就是我们常说的前台页面,通常由 HTML、JSP、CSS、JavaScript 等组成。 |
Controller(控制器) | 通常指的是,应用程序的 Servlet。它负责将用户的请求交给模型(Model)层进行处理,并将 Model 层处理完成的数据,返回给视图(View)渲染并展示给用户。 在这个过程中,Controller 层不会做任何业务处理,它只是 View(视图)层和 Model (模型)层连接的枢纽,负责调度 View 层和 Model 层,将用户界面和业务逻辑合理的组织在一起,起粘合剂的效果。 |
MVC 的工作流程如下:
- 用户发送请求到服务器;
- 在服务器中,请求被控制层(Controller)接收;
- Controller 调用相应的 Model 层处理请求;
- Model 层处理完毕将结果返回到 Controller;
- Controller 再根据 Model 返回的请求处理结果,找到相应的 View 视图;
- 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连接https://gitee.com/shen-xiao-yu/java104/tree/master/BlogSystem