✨哈喽,进来的小伙伴们,你们好耶!✨
🛰️🛰️系列专栏:【Java项目】
✈️✈️本篇内容:个人博客系统前后端分离实现!
🚀🚀个人代码托管github:博客系统源码地址!
⛵⛵作者简介:一名双非本科大三在读的Java编程小白,道阻且长,星夜启程!
本篇博客开始之前,需要说明的是前面我们已经实现了博客系统的纯前端页面,详情可以参考这篇博客:博客系统前端页面!或者直接点击本专栏的上一篇博客即可!
那么,本篇博客是基于"个人博客系统"的前端页面,编写一个后端服务器,和之前写的这个页面进行联动,实现前后端分离的功能;
OK,接下来,我们开始编写博客系统;
目录
一、准备工作
1、打开idea,创建maven项目;
2、引入依赖;
3、创建相关的目录;
4、编写代码
5、6:打包部署
7、 在浏览器验证程序;
二、开始正式编写服务器代码
model层;
Controller层;
一、准备工作
1、打开idea,创建maven项目;
1)File->new Project;
2)选择创建maven项目,然后点击next;
3)给咱们的maven项目取个名字,博主这里取的是Blog_System2;然后选择文件夹存放代码;
2、引入依赖;
这里我们需要引入的依赖是servlet,jackson,mysql;
引入方法:打开下面这个链接:maven中央仓库;
举例:servlet依赖的引入方式:
1)打开链接,在搜索框内输入servlet,点击search,找到下面箭头指向的这个,点进去;
2)鼠标滚轮向下滑动,找到3.0.1版本(跟自己电脑的tomcat和jdk的版本对应)
3)找到下面红色框框中的内容,Ctrl+c复制;
4)粘贴到我们刚刚创建的maven项目下pom.xml里面,注意需要用<dependency>包裹起来;
博主这里只演示一下servlet是如何引入的,像mysql和Jackson都是类似的,直接根据上面的步骤来引入即可;
3、创建相关的目录;
1)点开我们项目下的src文件,找到main,然后右键创建一个新的目录,webapp;在webapp下创建一个目录叫做WEB-INF,然后在WEB-INF下面创建一个新的xml文件叫做web.xml;创建好之后大概如下,(webapp下有些css、js文件这是后来引入的,这里不用管)
2)在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、编写代码
这里我们先简单写个无关的测试代码,方便咱们后续步骤的执行;
1)找到java文件,在java文件下创建一个类叫做HelloServlet;
2)写一个简单的实验代码;
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("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("你好,博客系统!");
}
}
5、6:打包部署
这里打包部署在博主之前的博客已经介绍过,使用idea自带的插件smart tomcat来完成;
7、 在浏览器验证程序;
二、开始正式编写服务器代码
1、开篇已经介绍过,这里我们的前端代码可以直接全部粘贴到webapp下,这样就把代码集成到我们的项目上了;后续打包部署的时候,这些资源会被一并打包部署,也就可以在浏览器访问到了;
粘贴过来之后,对应的目录结构;
2、先来实现我们MVC结构中的M部分;博主这里在java文件下建一个包"model"负责编写M部分的代码;
这里来介绍一下前面写的博客系统页面:
1、博客列表页:显示博客的列表;
2、博客详情页,点击博客列表页,上面列出的博客篇数,可以跳转到完整的博客内容;
3、登录页面;
4、博客编辑页,基于editor.md整了一个markdown编辑器,根据这个页面来发布博客;
联想到数据库,这里需要存储博客,点击发布的时候,博客被发表到服务器上,需要被存储起来;
获取博客,在博客列表页和博客详情页,能够拿到博客的内容;
即我们需要设计一张博客表,用来存储所有的博客数据;设计一张用户表,用户登录的时候就需要用到这个表;
开始编写数据库代码:
1)在main下创建一个File,叫做db.sql;
-- 编写建库建表的 sql
create database if not exists blog_system character set utf8mb4;
use blog_system;
-- 创建一个博客表.
drop table if exists blog;
create table blog (
blogId int primary key auto_increment,
title varchar(256),
content mediumtext, -- 正文较长,使用mediumtext类型
userId int, -- 文章作者的 id
postTime datetime -- 发布时间
);
-- 创建一个用户表
drop table if exists user;
create table user (
userId int primary key auto_increment,
username varchar(30) unique, -- 后续会使用用户名进行登录, 一般用于登录的用户名都是不能重复的.
password varchar(20)
);
insert into user values(null, 'zhangsan', '123');
insert into user values(null, 'lisi', '123');
2)编写完成之后,打开mysql客户端,然后复制上面的SQL语句,粘贴到我们的mysql客户端;
3)检查数据库和表是否创建完成;
2、封装数据库;
model层;
1)首先创建一个DBUtil类,用来封装数据库连接操作;
package model;
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/blog_system?characterEncoding=utf8&useSSL=false";
private static final String USERNAME = "root";
private static final String PASSWORD = "";//填写自己电脑mysql数据库密码
private static volatile DataSource dataSource = null;//注意加上volatile
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 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();
}
}
}
}
2)创建实体类;
使用实体类来表示数据库中的一条记录;
这里包含了两个类分别是Blog、User;
User:
package model;
// 每个 model.User 对象, 期望能够表示 user 表中的一条记录.
public class User {
private int userId = 0;
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:
package model;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
// 每个 model.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;
}
3、封装针对数据增删改查;
这里有个很形象的词叫做DAO,即把提供了对增删改查这样的类的叫法;
创建Blog_Dao:(核心代码都提供了注释)
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) 执行 SQL
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} 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 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");
// 这个 50 自定义,随便给一个就行
if (content.length() > 50) {
content = content.substring(0, 50) + "...";
}
blog.setContent(content);
blog.setUserId(resultSet.getShort("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
blogs.add(blog);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} 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 , 要么是 0.
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.getShort("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
return blog;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} 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 throwables) {
throwables.printStackTrace();
} finally {
DBUtil.close(connection, statement, null);
}
}
// 注意, 上述操作是 增删查, 没有改
}
与Blog_Dao对应的就是User_Dao:
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 throwables) {
throwables.printStackTrace();
} 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 throwables) {
throwables.printStackTrace();
} finally {
DBUtil.close(connection, statement, resultSet);
}
return null;
}
}
OK,到这里我们的数据库的操作都已经准备好了,接下来开始编写服务器中的Controller层;
Controller层;
1、首先,来约定前后端交互的接口,分别编写客户端代码,服务器代码;
1)从博客列表页开始;
这个页面的作用就是能够展示出数据库中博客的列表;
效果如下:
2) 约定
请求:GET/blog
响应:这里采用的是json格式来编写代码;在括号里面应该包含:
[{blogId:1,title:'这是第一篇博客',content:'这是博客正文',userId:1,postTime:'2023-01-20 19:00:00'},{blogId:2,title:'这是第二篇博客',content:'这是博客正文',userId:1,postTime:'2023-01-21 19:00:00'},]
由于是博客列表页,博客内容只能展示一部分,所以这里的content可以看成是摘要;即截取正文的一部分内容;
新建一个类BlogServlet:(部分代码)
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();
// 这个方法用来获取到数据库中的博客列表.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset=utf8");
BlogDao blogDao = new BlogDao();
// 先尝试获取到 req 中的 blogId 参数. 如果该参数存在, 说明是要请求博客详情
// 如果该参数不存在, 说明是要请求博客的列表.
String param = req.getParameter("blogId");
if (param == null) {
// 不存在参数, 获取博客列表
List<Blog> blogs = blogDao.selectAll();
// 把 blogs 对象转成 JSON 格式.
String respJson = objectMapper.writeValueAsString(blogs);
resp.getWriter().write(respJson);
} else {
// 存在参数, 获取博客详情
int blogId = Integer.parseInt(param);
Blog blog = blogDao.selectOne(blogId);
String respJson = objectMapper.writeValueAsString(blog);
resp.getWriter().write(respJson);
}
}
}
代码写完之后,我们来验证这个代码是否正确,可以使用postman来访问这个接口的数据;
1)首先打开postman,然后输入对应的请求;输入完成之后点击"send",可以看到结果;
可以看到结果是一个空,说明我们的json数组里面还没有插入数据;我们可以在博客表blog中插入一些数据;
-- 给博客表中插入点数据, 方便测试.
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());
insert into blog values(null, '这是第二篇博客', '从昨天开始, 我要认真学 C++', 2, now());
insert into blog values(null, '这是第三篇博客', '# 一级标题\n ### 三级标题\n > 这是引用内容', 2, now());
在MySQL终端中写以上数据;
3、编写客户端代码
这里我们想实现的是,在页面加载的时候,让页面通过ajax访问服务器,获取到数据库中的博客数据,并且填写到页面中;
首先编写的是blog_list.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_list.css">
</head>
<body>
<!-- 这是导航栏 -->
<div class="nav">
<img src="image/logo.jpeg" alt="">
<span>我的博客系统</span>
<!-- 空白元素, 用来占位置 -->
<div class="spacer"></div>
<a href="blog_list.html">主页</a>
<a href="blog_edit.html">写博客</a>
<a href="logout">注销</a>
</div>
<!-- 这里的 .container 作为页面的版心 -->
<div class="container">
<!-- 左侧个人信息 -->
<div class="left">
<!-- 表示整个用户信息区域. -->
<div class="card">
<img src="image/头像.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, 构造出一个个的 blogDiv
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;
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>
此时需要在浏览器访问这个url地址,可以观察到我们的博客系统页面,blog_list;
http://127.0.0.1:8080/blog_system2/blog_list.html
接下来编写blog_detail.html文件的内容;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客详情页</title>
<link rel="stylesheet" href="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/logo.jpeg" alt="">
<span>我的博客系统</span>
<!-- 空白元素, 用来占位置 -->
<div class="spacer"></div>
<a href="blog_list.html">主页</a>
<a href="blog_edit.html">写博客</a>
<a href="logout">注销</a>
</div>
<!-- 这里的 .container 作为页面的版心 -->
<div class="container">
<!-- 左侧个人信息 -->
<div class="left">
<!-- 表示整个用户信息区域. -->
<div class="card">
<img src="image/doge.png" 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.html
alert("当前您尚未登录! 请登录后再访问博客列表!");
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>
blog_login.html;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录页面</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/blog_login.css">
</head>
<body>
<!-- 这是导航栏 -->
<div class="nav">
<img src="image/logo.jpeg" 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界面;
直接点击登录,会出现以下提示;
在controller包下创建一个新的java文件,LoginServlet;
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();
@Override
protected 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");
}
// 这个方法用来让前端检测当前的登录状态.
@Override
protected 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));
}
}
这个代码里面,会在服务器这边拿到了session,并且视为也拿到了里面的user,视为登录成功!
若登录成功,服务器会给客户端返回sessionid,浏览器会保存这个sessionid,下次请求的时候就带上这个id,服务器拿到了sessionid就可以去hash表里查,就可以知道当前的session对象是谁;
注销功能;
这里的注销其实就是退出登录的状态;我们这里设计的是在导航栏里面给一个"注销"按钮;当用户点击注销,就会在服务器上取消登录状态,并且能够跳转到登录页面;
同样,这里需要先约定前后端交互的接口;
请求:GET/logout
响应:HTTP/1.1 302
Location:login.html
注销逻辑的服务器代码:
在controller包下创建一个新的类LogoutServlet;
package controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 先找到当前用户的会话,
HttpSession session = req.getSession(false);
if (session == null) {
// 用户没有登录!! 谈不上注销!
resp.getWriter().write("当前用户尚未登录! 无法注销!");
return;
}
// 然后把这个用户的会话中的信息给删掉就行了!!
session.removeAttribute("user");
resp.sendRedirect("blog_login.html");
}
}
运行实例:
1)当点击注销按钮时,会直接回到登录页面;
登录状态是啥样的?
这里用户有一个session,同时session有一个属性,两者同时具备,才算登录状态;那么注销的话只要破坏上面任意一个条件即可,这里选择的是把user属性从session中删除掉;
发布博客;
在博客编辑页中,当用户输入了博客标题和正文之后,点击发布,此时就会把博客数据提交到服务器,由服务器存储到数据库中;
blog_edit.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_edit.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/logo.jpeg" alt="">
<span>我的博客系统</span>
<!-- 空白元素, 用来占位置 -->
<div class="spacer"></div>
<a href="blog_list.html">主页</a>
<a href="blog_edit.html">写博客</a>
<a href="logout">注销</a>
</div>
<!-- 包裹整个博客编辑页内容的顶级容器 -->
<div class="blog-edit-container">
<form action="blog" method="post" style="height:100%">
<div class="title">
<input type="text" placeholder="在此处输入标题" name="title" id="title">
<!-- <button>发布文章</button> -->
<input type="submit" value="发布文章" id="submit">
</div>
<!-- 放置 md 编辑器 -->
<div id="editor">
<!-- 为了进行 form 的提交, 此处搞一个 textarea 多行编辑框, 借助这个编辑框来实现表单的提交 -->
<!-- 可以设置 editor.md, 让编辑器把 markdown 内容也同步的保存到这个隐藏的 textarea 中, 从而可以进行 form 提交 -->
<textarea name="content" style="display:none"></textarea>
</div>
</form>
</div>
<script>
// 初始化编辑器
let editor = editormd("editor", {
// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
width: "100%",
// 设定编辑器高度
height: "calc(100% - 50px)",
// 编辑器中的初始内容
markdown: "# 在这里写下一篇博客",
// 指定 editor.md 依赖的插件路径
path: "editor.md/lib/",
// 此处要加上一个重要的选项, 然后 editor.md 就会自动把用户在编辑器输入的内容同步保存到 隐藏的 textarea 中了!
saveHTMLToTextarea: true,
});
</script>
</body>
</html>
与之对应的在服务器代码中,在BlogServlet中添加一个doPost方法,来处理上述post请求;
最终效果如下:
OK,以上就是个人博客系统的全部分析代码+解析过程了,感谢您的阅读,项目源码已经放在文章开头,如有需要,可自取,我们下期再见!