文章目录
- 效果展示
-
- 1. 创建 maven 项目
- 2. 设计数据库
- 3. 封装数据库的操作代码
-
- 3.1 创建 DBUtil 类
- 3.2 创建 Blog(代表一篇博客)
- 3.3 创建 User(代表一个用户)
- 3.4 创建类 BlogDao(对博客表进行操作)
- 3.5 创建类 UserDao (对用户表进行操作)
- 4. 导入之前已经写好的前端代码
- 5. 实现博客主页界面
-
- 5.1 约定好前后端交互接口
- 5.2 实现 BlogServlet
- 5.3 实现 前端代码
- 6. 实现博客详情界面
-
- 6.1 约定好前后端交互接口
- 6.2 实现BlogServlet
- 6.3 实现前端代码
- 7. 实现登录界面
-
- 7.1 约定好前后端交互接口
- 7.2 实现 LoginServlet
- 7.3 实现前端代码
- 8. 实现登录状态判定功能
-
- 8.1 约定前后端交互接口
- 8.2 在 LoginServlet 进行代码添加
- 8.3 在前端代码中创建 common.js
- 8.4 修改前端代码
- 9. 实现显示用户信息功能
-
- 9.1 约定好前后端交互接口
- 9.2 实现 AuthorServlet 代码
- 9.3 实现前端代码
-
- 针对博客列表页进行修改
- 针对博客详情页
- 10. 实现注销功能
-
- 10.1 约定好前后端交互接口
- 10.2 实现 LogouServlet
- 10.3 实现前端代码
- 11. 实现发布博客功能
-
- 11.1 约定好前后端交互的接口
- 11.2 在BlogServlet中添加doPost方法
- 11.3 实现前端代码
- 12. 删除博客
-
- 12.1 约定号前后端交互接口
- 12.1 实现前端代码
- 12.2 实现BlogDeleteServlet
效果展示
1. 创建 maven 项目
创建必要的目录,引入需要的依赖
2. 设计数据库
本系统要存入博客文章的信息和用户的信息
创建博客表:
博客的 id,博客的标题,博客的内容,博客的日期,博文的博主 id
创建用户表:
用户 id 用户名 用户密码
-- 创建一个数据库
create database if not exists java102_blog;
use java102_blog;
-- 创建一个博客表.
drop table if exists blog;
create table blog (
blogId int primary key auto_increment,
title varchar(1024),
content mediumtext,
userId int, -- 文章作者的 id
postTime datetime -- 发布时间
);
---- 给博客表中插入点数据, 方便测试.
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());
-- 创建一个用户表
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');
insert into user values(null, 'ling', '123');
3. 封装数据库的操作代码
创建包 model 用来存放数据库的代码
3.1 创建 DBUtil 类
用于和数据库建立连接
package model;
import com.mysql.cj.jdbc.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/java102_blog?characterEncoding=utf8&useSSL=false";
private static final String USERNAME = "root";
private static final String PASSWORD = "707703";
private volatile static 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 static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(statement != null) {
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(connection != null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
3.2 创建 Blog(代表一篇博客)
package model;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
// 每个 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;
}
// 把这里 的 getter 方法给改了,不是返回一个 时间搓对象,而是返回一个 string(格式化的时间)
public String getPostTime() {
// 使用这个类 来完成时间戳到格式化日期的转换
// 这个 转换过程,需要构造方法中制定要转换的格式,然后调用 format 来进行转换
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(postTime);
}
public void setPostTime(Timestamp postTime) {
this.postTime = postTime;
}
}
3.3 创建 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;
}
}
3.4 创建类 BlogDao(对博客表进行操作)
注意:
对数据进行插入删除操作时,执行 sql:
其他操作:
插入顺序:
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 throwables) {
throwables.printStackTrace();
}finally {
// 4. 关闭连接,释放资源
DBUtil.close(connection,statement,null);
}
}
// 2. 获取博客列表中的所有博客信息
public List<Blog> selectAll() {
List<Blog> blogs = new ArrayList<>();
// JDBC 代码
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 1. 先建立链接
connection = DBUtil.getConnection();
// 2. 构造 sql 语句
String sql = "select * from blog order by postTime desc";
statement = connection.prepareStatement(sql);
// 3. 执行 sql
resultSet = statement.executeQuery();
// 把查询到的数据 存储到 blogs 当中
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() > 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{
// 1. 建立连接
connection = DBUtil.getConnection();
// 2. 构造 sql 语句
String sql = "select * from blog where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,blogId);
// 3. 执行 sql
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 {
// 4. 关闭连接 释放资源
DBUtil.close(connection,statement,resultSet);
}
return null;
}
// 4. 从博客列表中,根据博客 id 删除博客
public void delete(int blogId) {
Connection connection = null;
PreparedStatement statement = null;
try {
// 1. 建立 连接
connection = DBUtil.getConnection();
// 2. 构造 sql 语句
String sql = "delete from blog where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,blogId);
// 3. 执行 sql
statement.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
DBUtil.close(connection,statement,null);
}
}
}
3.5 创建类 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{
// 1. 建立 连接
connection = DBUtil.getConnection();
// 2. 构造 sql 语句
String sql = "select * from user where username = ?";
statement = connection.prepareStatement(sql);
statement.setString(1,username);
// 3. 执行 sql
resultSet = statement.executeQuery();
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 {
// 4. 关闭连接 释放资源
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 {
// 1. 建立连接
connection = DBUtil.getConnection();
// 2. 构造 sql 语句
String sql = "select * from user where userId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,userId);
// 3. 执行sql
resultSet = statement.executeQuery();
// 此处的 username 使用 unique 约束,要么能查到一个,要么一个都查不到
// 4. 遍历结果集
if(resultSet.next()) {
User user = new User();
user.setUserId(resultSet.getInt("userId"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
DBUtil.close(connection,statement,resultSet);
}
return null;
}
}
4. 导入之前已经写好的前端代码
5. 实现博客主页界面
5.1 约定好前后端交互接口
5.2 实现 BlogServlet
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();
// 这个方法用来获取到数据库中的博客列表.
@Override
protected 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);
}
}
5.3 实现 前端代码
在 blog_list.html 中 实现 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, 构造出一个个的 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_detil.html?blogId=' + blog.blogId;
blogDiv.appendChild(a);
// 把 blogDiv 挂到 dom 树上!
rightDiv.appendChild(blogDiv);
}
},
error: function() {
alert("获取博客列表失败!");
}
});
}
getBlogList();
</script>
6. 实现博客详情界面
6.1 约定好前后端交互接口
6.2 实现BlogServlet
这里后端代码和博客列表页的获取,基本相同,就直接放到一个方法中,来实现!使用blogId参数来区别是获取博客列表还是详情
这里注意:博客列表页在也在BlogServlet中实现的,我们如何去区别呢?:
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;
/**
* // doGet 获取博客列表页
*/
@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);
}
}
}
6.3 实现前端代码
修改 blog_detail.html
,让这个页面加载的时候,能够调用到上述的接口,来从服务器获取到博客数据
注意:
在博客详情页中引入 editor
<script>
function getBlogDetail() {
$.ajax({
type: 'get',
// location.search 拿到了形如 '?blogId=5' 这样的一段内容
url: 'blog2' + location.search,
success: function(body) {
// 根据 body 中的内容来构造页面
// 1. 构造博客标题S
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>
7. 实现登录界面
这里需要注意:啥样才算是已经登录
用户有一个 session,同时 session 有一个 user 属性 ~
两者同时具备,才叫登录状态
这里先给注销做铺垫:理解一下注销是如何操作的:
注销只要破坏掉上面的任意一个条件就行了
7.1 约定好前后端交互接口
7.2 实现 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("utf-8");
resp.setCharacterEncoding("utf-8");
// 1. 获取到请求中的参数
String username = req.getParameter("username");
String password = req.getParameter("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");
}
}
7.3 实现前端代码
修改blog_login.html
这里只需要在原来登录操作上套上一层 form 标签就可以了
因为这里在原有的基础上填上了 form表单,我们在 css 中也会有一些改动
<!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>
<!--这是导航栏 nav-->
<div class = "nav">
<img src="imge/ph.png" alt="">
<!--标题 span-->
<span>我的博客系统</span>
<!--这是一个空白元素用来占位置-->
<div class="spacer"></div>
<!--链接标签<a>-->
<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>
</div>
</body>
</html>
8. 实现登录状态判定功能
8.1 约定前后端交互接口
8.2 在 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("utf-8");
resp.setCharacterEncoding("utf-8");
// 1. 获取到请求中的参数
String username = req.getParameter("username");
String password = req.getParameter("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");
}
// // 这个方法用来让前端检测当前的登录状态~~~~~~~~~~~~~~~~~~~~~~
// 1)检测有无会话 2)检测有无属性
@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) {
// 1) 检测有无会话,无说明未登录
// 创建对象--> 里面为空
User user = new User();
resp.getWriter().write(objectMapper.writeValueAsString(user));
return;
}
User user = (User) session.getAttribute("user");
if(user == null) {
// 2)虽然有会话,但是会话里如果没有 user 对象,也视为未登录
user = new User();
resp.getWriter().write(objectMapper.writeValueAsString(user));
return;
}
// 已经登录的状态!
// 注意,此处不要把密码返回到前端
user.setPassword("");
resp.getWriter().write(objectMapper.writeValueAsString(user));
}
}
8.3 在前端代码中创建 common.js
// 这个文件里放一些 页面公共的代码
// 加上一个逻辑,通过 GET/login 这个接口来获取下当前的登录状态
function getUserInfo() {
$.ajax({
type: 'get',
url: 'login',
success: function(body) {
// 判定此处的 body 是不是一个有效的 user 对象(userId 是否非 0)
if(body.userId && body.userId >0) {
// 登录成功
// 不做处理
console.log("当前用户登录成功!用户名:" + body.username);
}else{
// 登录失败
// 让前端页面,跳转到 login.html
alert("当前你尚未登录!请登录后再访问博客列表!");
location.assign('blog_login.html');
}
},
error: function() {
alert("当前你尚未登录!请登录后再访问博客列表!");
location.assign('blog_login.html');
}
});
}
getUserInfo();
8.4 修改前端代码
在
blog_list.html
blog_detil.html
中引入 js文件,就可以执行到里面的代码,也就进行了登录状态的监测了
<script src="js/commom.js"></script>
9. 实现显示用户信息功能
9.1 约定好前后端交互接口
9.2 实现 AuthorServlet 代码
package controller;
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;
/**
* 根据博客 id 查 博客信息
* 根据 博客信息里的 作者 id 查 作者信息
*/
@WebServlet("/authorInfo")
public class AuthorServlet 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 param = req.getParameter("blogId");
if (param == null || "".equals(param)) {
// 参数缺少了.
resp.getWriter().write("{ \"ok\": false, \"reason\": \"参数缺失!\" }");
return;
}
// 根据当前 blogId 在数据库中进行查找, 找到对应的 Blog 对象, 再进一步的根据 blog 对象, 找到作者信息.
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(Integer.parseInt(param));
if (blog == null) {
resp.getWriter().write("{ \"ok\": false, \"reason\": \"要查询的博客不存在!\" }");
return;
}
// 根据 blog 对象, 查询到用户对象
UserDao userDao = new UserDao();
User author = userDao.selectById(blog.getUserId());
if (author == null) {
resp.getWriter().write("{ \"ok\": false, \"reason\": \"要查询的用户不存在!\" }");
return;
}
// 把 author 返回到浏览器这边
// 注意要把密码给干掉!
author.setPassword("");
resp.getWriter().write(objectMapper.writeValueAsString(author));
}
}
9.3 实现前端代码
针对博客列表页进行修改
在 common.js 中 修改代码
针对博客详情页
10. 实现注销功能
在导航栏中安排一个“注销”按钮,当用户点击注销之后,就会在服务器上取消登录状态,并且能够跳转到登录页面
10.1 约定好前后端交互接口
10.2 实现 LogouServlet
package controller;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.Enumeration;
@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");
}
}
10.3 实现前端代码
将 blog_detail.html blog_list.html bloh_edit.htm中的注销标签进行修改
11. 实现发布博客功能
在博客编辑页
中,当用户输入博客标题
,和正文
之后,点击发布
此时就会把博客数据提交到服务器,由服务器存储到数据中
11.1 约定好前后端交互的接口
11.2 在BlogServlet中添加doPost方法
@Override
protected 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");
}
11.3 实现前端代码
<!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.min.js"></script>
</head>
<body>
<!-- 这是导航栏 -->
<div class="nav">
<img src="imge/ph.png" 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">
<!-- <button>发布文章</button> -->
<input type="submit" value="发布文章" id="submit">
</div>
<!-- 放置 md 编辑器 -->
<div id="editor">
<!-- 为了进行 form的提交,此处搞一下 textarea 多行编辑框,借助这个编辑框来实现表单的提交-->
<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/",
// 此处要加上一个重要的选项,然后 editr.md 就会自动把用户在编辑器输入的内容同步保存到 隐藏的 textarea中
saveHTMLToTextarea:true,
});
</script>
</body>
</html>
12. 删除博客
12.1 约定号前后端交互接口
12.1 实现前端代码
<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
});
}
});
}
// 加上一个逻辑,通过 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) {
getBlogDetail();
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>
12.2 实现BlogDeleteServlet
package controller;
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("/blogDelete")
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.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;
}
// 2. 获取到参数中的 blogId
String blogId = req.getParameter("blogId");
if (blogId == null || "".equals(blogId)) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前 blogId 参数不对!");
return;
}
// 3. 获取要删除的博客信息.
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
if (blog == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前要删除的博客不存在!");
return;
}
// 4. 再次校验, 当前的用户是否就是博客的作者
if (user.getUserId() != blog.getUserId()) {
// 这一点在前端这里其实也处理过~~ 但是此处还是再校验一次, 不是坏事!!!
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前登录的用户不是作者, 没有权限删除!");
return;
}
// 5. 确认无误, 开始删除
blogDao.delete(Integer.parseInt(blogId));
// 6. 重定向到博客列表页
resp.sendRedirect("blog_list.html");
}
}