【Java项目】完善基于Java+MySQL+Tomcat+maven+Servlet的博客系统
目录
- 一、准备工作
- 二、引入依赖
- 三、创建必要的目录
- 四、编写代码
- 五/六、打包部署(直接基于 smart tomcat)
- 七、验证代码
- 正式编写服务器代码
- 编写数据库相关的操作代码
- 创建数据库/表结构(数据库设计)
- 数据库代码
- 封装数据库操作
- 封装针对数据的增删改查!
- 博客列表页
- 约定前后端接口
- 编写服务器代码
- 编写客户端代码
- 问题一:
- 问题二:刷新页面的时候,发现一哆嗦~
- 博客详情页
- 约定前后端交互接口
- 前端代码
- 登录页
- 约定前后端交互接口
- 后端逻辑
- 约定前后端交互接口
- 正确显示用户信息
- 针对博客列表页
- 针对博客详情页
- 实现"注销"功能
- 约定前后端交互接口
- 发布博客功能
- 约定前后端交互接口
- 删除博客
- 完整代码
一、准备工作
打开你的idea,创建一个Maven。
-
File->new->Project;
-
选择创建maven项目,然后点击next
-
取个名字
二、引入依赖
这里我们需要引入的依赖是servlet,jackson,mysql。
这个操作嘛!直接看JavaEE-实现一个服务器版本的“表白墙”
里面有引入依赖的相关操作~
pox.mal
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>blog_system</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.6.1</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency></dependencies>
</project>
三、创建必要的目录
点开我们项目下的src文件,找到main,然后右键创建一个新的目录,webapp;在webapp下创建一个目录叫做WEB-INF,然后在WEB-INF下面创建一个新的xml文件叫做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>
四、编写代码
找到java文件,在java文件下创建一个类叫做HelloServlet
写一个简单的实验代码
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class HelloServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("你好,博客系统!");}
}
五/六、打包部署(直接基于 smart tomcat)
这里打包部署在博主之前的博客已经介绍过,使用idea自带的插件smart tomcat来完成
七、验证代码
在浏览器验证程序。
正式编写服务器代码
找到我们以前的博客系统的前端代码,然后复制粘贴到当前的webapp目录下
就可以把前端页面给引入项目中。
C和M,先来实现Model层~~ 先来实现数据库相关的代码
编写数据库相关的操作代码
创建数据库/表结构(数据库设计)
设计数据库,需要根据当前的需求来进行设计~~
这里来介绍一下前面写的博客系统页面:
1.博客列表页:显示博客的列表;
2.博客详情页,点击博客列表页,上面列出的博客篇数,可以跳转到完整的博客内容;
3.登录页面;
4.博客编辑页,基于editor.md整了一个markdown编辑器,根据这个页面来发布博客
存储博客~当点击发布的时候,博客被发布到服务器上,就要被存起来
获取博客~当博客列表页和博客详情页,能够拿到博客的内容,还能够进行登录校验
设计表,就需要抓住需求中的实体(关键性的名词)
博客表,用来存储所有博客数据~
用户表,用户登录,就需要用到这个表~
数据库代码
-- 编写建库建表的 sqlcreate database if not exists java107_blog;use java107_blog;-- 创建一个博客表
drop table if exists blog;
create table blog (blogId int primary key auto_increment,title varchar(1024),content mediumtext,userId int, -- 文章作者的 idpostTime datetime -- 发布时间
);-- 创建一个用户表
drop table if exists user;
create table user (userId int primary key auto_increment,username varchar(128) unique, -- 后续会使用用户名来登录,一般用于登录的用户名都是不能重复的password varchar(128)
);insert into user values(null, 'zhangsan', '123');
insert into user values(null, 'lisi', '123');522215
编写完成之后,打开mysql客户端,然后复制上面的SQL语句,粘贴到我们的mysql客户端
检查数据库和表是否创建完成
封装数据库操作
先创建DBUtil封装数据库连接的操作
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;// 使用这个类和数据库建立连接
public class DBUtil {private static final String URL = "jdbc:mysql://127.0.0.1:3306/java107_blog?characterEncoding=utf8&&useSSL=false";private static final String USERNAME = "root";private static final String PASSWORD = "1234";private static volatile DataSource dataSource = null;private static DataSource getDataSource() {if (dataSource == null) {synchronized (DBUtil.class) {if (dataSource == null) {dataSource = new MysqlDataSource();((MysqlDataSource) dataSource).setURL(URL);((MysqlDataSource) dataSource).setUser(USERNAME);((MysqlDataSource) dataSource).setPassword(PASSWORD);}}}return dataSource;}public static Connection getConnection() throws SQLException {return getDataSource().getConnection();}public void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (statement != null) {try {statement.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (connection != null) {try {connection.close();} catch (SQLException e) {throw new RuntimeException(e);}}}}
创建实体类!
使用实体类表示数据库中的一条记录
此处主要创建了Blog类和User类
User类
// 每个 User 对象,对应 user 表里的一条记录
public class User {private int userId;private String username;private String password;public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}
Blog类
import java.sql.Timestamp;// 每个 Blog 对象,对应 blog 表里的一条记录
public class Blog {private int blogId;private String title;private String content;private int userId;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;}public Timestamp getPostTime() {return postTime;}public void setPostTime(Timestamp postTime) {this.postTime = postTime;}
}
封装针对数据的增删改查!
把提供了增删改查这样的类,称为DAO
BlogDao类
package Model;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;// 用于去封装博客表的基本操作
public class BlogDao {// 1. 往博客表里,插入一个博客.public void insert(Blog blog) {// JDBC 基本代码~Connection connection = null;PreparedStatement statement = null;try {// 1) 和数据库建立连接connection = DBUtil.getConnection();// 2) 构造 SQL 语句String sql = "insert into blog values(null, ?, ?, ?, now())";statement = connection.prepareStatement(sql);statement.setString(1, blog.getTitle());statement.setString(2, blog.getContent());statement.setInt(3, blog.getUserId());// 3) 执行 SQLstatement.executeUpdate();} catch (SQLException e) {throw new RuntimeException(e);} finally {// 4) 关闭连接,释放资源DBUtil.close(connection, statement, null);}}// 2. 能够获取到博客表中的所有博客表的信息(用于博客列表页),此处每篇博客不一定获取到完整的正文public List<Blog> selectAll() {List<Blog> blogs = new ArrayList<>();Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from blog";statement = connection.prepareStatement(sql);resultSet = statement.executeQuery();while (resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));blog.setContent(resultSet.getString("content"));blog.setUserId(resultSet.getShort("userId"));blog.setPostTime(resultSet.getTimestamp("postTime"));blogs.add(blog);}} catch (SQLException e) {throw new RuntimeException(e);} finally {DBUtil.close(connection, statement, resultSet);}return blogs;}// 3. 能够根据博客 id 获取到指定的博客内容(用于博客详情页)public Blog selectOne(int blogId) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);resultSet = statement.executeQuery();// 此处我们是使用 主键 来作为查询条件的,查询结果,要么是1,要么是0if (resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));blog.setContent(resultSet.getString("content"));blog.setUserId(resultSet.getShort("userId"));blog.setPostTime(resultSet.getTimestamp("postTime"));return blog;}} catch (SQLException e) {throw new RuntimeException(e);} finally {DBUtil.close(connection, statement, resultSet);}return null;}// 4. 从博客表中,根据博客id删除博客.public void delete(int blogId) {Connection connection = null;PreparedStatement statement = null;try {connection = DBUtil.getConnection();String sql = "delete from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);statement.executeUpdate();} catch (SQLException e) {throw new RuntimeException(e);} finally {DBUtil.close(connection, statement, null);}}// 注意,上述操作是增删查,没有改~}
UserDao类
package Model;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;// 提供了针对 用户表 的基本操作
public class UserDao {// 需要实现的操作// 针对这个类来说,就简化的写就行了,像注册/销号这样的功能就不考虑了。// 主要实现,// 1. 根据用户名来查找用户信息。// 会在登录逻辑中使用~public User selectByName(String username) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from user where username = ?";statement = connection.prepareStatement(sql);statement.setString(1, username);resultSet = statement.executeQuery();// 此处 username 使用了 unique 约束,要么能查到一个,要么一个都查不到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) {throw new RuntimeException(e);} finally {DBUtil.close(connection, statement, resultSet);}return null;}// 2. 根据用户 id 来找用户信息// 博客详情页,就可以根据用户 id 来查询作者的名字,把作者名字显示出来。public 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();// 此处 username 使用了 unique 约束,要么能查到一个,要么一个都查不到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) {throw new RuntimeException(e);} finally {DBUtil.close(connection, statement, resultSet);}return null;}
}
结过上述的逻辑,终于把数据库操作都准备好了~
接下来,就可以实现服务器中的后续代码了~
(Model就搞定了,要去写Controller了)
针对这里的这四个页面
分别来“约定前后端交互接口”,“编写服务器代码”,“编写客户端代码”
博客列表页
这个页面需要能够展示出数据库中的博客的列表
约定前后端接口
请求:
GET /blog响应:
[{blogId: 1,title: ' 这是第一篇博客',content: '这是博客正文',userId: 1,postTime: '2023-03-18 09:00:00'},{blogId: 2,title: ' 这是第二篇博客',content: '这是博客正文',userId: 1,postTime: '2023-03-18 09:00:00'},
]
这里的content与其说是“正文”,不如说是正文的摘要。
博客列表里面,主要显示有哪些博客~
因此,就需要在这个地方把正文进行截取(如果正文太长,就只截取前面的一部分)
编写服务器代码
接下来进一步编写服务器和客户端之间的代码~
我们试着先来写一下,看一下是否能连接成功!
package controller;import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;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;
import java.util.List;// 通过这个类,来处理 /blog 的对应请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();// 这个方法用来获取到数据库中的博客列表@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 从数据库中查询到博客列表,转成JSON格式 然后直接返回即可BlogDao blogDao = new BlogDao();List<Blog> blogs = blogDao.selectAll();// 把 blogs 对象转成JSON格式String respJson = objectMapper.writeValueAsString(blogs);resp.setContentType("application/json;charset=utf8");resp.getWriter().write(respJson);}
}
发现连接成功了~
在数据库中插入点信息
-- 给博客表中插入数据,方便测试。
insert into blog values(null, '这是第一篇博客', '从今天开始,我要认真学 Java', 1, now());
insert into blog values(null, '这是第二篇博客', '从昨天开始,我要认真学 Java', 1, now());
insert into blog values(null, '这是第三篇博客', '从前天开始,我要认真学 Java', 1, now());
insert into blog values(null, '这是第一篇博客', '从今天开始,我要认真学 C++', 2, now());
此处得到的响应,和预期的效果,有一点点差别,时间格式~
预期得到的是一个格式化时间,实际上得到一个毫秒时间戳
此处就需要把时间戳转成格式化时间(可以在前端来做,也可以在后端来做)
这里在后端来改!
在Blog类中:
public String getPostTime() {// 使用 SimpleDateFormat 来完成时间戳到格式化日期的转换// 这个转换过程,需要在构造方法中指定要转换格式,然后调用format来进行转换SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return simpleDateFormat.format(postTime);}
这里就看到我们的格式改变了~
编写客户端代码
在页面加载的时候,让页面通过ajax访问服务器,获取到数据库中的博客数据,并且填写到页面中!
处理服务器响应的逻辑:
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script>// 在页面加载的时候,通过 ajax 给服务器发送数据,获取到博客列表信息,并且显示在页面上function getBlogList() {$.ajax({type: 'get',url: 'blog',success: function(body) {// 获取到的 body 就是一个 js 对象数组,每个元素就是一个 js 对象,根据这个对象来构造一个div// 1. 先把 .right 里原有的内容清空let rightDiv = document.querySelector('.right');rightDiv.innerHTML = '';// 2. 遍历 body,构造出一个个的 blogDivfor (let blog of body) {let 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 = '查看全文 >>';// 此处希望点击之后能够跳转博客详情页// 这个跳转过程需要告知服务器要访问哪个博客的详情页a.href = 'blog_detail.html?blogId=' + blog.blogId;blogDiv.appendChild(a);// 把 blodDiv 挂到 dom树上rightDiv.appendChild(blogDiv);}},error: function() {alert("获取博客列表失败!")}});}getBlogList();</script>
完整代码:
<!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="css/common.css"><link rel="stylesheet" href="css/blog_list.css">
</head>
<body><!-- 这是导航栏 --><div class="nav"><img src="image/log.jpg" alt=""><span>我的博客系统</span><!-- 空白元素,用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="#">注销</a></div><div class="container"><!-- 左侧个人信息 --><div class="left"><!-- 表示整个用户信息区 --><div class="card"><img src="image/2.jpg" alt=""><h3>摸鱼王胖嘟嘟</h3><a href="#">gitee 地址</a><div class="counter"><span>文章</span><span>分类</span></div><div class="counter"><span>2</span><span>1</span></div></div></div><!-- 右侧个人信息 --><div class="right"><!-- .blog就对应一个博客 --><div class="blog"><!-- 博客标题 --><div class="title">我的第一篇博客</div><!-- 博客发布时间 --><div class="date">2023-02-11 18:26:00</div><!-- 博客摘要 --><div class="desc">从今天起,我要认真敲代码. Lorem, ipsum dolor sit amet consectetur adipisicing elit. Neque exercitationem, ut doloribus blanditiis, eveniet earum culpa accusamus rem eum cum deleniti, quisquam expedita distinctio tempora quaerat adipisci officia esse reprehenderit.</div><a href="#">查看全文 ></a></div><!-- .blog就对应一个博客 --><div class="blog"><!-- 博客标题 --><div class="title">我的第一篇博客</div><!-- 博客发布时间 --><div class="date">2023-02-11 18:26:00</div><!-- 博客摘要 --><div class="desc">从今天起,我要认真敲代码. Lorem, ipsum dolor sit amet consectetur adipisicing elit. Neque exercitationem, ut doloribus blanditiis, eveniet earum culpa accusamus rem eum cum deleniti, quisquam expedita distinctio tempora quaerat adipisci officia esse reprehenderit.</div><a href="#">查看全文 >></a></div></div><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script>// 在页面加载的时候,通过 ajax 给服务器发送数据,获取到博客列表信息,并且显示在页面上function getBlogList() {$.ajax({type: 'get',url: 'blog',success: function(body) {// 获取到的 body 就是一个 js 对象数组,每个元素就是一个 js 对象,根据这个对象来构造一个div// 1. 先把 .right 里原有的内容清空let rightDiv = document.querySelector('.right');rightDiv.innerHTML = '';// 2. 遍历 body,构造出一个个的 blogDivfor (let blog of body) {let 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 = '查看全文 >>';// 此处希望点击之后能够跳转博客详情页// 这个跳转过程需要告知服务器要访问哪个博客的详情页a.href = 'blog_detail.html?blogId=' + blog.blogId;blogDiv.appendChild(a);// 把 blodDiv 挂到 dom树上rightDiv.appendChild(blogDiv);}},error: function() {alert("获取博客列表失败!")}});}getBlogList();</script>
</body>
</html>
BlogDao.java逻辑的处理
package model;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;// 用于去封装博客表的基本操作
public class BlogDao {// 1. 往博客表里,插入一个博客.public void insert(Blog blog) {// JDBC 基本代码~Connection connection = null;PreparedStatement statement = null;try {// 1) 和数据库建立连接connection = DBUtil.getConnection();// 2) 构造 SQL 语句String sql = "insert into blog values(null, ?, ?, ?, now())";statement = connection.prepareStatement(sql);statement.setString(1, blog.getTitle());statement.setString(2, blog.getContent());statement.setInt(3, blog.getUserId());// 3) 执行 SQLstatement.executeUpdate();} catch (SQLException e) {throw new RuntimeException(e);} finally {// 4) 关闭连接,释放资源DBUtil.close(connection, statement, null);}}// 2. 能够获取到博客表中的所有博客表的信息(用于博客列表页),此处每篇博客不一定获取到完整的正文public List<Blog> selectAll() {List<Blog> blogs = new ArrayList<>();Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from blog";statement = connection.prepareStatement(sql);resultSet = statement.executeQuery();while (resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));blog.setContent(resultSet.getString("content"));blog.setUserId(resultSet.getShort("userId"));blog.setPostTime(resultSet.getTimestamp("postTime"));blogs.add(blog);}} catch (SQLException e) {throw new RuntimeException(e);} finally {DBUtil.close(connection, statement, resultSet);}return blogs;}// 3. 能够根据博客 id 获取到指定的博客内容(用于博客详情页)public Blog selectOne(int blogId) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);resultSet = statement.executeQuery();// 此处我们是使用 主键 来作为查询条件的,查询结果,要么是1,要么是0if (resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));// 这里需要针对内容进行截断(太长了,就去掉后面)String content = resultSet.getString("content");if (content.length() > 50) {content = content.substring(0, 50) + "...";}blog.setContent(content);blog.setUserId(resultSet.getShort("userId"));blog.setPostTime(resultSet.getTimestamp("postTime"));return blog;}} catch (SQLException e) {throw new RuntimeException(e);} finally {DBUtil.close(connection, statement, resultSet);}return null;}// 4. 从博客表中,根据博客id删除博客.public void delete(int blogId) {Connection connection = null;PreparedStatement statement = null;try {connection = DBUtil.getConnection();String sql = "delete from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);statement.executeUpdate();} catch (SQLException e) {throw new RuntimeException(e);} finally {DBUtil.close(connection, statement, null);}}// 注意,上述操作是增删查,没有改~
}
问题一:
当前这个博客列表页,还有点小问题~
当前拿到的博客列表顺序,是不太科学的
需要保证我们最新的博客得在最上面才行~
如果在sql中没有指定 order by ~ 此时查询到的结果是不确定的~
因此,在sql中不加 order by 之前,是不应该依赖查询结果的顺序的~
在BlogDao.java中找到此处
可以看到是降序排序的了
问题二:刷新页面的时候,发现一哆嗦~
旧的内容(测试时写死的数据) => 新的内容(从数据库里查的数据)
是通过网络交互的,花个几十 ms 都是很正常的,人眼是能捕捉到这个变化的~
直接把旧的测试数据删了就行了~
像这样~~
博客详情页
约定前后端交互接口
请求:
GET /blog?blogId= 1
响应:
HTTP/1.1 200 OK
Content-Type: application/json;{blogId: 1,title: “第一篇博客”,content: "这是正文",userId: 1,postTime: '2023-03-18 15:58:00'
}
和获取博客列表的区别~
1.请求里面,带有参数,blogId
2.响应结果不是json数组了,而只是单一的对象
3.响应的博客正文,不再截断了。
获取博客列表的请求
GET/ blog
不带参数的~
package controller;import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;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;
import java.util.List;// 通过这个类,来处理 /blog 的对应请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();// 这个方法用来获取到数据库中的博客列表@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json;charset=utf8");BlogDao blogDao = new BlogDao();// 先尝试获取到 req 中的 blogId参数,如果该参数存在,说明要请求博客详情// 如果不存在,就说明要请求博客列表String pararm = req.getParameter("blogId");if (pararm == null) {// 不存在参数,获取博客列表// 从数据库中查询到博客列表,转成JSON格式 然后直接返回即可List<Blog> blogs = blogDao.selectAll();// 把 blogs 对象转成JSON格式String respJson = objectMapper.writeValueAsString(blogs);resp.getWriter().write(respJson);} else {// 存在参数,获取博客详情int blogId = Integer.parseInt(pararm);Blog blog = blogDao.selectOne(blogId);String respJson = objectMapper.writeValueAsString(blog);resp.getWriter().write(respJson);}}
}
后端代码实现和博客列表页的获取,基本相同,就直接放到一个方法中来实现了,使用blogId参数来区分是获取博客列表还是详情~
前端代码
修改blog_detail.html,让这个页面加载的时候,能够调用上述接口,来从服务器获取到博客数据!
在前端代码这边,要想构造一个请求获取博客详情,就得知道当前用户点击的博客的id!
这个id就已经包含在 当前的 blog_detail.html页面的url里面了~
希望在博客详情页拿到博客的具体内容。
就需要构造一个请求 /blog?blogId=5
这个请求是 blog_detail.html通过ajax发送的
blog_detail.html就需要构造出blogId(5)的这个参数
关键问题就是这个参数(5)从哪里来的呢?
其实当来到blog_detail.html这个页面的时候,url里就已经带上了这个参数~
通过location.search就能够拿到?blogId=5这段内容,从而构造出/blog?blogId=5这样的请求。
既然如此,那应该如何处理?能够让我们这里的markdown文本内容,被渲染成带有特定样式的html片段呢?
仍然要使用edlitor.md这个库来实现。
这个库不仅提供了markdown编辑器,也提供了渲染功能~
当我们在进行程序验证的时候,要时刻牢记,浏览器缓存可能会影响到结果.
之前已经访问过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="css/common.css"><link rel="stylesheet" href="css/blog_detail.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="nav"><img src="image/log.jpg" alt=""><span>我的博客系统</span><!-- 空白元素,用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="#">注销</a></div><div class="container"><!-- 左侧个人信息 --><div class="left"><!-- 表示整个用户信息区 --><div class="card"><img src="image/2.jpg" alt=""><h3>摸鱼王胖嘟嘟</h3><a href="#">gitee 地址</a><div class="counter"><span>文章</span><span>分类</span></div><div class="counter"><span>2</span><span>1</span></div></div></div><!-- 右侧个人信息 --><div class="right"><!-- 使用这个 div 来包裹整个博客内容详情 --><div class="blog-content"><!-- 博客标题 --><h3></h3><!-- 博客的时间 --><div class="date"></div><!-- 博客的正文内容 --><div id="content" style="opacity: 80%"></div></div></div><script>function getBlogDetail() {$.ajax({type: 'get',// location.search 拿到了形如 '?blogId=5' 这样的一段内容url: 'blog'+ location.search,success: function(body) {// 根据 body 中的内容来构造页面// 1. 构造博客标题let h3 = document.querySelector(".blog-content>h3");h3.innerHTML = body.title;// 2. 构造博客发布时间let dateDiv = document.querySelector('.date');dateDiv.innerHTML = body.postTime;// 3. 构造博客正文// 如果直接把content设为 innerHtML,此时展示在界面上的内容是原始的markdown字符串// 咱们需要的是渲染后的,带有格式的效果// let content = document.querySelector('#content');// content.innerHTML = body.content;// 第一个参数对应 id=content 的html标签,渲染后得到的HTML片段就会放到这个标签下editormd.markdownToHTML('content', {markdown: body.content});}});}getBlogDetail();</script></div>
</body>
</html>
当前已经把博客列表页和详情页搞出来了
接下来来写登录页
登录页
约定前后端交互接口
请求:
POST /login
Content-Type: application/x-ww-form-urlencoded
这里的逻辑,可以直接使用form表单来进行提交,没必要非要使用ajax~
username=zhangsan&password=123
响应:
HTTP/1.1 302
Location:blog.list.html
当登录成功之后,就自动跳转到,主页(博客列表页)
1)给这部分代码套上一层form标签!
给input加上name属性
2)username=zhangsan&password=123
3)把button按钮换成input标签
在前端页面开发的过程中,html页面结构,是非常重要的,后续的CSS和JS很多都依赖了这个页面的结构
一旦页面结构发生了调整,就可能导致CSS或者js失效
<!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="css/common.css"><link rel="stylesheet" href="css/blog_login.css">
</head>
<body><!-- 这是导航栏 --><div class="nav"><img src="image/log.jpg" alt=""><span>我的博客系统</span><!-- 空白元素,用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><!-- 注销页面没必要在登陆页面展示 --><!-- <a href="#">注销</a> --></div><div class="login-container"><form action="login" method="post"><div class="login-dialog"><h3>登录</h3><div class="row"><span>用户名</span><input type="text" id = "username" name="username"></div><div class="row"><span>密码</span><input type="password" id="password" name="password"></div><div class="row"><!-- <button>提交</button> --><input type="submit" id="submit" value="提交"></div></div></form></div>
</body>
</html>
/* 登录页面的专用样式文件 */.login-container {width: 100%;height: calc(100% - 50px);/* 需要让里面的子元素垂直水平居中 */display: flex;align-items: center;justify-content: center;
}.login-dialog {width: 400px;height: 350px;background-color: rgba(255, 255, 255, 0.8);border-radius: 10px;
}.login-dialog h3 {text-align: center;padding: 50px 0;
}.login-dialog .row {height: 50px;width: 100%;display: flex;align-items: center;justify-content: center;
}.login-dialog .row span {/* 把span转换成块级元素,方便设置尺寸 */display: block;width: 100px;font-weight: 700;
}#username, #password {width: 200px;height: 40px;font-size: 22px;line-height: 40px;padding-left: 10px;border-radius: 10px;/* 去掉边框 */border: none;/* 去掉轮廓线 */outline: none;
}.row #submit {width: 300px;height: 50px;border-radius: 10px;color: white;background-color: rgb(0,128,0);border: none;outline: none;margin-top: 50px;
}.row #submit:active {background-color: #666;
}
后端逻辑
此处约定的路径是/login,这是一个新的路径,就需要使用一个新的servlet来处理
使用getPararmeter读取参数的时候,如果参数的值是纯英文,那还好,一般没啥问题。
如果参数的值是中文,此时直接读取参数,很容易出现乱码
中文就涉及到乱码。
但是Servlet默认并不是按照utf8来解析的~
package controller;import model.User;
import model.UserDao;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("utf8");resp.setCharacterEncoding("utf8");// 1. 获取到请求中的参数String username = req.getParameter("username");String password = req.getParameter("password");System.out.println("username=" + username + ", password=" + password);if (username == null || "".equals(username) || password == null || "".equals(password)) {// 请求的内容缺失,肯定是登录失败~resp.setContentType("text/html; charset=utf8");resp.getWriter().write("当前的用户名或密码为空!");return;}// 2. 和数据库中的内容进行比较UserDao userDao = new UserDao();User user = userDao.selectByName(username);if (user == null || !user.getPassword().equals(password)) {// 用户没有查到或者密码不匹配,也是登录失败resp.setContentType("text/html; charset=utf8");resp.getWriter().write("用户名或密码错误!");return;}// 3. 如果比较通过,就创建会话.HttpSession session = req.getSession(true);// 把刚才的用户信息,存储到会话中session.setAttribute("user", user);// 4. 返回一个重定向报文,跳转到博客列表页resp.sendRedirect("blog_list.html");}
}
当登录功能完成之后,就需要调整一下之前的两个页面(博客列表和博客详情)
让这两个页面,必须登录后才能访问~~
此处就做出这样的限制~(这样限制一下,后面实现一些其他功能会更简单)
在进入博客列表页/详情页的时候,先检查一下用户的登录状态~如果用户当前已经是登录状态,才能继续使用,如果是未登录状态,则强制跳转到login页面
如何实现上述功能?
可以在博客列表页/详情页,加载的时候,通过ajax访问一下服务器,获取当前的登录状态~看能不能获取到,如果获取到了,就说明当前确实是已经登录了。此时就可以留在这个页面了。
如果没有获取到,说明就未登录。就需要跳转到登录页面~
约定前后端交互接口
请求:GET /login响应:HTTP/1.1 200 OKContent-Type:application/json{userId: 1,username: 'zhangsan',}
登录了,就直接返回当前登录的用户信息.
未登录,则直接返回一个userId为0的对象
{userId: 0,username: '',}
此处只是一种典型的约定方式,完全也可以采用其他的方式来约定~
比如使用403表示当前未登录~
package controller;import com.fasterxml.jackson.databind.ObjectMapper;
import model.User;
import model.UserDao;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;@WebServlet("/login")
public class LoginServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("utf8");resp.setCharacterEncoding("utf8");// 1. 获取到请求中的参数String username = req.getParameter("username");String password = req.getParameter("password");System.out.println("username=" + username + ", password=" + password);if (username == null || "".equals(username) || password == null || "".equals(password)) {// 请求的内容缺失,肯定是登录失败~resp.setContentType("text/html; charset=utf8");resp.getWriter().write("当前的用户名或密码为空!");return;}// 2. 和数据库中的内容进行比较UserDao userDao = new UserDao();User user = userDao.selectByName(username);if (user == null || !user.getPassword().equals(password)) {// 用户没有查到或者密码不匹配,也是登录失败resp.setContentType("text/html; charset=utf8");resp.getWriter().write("用户名或密码错误!");return;}// 3. 如果比较通过,就创建会话.HttpSession session = req.getSession(true);// 把刚才的用户信息,存储到会话中session.setAttribute("user", user);// 4. 返回一个重定向报文,跳转到博客列表页resp.sendRedirect("blog_list.html");}// 这个方法用来让前端检测当前的登录状态@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 = new User();resp.getWriter().write(objectMapper.writeValueAsString(user));return;}User user = (User) session.getAttribute("user");if (user == null) {// 虽然有会话,但是会话里没有 user 对象,也视为未登录user = new User();resp.getWriter().write(objectMapper.writeValueAsString(user));return;}// 已经登录的状态!// 注意,此处不要把密码返回到前端user.setPassword("");resp.getWriter().write(objectMapper.writeValueAsString(user));}
}
前端代码:
<!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="css/common.css"><link rel="stylesheet" href="css/blog_list.css">
</head>
<body><!-- 这是导航栏 --><div class="nav"><img src="image/log.jpg" alt=""><span>我的博客系统</span><!-- 空白元素,用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="#">注销</a></div><div class="container"><!-- 左侧个人信息 --><div class="left"><!-- 表示整个用户信息区 --><div class="card"><img src="image/2.jpg" alt=""><h3>摸鱼王胖嘟嘟</h3><a href="#">gitee 地址</a><div class="counter"><span>文章</span><span>分类</span></div><div class="counter"><span>2</span><span>1</span></div></div></div><!-- 右侧个人信息 --><div class="right"><!-- .blog就对应一个博客 --><!-- <div class="blog"> --><!-- 博客标题 --><!-- <div class="title">我的第一篇博客</div> --><!-- 博客发布时间 --><!-- <div class="date">2023-02-11 18:26:00</div> --><!-- 博客摘要 --><!-- <div class="desc">从今天起,我要认真敲代码. Lorem, ipsum dolor sit amet consectetur adipisicing elit. Neque exercitationem, ut doloribus blanditiis, eveniet earum culpa accusamus rem eum cum deleniti, quisquam expedita distinctio tempora quaerat adipisci officia esse reprehenderit.</div> --><!-- <a href="#">查看全文 ></a> --><!-- </div> --><!-- .blog就对应一个博客 --></div></div><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script>// 在页面加载的时候,通过 ajax 给服务器发送数据,获取到博客列表信息,并且显示在页面上function getBlogList() {$.ajax({type: 'get',url: 'blog',success: function(body) {// 获取到的 body 就是一个 js 对象数组,每个元素就是一个 js 对象,根据这个对象来构造一个div// 1. 先把 .right 里原有的内容清空let rightDiv = document.querySelector('.right');rightDiv.innerHTML = '';// 2. 遍历 body,构造出一个个的 blogDivfor (let blog of body) {let 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 = '查看全文 >>';// 此处希望点击之后能够跳转博客详情页// 这个跳转过程需要告知服务器要访问哪个博客的详情页a.href = 'blog_detail.html?blogId=' + blog.blogId;blogDiv.appendChild(a);// 把 blodDiv 挂到 dom树上rightDiv.appendChild(blogDiv);}},error: function() {alert("获取博客列表失败!")}});}getBlogList();// 加上一个逻辑,通过 GET /login 这个接口来获取下当前的登录状态~function getUserInfo() {$.ajax({type: 'get',url: 'login',success: function(body) {// 判定此处的body是不是一个有效的user对象if (body.userId && body.userId > 0) {// 登录成功!// 不做处理console.log("当前用户登录成功!用户名:" + body.username);} else {// 登录失败!// 让前端页面,跳转到 login.hmtlalert("当前您尚未登录!请登录后再访问博客列表!");location.assign('blog_login.html');}},error: function() {location.assign('blog_login.html');}});}getUserInfo();</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="css/common.css"><link rel="stylesheet" href="css/blog_detail.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="nav"><img src="image/log.jpg" alt=""><span>我的博客系统</span><!-- 空白元素,用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="#">注销</a></div><div class="container"><!-- 左侧个人信息 --><div class="left"><!-- 表示整个用户信息区 --><div class="card"><img src="image/2.jpg" alt=""><h3>摸鱼王胖嘟嘟</h3><a href="#">gitee 地址</a><div class="counter"><span>文章</span><span>分类</span></div><div class="counter"><span>2</span><span>1</span></div></div></div><!-- 右侧个人信息 --><div class="right"><!-- 使用这个 div 来包裹整个博客内容详情 --><div class="blog-content"><!-- 博客标题 --><h3></h3><!-- 博客的时间 --><div class="date"></div><!-- 博客的正文内容 --><div id="content" style="opacity: 80%"></div></div></div><script>function getBlogDetail() {$.ajax({type: 'get',// location.search 拿到了形如 '?blogId=5' 这样的一段内容url: 'blog'+ location.search,success: function(body) {// 根据 body 中的内容来构造页面// 1. 构造博客标题let h3 = document.querySelector(".blog-content>h3");h3.innerHTML = body.title;// 2. 构造博客发布时间let dateDiv = document.querySelector('.date');dateDiv.innerHTML = body.postTime;// 3. 构造博客正文// 如果直接把content设为 innerHtML,此时展示在界面上的内容是原始的markdown字符串// 咱们需要的是渲染后的,带有格式的效果// let content = document.querySelector('#content');// content.innerHTML = body.content;// 第一个参数对应 id=content 的html标签,渲染后得到的HTML片段就会放到这个标签下editormd.markdownToHTML('content', {markdown: body.content});}});}getBlogDetail();// 加上一个逻辑,通过 GET /login 这个接口来获取下当前的登录状态~function getUserInfo() {$.ajax({type: 'get',url: 'login',success: function(body) {// 判定此处的body是不是一个有效的user对象if (body.userId && body.userId > 0) {// 登录成功!// 不做处理console.log("当前用户登录成功!用户名:" + body.username);} else {// 登录失败!// 让前端页面,跳转到 login.hmtlalert("当前您尚未登录!请登录后再访问博客详情!");location.assign('blog_login.html');}},error: function() {location.assign('blog_login.html');}});}getUserInfo();</script></div>
</body>
</html>
正确显示用户信息
当下用户信息,显示的是”摸鱼王胖嘟嘟“,实际登录的用户,显示”zhangsan“。
针对博客列表页
<!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="css/common.css"><link rel="stylesheet" href="css/blog_list.css">
</head>
<body><!-- 这是导航栏 --><div class="nav"><img src="image/log.jpg" alt=""><span>我的博客系统</span><!-- 空白元素, 用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="#">注销</a></div><!-- 这里的 .container 作为页面的版心 --><div class="container"><!-- 左侧个人信息 --><div class="left"><!-- 表示整个用户信息区域. --><div class="card"><img src="image/2.jpg" alt=""><h3></h3><a href="#">github 地址</a><div class="counter"><span>文章</span><span>分类</span></div><div class="counter"><span>2</span><span>1</span></div></div></div><!-- 右侧内容详情 --><div class="right"><!-- .blog 就对应一个博客 --><!-- <div class="blog"><div class="title">我的第一篇博客</div><div class="date">2022-05-05 20:52:00</div><div class="desc">从今天起, 我要认真敲代码. Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla alias tenetur ut velit ex voluptatibus consequatur quam exercitationem, assumenda ea blanditiis repudiandae? Repellendus tenetur nostrum asperiores molestias doloremque cupiditate maiores.</div><a href="#">查看全文 >> </a></div> --></div></div></div><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script>// 在页面加载的时候, 通过 ajax 给服务器发送数据, 获取到博客列表信息, 并且显示在界面上. function getBlogList() {$.ajax({type: 'get',url: 'blog',success: function(body) {// 获取到的 body 就是一个 js 对象数组, 每个元素就是一个 js 对象, 根据这个对象构造 div// 1. 先把 .right 里原有的内容给清空let rightDiv = document.querySelector('.right');rightDiv.innerHTML = '';// 2. 遍历 body, 构造出一个个的 blogDivfor (let blog of body) {let 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 = '查看全文 >>';// 此处希望点击之后能够跳转到 博客详情页 !!// 这个跳转过程需要告知服务器要访问的是哪个博客的详情页. a.href = 'blog_detail.html?blogId=' + blog.blogId;blogDiv.appendChild(a);// 把 blogDiv 挂到 dom 树上!rightDiv.appendChild(blogDiv);}}, error: function() {alert("获取博客列表失败!");}});}getBlogList();</script><!-- 在这里引入上述的 js 文件, 就可以执行到里面的代码, 也就进行了登录状态的监测了 --><script src="js/common.js"></script><script>// 针对博客列表页, 调用的时候传入参数getUserInfo('blog_list.html');</script>
</body>
</html>
针对博客详情页
当前看到的是,博客详情页,用户名,也是成了zhangsan了,此处就需要处理一下,让博客列表页和详情页,能够做出一些区分~
让服务器,提供一个新的接口~这个接口可以让客户端指定blogId,获取到指定blogId的作者信息!
请求:
GET/authorInfo?blogId=6响应:
{userId:2,username: 'lisi',
}
<!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="css/common.css"><link rel="stylesheet" href="css/blog_detail.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="nav"><img src="image/log.jpg" alt=""><span>我的博客系统</span><!-- 空白元素, 用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="#">注销</a></div><!-- 这里的 .container 作为页面的版心 --><div class="container"><!-- 左侧个人信息 --><div class="left"><!-- 表示整个用户信息区域. --><div class="card"><img src="image/2.jpg" alt=""><h3></h3><a href="#">github 地址</a><div class="counter"><span>文章</span><span>分类</span></div><div class="counter"><span>2</span><span>1</span></div></div></div><!-- 右侧内容详情 --><div class="right"><!-- 使用这个 div 来包裹整个博客的内容详情 --><div class="blog-content"><!-- 博客标题 --><h3></h3><!-- 博客的时间 --><div class="date"></div><!-- 博客的正文内容 --><div id="content" style="opacity: 80%"></div></div></div></div><script>function getBlogDetail() {$.ajax({type: 'get',// location.search 拿到了形如 '?blogId=5' 这样的一段内容url: 'blog' + location.search,success: function(body) {// 根据 body 中的内容来构造页面// 1. 构造博客标题let h3 = document.querySelector(".blog-content>h3");h3.innerHTML = body.title;// 2. 构造博客发布时间let dateDiv = document.querySelector('.date');dateDiv.innerHTML = body.postTime;// 3. 构造博客正文// 如果直接把 content 设为 innerHTML, 此时展示在界面上的内容, 是原始的 markdown 字符串// 咱们需要的是渲染后的, 带有格式的效果// let content = document.querySelector('#content');// content.innerHTML = body.content;// 第一个参数对应 id=content 的 html 标签. 渲染后得到的 html 片段就会被放到这个 标签下. editormd.markdownToHTML('content', {markdown: body.content});}});}getBlogDetail();// 加上一个逻辑, 通过 GET /login 这个接口来获取下当前的登录状态~function getUserInfo(pageName) {$.ajax({type: 'get',url: 'login',success: function(body) {// 判定此处的 body 是不是一个有效的 user 对象(userId 是否非 0)if (body.userId && body.userId > 0) {// 登录成功!// 不做处理!console.log("当前用户登录成功! 用户名: " + body.username);// 在 getUserInfo 的回调函数中, 来调用获取作者信息getAuthorInfo(body);} else {// 登录失败!// 让前端页面, 跳转到 login.htmlalert("当前您尚未登录! 请登录后再访问博客列表!");location.assign('blog_login.html');}},error: function() {alert("当前您尚未登录! 请登录后再访问博客列表!");location.assign('blog_login.html');}});}// 判定用户的登录状态getUserInfo("blog_detail.html");// 从服务器获取一下当前博客的作者信息, 并显示到界面上. // 参数 user 就是刚才从服务器拿到的当前登录用户的信息function getAuthorInfo(user) {$.ajax({type: 'get',url: 'authorInfo' + location.search,success: function(body) {// 此处的 body, 就是服务器返回的 User 对象, 是文章的作者信息if (body.username) {// 如果响应中的 username 存在, 就把这个值设置到页面上. changeUserName(body.username);if (body.username == user.username) {// 作者和登录的用户是一个人, 则显示 "删除按钮"let navDiv = document.querySelector('.nav');let a = document.createElement('a');a.innerHTML = '删除';// 期望点击删除, 构造一个形如 blogDelete?blogId=6 这样的请求a.href = 'blogDelete' + location.search;navDiv.appendChild(a);}} else {console.log("获取作者信息失败! " + body.reason);}}});}function changeUserName(username) {let h3 = document.querySelector('.card>h3');h3.innerHTML = username;}</script>
</body>
</html>
实现"注销"功能
退出当前登录的状态
在导航栏中安排一个“注销”按钮
当用户点击注销之后,就会在服务器上取消登录状态,并且跳转到登录页面。
约定前后端交互接口
请求:
GET / logout
响应:
HTTP/1.1 302
Location:login.html
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;@SuppressWarnings({"all"})
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 先找到当前用户的会话HttpSession session = req.getSession(false);if (session == null) {// 用户没有登录!谈不上注销!resp.getWriter().write("当前用户尚未登录,无法注销1");return;}// 然后把这个用户的会话中的信息删掉session.removeAttribute("user");resp.sendRedirect("blog_login.html");}
}
发布博客功能
在博客编辑页中,当用户输入了博客标题,和正文之后,点击发布~
此时就会把博客数据提交到服务器,由服务器存储到数据库中~
约定前后端交互接口
请求:
POST/blog
Content-Type:application/x-www-form-urlencodedtitle=这是标题&content=这是正文....
响应:
HTTP/1.1 302
Location:blog_list.html
package controller;import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
import model.User;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;// 通过这个类,来处理 /blog 的对应请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();// 这个方法用来获取到数据库中的博客列表@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json;charset=utf8");BlogDao blogDao = new BlogDao();// 先尝试获取到 req 中的 blogId参数,如果该参数存在,说明要请求博客详情// 如果不存在,就说明要请求博客列表String pararm = req.getParameter("blogId");if (pararm == null) {// 不存在参数,获取博客列表// 从数据库中查询到博客列表,转成JSON格式 然后直接返回即可List<Blog> blogs = blogDao.selectAll();// 把 blogs 对象转成JSON格式String respJson = objectMapper.writeValueAsString(blogs);resp.getWriter().write(respJson);} else {// 存在参数,获取博客详情int blogId = Integer.parseInt(pararm);Blog blog = blogDao.selectOne(blogId);String respJson = objectMapper.writeValueAsString(blog);resp.getWriter().write(respJson);}}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HttpSession session = req.getSession(false);if (session == null) {// 当前用户未登录,不能提交博客resp.setContentType("text/html;charset=utf8");// 直接告诉客户端,请求参数不对resp.getWriter().write("当前用户未登录,不能提交博客!");return;}User user = (User) session.getAttribute("user");if (user == null) {// 当前用户未登录,不能提交博客resp.setContentType("text/html;charset=utf8");// 直接告诉客户端,请求参数不对resp.getWriter().write("当前用户未登录,不能提交博客!");return;}// 一定哟啊先指定好请求按照哪种编码来解析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/html;charset=utf8");// 直接告诉客户端,请求参数不对resp.getWriter().write("提交博客失败!缺少必要的参数!");return;}// 构造 Blog 对象,把当前的信息填进去,并插入数据库中// 此处要给 Blog 设置的属性,主要是 title,content,userId// postTime 和 blogId 都不需要手动指定,都是插入数据库的时候自动生成的Blog blog = new Blog();blog.setTitle(title);blog.setContent(content);// 作者 id 就是当前提交这个博客的用户的身份信息!blog.setUserId(user.getUserId());BlogDao blogDao = new BlogDao();blogDao.insert(blog);// 重定向到博客列表页resp.sendRedirect("blog_list.html");}
}
这里就需要整一个form表单,把这里的内容给套上!
删除博客
服务器处理
用户点击删除按钮,触发一个HTTP请求,HTTP请求就会让服务器删除指定的博客~
完整代码
完整代码我放gitee仓库了,需要的可以去博客系统仓库找。
相关文章:

【Java项目】完善基于Java+MySQL+Tomcat+maven+Servlet的博客系统
目录一、准备工作二、引入依赖三、创建必要的目录四、编写代码五/六、打包部署(直接基于 smart tomcat)七、验证代码正式编写服务器代码编写数据库相关的操作代码创建数据库/表结构(数据库设计)数据库代码封装数据库操作封装针对数据的增删改查!博客列表页约定前后端…...

详解结构体内存对齐
目录 前言 一、内存大小的计算 1.规则 2.练习 二、为什么要有内存对齐 1.移植原因 2.性能原因 三、修改默认对齐数 总结 前言 本文针对结构体大小的计算进行深度剖析。结构体的大小要遵守内存对齐,在绝大数情况下,会浪费空间。但是有其的价值&…...

指针:程序员的望远镜
指针:程序员的望远镜一、什么是指针1.1 指针的定义1.2 指针和普通变量的区别1.3 指针的作用1.4 指针的优点和缺点二、指针的基本操作2.1 取地址运算符"&"2.2 指针的声明与定义2.3 指针的初始化2.4 指针的解引用2.5 指针的赋值2.6 指针的运算2.7 指针的…...

【python实现学生选课系统】
一、要求: 选课系统 管理员: 创建老师:姓名、性别、年龄、资产 创建课程:课程名称、上课时间、课时费、关联老师 使用pickle保存在文件 学生: 学生:用户名、密码、性别、年龄、选课列表[]、上课记录{课程…...

备受青睐的4D毫米波成像雷达,何以助力高阶自动驾驶落地?
近日,海外媒体曝出特斯拉已向欧洲监管机构提交车辆变更申请,并猜测特斯拉最新的自动驾驶硬件HW4.0或将很快量产上车。据爆料,HW4.0最大的变化是马斯克放弃的毫米波雷达又加了回来,根据国外知名博主Greentheonly的拆解分析…...

3.20算法题(一) LeetCode 合并两个有序数组
题目链接:算法面试题汇总 - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台 题目描述:给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元…...

QT | 编写一个简单的上位机
QT | 编写一个简单的上位机 时间:2023-03-19 参考: 1.易懂 | 手把手教你编写你的第一个上位机 2.QT中修改窗口的标题和图标 3.图标下载 1.打开QT Creator 2.新建工程 Qt Creator 可以创建多种项目,在最左侧的列表框中单击“Application”&am…...

DirectX12(D3D12)基础教程(二十一)—— PBR:IBL 的数学原理(2/5)
目录3、IBL 数学原理3.1、基于微平面理论的 “Cook-Torrance” 模型回顾3.2、 ksk_sks 项与菲涅尔项等价消除3.3、拆分“漫反射项”和“镜面反射项”3、IBL 数学原理 接下来,就让我们正式进入整个 IBL 的数学原理的旅程。请注意,前方高能! …...

嵌入式学习笔记——SysTick(系统滴答)
系统滴答前言SysTick概述SysTick是个啥SysTick结构框图1. 时钟选择2.计数器部分3.中断部分工作一个计数周期(从重装载值减到0)的最大延时时间工作流程SysTick寄存器1.控制和状态寄存器SysTick->CTRL2.重装载值寄存器SysTick->LOAD3.当前值寄存器Sy…...

Linux实操之服务管理
文章目录一、服务(service)管理介绍:service管理指令查看服务名服务的运行级别(runlevel):CentOS7后运行级别说明chkconfig指令介绍一、服务(service)管理介绍: 服务(service)本质就是进程,但是是运行在后台的,通常都会监听某个端口,等待其它…...

基于Java+SpringBoot+vue的毕业生信息招聘平台设计和实现【源码+论文+演示视频+包运行成功】
博主介绍:专注于Java技术领域和毕业项目实战 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇🏻 不然下次找不到哟 Java项目精品实战案例(200套) 目录 一、效果演示 二、…...

智能生活垃圾检测与分类系统(UI界面+YOLOv5+训练数据集)
摘要:智能生活垃圾检测与分类系统用于日常生活垃圾的智能监测与分类,通过图片、视频和摄像头识别生活垃圾,对常见的可降解、纸板、玻璃、金属、纸质和塑料等类别垃圾进行检测和计数,以协助垃圾环保分类处理。本文详细介绍基于YOLO…...

建立农村污水处理设施已经成为了当务之急!
在现代社会中,随着城市化进程的加速和人口的增长,选择农村污水处理设备进行污水处理已经成为了一个非常重要的问题。虽然城市中的污水处理设施得到了很好的发展,但是农村地区的污水处理还存在很多问题。 在农村地区,由于缺乏污水…...

【Matlab算法】粒子群算法求解一维线性函数问题(附MATLAB代码)
MATLAB求解一维线性函数问题前言正文函数实现可视化处理可视化结果前言 一维线性函数,也称为一次函数,是指只有一个自变量xxx的函数,且函数表达式可以写成yaxbyaxbyaxb的形式,其中aaa和bbb是常数。具体来说,aaa称为斜…...

【JavaEE】Thread 类及常用方法
一、Thread 类Thread 类我们可以理解为是 java 用于管理线程的一个类,里面封装了操作系统提供的线程管理这一方面的 API (Thread 是优化后的结果), Java 代码创建的每一个线程,可以理解为为 Thread 实例化的对象,Threa…...

C语言数据结构初阶(7)----队列
CSDN的uu们,大家好。这里是C语言数据结构的第七讲。 目标:前路坎坷,披荆斩棘,扶摇直上。 博客主页:姬如祎队列的基础知识队列(queue)是只允许在一端进行插入操作,而在另一端进行删除…...

代码随想录二刷 day01 | 704. 二分查找 27. 移除元素 977. 有序数组的平方
代码随想录二刷day01704. 二分查找27. 移除元素977. 有序数组的平方704. 二分查找 题目链接 做这种题最好现在纸上写一写,如果在大脑中想,可能一会就晕了。 二刷的时候发现了一个新的知识点 即: >>的作用 二分法第二种写法:…...

Linux 终端、进程组、会话、守护进程
文章目录一、终端概念终端概念控制终端二、进程组概念进程组概述进程组相关 API会话会话概念会话相关 API创建会话注意事项守护进程守护进程介绍守护进程模型守护进程参考代码守护进程相关 API参考文章一、终端概念 终端概念 1、终端(Terminal) 终端是…...

你是否有潜质成为谷歌开发者专家?加入 GDE 成长计划,释放潜力!
谷歌开发者专家 (Google Developer Experts,GDE),又称谷歌开发者专家项目,是由一群经验丰富的技术专家、具有社交影响力的开发者和思想领袖组成的全球性社区。通过在各项活动演讲以及各个平台上发布优质内容来积极助力开发者、企业和技术社区…...

安全防御之防火墙篇(二)
目录 1.防火墙如何处理双通道协议? 2.防火墙如何处理NAT? 3.防火墙支持哪些NAT技术,主要应用的场景是什么? 4.当内网PC通过公网域名解析访问内网服务器的时候,会存在什么问题,如何解决?请详细…...

设计必备,5个png免抠素材网站,建议收藏
做设计、PPT都需要用到大量的免抠素材,职场中熟练使用Photoshop的人毕竟是少数,也很少有人愿意花费时间去精细抠图。那这5个免抠素材网站一定要收藏好,可以有效帮你节省时间,提高工作效率。 1、菜鸟图库 https://www.sucai999.co…...

shell 脚本expect
expect 是什么 expect - programmed dialogue with interactive programs(与互动程序进行程序对话) 定义脚本执行的 shell #!/usr/bin/expect -f 定义的是执行 expect 可执行文件的链接路径(或真实路径),功能类似于bas…...

第十九天 Maven总结
目录 Maven 1. 前言 2. 概述 2.1 介绍 2.2 安装 3. IDEA集成Maven 3.1 集成Maven环境 3.2 创建Maven项目 3.3 Maven坐标详解 3.4 导入maven项目 4. 依赖管理 4.1 依赖配置 4.2 依赖传递 4.3 依赖范围 4.4 生命周期 4.5 插件 Maven 1. 前言 1). 什么是Maven? …...

ESP8266-NodeMCU开发板-------开发板介绍(1)
目录 认识ESP8266-NodeMCU开发板编辑 GPIO编号与NodeMCU开发板引脚名的区别: ESP8266 GPIO编号与NodeMCU开发板引脚名的对应关系 可用引脚 电压电流限制 特殊引脚情况说明 上拉电阻/下拉电阻 模拟输入 通讯 认识ESP8266-NodeMCU开发板 初识NodeMCU开发板 (第1章-第…...

【测试开发篇3】软件测试的常用概念
目录 一、软件测试的生命周期(5个步骤) ①需求分析(两个角度) 用户角度: 开发人员的角度: ②测试计划 ③测试设计、测试开发 ④执行测试 ⑤测试评估 二、软件测试贯穿项目的整个生命周期的体现 需求分析阶段 计划阶段 设计阶段 编码阶段 …...

javaEE初阶 — JavaScript WebAPI
文章目录什么是 DOMDOM 树获取元素1. querySelector2. querySelectorAll事件1. 事件三要素2. 代码案例获取 / 修改元素内容1. innerHTML获取 / 修改元素属性获取 / 修改表单元素属性获取 / 修改样式属性1. 修改内联样式(修改 style 属性的值)2. 修改元素…...

UE实现地面动态交互效果
文章目录 1.实现目标2.实现过程2.1 SphereMask2.2 材质实现2.3 位置更新3.参考资料1.实现目标 基于SphereMask材质节点实现人物在地面一定范围内的颜色高亮效果。 2.实现过程 实现原理是首先通过,SphereMask材质节点更具计算输出Mask值,其中在球体半径内的输入1,在外部的则…...

如何用自己的数据训练YOLOv5
如何训练YOLOv5 1. Clone the YOLOv5 repository and install dependencies: git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt2. 整理数据,使其适配YOLO训练 Step1:Organize your dataset in the fo…...

【基础算法】数组相关题目
系列综述: 💞目的:本系列是个人整理为了秋招算法的,整理期间苛求每个知识点,平衡理解简易度与深入程度。 🥰来源:材料主要源于代码随想录进行的,每个算法代码参考leetcode高赞回答和…...

MatBox—基于PyQt快速入门matplotlib的教程库
MatBox—基于PyQt快速入门matplotlib的教程库 __ __ _ _ _ _ _ _ _______ _ _ _ | \/ | | | | | | | | |(_)| | |__ __| | | (_) | || \ / | __ _ |…...