前言
在本博客中,我将详细介绍整个博客系统的设计与实现过程,包括Smart Tomcat的部署和servlet框架的使用。无论你是初学者还是有一定经验的开发者,我相信这篇博客都能带给你一些收获。在编写本文时,我尽量采用通俗易懂的语言,希望让大家都能轻松理解和学习。
博客系统简要拆解分析
- 包含四个界面:博客列表页,博客详情页,博客编辑页以及博客登录页
- 包含以下八个功能:
- 博客列表页展示
- 博客详情页展示
- 博客登录页展示
- 博客编辑页展示(发布博客功能)
- 限制用户权限(必须登录才可以访问其他页面)
- 用户信息与作者信息显示分离
- 注销功能(退出登录)
- 删除博客
一、数据库的设计
1.1 设计分析:
- 设计表结构,有几张表,每个表里面都有啥
- 要存储的有2个部分,博客数据、用户数据
- 博客表:blog{blogID,title,content,userID,postTime}
- 用户表:user{userID,username,password}
1.2 代码实现(sql语句):
create database if not exists Blog_System charset=utf8;
use Blog_System;
drop table if exists blog;
create table blog (
blogID int primary key auto_increment,
title varchar(128),
content varchar(4096),
userID int,
postTime datetime
);
drop table if exists user;
create table user (
userID int primary key auto_increment,
username varchar(50),
password varchar(50)
);
insert into user values(null,'Pharaoh','123456'),(null,'xiaoming','123');
insert into blog values(null,'Pharaoh的第一篇博客','这是博客正文',1,'2023-7-20 12:30:00');
insert into blog values(null,'Pharaoh的第二篇博客','这是博客正文',1,'2023-7-21 12:30:00');
注意此处博客表中的blogID和用户表中userID设置了自增主键,所以下面进行insert插入操作时,设置null即可。
二、封装数据库(JDBC)
2.1 准备工作
1.创建Maven项目
2.引入相关依赖(pom.xml)
可以从Maven中央仓库中查找相关依赖
<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.15.0</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>
3.创建必要的目录
在src下创建webapp目录,然后再创建子目录WEB-INF,在子目录中创建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>
4.配置smartTomcat
2.2 封装数据库的连接操作
此处使用 DataSource引入单例模式中的懒汉模式,对连接、关闭操作进行封装。
package model;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.*;
//---------------------------------封装数据库的连接操作---------------------------------
//创建dataSource
public class DBUtil {
//DataSource对于一个项目,有一个就可以了(单例)--懒汉模式
//加上volatile和synchronized以保证线程安全
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("jdbc:mysql://127.0.0.1:3306/blog_system?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("123456");
}
}
}
return dataSource;
}
//建立连接
public static Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
//关闭资源
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
//注意释放的顺序,谁后创建谁先释放
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
1.为什么datasource前后要判定两次是否为空?
先判断null如果为null再尝试获取锁(提高性能,如果已经创建就不用加锁判断了),获取锁后还不能直接创建,因为之前可能也有判断为null的已经获取过锁并创建对象,所以锁内需要再次检测。两次的if判断与synchronized称为双重校验锁。
2.为什么close操作中不使用thorws抛异常而使用try/catch语句?
因为如果前面的语句如果发生异常,会导致后面的资源没释放从而导致资源泄漏!
2.3 对两个表创建对应的实体类
package model;
//这个类表示数据库中的Blog表的内容
//每个Blog对象,就对应着Blog表中的一条记录
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
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 String getPostTime() {
SimpleDateFormat format = new SimpleDateFormat();
return format.format(postTime);
}
public void setPostTime(Timestamp postTime) {
this.postTime = postTime;
}
}
package model;
//这个类表示数据库中的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;
}
}
这里就是设置相关的属性以及提供对应的getter和setter方法。
注意此处更改了getPostTime方法,需要用到SimpleDataFormat这个类,使返回数据不再是时间戳而是一个正常格式的时间,此处格式具体如何填写需要参考官方文档,不同语言需要提供的格式不同。
2.4 封装两个表对应的增删改查操作
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;
//DAO => Data Access Object (对象访问数据)--通过这样的对象来访问数据
public class BlogDao {
//插入一个Blog对象
public void insert(Blog blog) {
Connection connection = null;
PreparedStatement statement = null;
try {
//1.建立连接
connection = DBUtil.getConnection();
//2.构造sql
String sql = "insert into blog values(null,?,?,?,?)";
statement = connection.prepareStatement(sql);
statement.setString(1,blog.getTitle());
statement.setString(2,blog.getContent());
statement.setInt(3,blog.getUserID());
//如果数据库表中的datetime类型,插入数据的时候,按照Timestamp来插入或者按照格式化时间来插入均可!
statement.setString(4,blog.getPostTime());
//3.执行sql
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection,statement,null);
}
}
//查询Blog表中的所有数据内容
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 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 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();
if(resultSet.next()) {
Blog blog = new Blog();
blog.setBlogID(resultSet.getInt("blogID"));
blog.setTitle(resultSet.getString("title"));
blog.setContent(resultSet.getString("content"));
blog.setUserID(resultSet.getInt("userID"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
return blog;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection,statement,resultSet);
}
return null;
}
//指定一个博客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);
}
}
}
package model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//DAO => Data Access Object (对象访问数据)--通过这样的对象来访问数据
public class UserDao {
public User selectUserByID(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;
}
public User selectUserByName(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();
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;
}
}
1.此处为什么取名DAO?
DAO => Data Access Object 通过这样的对象来访问数据
此处即为通过这两个类操作数据库中表的数据
三、实现博客列表页
3.1 设计分析
在博客列表页页面加载的时候,需要在前端代码中使用ajax构造HTTP请求,从服务器中获取到博客数据再返回到页面上,所以此处要预先约定好构造怎么样的请求,返回怎么样的响应。
效果如下:
3.2 约定前后端交互接口
此处的响应以json格式的数组来创建
3.3 实现前端代码(ajax部分)
// 通过 ajax 给服务器发请求, 获取到所有的博客数据. 并且构造到页面上.
function getBlogs() {
$.ajax({
type: 'get',
url: 'blog',
success: function(body) {
// 根据返回的数据, 构造出页面中对应的元素
let containerRight = document.querySelector('.container-right');
for (let blog of body) {
let blogDiv = document.createElement("div");
blogDiv.className = 'blog';
let titleDiv = document.createElement("div");
titleDiv.className = 'title';
titleDiv.innerHTML = blog.title;
let dateDiv = document.createElement("div");
dateDiv.className = 'date';
dateDiv.innerHTML = blog.postTime;
let descDiv = document.createElement("div");
descDiv.className = 'desc';
descDiv.innerHTML = blog.content;
let a = document.createElement("a");
a.href = 'blog_detail.html?blogID=' + blog.blogID;
a.innerHTML = '查看全文 >>';
// 把上述标签构造好了之后, 还需要组合起来.
blogDiv.appendChild(titleDiv);
blogDiv.appendChild(dateDiv);
blogDiv.appendChild(descDiv);
blogDiv.appendChild(a);
containerRight.appendChild(blogDiv);
}
}
});
}
3.4 实现后端代码(BlogServlet类)
//通过该类,来实现一些后端接口
//约定前后端接口为: 请求:Get /blog
// 响应: json格式来存储每一条博客,用json数组的形式
//
//每个Servlet都对应了一个路径,如果路径确定了,对应的Servlet也明确了.
@WebServlet("/blog")
public class BlogServlet extends HttpServlet{
//用这个类转换成json形式的字符串
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//从query String中获取blogID,如果有则视为查询指定博客,没有则查询全文
String blogID = req.getParameter("blogID");
BlogDao blogDao = new BlogDao();
if(blogID == null) {
List<Blog> blogs = blogDao.selectAll();
String respString = objectMapper.writeValueAsString(blogs);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respString);
}
else {
Blog blog = blogDao.selectOne(Integer.parseInt(blogID));
String respString = objectMapper.writeValueAsString(blog);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respString);
}
}
}
四、实现博客详情页
4.1 设计分析
博客列表页中点击查看全文,跳转到博客详情页
点击了查看全文之后就在blog_list.html中发起一个get请求,此时需要告诉服务器你查找的是哪一篇博客,于是就要在url中加上query String。
let a = document.createElement("a");
a.href = 'blog_detail.html?blogID=' + blog.blogID;
a.innerHTML = '查看全文 >>';
此时需要博客详情页再发送一个ajax请求到服务器中去,向服务器获取当前的博客全文数据,再展示到页面上
效果如下:
4.2 约定前后端交互接口
4.3 实现前端代码(ajax部分)
function getBlog() {
$.ajax({
type: 'get',
url: 'blog' + location.search,
success: function(body) {
// 设置博客的标题
let h3 = document.querySelector('.container-right h3');
h3.innerHTML = body.title;
// 设置发布时间
let dateDiv = document.querySelector('.container-right .date');
dateDiv.innerHTML = body.postTime;
// 设置正文. 正文内容应该是 markdown 格式的数据.
// 此处要显示的应该是渲染过的 markdown 的内容, 而不是 markdown 的原始字符串.
// 第一个参数, 是一个 html 元素的 id, 接下来渲染的结果会放到对应的 元素中.
editormd.markdownToHTML('content', {markdown: body.content});
}
});
}
注意:
- 此处的location.search 即为 query String,location是一个全局变量
- 由于当前响应的Content-Type返回的是application/json格式,此时json就会把这个body直接转成js对象。
- 数据库中存储的是markdown原始的字符串,最终需要展示渲染后的效果,这里就需要在博客详情页中,针对markdown字符串进行渲染(直接借助editor.md这个库即可)
4.4 实现后端代码
与博客列表页代码一致,此处先判断blogID是否为空,为空则展示博客列表页,不为空则判定具体blogID等于多少,返回相应的博客详情页。
五、实现博客登录页
5.1 设计分析
用户在登录页输入用户名和密码,点击登录按钮,发送一个POST请求给服务器,服务器对用户名和密码进行核验,如果正确则跳转到博客列表页,如果错误则返回相应的错误信息。
效果如下:
5.2 约定前后端交互接口
form表单和ajax构造请求的区别是:
- from会跳转页面,而ajax不会
- form只支持get和post请求,而ajax可以支持其他更多请求
5.3 实现前端代码(form部分)
<!-- 使用 form 包裹一下下列内容, 便于后续给服务器提交数据 -->
<form action="login" method="post">
<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">
<input type="submit" id="submit" value="登录">
</div>
</form>
注意:
- form表单此处的action和method要和约定的接口相匹配
-
input标签中的name属性就是构造请求的键值对中的“键”,用户输入的内容即为“值”
-
必须使用submit来提交form表单
5.4 实现后端代码
package api;
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();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.从请求中获取用户名和密码
req.setCharacterEncoding("utf8");
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username == null || username.equals("") || password == null || password.equals("")) {
String html = "<h3>用户名或密码不得为空!</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
//2.从数据库中读取用户名和密码进行比对
UserDao userDao = new UserDao();
User user = userDao.selectUserByName(username);
if(user == null) {
String html = "<h3>用户名或密码错误!</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
if(!password.equals(user.getPassword())) {
String html = "<h3>用户名或密码错误!</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
//3.用户名和密码都正确,登录成功,设置会话!
//会话可以理解为一个Hash表
//每个用户(客户端)对应表中一个键值对,其中键是一个字符串:sessionID(唯一字符串),值是HttpSession对象
HttpSession session = req.getSession(true);
session.setAttribute("user",user);
//4.返回一个重定向响应,跳转回博客列表页
resp.sendRedirect("blog_list.html");
}
}
六、实现强制登录逻辑
6.1 设计分析
当用户访问博客列表页、详情页、编辑页页面时必须是登录状态,如果未登录,则返回到登录页面。
在博客列表页、详情页、编辑页页面加载时,发起一个ajax请求,通过这个请求访问服务器并获取到当前登录状态,如果未登录则返回到登录页,如果已登录则不作任何操作。
6.2 约定前后端交互接口
6.3 实现前端代码
function getLoginStatus() {
$.ajax({
type: 'get',
url: 'login',
success: function(body) {
// 响应 200 的时候, 执行 success 回调.
// 用户已经登录, 啥都不用干.
console.log("用户已经登录了. ");
},
error: function(body) {
// 响应 403 的时候, 执行 error 回调. (只要返回不是 2 开头的, 都会触发 error)
// 跳转到 login.html 主页.
location.assign("login.html");
}
})
}
由于此处在博客列表页、详情页、编辑页都要判定是否登录,于是我们可以将这段代码分装到一个.js文件里中,然后只需要在各个页面中直接调用即可
<script src="js/app.js"></script>
6.4 实现后端代码
//通过这个方法判定用户登录状态
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession(false);
//会话不存在,拒绝查看请求,返回403,未登录状态
if(session == null) {
resp.setStatus(403);
return;
}
User user = (User)session.getAttribute("user");
//虽然会话存在,但是用户对象不存在,未登录状态,依旧返回403无权限查看
if(user == null) {
resp.setStatus(403);
return;
}
resp.setStatus(200);
resp.setContentType("application/json;charset=utf8");
user.setPassword("");
String respjson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respjson);
}
七、实现用户信息与作者信息分离
7.1 设计分析
效果如下:
7.2 约定前后端交互接口
1.针对博客列表页
2.针对博客详情页
7.3 实现前端代码
1.博客列表页:
function getLoginStatus() {
$.ajax({
type: 'get',
url: 'login',
success: function(body) {
// 响应 200 的时候, 执行 success 回调.
// 用户已经登录, 啥都不用干.
console.log("用户已经登录了. ");
let h3 = document.querySelector('.card h3');
h3.innerHTML = body.username;
},
error: function(body) {
// 响应 403 的时候, 执行 error 回调. (只要返回不是 2 开头的, 都会触发 error)
// 跳转到 login.html 主页.
location.assign("login.html");
}
})
}
// 判定用户的登录状态
getLoginStatus();
2.博客详情页 :
function getAuthor() {
$.ajax({
type: 'get',
url: 'user' + location.search,
success: function(body) {
// 把响应中得到的 user 对象的数据, 给构造到页面上.
if (body.userId == 0) {
// 服务器没有找到匹配的用户.
alert("当前未找到作者信息!");
return;
}
// body 是一个合法的 user 对象
let h3 = document.querySelector('.card h3');
h3.innerHTML = body.username;
}
});
}
getAuthor();
此处即为插入一个h3标签把当前用户名称加上去,其他内容需要更改也是类似如此。
7.4 实现后端代码
1.博客列表页
在BlogServlet后面加上这段代码即可
注意此处返回给前端的响应最好是不带密码的,保证用户密码安全性(掩耳盗铃)
resp.setStatus(200);
resp.setContentType("application/json;charset=utf8");
user.setPassword("");
String respjson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respjson);
2. 博客详情页
package api;
import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
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 java.io.IOException;
@WebServlet("/user")
public class UserServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf8");
String blogID = req.getParameter("blogID");
//1.直接返回一个userID为0的对象,方便前端进行处理
if(blogID == null || blogID.equals("")) {
String respjson = objectMapper.writeValueAsString(new User());
resp.getWriter().write(respjson);
System.out.println("blogID为空!");
return;
}
//2.查询数据库,查询对应的Blog对象
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(Integer.parseInt(blogID));
if(blog == null) {
String respjson = objectMapper.writeValueAsString(new User());
resp.getWriter().write(respjson);
System.out.println("blogID不存在!");
return;
}
//3.根据blog中的blogID,查询对应的作者信息
UserDao userDao = new UserDao();
User user = userDao.selectUserByID(blog.getUserID());
if(user == null) {
String respjson = objectMapper.writeValueAsString(new User());
resp.getWriter().write(respjson);
System.out.println("该博客对应的作者不存在!");
return;
}
//4.把user对象返回给页面
String respjson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respjson);
}
}
八、实现注销功能(退出登录)
8.1 设计分析
注销按钮为一个a标签,这个标签在点击的时候会发送一个GET请求,服务器收到这个请求之后就会把会话中的user对象给删除掉。
注意:
此处其实能删除整个sessionID:session1是更好的,但是Servlet中没有提供对应的API(其实提供了但是并不好用,提供的不是删除方法而是设置过期时间)。
8.2 约定前后端交互接口
8.3 实现前端代码
<a href="logout">注销</a>
只需要在列表页中插入一个a标签即可,属性与约定接口相对应。
8.4 实现后端代码
package api;
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 LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession(false);
if(session == null) {
resp.sendRedirect("login.html");
return;
}
session.removeAttribute("user");
resp.sendRedirect("login.html");
}
}
九、实现发布博客功能(博客编辑页)
9.1 设计分析
在博客编辑页中,用户点击提交,就会发送一个POST请求,把用户写的博客标题、正文、时间、用户名一起上传给服务器,服务器则可以保存上述数据到数据库中,接下来,就可以在博客列表页和详情页中看到新的博客数据了。
效果如下:
9.2 约定前后端交互接口
9.3 实现前端代码
<form action="blog" method="post">
<!-- 标题编辑区 -->
<div class="title">
<input type="text" id="title-input" name="title">
<input type="submit" id="submit">
</div>
<!-- 博客编辑器 -->
<!-- 把 md 编辑器放到这个 div 中 -->
<div id="editor">
<textarea name="content" style="display: none;"></textarea>
</div>
</form>
9.4 实现后端代码
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf8");
//1.先从请求中拿到标题和正文
String title = req.getParameter("title");
String content = req.getParameter("content");
if(title == null || title.equals("") || content == null || content == "") {
String html = "<h3>标题或者正文不得为空!</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
//2.从会话中拿到userID
HttpSession session = req.getSession(false);
if(session == null) {
String html = "<h3>当前用户未登录,无权限发布博客!</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
User user = (User)session.getAttribute("user");
if(user == null) {
String html = "<h3>当前用户未登录,无权限发布博客!</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
//3.构造Blog对象
Blog blog = new Blog();
blog.setUserID(user.getUserID());
blog.setTitle(title);
blog.setContent(content);
blog.setPostTime(new Timestamp(System.currentTimeMillis()));
//blogID为自增主键,不需要手动设定
BlogDao blogDao = new BlogDao();
blogDao.insert(blog);
//4.跳转到博客列表页
resp.sendRedirect("blog_list.html");
}
在BlogServlet类中重写doPost方法即可。
十、实现删除博客功能
10.1 设计分析
在博客详情页中点击a标签删除博客,会发送一个GET请求给服务器,服务器会判定只有当你是登录状态且你是本文作者时你才有权删除。
10.2 约定前后端交互接口
10.3 实现前端代码
<a href="#" id="delete-btn">删除博客</a>
//删除博客
function updateDeleteURL() {
let deleteBtn = document.querySelector('#delete-btn');
deleteBtn.href = 'blog_delete' + location.search;
}
updateDeleteURL();
10.4 实现后端代码
package api;
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;
@WebServlet("/blog_delete")
public class BlogDelete extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession(false);
resp.setContentType("text/html;charset=utf8");
if(session == null) {
resp.setStatus(403);
resp.getWriter().write("您当前未登录,无法删除!");
return;
}
User user = (User)session.getAttribute("user");
if(user == null) {
resp.setStatus(403);
resp.getWriter().write("您当前未登录,无法删除!");
return;
}
// 2. 获取到 blogId
String blogID = req.getParameter("blogID");
if(blogID == null) {
// 这个 blogId 参数不存在, 无法删除
resp.setStatus(404);
resp.getWriter().write("您当前删除的blogID有误");
return;
}
//3.查询出这个blog对应的blog对象
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(Integer.parseInt(blogID));
if(blog == null) {
resp.setStatus(404);
resp.getWriter().write("您当前删除的博客不存在");
return;
}
//4.判定当前用户是否为博客作者
if(blog.getUserID() != user.getUserID()) {
resp.setStatus(403);
resp.getWriter().write("您不是当前博客的作者,无权删除");
return;
}
//5.执行删除操作
blogDao.delete(Integer.parseInt(blogID));
//6.返回重定向操作
resp.sendRedirect("blog_list.html");
}
}
十一、总结
怎么说呢,这是第一个手动+看各种指导完成的第一个项目也是编写的第一篇博客,中途也遇到了很多问题,还是花了很多心血希望能吃透这个项目的,博客结构及内容有所参考,也欢迎各位大佬多多与我交流指导,共勉~