当前位置: 首页 > news >正文

【基于前后端分离的博客系统】Servlet版本

 

🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

欢迎志同道合的朋友一起加油喔🤺🤺🤺


目录

一. 项目简介 

1. 项目背景

2. 项目用到的技术

3. 项目功能简单介绍 

二. 博客系统页面设计 

三. 项目准备工作

前后端交互约定内容的分析 

1. 接口路径

2. 请求方法

3. 请求参数

4. 响应数据

前后端交互 构思

四.博客系统中与数据库进行交互的类

设计数据库表

1.DBUtil类(数据库工具类)

2.Blog 类 (博客条目类)

3.User 类 (用户类)

4.BlogDao 类 (访问博客数据的对象)

5.UserDao 类 (访问用户数据的对象)

五. 博客系统的后端接口

1.AuthorServlet 类 (处理关于作者的请求)

2.BlogServlet 类 (处理关于的博客请求)

3.LogoutServlet 类 (处理注销的请求)

4. LoginServlet 类 (处理登录的请求)

六. 前端代码构造请求

1. 登陆页面功能设计 

2. 博客列表页面功能设计

3. 博客详情页面功能设计 

4. 博客编辑页面功能设计

5. 用户注销功能设计

七. 博客系统设计源码



一. 项目简介 

1. 项目背景

在网络学完HTTP协议,前端学完html,css,js,后端学完Servlet开发后,做一个博客系统,巩固一下所学知识,并将所学知识运用到实际当中,以此来进一步提升对学习编程的兴趣

2. 项目用到的技术

  • 前端使用到html,css,js,使用ajax技术发送http请求,请求body的格式为json格式
  • 后端使用Servlet进行开发
  • 使用Mysql数据库保存数据
  • 除此还引入了editor.md,editor.md是一个开源的页面markdown编辑器组件
  • 采用Maven构建工具搭建项目开发环境

3. 项目功能简单介绍 

  • 登陆页面:输入用户及密码,点击提交,如果用户或密码错误,会提示用户或密码错误,账号及密码正确则登陆成功,成功后跳转到博客列表页面
  • 博客列表页面:博客列表页面展示所有发布过的文章,文章显示最多显示50字,如果想查看全文,则需要点击文章下的显示全文
  • 博客详情页面:点击某篇文章的显示全文按钮,则会展示文章的全部内容
  • 博客编辑页面:点击博客列表的写博客,会跳转到博客编辑页面,输入文章题目及文章内容点击发布文章,文章即可发布成功,发布成功后会跳转到博客列表页面,可以查看发布过的文章 
  • 博客注销按钮:点击博客注销按钮,则会跳转到博客登陆页面 

二. 博客系统页面设计 

这里附上静态页面设计的码云地址,可以点击查看,本篇文章只展示后端代码与前端代码交互的部分,想要查看博客系统页面设计代码,请点击:博客系统页面设计 

页面功能展示: 

项目链接:博客登录页

三. 项目准备工作

创建Maven项目在pom.xml中添加项目依赖

  • 后端采用Servlet开发
  • 数据库使用Mysql
  • jackson框架可以进行序列化和反序列化,将java对象和json字符串相互转化
<?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>20230528</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/com.fasterxml.jackson.core/jackson-databind --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.14.1</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency></dependencies>
</project>

 创建与开发相关的包和类

 引入前端资源 

  • 将前端资源都放在main/webapp目录下,前端资源从上面码云地址中获取,web.xml存放在main/webapp/WEB-INF目录
  • 注意我这里的html页面全都存放在一个html文件夹下面了,意味我在HTML页面中直接不能使用@WebServle注解里面的路径来访问Servlet.

在构造请求去访问Servlet时,路径是根据@WebServlet注解或web.xml配置文件中定义的路径,而不是Servlet类文件在服务器上的实际位置。路径是相对于整个Web应用的上下文路径(通常是打包后的war包所在的路径),而不是HTML页面所在的位置。

让我们再次以一个例子进行说明,假设你的Web应用(war包解压)的结构如下:

/myApp       (Web应用的上下文路径,war包名)/WEB-INF/classesMyServlet.class (访问路径为/myServlet)/subdirindex.html

在这种情况下,无论你的HTML页面位于哪里(例如在"/myApp/subdir/index.html"),你都可以通过以下方式来访问"MyServlet":

  1. 使用相对路径:假设你正在创建一个从"index.html"页面到"MyServlet"的链接,你可以使用相对于当前页面的路径,即"../myServlet",来指向Servlet。

  2. 使用绝对路径:无论你在哪里,你都可以使用相对于上下文路径(即"/myApp")的绝对路径,"/myServlet",来访问Servlet。完整的URL将是"http://example.com/myApp/myServlet"。

在URL中,"example.com"代表了网站的域名,也就是服务器的网络地址。所以,访问Servlet的路径并不依赖于HTML页面所在的位置,而是依赖于Servlet的配置和Web应用的上下文路径。

前后端交互约定内容的分析 

1. 接口路径

  • 前端请求登录:/login
  • 前端请求博客列表:/blog
  • 前端请求某篇博客详情:/blog + location.search
  • 前端请求作者信息:/author + location.search
  • 前端请求注销:/logout

2. 请求方法

  • 登录请求使用POST方法:form表单提交登录信息。
  • 获取博客列表使用GET方法:通过AJAX发送GET请求。
  • 获取博客详情和作者信息使用GET方法:通过AJAX发送GET请求。
  • 注销请求使用GET方法:通过超链接发送GET请求。

3. 请求参数

  • 登录请求:在登录表单中,通过name属性定义的字段名usernamepassword,将用户输入的用户名和密码作为参数发送到后端。
  • 获取博客列表、博客详情和作者信息:通过URL的查询参数,例如blogId用于获取博客详情,username用于获取作者信息。

4. 响应数据

  • 后端返回的响应数据是以JSON格式返回的,前端使用AJAX进行处理。

前后端交互 构思

1. login.html (博客登录页)

  • 前端实现步骤:
  1. 当用户点击登录按钮时,触发表单的提交操作。
  2. 使用 $.ajax 发送 POST 请求,将用户名和密码作为请求参数发送到后端验证登录。
  3. 在请求成功的回调函数中,根据后端返回的结果进行判断,如果登录成功,则执行相应的操作(例如跳转到博客列表页),如果登录失败,则进行相应的提示。
  • 后端实现步骤:
  1. 接收前端发送的 POST 请求,获取请求中的用户名和密码参数。
  2. 在数据库中查询是否存在对应的用户信息,进行用户名和密码的验证。
  3. 根据验证结果,返回相应的结果给前端,表示登录成功或登录失败。

2. blog_list.html (博客列表页)

  • 前端实现步骤:
  1. 在页面加载时,使用 $.ajax 发送 GET 请求,向后端获取博客列表数据。
  2. 在请求成功的回调函数中,遍历返回的博客数据,动态构造博客内容并添加到页面中。
  • 后端实现步骤:
  1. 接收前端发送的 GET 请求,无需参数。
  2. 查询数据库中的博客数据,获取博客列表。
  3. 将查询结果封装为 JSON 格式,并返回给前端。

3. blog_edit.html (博客编辑页)

  • 前端实现步骤:
  1. 当用户在编辑框中填写完博客标题和内容后,点击发布文章按钮,触发表单的提交操作。
  2. 使用 $.ajax 发送 POST 请求,将博客标题和内容等数据发送到后端创建新的博客。
  3. 在请求成功的回调函数中,根据后端返回的结果进行判断,如果博客创建成功,则执行相应的操作(例如跳转到博客详情页),如果创建失败,则进行相应的提示。
  • 后端实现步骤:
  1. 接收前端发送的 POST 请求,获取请求中的博客标题和内容等参数。
  2. 将获取到的博客信息插入到数据库中,创建新的博客。
  3. 返回相应的结果给前端,表示博客创建成功或创建失败。

4. blog_detail.html (博客详情页)

  • 前端实现步骤:
  1. 在页面加载时,使用 $.ajax 发送 GET 请求,向后端获取指定博客的详细信息。
  2. 在请求成功的回调函数中,根据返回的博客数据更新页面上的标题、日期和博客正文内容。
  • 后端实现步骤:
  1. 接收前端发送的 GET 请求,从请求参数中获取博客ID或其他标识符。
  2. 根据博客ID查询数据库,获取指定博客的详细信息。
  3. 将查询结果封装为 JSON 格式,并返回给前端。

四.博客系统中与数据库进行交互的类

  1. DBUtil:提供了数据库连接和资源释放的工具方法,通过 JDBC 技术与数据库建立连接并执行 SQL 语句。

  2. Blog:表示博客对象,包含博客的标题、内容、发布时间等属性,以及相应的 getter 和 setter 方法。

  3. BlogDao:封装了对博客表的基本操作方法,包括新增博客、根据博客ID查询博客、查询所有博客列表、删除指定博客等。

  4. User:表示用户对象,包含用户的ID、用户名和密码等属性,以及相应的 getter 和 setter 方法。

  5. UserDao:封装了对用户表的基本操作方法,包括根据用户ID查询用户信息、根据用户名查询用户信息等。

这些类通过在后端实现了与数据库的交互,可以方便地进行用户登录验证、博客发布和查询等操作。

设计数据库表

  • 有用户登陆,所以有一张用户表,观察博客列表有显示用户昵称,所以用户表设计有四个字段:用户id,用户名,密码,昵称
  • 有文章展示,所以有一张文章表,文章有文章id,标题,发布时间,文章内容,关联用户的外键
  • 一个用户可以发布多篇文章,所以用户与文章对应关系为1:m,用户id作为文章表的外键 

 创建表的时候可以插入一些数据便于后续的测试

--这个文件主要用来写建库建表语句
--一般建议大家,在建表的时候把建表sql保留下来,以备后续部署其他机器的时候就方便了.create database if not exists java_blog_system;
use java_blog_system;--删除旧表,重新创建新表,删除旧表是为了防止之前的残留数据对后续的程序有负面影响
drop table if exists user;
drop table if exists blog;--真正创建表
create table blog (blogId int primary key auto_increment,title varchar(128),content varchar(4096),postTime datetime,userId int
);create table user(userId int primary key auto_increment,username varchar(20) unique,           --要求用户名和别人不重复password varchar(20)
);--构造测试数据
insert into blog values (1,'这是我的第一篇博客','从今天开始我要认真敲代码',now(),1);
insert into blog values (2,'这是我的第二篇博客','从昨天开始我要认真敲代码',now(),1);
insert into blog values (3,'这是我的第三篇博客','从前天开始我要认真敲代码',now(),1);--构造测试数据
insert into user values(1,"jack","123");
insert into user values(2,"Alice","123");

1.DBUtil类(数据库工具类)

这个类是数据库工具类,主要提供数据库连接和关闭资源的方法。

  • getConnection():这个方法是获取数据库连接。它通过 DataSource 获取连接,并返回这个连接。如果发生异常,它将打印错误信息并返回 null。

  • close(ResultSet rs, PreparedStatement ps, Connection conn):这个方法是关闭资源。它会检查传入的 ResultSetPreparedStatementConnection 是否为 null,如果不为 null,那么就关闭它。如果在关闭资源时发生异常,它将打印错误信息。

//针对用户表提供的基本操作
//由于此处没有写注册的功能, 也就不必 add
//也没有用户删号功能, 也就不必 delete
public class UserDao {//1.根据 userId 来查询用户信息public User selectById(int userId) {//获取连接数据库的对象Connection connection = null;//获取执行预编译SQL语句的对象PreparedStatement statement = null;//获取遍历结果集合的对象ResultSet resultSet = null;try {//1.和数据库建立连接connection = DBUtil.getConnection();//2.构造SQL语句String sql = "select * from user where userId = ?";statement = connection.prepareStatement(sql);statement.setInt(1,userId);//3.执行SQL语句resultSet = statement.executeQuery();//4.遍历结果结果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 {//5.释放必要的资源DBUtil.close(connection,statement,resultSet);}return null;}//2.    根据username来查询用户信息(登录的时候)public User selectByUsername(String username) {//获取连接数据库的对象Connection connection = null;//获取执行预编译SQL语句的对象PreparedStatement statement = null;//获取遍历结果集合的对象ResultSet resultSet = null;try {//1.和数据库建立连接connection = DBUtil.getConnection();//2.构造SQL语句String sql = "select * from user where username = ?";statement = connection.prepareStatement(sql);statement.setString(1,username);//3.执行SQL语句resultSet = statement.executeQuery();//4.遍历结果结果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 {//5.释放必要的资源DBUtil.close(connection,statement,resultSet);}return null;}}

2.Blog 类 (博客条目类)

该类代表一个博客条目, 这个类中有五个成员变量:

  • blogId:每篇博客的唯一标识,它是数据库表中的主键。
  • title:博客的标题。
  • content:博客的内容。
  • postTime:博客的发表时间,这是一个Timestamp类型的数据。
  • userId:发表这篇博客的用户的id。

然后有这些成员变量的 get 和 set 方法,用来获取和设置这些变量的值。

其中, getPostTime 方法返回一个格式化的字符串表示的时间,格式是 "yyyy-MM-dd hh:mm:ss"。

public class Blog {private int blogId;private String title;private String content;private Timestamp postTime;private int userId;public int getBlogId() {return blogId;}public void setBlogId(int blogId) {this.blogId = blogId;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public Timestamp getPostTimestamp() {return postTime;}public String getPostTime() {//把时间戳转化成格式化时间//格式化字符串一定不要背!!!不同的语言不同的库,都有格式化时间的操作不同库的格式化时间字符串设定不同!!!//SimpleDateFormat M表示月份,N表示分钟SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");return simpleDateFormat.format(postTime);}public void setPostTime(Timestamp postTime) {this.postTime = postTime;}public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}
}

3.User 类 (用户类)

此类代表一个用户,这个类中有三个成员变量:

  • userId:每个用户的唯一标识,它是数据库表中的主键。
  • username:用户的用户名。
  • password:用户的密码。

同样地,这个类有这些成员变量的 get 和 set 方法,用来获取和设置这些变量的值。

public class User {private int userId;private String username;private String password;public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}

4.BlogDao 类 (访问博客数据的对象)

这是一个数据访问对象, 执行与博客相关的数据库操作, 这个类中有四个方法:

  • add(Blog blog):这个方法是添加一篇新的博客。首先获取数据库连接,然后构造SQL语句,通过 PreparedStatement 设置参数,最后执行SQL。

  • selectById(int blogId):这个方法是根据博客id查询一篇博客。流程和 add 方法类似,只是执行的是查询操作,查询到结果后需要创建 Blog 对象并赋值。

  • selectAll():这个方法是查询所有的博客。同样是执行查询操作,不过这个方法需要遍历所有的查询结果,并创建 Blog 对象。

  • delete(int blogId):这个方法是删除一篇博客。首先获取数据库连接,然后构造SQL语句,通过 PreparedStatement 设置参数,最后执行SQL。

//通过这个类封装对 博客表的基本操作
//此处暂时不涉及到修改博客~~ (修改博客也可以通过 删除/新增 )
public class BlogDao {//1.新增一个博客public void add(Blog blog) {Connection connection = null;PreparedStatement statement = null;try {//1. 数据库建立连接connection = DBUtil.getConnection();//2. 构造 SQLString sql = "insert into blog values(null,?,?,?,?)";statement = connection.prepareStatement(sql);statement.setString(1, blog.getTitle());statement.setString(2,blog.getContent());statement.setTimestamp(3,blog.getPostTimestamp());statement.setInt(4,blog.getUserId(  ));//执行 sqlstatement.executeUpdate();} catch (SQLException e) {e.printStackTrace();} finally {DBUtil.close(connection,statement,null);}}//2.根据博客 id 来指定查询博客(博客详情页中)public Blog selectById(int blogId) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {//1.和数据库建立连接connection = DBUtil.getConnection();//2.构造 sql 语句String sql = "select * from blog where blogId = ?";//prepareStatement(sql)这个方法见sql语句发送给数据库进行预编译//返回了一个PreparedStatement对象用于执行预编译的SQL语句statement = connection.prepareStatement(sql);statement.setInt(1,blogId);//3.执行SQL语句resultSet = statement.executeQuery();//4.遍历结果集合, 由于 blogID 在 blog 表中是唯一的(它是主键)// 此时的查询结果, 要么是没有查到任何数据, 要么只有一条记录!!//此处可以不使用while,直接使用if即可if (resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));blog.setContent(resultSet.getString("content"));blog.setPostTime(resultSet.getTimestamp("postTime"));blog.setUserId(resultSet.getInt("userId"));return blog;}} catch (SQLException e) {e.printStackTrace();} finally {//5.释放必要的资源DBUtil.close(connection,statement,resultSet);}return null;}//3.直接查出数据库中所有的博客列表(用于博客列表页)public List<Blog> selectAll() {List<Blog> blogs = new ArrayList<>();//连接数据库的对象Connection connection = null;//执行预编译SQL语句的对象PreparedStatement statement = null;//遍历结果结合的对象ResultSet resultSet = null;try {//1.和数据库建立连接connection = DBUtil.getConnection();//2.构造SQL语句  在SQL中加入order by 让 postTime 按降序排序String sql = "select * from blog order by postTime desc";//获取执行预编译SQL语句的对象statement = connection.prepareStatement(sql);//3. 执行SQL语句resultSet = statement.executeQuery();//4.遍历结果结合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.setPostTime(resultSet.getTimestamp("postTime"));blog.setUserId(resultSet.getInt("userId"));blogs.add(blog);}} catch (SQLException e) {e.printStackTrace();} finally {//5.释放必要的资源DBUtil.close(connection,statement,resultSet);}return blogs;}//4.删除指定博客public void delete(int blogId) {Connection connection = null;PreparedStatement statement = null;try {//1.和数据库建立连接connection = DBUtil.getConnection();//2.构造SQL语句String sql = "delete from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);//3.执行SQL语句statement.executeUpdate();} catch (SQLException e) {e.printStackTrace();} finally {//4.释放必要的资源DBUtil.close(connection, statement, null);}}
}

5.UserDao 类 (访问用户数据的对象)

这也是一个数据访问对象, 执行与用户相关的数据库操作, 这个类有两个方法:

  • selectById(int userId):这个方法是根据用户id查询用户。流程和上面的方法类似,只是查询的是用户信息。

  • selectByUsername(String username):这个方法是根据用户名查询用户。这个方法主要用于用户登录的时候验证用户身份。

//针对用户表提供的基本操作
//由于此处没有写注册的功能, 也就不必 add
//也没有用户删号功能, 也就不必 delete
public class UserDao {//1.根据 userId 来查询用户信息public User selectById(int userId) {//获取连接数据库的对象Connection connection = null;//获取执行预编译SQL语句的对象PreparedStatement statement = null;//获取遍历结果集合的对象ResultSet resultSet = null;try {//1.和数据库建立连接connection = DBUtil.getConnection();//2.构造SQL语句String sql = "select * from user where userId = ?";statement = connection.prepareStatement(sql);statement.setInt(1,userId);//3.执行SQL语句resultSet = statement.executeQuery();//4.遍历结果结果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 {//5.释放必要的资源DBUtil.close(connection,statement,resultSet);}return null;}//2.    根据username来查询用户信息(登录的时候)public User selectByUsername(String username) {//获取连接数据库的对象Connection connection = null;//获取执行预编译SQL语句的对象PreparedStatement statement = null;//获取遍历结果集合的对象ResultSet resultSet = null;try {//1.和数据库建立连接connection = DBUtil.getConnection();//2.构造SQL语句String sql = "select * from user where username = ?";statement = connection.prepareStatement(sql);statement.setString(1,username);//3.执行SQL语句resultSet = statement.executeQuery();//4.遍历结果结果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 {//5.释放必要的资源DBUtil.close(connection,statement,resultSet);}return null;}}

这些类的注意事项:

  1. User 类和 Blog 类中,你应该确保每个属性都有相应的 getter 和 setter 方法。这些方法的实现通常是直接返回或者设置对应的属性。

  2. BlogDao 类和 UserDao 类中,你应该确保每个数据库操作都在 try-catch 块中进行,以便在出现异常时能够捕获到并进行处理。此外,不要忘记在使用完 PreparedStatementResultSet 后关闭它们,否则可能会导致资源泄漏。

  3. DBUtil 类中,你应该确保在获取连接时检查连接是否为 null,如果为 null,则说明连接获取失败,应该进行相应的处理。在关闭资源时,也应该检查资源是否为 null,如果不为 null,则需要关闭它。

五. 博客系统的后端接口

  1. AuthorServlet:根据博客ID获取博客作者的信息。

  2. BlogServlet:处理博客相关的请求,包括获取博客列表和获取指定博客的详细信息,以及发布新的博客。

  3. LogoutServlet:处理用户注销的请求,将用户从当前会话中移除。

  4. LoginServlet:处理用户登录的请求,验证用户名和密码,并在验证通过后创建会话以保存用户信息。

这些类通过使用不同的 URL 映射到相应的 Servlet,并根据请求的类型(GET 或 POST)执行相应的操作。它们与模型(UserBlog)和数据访问对象(UserDaoBlogDao)一起工作,从数据库中读取和写入数据。

这个博客系统的 API 提供了用户登录、注销、发布博客、获取博客列表和获取博客作者等基本功能,可以作为一个简单的博客系统的后端接口。

1.AuthorServlet 类 (处理关于作者的请求)

这个类是一个服务器端Servlet,其功能是处理关于作者信息的请求。具体来说,它从HTTP请求中获取blogId参数,然后基于这个blogId,首先从BlogDao中获取对应的Blog对象,再通过该Blog对象中的userId字段从UserDao中获取对应的User对象(即博客作者)。如果获取的信息合法,它会将User对象以JSON格式返回给客户端。

实现细节与步骤:

  • doGet 方法中,首先通过 HttpServletRequest 对象的 getParameter 方法获取请求参数 "blogId"。
  • 如果 "blogId" 参数不存在或者无效,那么直接返回错误信息,结束处理。
  • 如果 "blogId" 参数有效,那么通过 BlogDaoselectById 方法获取对应的 Blog 对象,再通过 Blog 对象的 getUserId 方法获取对应的用户ID,然后通过 UserDaoselectById 方法获取对应的 User 对象。
  • 如果这个 User 对象存在,那么将其转换为JSON格式,并写入到响应的正文中。
@WebServlet("/author")
public class AuthorServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String blogId = req.getParameter("blogId");if(blogId == null) {resp.setContentType("text/html;charset=utf8");resp.getWriter().write("参数非法, 缺少 blogId");return;}//根据 blogId 查询 Blog 对象BlogDao blogDao = new BlogDao();Blog blog = blogDao.selectById(Integer.parseInt(blogId));if(blog == null) {//博客不存在resp.setContentType("text/html;charset=utf8");resp.getWriter().write("没有找到指定博客: blogId = " + blogId);return;}//根据 blog 中的 userId 找到对应的用户信息UserDao userDao = new UserDao();User author = userDao.selectById(blog.getUserId());//设置响应正文的格式为 json格式的字符串resp.setContentType("application/json; charset=utf8");//将 author对象 转换成 json 格式的字符串写入到响应的 body 中objectMapper.writeValue(resp.getWriter(),author);}
}

2.BlogServlet 类 (处理关于的博客请求)

这个类是另一个服务器端Servlet,其功能主要是处理关于博客的请求,包括获取博客列表和发布新博客两部分功能。具体来说,它在处理GET请求时,会根据请求参数 "blogId" 来决定是返回所有博客的列表还是返回特定ID的博客;而在处理POST请求时,会从请求中读取 "title" 和 "content" 参数,然后创建一个新的Blog对象并将其添加到数据库中。

实现细节与步骤:

  • doGet 方法中,同样是首先尝试获取请求参数 "blogId"。如果 "blogId" 参数不存在,那么返回所有博客的列表;如果 "blogId" 参数存在,那么返回对应ID的博客。
  • doPost 方法中,首先检查用户是否已经登录,然后从请求中读取 "title" 和 "content" 参数,并根据这些信息创建一个新的 Blog 对象,然后将这个 Blog 对象添加到数据库中。
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 尝试获取一下 queryString 中的 blogId 字段.String blogId =req.getParameter("blogId");BlogDao blogDao = new BlogDao();if(blogId == null) {//queryString 不存在,说明这次请求是获取博客列表页List<Blog> blogs = blogDao.selectAll();//设置body(响应正文)的格式resp.setContentType("application/json; charset=utf8");//直接把 blogs 转换成符合要求的 json 格式字符串,同时写入http响应的body中 写法一objectMapper.writeValue(resp.getWriter(),blogs);///*//将blog先转换成json字符串  写法二String respJson = objectMapper.writeValueAsString(blogs);//然后写入http响应resp.getWriter().write(respJson);*/}else {//queryString存在,说明本次请求获取的是指定 id 的博客Blog blog = blogDao.selectById(Integer.parseInt(blogId));if(blog == null) {System.out.println("当前blogId= " + blogId + "对应的博客不存在!");}//设置body(响应正文)的格式resp.setContentType("application/json; charset=utf8");//直接把 blog 转换成符合要求的 json 格式字符串,同时写入http响应的body中objectMapper.writeValue(resp.getWriter(),blog);}}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//发布博客//读取请求,构造 Blog 对象,插入数据库即可!!HttpSession httpSession = req.getSession(false);if(httpSession == null) {resp.setContentType("text/http;charset=utf8");resp.getWriter().write("当前未登录, 无法发布博客");return;}User user = (User) httpSession.getAttribute("user");if(user == null) {resp.setContentType("text/http;charset=utf8");resp.getWriter().write("当前未登录, 无法发布博客!");return;}//确保登录之后, 就可以把作者拿到了//获取博客标题和正文req.setCharacterEncoding("utf8");String title = req.getParameter("title");String content = req.getParameter("content");if(title == null || "".equals(title) || content == null || "".equals(content)) {resp.setContentType("text/http;charset=utf8");resp.getWriter().write("当前提交数据有误! 标题或者正文为空!");return;}//构造 Blog 对象Blog blog = new Blog();blog.setTitle(title);blog.setContent(content);blog.setUserId(user.getUserId());//发布时间,在java中生成/在数据库中生成都行blog.setPostTime(new Timestamp(System.currentTimeMillis()));//插入数据库BlogDao blogDao = new BlogDao();blogDao.add(blog);//跳转到博客博客列表页resp.sendRedirect("html/blog_list.html");}
}

3.LogoutServlet 类 (处理注销的请求)

这个类的功能很简单,就是处理用户的登出请求。在处理GET请求时,它会从当前的会话中移除 "user" 属性,然后重定向到登录页面。

实现细节与步骤:

  • doGet 方法中,首先尝试获取当前的会话,然后从会话中移除 "user" 属性,最后通过 HttpServletResponse 对象的 sendRedirect 方法重定向到登录页面。
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HttpSession httpSession = req.getSession(false);if(httpSession == null) {//未登录状态, 就直接提示出错.resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前为登录!");return;}httpSession.removeAttribute("user");resp.sendRedirect("html/login.html");}
}

4. LoginServlet 类 (处理登录的请求)

这个类主要负责处理用户登录的请求。在处理POST请求时,会从请求中获取 "username" 和 "password" 参数,并通过 UserDaoselectByUsername 方法获取对应的 User 对象,然后比较用户输入的密码和数据库中存储的密码是否一致,如果一致,说明登录验证通过,会在当前会话中设置 "user" 属性为对应的 User 对象,最后重定向到博客列表页。此外,这个类还提供了一个处理GET请求的方法,用于获取当前已登录的用户信息。

实现细节与步骤:

  • doPost 方法中,首先从请求中获取 "username" 和 "password" 参数,然后通过 UserDaoselectByUsername 方法获取对应的 User 对象。
  • 然后比较用户输入的密码和数据库中存储的密码是否一致。如果一致,那么在当前会话中设置 "user" 属性为对应的 User 对象,然后通过 HttpServletResponse 对象的 sendRedirect 方法重定向到博客列表页。
  • 如果用户名不存在或密码不一致,那么返回错误信息,结束处理。
  • doGet 方法中,首先尝试获取当前的会话,然后从会话中获取 "user" 属性。如果这个 User 对象存在,那么将其转换为JSON格式,并写入到响应的正文中。
@WebServlet("/login")
public class LonginServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//设置请求的编码格式, 告诉 servlet 按照啥格式来理解请求req.setCharacterEncoding("utf8");//设置响应的编码格式, 告诉 servlet 按照啥格式来理解响应//resp.setCharacterEncoding("utf8");resp.setContentType("text/html;charset=utf8");//1.读取参数中的用户名和密码//注意!! 如果用户名和密码包括中文,此处可能会乱码String username = req.getParameter("username");String password = req.getParameter("password");if(username == null || "".equals(username) || password == null || "".equals(password)) {//登录失败String html = "<h3> 登录失败! 缺少username 或者 password 字段 </h3>";resp.getWriter().write(html);return;}//2.读数据库,看看用户名是否存在,并且密码是否匹配UserDao userDao = new UserDao();User user = userDao.selectByUsername(username);if(user == null) {//用户不存在String html = "<h3> 登录失败! 用户名或者密码错误 </h3>";resp.getWriter().write(html);return;}if(!password.equals(user.getPassword())) {//密码不对String html = "<h3> 登录失败! 用户名或者密码错误 </h3>";resp.getWriter().write(html);return;}//3.用户名和密码验证成功, 登录成功,接下来就会创建会话, 使用该会话保存用户的信息HttpSession session = req.getSession();session.setAttribute("user",user);//4.进行重定向,跳转到博客列表页resp.sendRedirect("html/blog_list.html");}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json;charset=utf8");//使用这个方法获取到用户的登录状态//如果未登录,这里的会话就拿不到!!HttpSession session = req.getSession(false);if(session == null) {//未登录 , 返回一个空user对象User user = new User();//将user对象转换成json格式的字符串写入响应的body中objectMapper.writeValue(resp.getWriter(),user);return;}User user = (User)session.getAttribute("user");if(user == null) {user = new User();//将user对象转换成json格式的字符串写入响应的body中objectMapper.writeValue(resp.getWriter(),user);return;}//确实成功取出了对象,直接返回即可//将user对象转换成json格式的字符串写入响应的body中objectMapper.writeValue(resp.getWriter(),user);}
}

六. 前端代码构造请求

1. 登陆页面功能设计 

  1. 表单(Form):整个登录页面被包含在一个HTML表单中。表单被设计用于收集用户输入的数据,这里的数据就是用户名和密码。表单被发送到../login这个路径,使用的是POST方法,意味着提交的数据将在请求体中发送。

  2. 用户名和密码输入框:这两个输入框分别用于接收用户的用户名和密码信息。用户名输入框的占位符提示用户可以输入手机号或邮箱作为用户名。密码输入框类型为password,所以输入的密码信息不会被显示出来,以保护用户的安全。

  3. 提交按钮:用户在输入完用户名和密码后,可以点击这个按钮提交表单。提交后,用户名和密码信息将被发送到服务器进行验证。

<!-- 登录页面的版心 --><div class="login-container"><!-- 使用from包裹下列内容,便于后续给服务器提交数据 --><form action="../login" method="post"><!-- 登录对话框 --><div class="login-dialog"><h3>登录</h3><div class="row"><span>用户名</span><input type="text" id="username" placeholder="手机号/邮箱" 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></form></div>

2. 博客列表页面功能设计

  • 获取并展示博客列表:通过调用getBlogs函数,在页面加载时向服务器发起GET请求获取博客列表数据。这个请求的响应体被期望为一个包含多个博客信息的JSON数组。每一个博客对象包含了博客的标题、发布时间和内容等信息。之后,代码通过DOM操作将这些博客信息显示在页面上。每一篇博客在页面上以一个div元素的形式存在,包括标题、发布时间、内容摘要和一个查看全文的链接。点击查看全文的链接会跳转到博客详情页,并且通过查询字符串参数把博客的ID传递给详情页。

详细步骤如下:

  1. 请求博客列表:页面加载时,getBlogs函数被调用。在这个函数中,jQuery的$.ajax方法用于向服务器发送一个GET请求,目标URL为'../blog'。

  2. 处理响应:服务器将以一个包含多个博客对象的JSON数组形式返回博客列表。这个JSON数组已经被jQuery自动解析成JavaScript的对象数组。

  3. 遍历博客对象:每一个博客对象都包含了博客的标题(title)、发布时间(postTime)、内容(content)等信息。getBlogs函数中的for循环对数组进行遍历,每次遍历都会处理一个博客对象。

  4. 构建博客元素:每个博客对象都会被转化为HTML元素以显示在页面上。为此,getBlogs函数创建了包含标题、发布时间、内容摘要和查看全文链接的div元素。其中,查看全文的链接会带有一个查询字符串参数,这个参数包含了当前博客的ID,可以用于在详情页识别出具体的博客。

  5. 添加到页面:构建完的博客div元素会被添加到页面的'.container-right'元素中,以在页面上显示出博客的信息。

  • 检查登录状态checkLogin函数通过向服务器发起GET请求到../login,检查用户的登录状态。如果响应体中包含有用户ID,并且用户ID大于0,那么就认为用户已经登录,然后在页面上显示出用户的用户名。如果没有用户ID,或者用户ID不大于0,那么就认为用户未登录,代码会强制跳转到登录页面。

整个过程是异步的,即浏览器不会等待博客列表的请求和处理过程完成就继续执行其他的JavaScript代码。相反,当请求的响应到达时,jQuery会自动调用预定义的成功回调函数来处理响应数据。

<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script><script>// 在页面加载时, 向服务器发起请求, 获取博客列表数据function getBlogs() {$.ajax({type: 'get',url: '../blog',success: function(body) {// 响应的正文 是一个 json 字符串, 此处已经被 jquery 自动解析成 js 对象数组了. // 直接 for 循环遍历即可.console.log(body); let containerRight = document.querySelector('.container-right');for (let blog of body) {// 构造页面内容, 参考之前写好的 html 代码// 构造整个博客 divlet blogDiv = document.createElement('div');blogDiv.className = 'blog';// 构造标题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 = '查看全文 &gt;&gt;';// 期望点击之后能跳转到博客详情页. 为了让博客详情页知道是点了哪个博客, 把 blogId 给传过去a.href = 'blog_detail.html?blogId=' + blog.blogId;blogDiv.appendChild(a);// 把 blogDiv 加到父元素中containerRight.appendChild(blogDiv);}}});}// 要记得调用getBlogs();function checkLogin() {$.ajax({type: 'get',url: '../login',success: function(body) {if(body.userId && body.userId >0) {//登录成功console.log("当前用户已经登录!");//加上这个功能,把当前用户的名字显示到界面上let h3 = document.querySelector('.container-left .card h3');  h3.innerHTML = body.username;}else {//当前未登录//强制跳转到登录页location.assign('login.html');}}});}checkLogin();</script>

3. 博客详情页面功能设计 

  • 获取博客详情:在页面加载后,执行一个ajax请求(即异步HTTP请求),以获取博客的具体信息。此请求使用GET方法,URL是 '../blog' + location.search。这里的 location.search 包含当前URL的查询部分,用于指示服务器我们想要获取哪篇博客的信息。一旦请求成功,服务器将返回一个包含博客信息的JSON对象。

  • 处理和展示博客详情:在ajax请求成功后,会触发success回调函数。在这个函数中,我们获取JSON对象中的博客信息,然后把这些信息插入到页面对应的元素中。标题和日期信息直接添加到对应的div元素中。而博客的内容,因为通常是Markdown格式,需要先转换成HTML。这个转换过程由editormd库提供的 markdownToHTML 函数完成。

  • 检查用户登录状态checkLogin 函数通过发送一个GET请求到 '../login' URL,来获取当前登录用户的信息。如果请求成功,success回调函数会被调用。在这个函数中,我们检查服务器返回的JSON对象中的userId属性。如果这个属性存在且大于0,就认为用户已经登录;否则,我们认为用户尚未登录,然后将页面重定向到登录页。

  • 获取作者信息getAuthor 函数通过向 '../author' + location.search URL发送GET请求,来获取博客作者的信息。如果请求成功,success回调函数会被调用。在这个函数中,我们从服务器返回的JSON对象中获取作者的用户名,然后将这个用户名显示到页面上。

这些函数在页面加载后会自动调用,从而实现上述功能。在调用这些函数之前,需要先加载所依赖的库(即jQuery和editormd)。这是通过 <script src="..."></script> 标签完成的。在这些标签中,src属性指向库文件的URL。

<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script><!-- 要保证这几个 js 的加载在jquery之后,因为editor.md 依赖了 jquery --><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><script>$.ajax({type: 'get',url: '../blog' + location.search,success: function(body) {//处理响应结果,此处的body就是表示一个博客的js对象//1.更新标题let titleDiv = document.querySelector(".container-right .title");titleDiv.innerHTML = body.title;     //2.更新日期let dateDiv = document.querySelector('.date');dateDiv.innerHTML = body.postTime; //3.更新博客正文//此处不应该把博客正文添加到这个标签里面//editormd里面有一个api,能将Markdown格式的字符串转换成HLML,输出到#conteneditormd.markdownToHTML('content',{ markdown: body.content });}});function checkLogin() {$.ajax({type: 'get',url: '../login',success: function(body) {if(body.userId && body.userId >0) {//登录成功console.log("当前用户已经登录!");}else {//当前未登录//强制跳转到登录页location.assign('login.html');}}});}checkLogin();//这是函数定义function getAuthor() {$.ajax({type: 'get',url: '../author' + location.search,success: function(body) {//把 username 设置到界面上let h3  = document.querySelector('.container-left .card h3');h3.innerHTML = body.username;}});}//这里函数调用该函数才会执行getAuthor();</script>

4. 博客编辑页面功能设计

  1. HTML表单设计:这部分的代码定义了用户用于输入和提交博客内容的表单。表单内包含一个用于输入标题的文本框以及一个用于提交文章的按钮。除此之外,还有一个占位的<textarea>元素,这个元素被设置为不显示(style="display: none;"),是为了与Markdown编辑器对接,让编辑器的内容可以被提交到服务器。

  2. Markdown编辑器初始化:通过调用editormd()函数,对Markdown编辑器进行初始化。编辑器的尺寸设置为全宽(width: "100%")和高度为父元素高度减去50像素(height: "calc(100% - 50px)"),50像素是预留给标题的空间。编辑器的初始内容被设置为"# 在这里写下一篇博客",这是一个Markdown格式的标题。editormd()函数中的path参数设置了Markdown编辑器库依赖插件的路径。

  3. 用户登录状态的检查:在checkLogin()函数中,使用jQuery的$.ajax()方法向服务器发送一个GET请求,目标URL为'../login'。如果请求成功,会获取到服务器返回的JSON对象,检查其中的userId属性。如果userId存在且大于0,说明用户已经登录,将在控制台打印"当前用户已经登录!"。如果userId不存在或者不大于0,说明用户未登录,那么会强制将页面重定向到登录页面。

通过这三个步骤,实现了博客编辑页的基本功能设计:提供了一个用户友好的Markdown编辑器界面供用户编写博客,同时确保只有登录的用户才能访问编辑页面和发布博客。

<!-- 博客编辑页的版心 --><div class="blog-edit-container"><!--  --><form action="../blog" method="post"><!-- 标题编辑区 --><div class="title"><input type="text" placeholder="在此处输入标题" name="title"><input type="submit" id="submit" value="发布文章"></div><!-- 博客编辑器 --><!-- 把md编辑器放到这个div中 --><!-- 博客编辑器, 这里用 id 是为了和 markdown 编辑器对接 --><div id="editor"><textarea name="content" style="display: none;"></textarea></div></form></div><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><script>// 初始化编辑器let editor = editormd("editor", {// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. width: "100%",// 设定编辑器高度height: "calc(100% - 50px)", /* 减 titile 的高度 */// 编辑器中的初始内容markdown: "# 在这里写下一篇博客",// 指定 editor.md 依赖的插件路径path: "../editor.md/lib/"});function checkLogin() {$.ajax({type: 'get',url: '../login',success: function(body) {if(body.userId && body.userId >0) {//登录成功console.log("当前用户已经登录!");}else {//当前未登录//强制跳转到登录页location.assign('login.html');}}});}checkLogin();</script>

5. 用户注销功能设计

  • 通过a标跳转去构造请求,后端接口接收到请求去帮我们完成注销的逻辑
  1. 获取到session
  2. 如果session不为空,将session中保存的user删除
  3. 删除后跳转到用户登陆页面 

 后端收到请求开始执行注销操作:

七. 博客系统设计源码

在做前后端逻辑处理的时候,前端代码有些稍微的改动,本文没有提及到,请点击查看源码,查看改动的细节以及所有后端的设计实现:博客系统前后端设计源码

后续还有很多扩展功能补充,大家敬请期待,我们下期再见 ! ! !

相关文章:

【基于前后端分离的博客系统】Servlet版本

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一. 项目简介 1. 项目背景 2. 项目用到的技…...

在线Excel绝配:SpreadJS 16.1.1+GcExcel 6.1.1 Crack

前端&#xff1a;SpreadJS 16.1.1 后端&#xff1a; GcExcel 6.1.1 全能 SpreadJS 16.1.1此版本的产品中包含以下功能和增强功能。 添加了各种输入掩码样式选项。 添加了在保护工作表时设置密码以及在取消保护时验证密码的支持。 增强了组合图以将其显示为仪表图。 添加了…...

一个轻量的登录鉴权工具Sa-Token 集成SpringBoot简要步骤

Sa-Token 集成SpringBoot简要步骤 1.1 简单介绍 Sa-Token是一个轻量级Java权限认证框架。 主要解决的问题如下&#xff1a; 登录认证 权限认证 单点登录 OAuth2.0 分布式Session会话 微服务网关鉴权等一系列权限相关问题。 1.2 登录认证 设计思路 对于一些登录之后…...

day 44 完全背包:518. 零钱兑换 II;377. 组合总和 Ⅳ

完全背包&#xff1a;物品可以使用多次 完全背包1. 与01背包区别 518. 零钱兑换 II1. dp数组以及下标名义2. 递归公式3. dp数组如何初始化4. 遍历顺序:不能颠倒两个for循环顺序5. 代码 377. 组合总和 Ⅳ:与零钱兑换类似&#xff0c;但是是求组合数1. dp数组以及下标名义2. 递归…...

K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods

K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods 你已了解Pod以及如何通过ReplicaSets等资源部署它们以确保持续运行。虽然某些Pod可以独立完成工作&#xff0c;但现今许多应用程序需要响应外部请求。例如&#xff0c;在微服务的情况…...

牛客网DAY2(编程题)

圣诞节来啦&#xff01;请用CSS给你的朋友们制作一颗圣诞树吧~这颗圣诞树描述起来是这样的&#xff1a; 1. "topbranch"是圣诞树的上枝叶&#xff0c;该上枝叶仅通过边框属性、左浮动、左外边距即可实现。边框的属性依次是&#xff1a;宽度为100px、是直线、颜色为gr…...

Java经典笔试题—day14

Java经典笔试题—day14 &#x1f50e;选择题&#x1f50e;编程题&#x1f36d;计算日期到天数转换&#x1f36d;幸运的袋子 &#x1f50e;结尾 &#x1f50e;选择题 (1)定义学生、教师和课程的关系模式 S (S#,Sn,Sd,Dc,SA &#xff09;&#xff08;其属性分别为学号、姓名、所…...

一个帮助写autoprefixer配置的网站

前端需要用到postcss的工具&#xff0c;用到一个插件叫autoprefixer&#xff0c;这个插件能够给css属性加上前缀&#xff0c;进行一些兼容的工作。 如何安装之类的问题在csdn上搜一下都能找到&#xff08;注意&#xff0c;vite是包含postcss的&#xff0c;不用在项目中安装pos…...

C语言中的类型转换

C语言中的类型转换 隐式类型转换 整型提升 概念&#xff1a; C语言的整型算术运算总是至少以缺省&#xff08;默认&#xff09;整型类型的精度来进行的为了获得这个精度&#xff0c;表达式中字符和短整型操作数在使用之前被转换为普通整型&#xff0c;这种转换成为整型提升 如…...

String底层详解(包括字符串常量池)

String a “abc”; &#xff0c;说一下这个过程会创建什么&#xff0c;放在哪里&#xff1f; JVM会使用常量池来管理字符串直接量。在执行这句话时&#xff0c;JVM会先检查常量池中是否已经存有"abc"&#xff0c;若没有则将"abc"存入常量池&#xff0c;否…...

C++ 里面lambda和函数指针的转换

问题说明 原始问题&#xff0c;代码如下会编译报错&#xff1a; using DecisionFn bool(*)();class Decide { public:Decide(DecisionFn dec) : _dec{dec} {} private:DecisionFn _dec; };int main() {int x 5;Decide greaterThanThree{ [x](){ return x > 3; } };retur…...

前端Rust开发WebAssembly与Swc插件快速入门

前言 现代前端对速度的追求已经进入二进制工具时代&#xff0c;Rust 开发成为每个人的必修课。 一般我们将常见的前端 Rust 开发分为以下几类&#xff0c;难度由上至下递增&#xff1a; 开发 wasm 。 开发 swc 插件。 开发代码处理工具。 我们将默认读者具备最简单的 Rus…...

【C++ 学习 ⑧】- STL 简介

目录 一、什么是 STL&#xff1f; 二、STL 的版本 三、STL 的 6 大组件和 13 个头文件 四、学习 STL 的 3 个境界 五、STL 的缺陷 参考资料&#xff1a; STL教程&#xff1a;C STL快速入门&#xff08;非常详细&#xff09; (biancheng.net)。 C STL是什么&#xff0c;有…...

论文笔记--Deep contextualized word representations

论文笔记--Deep contextualized word representations 1. 文章简介2. 文章概括3 文章重点技术3.1 BiLM(Bidirectional Language Model)3.2 ELMo3.3 将ELMo用于NLP监督任务 4. 文章亮点5. 原文传送门 1. 文章简介 标题&#xff1a;Deep contextualized word representations作者…...

【MySQL高级篇笔记-性能分析工具的使用 (中) 】

此笔记为尚硅谷MySQL高级篇部分内容 目录 一、数据库服务器的优化步骤 二、查看系统性能参数 三、统计SQL的查询成本&#xff1a;last_query_cost 四、定位执行慢的 SQL&#xff1a;慢查询日志 1、开启慢查询日志参数 2、查看慢查询数目 3、慢查询日志分析工具&#xf…...

大学生数学建模题论文

大学生数学建模题论文篇1 浅论高中数学建模与教学设想 论文关键词&#xff1a;数学建模 数学 应用意识 数学建模教学 论文摘要&#xff1a;为增强学生应用数学的意识&#xff0c;切实培养学生解决实际问题的能力&#xff0c;分析了高中数学建模的必要性&#xff0c;并通过对高中…...

论文阅读 —— 滤波激光SLAM

文章目录 FAST-LIO2FAST-LIOIMUR2LIVER3LIVEEKFLINS退化摘要第一句 FAST-LIO2 摘要&#xff1a; 本文介绍了FAST-LIO2&#xff1a;一种快速、稳健、通用的激光雷达惯性里程计框架。 FAST-LIO2建立在高效紧耦合迭代卡尔曼滤波器的基础上&#xff0c;有两个关键的新颖之处&#…...

JavaScript键盘事件

目录 一、keydown&#xff1a;按下键盘上的任意键时触发。 二、keyup&#xff1a;释放键盘上的任意键时触发。 三、keypress&#xff1a;在按下并释放能够产生字符的键时触发&#xff08;不包括功能键等&#xff09;。 四、input&#xff1a;在文本输入框或可编辑元素的内容…...

opengl灯光基础:2.1 光照基础知识

光照&#xff1a; 光照以不同的方式影响着我们看到的世界&#xff0c;有时甚至是以很戏剧化的方式。当手电筒照射在物体上时&#xff0c;我们希望物体朝向光线的一侧看起来更亮。我们所居住的地球上的点&#xff0c;在中午朝向太阳时候被照得很亮&#xff0c;但随着地球的自转…...

大屏时代:引领信息可视化的新潮流

在信息时代的浪潮下&#xff0c;数据已经成为推动各行各业发展的重要动力。然而&#xff0c;海量的数据如何快速、直观地呈现给用户&#xff0c;成为了一个亟待解决的难题。在这样的背景下&#xff0c;可视化大屏应运而生&#xff0c;以其出色的表现力和交互性成为信息展示的佼…...

ChatGTP全景图 | 背景+技术篇

引言&#xff1a;人类以为的丰功伟绩&#xff0c;不过是开端的开端……我们在未来100年取得的技术进步&#xff0c;将远超我们从控制火种到发明车轮以来所取得的一切成就。——By Sam Altman 说明&#xff1a;ChatGPT发布后&#xff0c;我第一时间体验了它的对话、翻译、编程、…...

计算机专业学习的核心是什么?

既然是学习CS&#xff0c;那么在这里&#xff0c;我粗浅的把计算机编程领域的知识分为三个部分&#xff1a; 基础知识 特定领域知识 框架和开发技能 基础知识是指不管从事任何方向的软件工程师都应该掌握的&#xff0c;比如数据结构、算法、操作系统。 特定领域知识就是你…...

基于springboot地方旅游系统的设计与实现

摘 要 本次设计内容是基于Springboot的旅游系统的设计与实现&#xff0c;采用B/S三层架构分别是Web表现层、Service业务层、Dao数据访问层&#xff0c;并使用Springboot&#xff0c;MyBatis二大框架整合开发服务器端&#xff0c;前端使用vue&#xff0c;elementUI技术&…...

一些学习资料链接

组件化和CocoaPods iOS 组件化的三种方案_迷曳的博客-CSDN博客 CocoaPods 私有化 iOS组件化----Pod私有库创建及使用 - 简书 CocoaPods1.9.1和1.8 使用 出现CDN: trunk URL couldnt be downloaded: - 简书 cocoapod制作私有库repo - 简书 【ios开发】 上传更新本地项目到…...

Webpack打包图片-JS-Vue

1 Webpack打包图片 2 Webpack打包JS代码 3 Babel和babel-loader 5 resolve模块解析 4 Webpack打包Vue webpack5打包 的过程&#xff1a; 在webpack的配置文件里面编写rules&#xff0c;type类型有多种&#xff0c;每个都有自己的作用&#xff0c;想要把小内存的图片转成bas…...

进程控制(Linux)

进程控制 fork 在Linux中&#xff0c;fork函数是非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 返回值&#xff1a; 在子进程中返回0&#xff0c;父进程中返回子进程的PID&#xff0c;子进程创建失败返回-1。 …...

C Primer Plus第十四章编程练习答案

学完C语言之后&#xff0c;我就去阅读《C Primer Plus》这本经典的C语言书籍&#xff0c;对每一章的编程练习题都做了相关的解答&#xff0c;仅仅代表着我个人的解答思路&#xff0c;如有错误&#xff0c;请各位大佬帮忙点出&#xff01; 由于使用的是命令行参数常用于linux系…...

又名管道和无名管道

一、进程间通信&#xff08;IPC&#xff0c;InterProcess Communication&#xff09; 概念&#xff1a;就是进程和进程之间交换信息。 常用通信方式 无名管道&#xff08;pipe&#xff09; 有名管道 &#xff08;fifo&#xff09; 信号&#xff08;signal&#xff09; 共…...

操作系统复习4.1.0-文件管理结构

定义 一组有意义的信息的集合 属性 文件名、标识符、类型、位置、大小、创建时间、上次修改时间、文件所有者信息、保护信息 操作系统向上提供的功能 创建文件、删除文件、读文件、写文件、打开文件、关闭文件 这6个都是系统调用 创建文件 创建文件时调用Create系统调用…...

【嵌入式烧录/刷写文件】-2.6-剪切/保留Intel Hex文件中指定地址范围内的数据

案例背景&#xff1a; 有如下一段HEX文件&#xff0c;保留地址范围0x9140-0x91BF内的数据&#xff0c;删除地址范围0x9140-0x91BF外的数据。 :2091000058595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576775F :2091200078797A7B7C7D7E7F808182838485868788898A…...