目录
2.1、首先通过创建Maven项目,基于Small Tomcat部署 servlet;
前言
这里所要介绍的博客系统,是一个简单的网站程序,如果你能吃透这个博客系统,也是可以作为一个项目写到简历上的;
博客系统简要分析
咱们将要实现的博客系统,分为四个网页:博客列表页、博客详情页、博客登录页、博客编辑页;要实现的功能有:实现博客列表的展示功能、实现博客详情的展示功能、登录功能、限制用户权限(强制要求用户登录后查看信息)、显示用户信息、实现注销(退出登录)、发布博客、删除博客;
接下来就来带大家实现一下~
一、数据库的设计
1.1 分析
实际上,咱们的业务逻辑较为简单,只需要一个库,两张表即可;
两张表分别是:
1. 博客表 :blog(blogId, title, content, postTime, userId); ——> (博客ID,博客标题,博客正文,发布时间,用户ID);
2. 用户表 user(userId, username, password); ——> (用户ID,用户名,用户密码)
1.2 代码实现(创建数据库和表)
create database if not exists blog_system;
use blog_system;
-- 这里删除是为了以防以前过相同的表有干扰
drop table if exists blog;
-- 这是博客table
create table blog (
blogId int primary key auto_increment, -- 博客id
title varchar(256), -- 标题
content text, -- 正文
postTime datetime, -- 发布时间
userId int -- 用户Id
);
drop table if exists user;
-- 这是用户table
create table user (
userId int primary key auto_increment, -- 用户id
username varchar(50) unique, -- 用户名(类似于账户不可以重复)
password varchar(50) -- 密码
);
-- 插入数据用于测试
insert into blog values(null, "这是第一篇博客", "努力敲代码,走上人生巅峰", '2022-11-24 18:00:00', 1);
insert into blog values(null, "这是第二篇博客", "努力敲代码,走上人生巅峰", '2022-11-24 18:00:00', 1);
insert into user values(null, "zhangsan", "123");
insert into user values(null, "lisi", "123");
二、封装数据库(JDBC代码的编写)
2.1、首先通过创建Maven项目,基于Small Tomcat部署 servlet;
这里如果不了解的,可以去看看我的这篇博客:
所需要引入的依赖:
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.4.1</version>
</dependency>
</dependencies>
2.2、封装数据库的DataSource
因为我们的博客系统网站是要给很多人用的,所以在数据库的创建数据源、建立连接、断开连接这里,可以引入单例模式中的懒汉模式,对以上所说步骤进行封装;
第一步:在java目录下创建类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 volatile DataSource dataSource = null;
public static DataSource getDataSource() {
if(dataSource == null) { //第一次是为了判断是否加锁
synchronized(DBUtil.class) {
if(dataSource == null) { //第二次判断是因为加锁开销大,时间长,有可能此时数据源已经被其他程序使用时创建好了
dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/blog_system?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("1111");
}
}
}
return dataSource;
}
//建立连接
public static Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
//关闭连接
public void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
//[注意1]这里不要直接用throws抛异常,因为一旦哪一个发生了异常,
// 就有可能存在后面的还有需要释放的资源没释放掉的情况;
//[注意2]注意释放顺序,和创建顺序是相反的
if(resultSet != null) {//这里判断是因为有的jdbc操作不需要他,但却要close其他的
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();
}
}
}
}
【对以上代码的解释】:为什么close方法中采用try catch的方式处理异常而不是throws?
这里不要直接用throws抛异常,因为一旦哪一个发生了异常,就有可能存在后面的还有需要释放的资源没释放掉的情况;
【对以上代码的解释】:为什么close方法中需要判断是否为null才执行close操作?
这里是将所有的close操作放在一起了,所以一旦执行close操作,就会对所以资源进行close,但实际上,有时候可能有的数据源我们都没有创建(例如,有时不用创建ResultSet),这时,对没有创建的数据进行close,就会导致null异常,所以这里需要判断是否为空,再释放;
第二步:在java目录下创建Blog类,用来表示实体类(如下代码)
import java.sql.Timestamp;
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 String getPostTime() {
//这里时间不能直接返回(直接返回是一个时间戳)
//所以需要改一下这个方法,让他返回一个格式化的时间日期
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;
}
}
【对以上代码的解释】:
简单来说,就是你的数据库中创建的blog表有什么属性,就写什么就ok;
【对以上代码的解释】:对getPostTime方法的解释
这里不能通过getter直接返回日期数据,直接返回的话,是一个时间戳(如下图)
这里需要通过SimpleDateFormat这个类将时间戳格式化成时间日期,初始化这个类的时候就需要填写的格式,具体怎么填写,建议大家不要背,因为不同语言之间这个大小写格式差异很大,所以建议用的时候查一下官方文档就好;(使用此类后,效果如下)
第三步:在java目录下创建类BlogDao,用数据库来操作博客数据
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
//对博客信息的相关操作
// +----------+--------------+------+-----+---------+----------------+
// | Field | Type | Null | Key | Default | Extra |
// +----------+--------------+------+-----+---------+----------------+
// | blogId | int(11) | NO | PRI | NULL | auto_increment |
// | title | varchar(256) | YES | | NULL | |
// | content | text | YES | | NULL | |
// | postTime | datetime | YES | | NULL | |
// | userId | int(11) | YES | | NULL | |
// +----------+--------------+------+-----+---------+----------------+
public class BlogDao {
//1.插入一个博客到数据库中 -- 发布博客
public void insert(Blog blog) {
Connection connection = null;
PreparedStatement statement = null;
try {
//建立连接
connection = DBUtil.getConnection();
//构造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.getBlogId());
//执行sql
int ret = statement.executeUpdate();
if(ret == 1) {
System.out.println("用户" + blog.getUserId() + "插入1个博客!");
} else {
System.out.println("用户" + blog.getUserId() + "博客插入失败!");
}
//[注意]:不可以在这里close,上面代码一旦抛出异常,这里就无法执行close;
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭数据源
DBUtil.close(null, statement, connection);
}
}
//2.根据博客id查询博客 -- 博客详情页
public Blog selectOne(int blogId) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//建立连接
connection = DBUtil.getConnection();
//创建sql
String sql = "select * from Blog where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, blogId);
//执行sql
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.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
return blog;
}
} catch(SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(resultSet, statement, connection);
}
return null;
}
//3.查询博客列表 -- 博客列表页
public List<Blog> selectAll() {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
List<Blog> blogList = new ArrayList<>();
try {
//建立连接
connection = DBUtil.getConnection();
//构造sql
String sql = "select * from blog order by postTime desc";
statement = connection.prepareStatement(sql);
//执行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");
//在博客列表页,正文一旦超出100个字,就截断,用...代替
if(content.length() > 100) {
content = content.substring(0, 100) + "...";
}
blog.setContent(content);
blog.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
blogList.add(blog);
}
} catch(SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(resultSet, statement, connection);
}
return blogList;
}
//4.删除指定博客 -- 删除博客
public void delete(String blogId) {
Connection connection = null;
PreparedStatement statement = null;
try {
//建立连接
connection = DBUtil.getConnection();
//构造sql
String sql = "delete from Blog where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setString(1, blogId);
//执行sql
int ret = statement.executeUpdate();
if(ret != 1) {
System.out.println("博客删除成功!");
} else {
System.out.println("博客删除失败!");
}
} catch(SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(null, statement, connection);
}
}
}
【对以上代码的解释】:BlogDao类名这样叫什么用意?
DAO是 Data Access Object 的缩写,也就是说访问数据库操作,就可以通过后缀含有DAO的类来操作;
【对以上代码的解释】:为什么使用try catch finally,而不用throws?
以防代码还没有关闭数据库连接就发生了异常,导致内存泄漏;
【对以上代码的解释】:为什么要对博客正文内容进行substring?
此处是博客列表页,不因该把博客正文全部显示出来,而是因该只显示文章摘要,所以这里就对摘要部分进行截断,长度超出限制(这里限制自己根据需求定义),就取出一部分子串,剩下部分用”…”代替;
第四步:在java目录下创建User类,用来实体话对象(和Blog类同理)
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;
}
}
第五步:在java目录下创建UserDao,用数据库来操作用户数据(和BlogDao类同理)
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//用户表的相关操作
public class UserDao {
//用户登录
public User selectByName(String username) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//建立连接
connection = DBUtil.getConnection();
//构造sql
String sql = "select * from User where username = ?";
statement = connection.prepareStatement(sql);
statement.setString(1, username);
//执行sql
resultSet = statement.executeQuery();
if (resultSet.next()) {
User user = new User();
user.setUserId(resultSet.getInt("userId"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
} catch(SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection, statement, resultSet);
}
return null;
}
//根据用户id查询用户信息
public User selectById(int userId) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//建立连接
connection = DBUtil.getConnection();
//构造sql
String sql = "select * from user where userId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, userId);
//执行sql
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;
}
}
三、实现博客列表页
3.1、业务逻辑及解释
在博客列表页中,在页面加载的时候,需要通过ajax发起HTTP请求,从服务器获取到博客列表数据,所以这里我们就要提前约定好前后端交换接口:考虑好发什么样的请求,返回什么样的响应。
3.2、约定前后端交互接口
在blog_list.html页面(前端HTML)中,发起如下请求:
在java目录下创建BlogServlet类,用来处理get请求,并返回如下http响应
3.3实现客户端代码代码
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
//发送 ajax 从服务器获取数据
function getBlogs() {
$.ajax({
type: 'get',
url: 'blog',
success: function(body) {
//获取成功,那么body就是js对象数据,每个元素就是一篇博客
let container = document.querySelector('.container-right');
for(let blog of body) {
//构造blogDiv
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');
//这里href加上了一个query string,是为了查看全文的内容可以通过ID对应到对应的文章
a.href = 'blog_detail.html?blogId=' + blog.blogId;
a.innerHTML = "查看全文 >>";
//拼装
container.appendChild(blogDiv);
blogDiv.appendChild(titleDiv);
blogDiv.appendChild(dateDiv);
blogDiv.appendChild(descDiv);
blogDiv.appendChild(a);
}
}
});
}
getBlogs();
</script>
3.4、实现服务器代码
这里创建了一个新的类BlogServlet;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
public class BlogServlet extends HttpServlet {
//json格式转换
ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//按照约定格式返回数据
resp.setContentType("application/json; charset=utf8");
BlogDao blogDao = new BlogDao();
List<Blog> blogList = blogDao.selectAll();
resp.getWriter().write(objectMapper.writeValueAsString(blogList));
}
}
四、实现博客详情页
4.1、业务逻辑及解释
在博客列表页点击查看详情,跳转到详情页,并看到详情页的博客正文;
点击查看全文,就在blog_html页面发起一个get请求,这里就需要告诉服务器,是请求的哪一个博客,那么就要约定,在请求页面url中加上query string(这里之前已经加好了,如下图)
进入详情页后,就需要让博客详情页再次发送ajax请求,向服务器获取当前blogId对应的博客内容,再由博客详情也把数据展示到页面中;
4.2 约定前后端交互接口
(例如获取博客ID为1的博客)
4.3、实现服务器代码
这里实际上对BlogServlet类进行了修改
import com.fasterxml.jackson.databind.ObjectMapper;
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;
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
//json数据格式的转换
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//博客列表页中已经使用了过了/blog下的doGet请求,但是
//博客详情页还想继续用,这里就需要用query string来区分了
//若请求中带有query string,也就是含有blogId这个参数,就说明是博客详情页发起的请求
//若没有,则说明是博客列表页发起的请求
resp.setContentType("application/json; charset=utf8");
BlogDao blogDao = new BlogDao();
String blogId = req.getParameter("blogId");
if(blogId == null) { //说明是博客列表页
List<Blog> blogList = blogDao.selectAll();
resp.getWriter().write(objectMapper.writeValueAsString(blogList));
} else { //说明是博客详情页
Blog blog = blogDao.selectOne(Integer.valueOf(blogId));
resp.getWriter().write(objectMapper.writeValueAsString(blog));
}
}
}
4.4、实现客户端代码
<!-- 通过ajax请求获取详细博客详细 -->
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
function getBlog() {
$.ajax({
type: 'get',
url: 'blog' + location.search, //location.search 是用来获取当前url中的query string
success: function(body) {//这里成功的话,会获取到一个blog
let h3 = document.querySelector('.container-right>.blog-detail>h3');
h3.innerHTML = body.title;
let dateDiv = document.querySelector('.blog-detail>.date');
dateDiv.innerHTML = body.postTime;
//content部分需要使用editor.md来渲染,否则就是一个普通文本格式(注意引入editor.md依赖)
//以下写法是官方约定的写法:
editormd.markdownToHTML('content', { markdown: body.content });
}
});
}
getBlog();
</script>
【对以上代码的解释】:location.search是干什么的?
这是用来获取当前url里的query string;
例如:假设当前url为: 127.0.0.1:8080/blog?blogId = 1;通过location.search就可以获取到
“?blogId = 1″;
【对以上代码的解释】:editormd.markdownToHTML是干什么的?
由于content部分的内容是使用markdown写的,如果直接通过body.content得到正文内容,就是一个简单的文本格式,所以这里需要editor.md渲染文本;这里的这段代码是官方文档上的固定写法;
注意:这里用editor.md渲染文本,需要引入依赖(如下)
<!-- 引入 editor.md 的依赖 -->
<link rel="stylesheet" href="editor.md/css/editormd.min.css" />
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/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>
五、实现博客登录页
5.1、业务逻辑及解释
用户访问login.html,输入用户名和密码,点击登录按钮,发起一个请求,将用户名和密码交给服务器;服务器对身份进行验证,若验证成功,就让页面跳转到博客列表页,若验证失败,就返回错误信息;
5.2、约定前后端交互接口
5.3、实现客户端代码
<div class="login-container">
<form action="login" method="post">
<!-- 登录对话框 -->
<div class="dialog">
<h3>登录</h3>
<div class="row">
<span>用户名</span>
<!-- 这两个框很关键, 后面还要用, 就起个 id 作为标识 -->
<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="login-btn" value="登录">
</div>
</div>
</form>
</div>
【对以上代码的解释】:
1.注意action和method和约定的请求格式是匹配的;
2.input标签中的name属性就是构造请求的键值对中的“键”,用户输入的内容则是“值”;
3.form约定,必须使用 input type=”submit”来提交表单;
5.4、实现服务器代码
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.从请求中获取用户名和密码
req.setCharacterEncoding("utf-8");//用户名有可能为中文
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username == null || username.equals("") || password == null || password.equals("")) {
//判断用户名和密码为空,返回登录失败
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;
}
//4.若正确,创建一个会话
HttpSession session = req.getSession(true);
//会话中保存user信息,为了后续访问其他页面,就可以直接通过会话拿到当前页面是哪一个用户在访问
session.setAttribute("user", user);
//5.构造重定向302报文
resp.sendRedirect("blog_list.html");
}
}
六、实现强制登录逻辑
6.1、业务逻辑及解释
在博客列表页、详情页、编辑页、都在页面加载后、发起一个ajax;这个ajax就从服务器获取当前登陆状态,若当下是未登录,就直接重定向到登录页,若已登录,不做任何处理;
6.2、前后端交互接口
6.3、实现客户端代码
function getLoginStatus() {
$.ajax({
type: 'get',
url: 'login',
success: function() {
//状态码是200,就不用做任何事情
console.log("当前页面已经登录过~");
},
error: function() {
//只要是非2开头的状态码,就会触发这个error
//就让页面强制跳转到登录页面
console.log("当前还未登录");
location.assign('login.html');
}
});
}
getLoginStatus();
【对以上代码的解释】:
判断状态码:若状态码是200则没有事情发生,若状态码是403,则强制跳转;
6.4、实现服务器代码
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这个方法用来检验当前的登录状态
//1.获取当前会话
HttpSession session = req.getSession(false);
if(session == null) {
//没有回话就是未登录状态
resp.setStatus(403);
return;
}
//检查user对象是否存在
//已经判断了session,为什么还要判断user?
//因为当点击“退出登录”之后,我们实现的逻辑是删除user对象,session并没有删除
//所以这里需要判断
User user = (User)session.getAttribute("user");
if(user == null) {
//有会话,但是没有user对象,也认为是未登录状态
resp.setStatus(403);
return;
}
//2.返回状态码200(也可以不返回,因为默认状态码是200)
resp.setStatus(200);
}
【对以上代码的解释】:已经判断了session,为什么还要判断user?
我们实现退出登录的逻辑是只删除user对象,不删除session,那么当点击退出登录之后,就会出现session存在,而user对象不存在的情况,所以这里仍需要判断user,即使有会话,user不存在,也认为是未登录状态;
七、博客列表页中的显示用户信息
7.1、业务逻辑及解释
博客列表页中:加载页面时,从服务器获取当前登录用户信息,将信息显示到页面上;
博客详情页中:加载页面时,从服务器获取博客作者用户信息,把信息显示到页面上;
7.2、前后端交互接口
7.3实现服务器代码
import com.fasterxml.jackson.databind.ObjectMapper;
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("/userInfo")
public class UserInfoServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取用户信息
String blogId = req.getParameter("blogId");
if(blogId == null) {
//说明当前访问的是博客列表页,获取登录用户信息即可
//直接从session中获取即可
getUserInfoFromSession(req, resp);
}else {
//说明当前访问的是博客详情页,获取文章作者即可
//从数据库中获取
getUserInfoFromDB(req, resp, Integer.valueOf(blogId));
}
}
private void getUserInfoFromDB(HttpServletRequest req, HttpServletResponse resp, int blogId) throws IOException {
//1.根据blogId查询Blog对象,获取到userId(作者)
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(blogId);
if(blog == null) {
//若blogId是瞎写的,数据库中查询不到
resp.setStatus(404);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("blogId不存在");
return;
}
//2.根据userId查询User对象
UserDao userDao = new UserDao();
User user = userDao.selectById(blog.getUserId());
if(user == null) {
resp.setStatus(404);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("userId不存在");
return;
}
//2.获取到user后,将其中的password删除(保护用户信息安全),并返回
user.setPassword("");
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(objectMapper.writeValueAsString(user));
}
private void getUserInfoFromSession(HttpServletRequest req, HttpServletResponse resp) throws IOException {
//1.获取session会话
HttpSession session = req.getSession(false);
if(session == null) {
resp.setStatus(404);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前未登录");
}
User user = (User) session.getAttribute("user");
if(user == null) {
resp.setStatus(404);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前未登录");
}
//2.获取到user后,将其中的password删除(保护用户信息安全),并返回
user.setPassword("");
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(objectMapper.writeValueAsString(user));
}
}
7.4、实现客户端代码
博客详情页:
//博客详情页,对当前作者信息进行获取
function getUserInfo() {
$.ajax({
type: 'get',
url: 'userInfo' + location.search,//注意这里要加上qurey string,才能得到当前作者信息
success: function(body) {
//这里主要改用户名,其他内容要改逻辑都是一样的
let h3 = document.querySelector('.container-left>.card>h3');
h3.innerHTML = body.username;
}
});
}
getUserInfo();
博客列表页:
//博客列表页,对当前用户信息进行获取
function getUserInfo() {
$.ajax({
type: 'get',
url: 'userInfo',
success: function(body) {
//这里主要改用户名,其他内容要改逻辑都是一样的
let h3 = document.querySelector('.container-left>.card>h3');
h3.innerHTML = body.username;
}
});
}
getUserInfo();
八、实现注销功能(退出登录)
8.1、业务逻辑及解释
注销按钮是 a标签 ,点击他就会给服务器发送一个http请求,发送的http请求会告诉服务器要退出登录了,服务器就会把user对象删除(此处不是删除HttpSession对象的是因为HttpSession没有一个直接用于删除的方法,即使可以通过过期时间来删除,但并不是一个很好的办法;这里就呼应了前写判断登录状态的条件是HttpSession&&user存在),并且重定向到 登录页;
8.2、前后端交互接口
8.3、实现客户端代码
这里只需要修改前端代码中的注销a标签的href属性即可;
<a href="logout">注销</a>
8.4、实现服务器代码
这里需要重新创建一个类LogoutServlet,用来服务注销;
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 {
//1.获取session会话
HttpSession session = req.getSession(false);
if(session == null) {
resp.setStatus(404);
return;
}
//2.直接删除session中的user对象
session.removeAttribute("user");
//3.重定向到登录界面
resp.sendRedirect("login.html");
}
}
九、实现发布博客功能
9.1、业务逻辑及解释
用户在博客编辑页里,填写博客标题和正文,点击发布博客,发起HTTP请求,服务器接收到这些数据,构造Blog对象保存到数据库中;
9.2、前后端交互接口
9.3、实现服务器代码
在BlogServlet类下,创建doPost方法
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.检查用户登录状态
HttpSession session = req.getSession(false);
if(session == null) {
resp.setStatus(403);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前未登录,不能发布博客!");
return;
}
User user = (User) session.getAttribute("user");
if(user == null) {
resp.setStatus(403);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前未登录,不能发布博客!");
return;
}
//2.获取博客标题和正文
req.setCharacterEncoding("utf8");
String title = req.getParameter("title");
String content = req.getParameter("content");
//3.构造Blog对象,插入数据库中
Blog blog = new Blog();
//blogId是自增主键,不用设置
blog.setTitle(title);
blog.setContent(content);
blog.setUserId(user.getUserId());
BlogDao blogDao = new BlogDao();
blogDao.insert(blog);
//4.返回主页
resp.sendRedirect("blog_list.html");
}
9.4、实现客户端代码
<div class="blog-edit-container">
<form action="blog" method="post" style="height: 100%;">
<!-- 标题的编辑区 -->
<div class="title">
<!-- 输入的标题内容 -->
<input type="text" id="blog-title" placeholder="在这里输入博客标题" name="title">
<input id="submit" value="发布文章" type="submit">
</div>
<!-- 正文的编辑区 -->
<div id="editor">
<textarea name="content" style="display:none"></textarea>
</div>
</form>
</div>
<script src="js/app.js"></script>
<script>
// 初始化编辑器, 代码也是截取自 官方文档 .
var editor = editormd("editor", {
// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
width: "100%",
// 设定编辑器高度
height: "calc(100% - 50px)",
// 编辑器中的初始内容
markdown: "## hello world",
// 指定 editor.md 依赖的插件路径
path: "editor.md/lib/",
//用户输入markdown内容想要被textarea获取到,就需要如下写法(官方文档规定)
saveHTMLToTextarea: true
});
//强制登录逻辑
getLoginStatus();
【对以上代码的解释】:saveHTMLToTextarea是什么?
当用户使用markdown输入内容时,要想让textarea获取到被渲染的内容,就需要这样写,这是官方文档规定;
十、实现删除博客功能
10.1、业务逻辑及解释
点击删除按钮,会触发删除操作;通过a标签的href属性发起一个HTTP GET请求,但是删除的时候会做出一个判定,只有当前登录用户时文章作者,才能执行删除;
10.2、约定前后端接口
10.3、实现服务器代码
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 BlogDeleteServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 先判定用户的登陆状态
HttpSession session = req.getSession(false);
if (session == null) {
resp.setStatus(403);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("您当前未登录, 不能删除!");
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
resp.setStatus(403);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("您当前未登录, 不能删除!");
return;
}
// 2. 获取到 blogId
String blogId = req.getParameter("blogId");
if (blogId == null) {
// 这个 blogId 参数不存在, 无法删除
resp.setStatus(404);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("您当前删除的 blogId 有误");
return;
}
// 3. 查询出这个 blogId 对应的 Blog 对象
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
if (blog == null) {
// 这个 blogId 参数不存在, 无法删除
resp.setStatus(404);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("您当前删除的博客不存在! blogId=" + blogId);
return;
}
// 4. 判定登陆用户是否就是文章作者
if (blog.getUserId() != user.getUserId()) {
// blog.getUserId() 文章的作者
// user.getUserId() 从 session 里拿的登陆的用户是谁.
// 不一样, 说明在删别人的文章.
// 直接返回 403
resp.setStatus(403);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前您不能删除别人的博客!");
return;
}
// 5. 真正执行删除操作.
blogDao.delete(blogId);
// 6. 返回 302 重定向
resp.sendRedirect("blog_list.html");
}
}
【对以上代码的解释】:
大部分逻辑都是判定非法情况,这是一个好意识,这些情况都尽可能进行处理;
10.4、实现客户端代码
1.加上一个a标签即可
<a href="#" id="delete-btn">删除</a>
2.初始化的时候给href赋值上对应的url加上blogId,就能识别出当前是谁在删博客
//删除博客
function updateDeleteURL() {
let deleteBtn = document.querySelector('#delete-btn');
deleteBtn.href = 'blog_delete' + location.search;
}
updateDeleteURL();
十一、结语
想要前端HTML代码可以私信我哦~实际上大家也可以自己尝试一下,做一个大体的网页框架,本文已经详细给出了前后端交互的具体实现,希望可以帮助到你!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/129836.html