目录
一、项目效果展示
二、创建 Servlet 项目
整个项目的目录:
pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>blog_system</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.6.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
</project>
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>
三、编写数据库的操作代码
1、创建数据库/表结构(数据库设计)(Model)
mysql里,unique + not null = primary key,又要求一个表里只能有一个 primary key。
-- 编写建库建表的 sql
-- 建库
create database if not exists blogsystem;
use BlogSystem;
-- 创建一个博客表
-- 包括:博客ID,博客标题,博客内容,博客作者,博客发布时间
drop table if exists blog;
create table blog (
blogId int primary key auto_increment,
title varchar(1024),
content mediumtext,
userId int,
postTime datetime
);
-- 创建一个用户表
-- 包括:用户ID,用户名,用户密码
drop table if exists user;
create table user (
userId int primary key auto_increment,
username varchar(128) unique, -- 后续会使用用户名进行登录,因此用户名是不可以重复的。
password varchar(128)
);
-- 向表中添加数据测试。
-- now() 返回的是一个时间戳,需要将时间戳转化成格式化时间,
-- 这个转化可以在前端来做,也可以在后端做。
-- 这里选择在 Blog类中进行转换。(修改 getter 方法)
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,'这是第 1 篇博客','从今天开始我要认真学习咯',2,now()) ;
insert into blog values(null,'这是第 2 篇博客','从昨天 开始我要认真学习咯',2,now());
insert into blog values(null,'这是第 3 博客','# 一级标题\n ### 三级标题\n > 这是引用内容',2,now());
insert into blog values(null,'这是第 四 博客','# 一级标题\n',1,now());
insert into user values(null,'zhangsan','123');
insert into user values(null,'www','1234');
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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WangWZ
* Date: 2023-05-07
* Time: 15:11
*/
public class DBUtil {
private static String URL = "jdbc:mysql://127.0.0.1:3306/blogsystem?characterEncoding=utf8&useSSL=false";
private static String USERNAME = "root";
private static String PASSWORD = "12345";
//创建一个数据源对象
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 preparedStatement, ResultSet resultSet) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
(2)创建实体类。使用实体类表示数据库中的一条记录
此处 主要创建了 Blog 类 和 User 类。
package Model;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WangWZ
* Date: 2023-05-07
* Time: 17:22
*/
public class Blog {
private int blogId;
private String title;
private String content;
private int uesrId;
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 getUesrId() {
return uesrId;
}
public void setUesrId(int uesrId) {
this.uesrId = uesrId;
}
// public Timestamp getPostTime() {
// return postTime;
// }
public String getPostTime() {
//使用 SimpleDataFormat 来完成时间戳到格式化时间的转换
//这个方法需要指定转化的格式,然后调用 format 来进行转换
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(postTime);
}
public void setPostTime(Timestamp postTime) {
this.postTime = postTime;
}
}
package Model;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WangWZ
* Date: 2023-05-07
* Time: 17:32
*/
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)封装针对数据的增删改查。
提供了增删改查这样的类,称为 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;
/**
* Created with IntelliJ IDEA.
* Description: 这个类用于封装 博客表 的基本操作(没有改的操作)
* (1)往博客表里插入博客
* (2)从博客表里,根据 博客ID 删除博客
* (3)查询所有的博客(用于博客列表页)
* (4)根据 博客ID 查询博客内容(用于博客详情页)
*
* 每个方法都要实现 JDBC 基本代码
* (1)和数据库建立连接
* (2)构造 SQL 语句
* (3)执行 SQL 语句
* (4)关闭连接,释放资源
* User: WangWZ
* Date: 2023-05-07
* Time: 17:34
*/
public class BlogDao {
//1、往 博客表 里插入一个博客
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,?,?,?,now())";
statement = connection.prepareStatement(sql);
statement.setString(1,blog.getTitle());
statement.setString(2,blog.getContent());
statement.setInt(3,blog.getUesrId());
//3.执行 SQL
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection,statement,null);
}
}
//2、从 博客表 中,根据 博客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) {
e.printStackTrace();
} finally {
DBUtil.close(connection,statement,null);
}
}
//3、查询所有博客(用于博客列表页,注意这里不一定会获取到完整的正文)
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"));
//获取博客内容的时候要进行截取
//自定义只显示前 50 行
// blog.setContent(resultSet.getString("content"));
String content = resultSet.getString("content");
if(content.length() > 50) {
content = content.substring(0,50) + ".....";
}
blog.setContent(content);
blog.setUesrId(resultSet.getInt("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
blogs.add(blog);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection,statement,resultSet);
}
return blogs;
}
//4、根据 博客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.setUesrId(resultSet.getInt("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
return blog;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection,statement,resultSet);
}
return null;
}
}
package Model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Created with IntelliJ IDEA.
* Description: 用户类 的基本操作
* 这里实现的博客系统,不涉及用户的注册和注销操作。因此只有在数据库中进行查找的相关操作。
* (1)根据 用户ID 查找用户信息(用于博客详情页,可以根据 用户ID 来查询作者的名字,并进行显示)
* (2)根据 用户名 查找用户信息(用于登录页面)
*
* User: WangWZ
* Date: 2023-05-07
* Time: 19:05
*/
public class UserDao {
//1.根据 用户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();
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;
}
//2.根据 用户名 查找用户信息(用于登录页面)
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();
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、博客列表页
展示出数据库中的博客的列表。
(1)约定前后端接口
获取博客列表。
请求:
GET /blog
响应:(使用json格式)json数组,每一个元素又是一个json对象。
[
{
blogId:1,
title:' 这是第一篇博客 ',
content:' 博客正文摘要 ',
userId:1,
postTimer:' 2023-05-7 20:00:00 '
},
{
blogId:2,
title:' 这是第二篇博客 ',
content:' 博客正文摘要 ',
userId:1,
postTimer:' 2023-05-7 20:01:00 '
},
......
]
摘要:如果正文太长,就只截取一小部分。
(2)编写服务器代码
这里的代码会更新。
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
resp.setContentType("application/json; charset=utf-8");
BlogDao blogDao = new BlogDao();
List<Blog> blogs = blogDao.selectAll();
String jsonString = objectMapper.writeValueAsString(blogs);
resp.getWriter().write(jsonString);
}
(3)编写客户端代码
在页面加载的时候,让页面通过 ajax 访问服务器,获取到数据库中的博客数据,并且填到页面中。
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
// 在页面加载的时候,就通过调用函数 getBlogList 来通过 ajax 给服务器发送数据,获取博客列表信息,并且显示在界面上
function getBlogList() {
$.ajax({
type: 'get',
url: 'blog',
success: function(body) {
//获取到的 body 就是一个 js 数组。(ajax 自动帮我们转换了)
//每个元素就是一个 js 对象,根据这个对象,构造 div
//1、先把之前的 div 里的内容清空。
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);
}
}
})
}
getBlogList();
</script>
2、博客详情页
在 blog_detail.html 页面加载的时候,触发 ajax 请求来访问服务器,获取到博客内容,再次填充到博客详情页里。
(1)约定前后端交互接口
请求:
GET /blog?blogId=1
响应:这里的响应结果不是数组,而是单一的对象
HTTP/1.1 200 OK
Content-Type:application/json;
{
blogId:1,
title:' 第一篇博客 ',
content:' 正文 ',
userId:1,
postTimer:' 2023-05-7 20:00:00 '
}
(2)实现服务器端代码
因为上面传的url还是blog下,所以依旧在 BlogServlet 类中写。
注意:这里都是GET请求,但是只有一个doGet
一个是 GET /blog ,一个是GET /blog?blogId=1,参数不同,我们就可以实现不同的参数实现不同的功能。
package Controller;
import Model.Blog;
import Model.BlogDao;
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;
/**
* Created with IntelliJ IDEA.
* Description: 通过这个类,处理 blog 路径对应的请求
* 用于 博客列表页
* User: WangWZ
* Date: 2023-05-07
* Time: 19:20
*/
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BlogDao blogDao = new BlogDao();
resp.setContentType("application/json;charset=utf8");
//判断传过来的参数中是否有 blogId 的参数
//1.先尝试获取 req 中的blogId 参数,如果参数存在,说明是要请求博客详情的。
//如果参数不存在,说明是请求博客的列表。
String param = req.getParameter("blogId");
if (param == null) {
//不存在参数 blogId,说明要请求博客列表。
//从数据库中查询到博客列表,转成 JSON 格式,然后直接返回即可
List<Blog> blogs = blogDao.selectAll();
//把 blog 对象转成 JSON 格式
String respJson = objectMapper.writeValueAsString(blogs);
resp.getWriter().write(respJson);
} else {
//存在参数 blogId,说明要请求博客的详情
//这里要赋值 blogId,但是我们使用 req.getParameter 获取的是 String类型的值
//因此使用 Integer.parseInt(),将 String 类型转为 int类型。
int blogId = Integer.parseInt(param);
Blog blog = blogDao.selectOne(blogId);
//把 blog 对象转成 json 对象格式
String respJson = objectMapper.writeValueAsString(blog);
resp.getWriter().write(respJson);
}
}
}
(3)实现前端代码
修改 blog_detail.html,让这个页面加载的时候,能够调用上述接口,来从服务器获取到博客数据。
在前端代码中,要想构造一个请求获取博客详情,就得知道当前用户点击的博客的ID,而这个ID 已经包含在当前的 blog_detail.html 页面的 url 里了。通过 location.search 可以进行获取,获取后在 ajax 中的 url 后面一加就可以了。
<script>
function getBlogDetail() {
$.ajax({
type: 'get',
// 注意:
//① 这里的 blog :相对路径; /blog :绝对路径
//② loction.search 拿到了形如 '?blog=1' 这样的一段内容
url: 'blog' + location.search,
success:function(body) {
//请求成功,对于服务器返回来的 body 进行页面的构造
//1.构造博客标题
let h3 = document.querySelector(".blog-content>h3");
h3.innerHTML = body.title;
//2.构造博客发布时间
let date = document.querySelector('.date');
date.innerHTML = body.postTime;
//3.构造正文
// 注意我们写的博客是用 merkdown 写的,因此如果直接把 content 设为 innerHTML ,此时展示在界面上的内容,是原始的 Markdown 字符串
// 而我们这里需要的是渲染后的,带有格式的效果的正文。
//因此这里还要导入 Markdown的依赖,利用里面提供的方法进行转换。
//第一个参数对应 id=content 的 html 标签,渲染后得到的 html 片段就会被放到这个标签下。
editormd.markdownToHTML('content',{
markdown: body.content
});
}
});
}
getBlogDetail();
</script>
3、博客登录页
实现用户登录功能。
(1)约定前后端交互接口
请求:
POST /login
Content-Type:application/x-www-form-urlencoded
username=zhangsan&password=123
这里的逻辑,可以直接使用 form表单 来进行提交,没必要非得用 ajax(使用 ajax 也可以)。要使用表单,就需要把按钮改成 input type= " submit "
响应:
HTTP/1.1 302
Location:blog_list.html
(2)实现客户端代码
修改 blog_login.html。
套上 form 标签;
给 input 加上 name 属性,是后续提交数据的键值对的key;
把 button 按钮换成 input 标签;submit
注意页面样式是否会改变(css/js)
<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>
代码中约定的路径是/login,因此创建一个新的 Servlet。
(3)实现服务器端代码
LoginServlet.java。
为了避免Servlet解析请求数据时,汉字出现乱码:
req.setCharacterEncoding("utf8"); 针对请求进行设置,使用utf8格式来解析请求。
resp.setCharacterEncoding("utf8"); 针对响应进行设置,构造的数据要按照utf8构造。
//1.获取到请求中的参数
//2.和数据库中的进行比较
//3.如果比较通过,就创建会话
//4.返回一个重定向报文,跳转到博客列表页
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//考虑当我们的用户名有中文时,Servlet汉字解析编码不是utf8,所以我们要进行规定。
req.setCharacterEncoding("utf8");
resp.setCharacterEncoding("utf8");
//1.获取到请求中的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//2.和数据库中的进行比较
//特殊情况
if(username == null || "".equals(username) || password == null || "".equals(password)) {
//请求的内容缺失,登录失败
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前用户登录用户名或密码为空");
return;
}
//在数据库中找
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");
}
4、检测用户登录状态
调整博客列表页和博客详情页,让这两个页面必须登录后才能访问。在进入博客列表页/博客详情页的时候,先检查一下用户的登录状态,如果用户当前已经是登录状态,才能继续使用。如果是未登录状态,则强制跳转到 login 页面。
在博客列表页和博客详情页加载的时候,通过 ajax 访问一下服务器,获取当前的登录状态。如果获取到了,就说明当前已经登录了,可以留在这个页面。如果没有获取到,说明未登录,跳转到登录页面。
(1)约定前后端交互接口
请求:
GET /login
响应:
HTTP/1.1 200 OK
Content-Type:application/json
{
userId:1,
username:' zhangsan ',
}
登录了,就直接返回当前登录的用户信息;
没登录,则返回一个 userId 为 0 的对象。(也可以用其他方式来约定:403表示未登录... )
{
userId:0,
username:' ',
}
(2)实现服务端代码
在LoginServlet.java中添加doGet方法。
//用这个方法来让前端检测当前的登录状态
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//在服务器这边拿到了 session,并且也拿到了里面的 user。视为是登录成功。
//(如果登陆成功的话,服务器会给客户端返回 session,浏览器就会保存这个 session。下次请求的事就就会带上这个 id )
//服务器拿到 sessionId 就可以去 hash 表里查看,就知道了当前的 session 对象是谁。
resp.setContentType("application/json;charset=utf8");
HttpSession session = req.getSession(false);
//因为当用户存在在数据库时,才创建了会话,所以先判断会话是否存在
//若不存在,说明一定未登录
if (session == null) {
User user = new User();
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
return;
}
//存在会话,继续判断
User user = (User)session.getAttribute("user");
if (user == null) {
//有会话,但是会话里没有 User 对象,也视为未登录
user = new User();
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
return;
}
//剩下情况就是登录了的情况
//注意不要把密码返回给前端
user.setPassword("");
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
}
(3)实现客户端代码
在blog_list.html 和 blog_detail.html 中添加如下代码。
<script>
function getUserInfo(pageName) {
$.ajax({
type: 'get',
url: 'login',
success:function(body) {
//判定此处的 body 是不是一个有效的 user 对象(userId 是否非0)
if(body.userId && body.userId > 0) {
//登陆成功不作处理
consloe.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");
</script>
5、显示用户信息
- 在博客列表页,显示当前登录的用户。
- 在博客详情页,显示当前作者的信息。
(1)针对博客列表页
其实前面一斤处理过了(检测用户登录状态的时候)。调整前端代码即可。
在 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);
//根据当前用户登录的情况,把当前用户名设置到界面上(博客列表页的左边信息部分)
changeUserName(body.username);
} else {
//登录失败
//让前端页面,跳转到哦 login.html
alert("当前您尚未登录!请登录后自访问博客列表!");
location.assign('blog_login.html');
}
},
error:function() {
alert("当前您尚未登录!请登录后再访问博客列表!");
location.assign('blog_login.html');
}
});
}
getUserInfo();
//修改用户名,填入列表页的登录用户信息区域的名字里
function changeUserName(username) {
// .card>h3:通过类名 card 找到card,再找到里面的h3标签。再用innerHTML放入
let h3 = document.querySelector('.card>h3');
h3.innerHTML = username;
}
blog_list.html:
<!-- 因为下面的代码要重复使用(博客列表页和博客详情页),所以写在一个新的文件里,直接引入文件即可 -->
<!-- 在这里引入 js 代码,就可以执行到里面的代码,也就实现了登录状态的检测了 -->
<script src="js/common.js"></script>
注意:这里的头像、github、文章的统计没有设置在数据库中,所以这里没有完全实现显示不同用户的全部信息。(逻辑上没有区别)
(2)针对博客详情页
对上面的 html 继续进行修改。让博客列表页(用户)和博客详情页(作者)显示不同的内容。
让服务器提供一个新的接口,这个接口可以让客户端指定 blogId,获取到指定的blogId的作者信息。
请求:
GET /authorInfo? blogId=6
响应:
{
userId:6,
username:' wwz',
}
然后就在博客详情页,给服务器发送这个接口,来获取到当前的数据。另外对于博客详情页的 html 来说,也需要进行修改(不能设置名字了)。
common.js
// 这个文件里放一些公共代码
// 加上一个逻辑,通过 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);
//根据当前用户登录的情况,把当前用户名设置到界面上(博客列表页的左边信息部分)
//博客详情页,通过其他的API来进行设定页面中的用户信息
if(pageName == 'blog_list.html') {
changeUserName(body.username);
}
changeUserName(body.username);
} else {
//登录失败
//让前端页面,跳转到哦 login.html
alert("当前您尚未登录!请登录后自访问博客列表!");
location.assign('blog_login.html');
}
},
error:function() {
alert("当前您尚未登录!请登录后再访问博客列表!");
location.assign('blog_login.html');
}
});
}
//修改用户名,填入列表页的登录用户信息区域的名字里
function changeUserName(username) {
// .card>h3:通过类名 card 找到card,再找到里面的h3标签。再用innerHTML放入
let h3 = document.querySelector('.card>h3');
h3.innerHTML = username;
}
blog_list.html
<!-- 因为下面的代码要重复使用(博客列表页和博客详情页),所以写在一个新的文件里,直接引入文件即可 -->
<!-- 在这里引入 js 代码,就可以执行到里面的代码,也就实现了登录状态的检测了 -->
<script src="js/common.js"></script>
<script>
//针对博客列表页,调用的时候传入参数
getUserInfo('blog_list.html');
</script>
blog_detail.html
function getUserInfo(pageName) {
$.ajax({
type: 'get',
url: 'login',
success:function(body) {
//判定此处的 body 是不是一个有效的 user 对象(userId 是否非0)
if(body.userId && body.userId > 0) {
//登陆成功不作处理
consloe.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");
创建新类 AuthorServlet 来获取指定博客的作者信息。
package Controller;
import Model.Blog;
import Model.BlogDao;
import Model.User;
import Model.UserDao;
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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WangWZ
* Date: 2023-05-09
* Time: 9:02
*/
@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)) {
//blogId 不存在 或 为0
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.getUesrId());
if (author == null) {
//数据库中没有要查找的用户信息
resp.getWriter().write("{ \"ok\": false, \"reason\": \"要查找的用户不存在\" }");
return;
}
//找到作者信息了,进行返回
//注意直接返回会返回用户密码,所以要读密码进行覆盖。
author.setPassword("");
//注意把 author Java对象转成 Json字符串再进行传输
resp.getWriter().write(objectMapper.writeValueAsString(author));
}
}
并且在 blog_detail.html 中添加逻辑,发送 ajax 请求,获取当前博客的作者信息,并让作者信息进行显示。
// 从服务器获取一下当前博客的作者信息,并显示到界面上
function getAuthorInfo() {
$.ajax({
type:'get',
url:'authorInfo' + location.search,
success:function(body) {
//此处的 body,就是服务器返回的 User 对象
if(body.username) {
//如果响应中的 username 存在,就把这个值设置到界面上
changeUserName(body.username);
} else {
console.log("获取作者信息失败!" + body.reason);
}
}
});
}
getAuthorInfo();
1、博客列表页,显示登录的用户信息,在检测用户是否登录的接口中,就已经拿到了。只用把拿到的用户信息进行显示即可。(直接在前端代码中修改)
2、博客详情页,提供了一个新的 API,让客户端传一个 博客id过去,然后再服务器这里查询当前的用互信息。查到后返回给页面。
6、注销功能
退出当前登录的状态。在导航栏中设置一个注销按钮,用户点击后,就取消登录状态,并跳转到登录页面。
(1)约定前后端交互接口
点击注销后,给服务器发送一个请求,实现注销。即把会话中的信息删除。
请求:
GET /logout
响应:
HTTP/1.1 302
Location:login.html
(2)实现服务器代码
创建 LogoutServlet 进行处理这个请求。
登录:用户有一个session ,同时 session 有一个 user 属性,两者同时具备才是登录状态。
注销:只要破坏上面的任意一个条件就行。这里把 user 属性从 session 中删除。
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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WangWZ
* Date: 2023-05-09
* Time: 9:59
*/
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//通过这个方法实现注销,即从会话中删除 user 属性。
HttpSession session = req.getSession();
if (session == null) {
//没有会话,说明用户没有登录,谈不上注销
resp.getWriter().write("当前用户尚未登录!无法注销!");
return;
}
//有会话,在会话中将 user 属性删除。
session.removeAttribute("user");
//删除后注销了,跳转到登录页面
resp.sendRedirect("blog_login.html");
}
}
(2)实现客户端代码
只用修改注销中的 href 即可。(除了登录页面没有注销按钮,其他页面都要添加)
<a href="logout">注销</a>
7、发布博客功能
在写博客的页面里,点击发布文章按钮,进行博客发布。
(1)约定前后端交互接口
点击发布后,将博客数据提交到服务器,由服务器存储到数据库中。
请求:
POST /blog
Content-Type:application/x-www-form-urlencode
title=这是标题&content=这是正文
(内容都是需要 urlencode 的,浏览器自己自动进行编码实现)
响应:
HTTP/1.1 302
Location:blog_list.html
(2)实现服务器端代码
在 blogServlet 代码中,添加 doPost 方法,来处理这个 POST 请求。
//通过这个方法实现发布博客
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//读取请求中的标题和正文,构造 blog 对象,并插入数据库。最后跳转到博客列表页
HttpSession session = req.getSession(false);
if (session == 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 || content == null || "".equals(title) || "".equals(content)) {
//直接告诉客户端,请求参数不对
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("提交博客失败,缺少必要的参数");
return;
}
//数据正常,构造blog 对象,进行插入数据库
Blog blog = new Blog();
blog.setTitle(title);
blog.setContent(content);
//这里还需要插入博客作者的 id,而这个id 存放在session 中,因此再使用 session 来获取 user属性中的 userId
User user =(User)session.getAttribute("user");
if (user == null) {
//说明当前用户未登录,不能提交博客。
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前用户未登录,不能提交博客");
return;
}
blog.setUesrId(user.getUserId());
//将 blog 对象插入数据库
BlogDao blogDao = new BlogDao();
blogDao.insert(blog);
//发布完成,重定向到博客列表页
resp.sendRedirect("blog_list.html");
}
(3)实现客户端代码
使用 form表单,把这里的内容套上。
① 添加 form 表单,把之前的输入框等部分进行包裹;
② 添加 name 属性
③发布按钮,改成input标签
④创建隐藏的 textarea,为了后续的提交
⑤在初始化编辑器部分,添加设置标志位,使当前的输入框的内容能自动保存到 textarea 中。
⑥更改发布按钮的样式。以为父元素改变,设置新父元素的高度。
<!-- 包裹整个博客编辑页内容的顶级容器 -->
<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 多行编辑框,借助这个编辑框来实现表单的提交 -->
<!-- 可以设置 editor.md,让编辑器把 markdown 内容也同步的保存到这个隐藏的 textarea 中,从而可以进行 form 提交 -->
<textarea name="content" style="display: none;"></textarea>
</div>
</form>
</div>
<script>
// 初始化编辑器
var editor = editormd("editor", {
// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
width: "100%",
// 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
height: "calc(100% - 50px)",
// 编辑器中的初始内容
markdown: "# 在这里写下一篇博客",
// 指定 editor.md 依赖的插件路径
path: "editor.md/lib/",
// 注意要加上这个选项,有了这个选项,editor.md就会自动把用户在编辑器输入的内容同步保存到隐藏的 textarea 中了
saveHTMLTOTextarea: true,
});
</script>
css中的样式:将 button 改为#submit。
8、删除博客功能
前提,只有自己能删除自己的博客,不能删除别人的博客。
界面上:博客详情页中,判断当前登录用户是否是该博客的作者,如果是,显示一个删除按钮;如果不是,就不显示。
在博客详情页中,两个 ajax 是异步并发的关系。 第一个 ajax 发出后,不等响应回来,就已经发了第二个 ajax。如果这两个响应到达的顺序不确定,就不好进行判定了,因此必须手动的调整这两个方法,按照一定的顺序来进行发送。
若想先发UserInfo,后发AuthorInfo。在第一个 ajax 中执行第二个 ajax,才能保证两个 ajax 之间获取数据的顺序先后:
function getUserInfo(pageName) {
$.ajax({
type: 'get',
url: 'login',
success:function(body) {
//判定此处的 body 是不是一个有效的 user 对象(userId 是否非0)
if(body.userId && body.userId > 0) {
//登陆成功不作处理
consloe.log("当前用户登录成功!用户名: " + body.username);
//在 getUserInfo 的回调函数中,用来调用获取作者信息
getAuthorInfo(body);
} else {
//登录失败
//让前端页面,跳转到 login.html
alert("当前您尚未登录!请登录后再访问博客列表!");
//这个方法是在前端页面进行跳转的方式。
location.assign('blog_login.html');
}
},
error: function() {
alert("当前您尚未登录!请登录后再访问博客列表!");
location.assign('blog_login.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);
}
}
});
}
(1)约定前后端交互接口
服务器上:用户点击删除按钮,就发送一个 HTTP 请求,让服务器删除指定的博客,服务器收到请求后,就将该博客从数据库中删除。
请求:
GET /blogDelete?blogId=4
响应:
直接跳转到博客列表页即可。
(1)实现服务器代码
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;
/**
* Created with IntelliJ IDEA.
* Description:
*1.检查当前用户是否登录
*2.获取到参数中的 blogId
*3.获取要删除的博客信息
*4.再次校验,当前用户是否就是博客的作者
*5.确认无误,开始删除
*6.重定向到博客列表
* User: WangWZ
* Date: 2023-05-09
* Time: 14:49
*/
@WebServlet("/blogDelete")
public class BlogDeleteServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//通过这个方法来删除博客
//1.检查当前用户是否登录
HttpSession session = req.getSession();
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("当前博客Id参数不对");
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.getUesrId()) {
//这里虽然在前端已经判断过了,此处再校验一次
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前登录的用户不是作者,没有权限进行删除");
return;
}
//5.确认无误,开始删除
blogDao.delete(Integer.parseInt(blogId));
//6.重定向到博客列表
resp.sendRedirect("blog_list.html");
}
}