JavaWeb项目 -- 博客系统
- 前言:页面展示
- 一、创建 Maven 项目
- 二、设计数据库
- 三、封装数据库的操作
- 3.1 创建 DBUtil 类
- 3.2 创建 Blog 类
- 3.3 创建 User 类
- 3.4 创建类 BlogDao
- 3.5 创建类 UserDao
- 四、导入准备好的前端代码
- 五、实现博客列表界面
- 5.1 约定好前后端交互接口
- 5.2 实现 BlogServlet
- 5.3 实现前端代码
- 六、实现博客详情界面
- 6.1 约定好前后端交互接口
- 6.2 实现 BlogServlet
- 6.3 实现前端代码
- 七、实现登录界面
- 7.1 约定好前后端交互接口
- 7.2 实现 LoginServlet
- 7.3 实现前端代码
- 八、强制用户登录 (列表页和详情页)
- 8.1 约定好前后端交互接口
- 8.2 实现 LoginServlet
- 8.3 实现前端代码
- 九、实现显示用户信息
- 9.1 约定好前后端交互接口
- 9.1.1 博客列表页
- 9.1.2 博客详情页
- 9.2 实现 UserInfoServlet
- 9.3 实现前端代码
- 9.3.1 博客列表页
- 9.3.2 博客详情页
- 十、实现注销
- 10.1 约定好前后端交互接口
- 10.2 实现 LogoutServlet
- 10.3 实现前端代码
- 十一、实现发布博客
- 11.1 约定好前后端交互接口
- 11.2 实现 BlogServlet
- 11.3 实现前端代码
- 十二、删除博客
- 12.1 约定好前后端交互接口
- 12.2 修改 UserInfoServlet
- 12.4 实现前端代码
- 12.3 实现 BlogDeleteServlet
前言:页面展示
效果图:
登录页:
博客列表页:
博客详情页:
博客编辑页:
一、创建 Maven 项目
参考博客:https://blog.csdn.net/yyhgo_/article/details/128468738?spm=1001.2014.3001.5501
按步骤创建出 Maven 项目并引入依赖、创建目录!
(引入 Servlet 依赖、Jackson 依赖、mysql 依赖)
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.13.4</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency></dependencies>
</project>
二、设计数据库
本系统要存入博客文章的信息和用户的信息
1)创建博客表:
博客的 id,博客的标题,博客的内容,博客的日期,博文的博主 id
2)创建用户表:
用户 id 用户名 用户密码
在 main 目录下创建文件 db.sql:
create database if not exists yyhgo charset utf8mb4;use yyhgo;drop table if exists blog;
create table blog (blogId int primary key auto_increment,title varchar(1024), -- 博客标题content mediumtext, -- 博客正文userId int, -- 作者的 idpostTime datetime -- 发布时间
);drop table if exists user;
create table user (userId int primary key auto_increment,username varchar(128) unique,password varchar(128)
);insert into blog values(null, "这是第一篇博客", "从今天开始, 我要认真写代码", 1, '2022-11-24 20:00:00');
insert into blog values(null, "这是第二篇博客", "从今天开始, 我要认真写代码", 1, '2022-11-25 20:00:00');
insert into blog values(null, "这是第三篇博客", "从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码", 1, '2022-11-26 20:00:00');insert into user values(null, "张三", "123");
insert into user values(null, "李四", "12345");
同时在数据库中复制粘贴这段代码。
建议把 sql 写到一个文件中。后续如果需要把数据库往别的主机上部署,就可以直接复制粘贴建库建表语句完成 ~~
三、封装数据库的操作
把一会需要用到的数据库操作的 jdbc 代码封装起来,以备后用 ~~
创建包 model 用来存放数据库的代码!
3.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;// 通过这个类, 来封装数据库建立连接/断开连接操作
// 懒汉模式要考虑线程安全问题~~
// Servlet 程序天然就是运行在多线程环境中的. 每个请求都可能对应着一个线程(Tomcat 是通过多线程的方式来处理很多请求)
public class DBUtil {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("jdbc:mysql://127.0.0.1:3306/yyhgo?characterEncoding=utf8&useSSL=false");((MysqlDataSource)dataSource).setUser("root");((MysqlDataSource)dataSource).setPassword("");}}}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();}}}
}
3.2 创建 Blog 类
是实体类,代表一篇博客。
实体类:对应数据库表里的一条记录 ~~
package model;import java.sql.Timestamp;
import java.text.SimpleDateFormat;// 这个类的对象, 表示一篇博客
public class Blog {private int blogId;private String title;private String content;private int userId;private Timestamp postTime;public int getBlogId() {return blogId;}public void setBlogId(int blogId) {this.blogId = blogId;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}// 把这个方法魔改一下!! 在方法里面把时间戳构造成格式化时间. 以 String 的方式来返回.public String getPostTime() {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return simpleDateFormat.format(this.postTime);}public void setPostTime(Timestamp postTime) {this.postTime = postTime;}
}
细节:
3.3 创建 User 类
是实体类,代表一个用户。
package model;// 这个类的对象表示一个用户
public class User {private int userId;private String username;private String password;private int isYourBlog = 0;public int getIsYourBlog() {return isYourBlog;}public void setIsYourBlog(int isYourBlog) {this.isYourBlog = isYourBlog;}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
主要是针对博客表进行增删改查操作。
Dao:Data Access Object
用来访问数据的类。这是习惯命名方式 ~
涉及到大量的 jdbc 操作,参考博客:https://blog.csdn.net/yyhgo_/article/details/128061324?spm=1001.2014.3001.5501
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;// 针对博客要实现的功能:
// 1. 新增博客 (博客编辑页)
// 2. 查询出博客列表 (博客列表页)
// 3. 查询出指定博客的详情 (博客详情页)
// 4. 删除指定的博客 (可以在博客详情页中加入)
public class BlogDao {// 下列代码都是 JDBC 操作. 代码相似性非常高的!!// 此处的 Blog 对象是前端提交给后端的.public void insert(Blog blog) {Connection connection = null;PreparedStatement statement = null;try {// 1. 先和数据库建立连接connection = DBUtil.getConnection();// 2. 构造 SQL 语句String sql = "insert into blog values(null, ?, ?, ?, now())";statement = connection.prepareStatement(sql);statement.setString(1, blog.getTitle());statement.setString(2, blog.getContent());statement.setInt(3, blog.getUserId());// 3. 执行 SQLint ret = statement.executeUpdate();if (ret == 1) {System.out.println("插入成功!");} else {System.out.println("插入失败!");}} catch (SQLException e) {e.printStackTrace();} finally {DBUtil.close(connection, statement, null);}}// 当前这个方法, 是给博客列表页使用的.// 博客列表页里面, 不需要显示博客的完整正文, 只需要有一小部分即可 (作为一个用来预览的摘要)public List<Blog> selectAll() {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;List<Blog> blogs = new ArrayList<>();try {// 1. 和数据库建立连接connection = DBUtil.getConnection();// 2. 构造 SQL// 排序:新插入的博客在上面!String sql = "select * from blog order by postTime desc"; statement = connection.prepareStatement(sql);// 3. 执行 SQLresultSet = statement.executeQuery();// 4. 遍历结果集合.while (resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));String content = resultSet.getString("content");// 只截取前 100 个字符作为摘要. 注意! 此处的 100 是拍脑门的!!// 具体设置成几~~ 没关系, 只要最终的效果正确好看即可!if (content.length() > 100) {content = content.substring(0, 100);}blog.setContent(content);blog.setUserId(resultSet.getInt("userId"));blog.setPostTime(resultSet.getTimestamp("postTime"));blogs.add(blog);}} catch (SQLException throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return blogs;}public Blog selectOne(int blogId) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {// 1. 和数据库建立连接connection = DBUtil.getConnection();// 2. 构造 SQLString sql = "select * from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);// 3. 执行 SQLresultSet = statement.executeQuery();// 4. 遍历结果集. 由于是按照 blogId 来查询. blogId 是自增主键, 不能重复.// 此处的查询结果不可能是多条记录. 只能是 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.setPostTime(resultSet.getTimestamp("postTime"));blog.setUserId(resultSet.getInt("userId"));return blog;}} catch (SQLException throwables) {throwables.printStackTrace();} finally {// 5. 关闭资源DBUtil.close(connection, statement, resultSet);}return null;}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);int ret = statement.executeUpdate();if (ret == 1) {System.out.println("删除成功!");} else {System.out.println("删除失败!");}} catch (SQLException e) {e.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;// 关于 User 表, 涉及到的操作
// 1. 根据用户名来查询用户信息(实现登录的时候)
// 2. 根据用户的 id 来查询用户信息 (获取文章的时候, 根据博客的 userId 拿到作者的信息)
public class UserDao {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 throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return null;}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 throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return null;}
}
四、导入准备好的前端代码
只要把前面的静态页面拷贝到项目的 webapp 目录中,就可以启动 tomcat 通过浏览器访问了 ~~
五、实现博客列表界面
5.1 约定好前后端交互接口
5.2 实现 BlogServlet
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json;charset=utf8");BlogDao blogDao = new BlogDao();List<Blog> blogs = blogDao.selectAll();String jsonString = objectMapper.writeValueAsString(blogs);resp.getWriter().write(jsonString);}
}
5.3 实现前端代码
在 blog_list.html 中 实现 ajax
注意:引入依赖 (jquery)
参考代码:
<!-- 右侧内容详情 (参考) --><div class="container-right"><!-- 用这个表示一篇博客 --><!-- <div class="blog"><div class="title">这是第一篇博客</div><div class="date">2022-09-19 12:00:00</div><div class="desc">从今天起我要认真敲代码. Lorem ipsum dolor sit amet consectetur adipisicing elit. Incidunt amet nobis sed assumenda laboriosam nam ex cupiditate sequi ullam doloremque eius corporis, veritatis est ipsa nesciunt eveniet. Reprehenderit, error consequatur.</div><a href="#">查看全文 >> </a></div> --></div>
实现:
<script src="https://code.jquery.com/jquery-3.6.1.min.js"></script><script src="js/app.js"></script><script>// 通过这个函数, 来从服务器获取到博客列表的数据function getBlogs() {$.ajax({type: 'get',url: 'blog',success: function(body) {// 根据返回的 json 数据, 来构造出页面内容, div.blog// jquery ajax 会自动的把响应得到的 body 按照响应的 Content-Type 进行转换格式. // 如果响应的 Content-Type 是 json, 此时就会自动把 body 转成 js 的对象let container = document.querySelector('.container-right');for (let blog of body) {let blogDiv = document.createElement('div');blogDiv.className = 'blog';// 创建博客标题let titleDiv = document.createElement('div');titleDiv.className = 'title';titleDiv.innerHTML = blog.title;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 加入外层元素container.appendChild(blogDiv);}}});}// 获取博客列表getBlogs();</script>
六、实现博客详情界面
点击"查看全文",先让页面跳转到博客详情页 (blog_detail.html)。跳转的过程中,给 URL 带上当前要获取的博客 id。在 blog_detail.html 页面中,再通过 ajax 从服务器获取博客详情内容 ~~
前面设计跳转时,已经在 query string 里设置了 博客 id !!!
6.1 约定好前后端交互接口
6.2 实现 BlogServlet
约定请求的路径是 /blog,代码中当前已经有了一个 /blog 的 Servlet 了,就在之前的 Servlet 基础上做出修改即可!
基于同一个 Servlet,同一个 doGet 方法,让它既可以处理获取博客列表,又能获取博客详情:
- 获取博客列表,请求是 /blog
- 获取博客详情,请求是 /blog?blogld=1
在 doGet 里面就可以根据这个参数 是否存在 来决定 是返回博客列表还是博客详情!!!
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json;charset=utf8");String blogId = req.getParameter("blogId");BlogDao blogDao = new BlogDao();if (blogId == null) {// 不存在 blogId 这个参数, 这就是获取博客列表.List<Blog> blogs = blogDao.selectAll();String jsonString = objectMapper.writeValueAsString(blogs);resp.getWriter().write(jsonString);} else {// 存在 blogId 参数, 就是获取博客详情.Blog blog = blogDao.selectOne(Integer.parseInt(blogId));String jsonString = objectMapper.writeValueAsString(blog);resp.getWriter().write(jsonString);}}
}
6.3 实现前端代码
参考代码:
<!-- 右侧内容详情 --><div class="container-right"><!-- 这个 div 里来放博客的内容 --><div class="blog-content"><!-- 博客标题 --><h3>我的第一篇博客</h3><!-- 博客时间 --><div class="date">2022-09-21 12:00:00</div><!-- 博客正文 --><div id="content" style="opacity: 80%"></div></div></div>
实现:
<script src="https://code.jquery.com/jquery-3.6.1.min.js"></script><script src="js/app.js"></script><script>function getBlog() {$.ajax({type: 'get',url: 'blog' + location.search,success: function(body) {// body 就是得到的一个 json 格式的 博客数据. 由于响应的 Content-Type 是 application/json// 因此 jquery 就会自动把响应数据转成 js 对象. let h3 = document.querySelector('.blog-content h3');h3.innerHTML = body.title;let divDate = document.querySelector('.blog-content .date');divDate.innerHTML = body.postTime;// 刚才这里是直接把正文内容设置到 innerHTML 中了, 没有渲染的过程的. // let divContent = document.querySelector('#content');// divContent.innerHTML = body.content;// 靠谱的做法, 应该是先使用 editor.md 进行渲染. // [重要] 这个方法就是 editor.md 提供的一个方法把 markdown 字符串转成格式化的效果. // 第一个参数是一个 div 的 id, 表示要把渲染结果放到哪个 div 中. // 第二个参数是一个 js 对象, 把正文内容传入即可. (还支持很多别的参数属性, 此处暂时不涉及)editormd.markdownToHTML('content', {markdown: body.content});}});}// 在页面加载之后, 要调用代码. getBlog();</script>
细节:
1)url: 'blog' + location.search,
在 js 中,可以通过这样的方式得到当前页面的 query string,即 ?blogId=1
(location 是 js 中特殊的全局变量)
2)运行后在页面控制台会有这样的警告:
这里的 404 没事 ~ (favicon 是页面的图标,当前没这个图标就报错了)
类似于 b站 的:
3)博客编辑页是一个 markdown 格式的数据。就希望当获取到博客详情的时候,也能按照 markdown 来渲染!
1.在页面中引入 editor.md:
2.通过 editormd.markdownToHTML('content', { markdown: body.content });
渲染。
(关注代码注释)
七、实现登录界面
7.1 约定好前后端交互接口
7.2 实现 LoginServlet
@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected 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");if (username == null || password == null || username.equals("") || password.equals("")) {resp.setContentType("text/html;charset=utf8");resp.getWriter().write("您当前的用户名或者密码为空!");return;}// 加上一个打印, 看看服务器读取的用户名密码到底是什么鬼!!System.out.println("username=" + username + ", password=" + password);// 2. 查询数据库, 看密码是否匹配UserDao userDao = new UserDao();User user = userDao.selectByName(username);if (user == null) {// 用户不存在.resp.setContentType("text/html;charset=utf8");resp.getWriter().write("您的用户名或者密码错误!");return;}if (!user.getPassword().equals(password)) {// 密码错误!resp.setContentType("text/html;charset=utf8");resp.getWriter().write("您的用户名或者密码错误!");return;}// 3. 登录成功之后, 构造会话.HttpSession session = req.getSession(true);// 把刚才获取到的 user 对象给存到 session 里, 方便后续使用.session.setAttribute("user", user);// 4. 返回一个重定向报文, 跳转到博客列表页.resp.sendRedirect("blog_list.html");}
7.3 实现前端代码
form 表单是可以搭配 302 进行跳转的!
如果是使用 ajax,其响应是不能处理 302,不会有跳转的 (需要使用别的方法来跳转)!
修改代码,放到 form 表单里面去!!!
<!-- 构造一个页面版心 --><div class="login-container"><div class="login-dialog"><form action="login" method="post"><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"><input type="submit" value="提交" id="submit"></div></form></div></div>
八、强制用户登录 (列表页和详情页)
在博客列表页 / 详情页里,访问页面的时候验证用户的登录状态。
- 如果用户是已经登录了,自然允许访问;
- 如果用户未登录,则强制跳转到博客登录页面!
我们登录之后,服务器会在内存中保存 session 对象 (维护了用户的信息)
一旦服务器重启,此时内存中的数据就没了,自然登录状态就丢失了!
8.1 约定好前后端交互接口
当用户进入博客列表 / 博客详情页的时候,先发起一个单独的 ajax 请求,通过这个请求来验证用户登录状态!
服务器这边根据登录 / 未登录,返回不同的结果。客户端就可以根据返回的响应,来决定是否要强制跳转到登录页!!!
一个页面是可以发送多个 ajax 的,想发几个都行 ~~
8.2 实现 LoginServlet
在原有 LoginServlet 的基础上添加代码!
@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected 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");if (username == null || password == null || username.equals("") || password.equals("")) {resp.setContentType("text/html;charset=utf8");resp.getWriter().write("您当前的用户名或者密码为空!");return;}// 加上一个打印, 看看服务器读取的用户名密码到底是什么鬼!!System.out.println("username=" + username + ", password=" + password);// 2. 查询数据库, 看密码是否匹配UserDao userDao = new UserDao();User user = userDao.selectByName(username);if (user == null) {// 用户不存在.resp.setContentType("text/html;charset=utf8");resp.getWriter().write("您的用户名或者密码错误!");return;}if (!user.getPassword().equals(password)) {// 密码错误!resp.setContentType("text/html;charset=utf8");resp.getWriter().write("您的用户名或者密码错误!");return;}// 3. 登录成功之后, 构造会话.HttpSession session = req.getSession(true);// 把刚才获取到的 user 对象给存到 session 里, 方便后续使用.session.setAttribute("user", user);// 4. 返回一个重定向报文, 跳转到博客列表页.resp.sendRedirect("blog_list.html");}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 验证登录状态.// 直接去取登录状态. 看能不能取到.HttpSession session = req.getSession(false);if (session == null) {// 未登录, 直接设置状态码 403 即可. body 都不需要~resp.setStatus(403);return;}User user = (User) session.getAttribute("user");if (user == null) {// 未登录, 直接设置状态码 403 即可. body 都不需要~resp.setStatus(403);return;}// 已登录!resp.setStatus(200);}
}
8.3 实现前端代码
function checkLogin() {$.ajax({type: 'get',url: 'login',success: function (body) {// 200, 登录成功, 不必做任何处理. },error: function () {// 403 就会触发 error// 强行跳转到登录页面. location.assign('login.html');}});
}
location.assign()
是前端代码实现页面跳转的方式!!!
博客列表页和详情页都需要去完成这样一件事,所以把代码写进 app.js 文件,放进 js 目录里 ~~
在 blog_list.html 和 blog_detail.html 文件中引入 app.js:
<script src="js/app.js"></script>
并且调用函数!:
checkLogin();
ajax 是异步的:发起请求的主体,不负责接受结果,而是由别人主动推送过来!
所以多个 ajax 之间执行间隔极短,就可以近似看作是同时发送!
所以不必关注多个 ajax 之间的先后顺序!!!
九、实现显示用户信息
在博客列表页,和博客详情页都有用户信息。
这里的信息不要写死,而是能够从服务器动态获取!
- 如果是博客列表页,此处显示当前登录的用户信息
- 如果是博客详情页,此处显示文章的作者信息
图片的话一般是保存在一个单独的位置,在数据库里存图片的路径 ~~
9.1 约定好前后端交互接口
9.1.1 博客列表页
9.1.2 博客详情页
9.2 实现 UserInfoServlet
@WebServlet("/userInfo")
public class UserInfoServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String blogId = req.getParameter("blogId");if (blogId == null) {// 请求来自博客列表页, 直接返回登录的用户信息.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;}user.setPassword("");resp.setContentType("application/json; charset=utf8");String jsonString = objectMapper.writeValueAsString(user);resp.getWriter().write(jsonString);} else {// 请求来自博客详情页, 返回文章作者信息.BlogDao blogDao = new BlogDao();Blog blog = blogDao.selectOne(Integer.parseInt(blogId));if (blog == null) {resp.setStatus(403);resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前 blogId 有误!");return;}UserDao userDao = new UserDao();User author = userDao.selectById(blog.getUserId());if (author == null) {resp.setStatus(403);resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前博客对应的作者没有找到!");return;}author.setPassword(""); // 为了安全,隐藏密码~~resp.setContentType("application/json; charset=utf8");String jsonString = objectMapper.writeValueAsString(author);resp.getWriter().write(jsonString);}}
}
9.3 实现前端代码
参考代码:
<!-- 左侧个人信息 --><div class="container-left"><!-- 用这个 .card 来表示用户的信息 --><div class="card"><img src="image/doge.jpg" alt=""><h3>小豪</h3><a href="#">github 地址</a><div class="counter"><span>文章</span><span>分类</span></div><div class="counter"><span>2</span><span>3</span></div></div></div>
9.3.1 博客列表页
实现:
<script>// 获取当前用户的信息function getUserInfo() {$.ajax({type: 'get',url: 'userInfo',success: function (body) {// 让后端在查询失败的时候, 不要返回 200 , 而是返回 403 . // 避免在前端触发 success 分支. let h3 = document.querySelector('.card h3');h3.innerHTML = body.username;}});}getUserInfo();</script>
9.3.2 博客详情页
实现:
<script>// 获取用户信息function getUserInfo() {$.ajax({type: 'get',url: 'userInfo' + location.search,success: function (body) {// 让后端在查询失败的时候, 不要返回 200 , 而是返回 403 . // 避免在前端触发 success 分支. let h3 = document.querySelector('.card h3');h3.innerHTML = body.username;}});}getUserInfo();</script>
十、实现注销
这个功能要做的工作:
- 清除当前用户的登录状态 (删除会话)
- 跳转到博客登录页
此处的注销就是退出登录。
10.1 约定好前后端交互接口
点击注销的时候,发送一个 GET 请求,并且跳转到博客登录页。
直接借助 a 标签 来实现!
a 标签 点击之后正好是发送了一个 GET 请求,也正好能触发页面跳转 ~~
10.2 实现 LogoutServlet
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 注销要做的是删除用户的会话信息. 因此就得先确认用户有没有会话.// req 对象没有直接提供一个 删除会话 的操作~~// 删除会话有个办法, 就是把过期时间设置成 0. 比较麻烦.// 更简单的办法, 虽然保留会话对象, 但是把会话里的 user 给删了.HttpSession session = req.getSession(false);if (session == null) {// resp.setStatus(403);resp.sendRedirect("login.html");return;}session.removeAttribute("user");resp.sendRedirect("login.html");}
}
注销要做的是删除用户的会话信息,因此先确认用户有没有会话。
req 对象没有直接提供一个 删除会话 的操作!删除会话有个办法,就是把过期时间设置成 0,但比较麻烦。
更简单的办法:虽然保留会话对象,但是把会话里的 user 给删了!
10.3 实现前端代码
直接通过 a 标签 来进行实现,不需要加上任何的 ajax 请求!
<!-- 导航栏 --><div class="nav"><img src="image/logo2.jpg" alt=""><span class="title">我的博客系统</span><!-- 这个 spacer 用来占位 --><span class="spacer"></span><!-- 来几个按钮 --><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="logout">注销</a> <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --></div>
博客列表页、详情页、编辑页都进行修改!!!
十一、实现发布博客
11.1 约定好前后端交互接口
11.2 实现 BlogServlet
把请求中的博客数据拿到,同时写入数据库!
前面实现博客列表界面时,已经使用过 blog 路径,有了 BlogServlet 这个类 (doGet 方法)。
在这个类里添加 doPost 方法即可 ~~
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json;charset=utf8");String blogId = req.getParameter("blogId");BlogDao blogDao = new BlogDao();if (blogId == null) {// 不存在 blogId 这个参数, 这就是获取博客列表.List<Blog> blogs = blogDao.selectAll();String jsonString = objectMapper.writeValueAsString(blogs);resp.getWriter().write(jsonString);} else {// 存在 blogId 参数, 就是获取博客详情.Blog blog = blogDao.selectOne(Integer.parseInt(blogId));String jsonString = objectMapper.writeValueAsString(blog);resp.getWriter().write(jsonString);}}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("utf8");// 1. 获取到用户的登录状态.HttpSession session = req.getSession(false);if (session == null) {resp.setStatus(403);return;}User user = (User) session.getAttribute("user");if (user == null) {resp.setStatus(403);return;}// 2. 读取请求的内容String title = req.getParameter("title");String content = req.getParameter("content");if (title == null || title.equals("") || content == null || content.equals("")) {resp.setStatus(400);resp.setContentType("text/html; charset=utf8");resp.getWriter().write("请求中的标题或正文不完整");return;}// 3. 构造 Blog 对象, 并插入到数据库中.Blog blog = new Blog();blog.setTitle(title);blog.setContent(content);// 博客的作者. 作者是谁? 当前谁登录, 作者就是谁!!blog.setUserId(user.getUserId());BlogDao blogDao = new BlogDao();blogDao.insert(blog);// 4. 插入成功之后, 跳转到博客列表页.resp.sendRedirect("blog_list.html");}
}
11.3 实现前端代码
这个地方可以使用 ajax,也可以使用 form 表单。
此处就使用 form 表单 即可 ~~
<div class="blog-edit-container"><form action="blog" method="post" style="height: 100%"><!-- 标题编辑区 --><div class="title"><input type="text" id="title" placeholder="请输入文章标题" name="title"><input type="submit" id="submit" value="发布文章"></div><!-- 博客编辑器标签 --><div id="editor"><!-- 需要在这里加上一个隐藏的 textarea --><!-- 属于 editor.md 这个库要求的. --><textarea name="content" style="display: none;" ></textarea></div></form></div>
初始化编辑器代码也要稍作修改:
<script>// 初始化编辑器var editor = editormd("editor", {// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. width: "100%",// 设定编辑器高度height: "calc(100% - 50px)",// 编辑器中的初始内容markdown: "# 在这里写下一篇博客",// 指定 editor.md 依赖的插件路径path: "editor.md/lib/",// 加上这个属性, 效果就是把编辑器里的内容给自动保存到 textarea 里. saveHTMLToTextArea: true, // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!});</script>
有时页面会触发缓存,这时候要强制刷新!Ctrl + F5!
记得修改 form 元素的属性 (设置高度什么的 ~)
十二、删除博客
删除按钮可以放在列表页,也可以放在详情页 ~~
这里我们放在详情页。
- 如果当前博客作者是登录用户自己,则在详情页导航栏中显示这个删除按钮;
- 如果当前博客作者不是登录的用户,则不显示删除按钮!
所以此处要先根据当前用户的状态,来判断是否显示删除按钮!!!
12.1 约定好前后端交互接口
12.2 修改 UserInfoServlet
需要修改一下 UserInfoServlet 的 doGet 方法,来判断一下登录用户与博客作者 (从数据库中查) 的关系!
@WebServlet("/userInfo")
public class UserInfoServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String blogId = req.getParameter("blogId");// 先获取一下当前是哪个用户登录的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;}if (blogId == null) {// 请求来自博客列表页, 直接返回登录的用户信息.user.setPassword("");resp.setContentType("application/json; charset=utf8");String jsonString = objectMapper.writeValueAsString(user);resp.getWriter().write(jsonString);} else {// 请求来自博客详情页, 返回文章作者信息.BlogDao blogDao = new BlogDao();Blog blog = blogDao.selectOne(Integer.parseInt(blogId));if (blog == null) {resp.setStatus(403);resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前 blogId 有误!");return;}UserDao userDao = new UserDao();// author 是博客的作者User author = userDao.selectById(blog.getUserId());if (author == null) {resp.setStatus(403);resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前博客对应的作者没有找到!");return;}author.setPassword(""); // 为了安全,隐藏密码~~if (user.getUserId() == author.getUserId()) {author.setIsYourBlog(1);} else {author.setIsYourBlog(0);}resp.setContentType("application/json; charset=utf8");String jsonString = objectMapper.writeValueAsString(author);resp.getWriter().write(jsonString);}}
}
User 类中有一个 isYourBlog 属性 ~~
(使用 int 类型!若使用 boolean 类型可能会与属性名中的 “is” 产生一些冲突,会出问题)
12.4 实现前端代码
在这里复用一下这个接口:
在响应中返回一个 当前博客是否是登录用户自己的文章,根据这个结果来判断是否显示删除按钮 ~~
参考代码:
<!-- 导航栏 --><div class="nav"><img src="image/logo2.jpg" alt=""><span class="title">我的博客系统</span><!-- 这个 spacer 用来占位 --><span class="spacer"></span><!-- 来几个按钮 --><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="logout">注销</a><!-- 在这里加一个 删除 按钮 --></div>
实现:
<script>// 获取用户信息function getUserInfo() {$.ajax({type: 'get',url: 'userInfo' + location.search,success: function (body) {// 让后端在查询失败的时候, 不要返回 200 , 而是返回 403 . // 避免在前端触发 success 分支. let h3 = document.querySelector('.card h3');h3.innerHTML = body.username;if (body.isYourBlog) {// 在导航栏中加个按钮, 用来删除文章. let deleteA = document.createElement('a');// location.search 就是当前页面 url 的 query string, 也就是// ?blogId=1 这样的结果. deleteA.href = 'blogDelete' + location.search;deleteA.innerHTML = '删除';let navDiv = document.querySelector('.nav');navDiv.appendChild(deleteA);}}});}getUserInfo();</script>
JavaScript 是弱类型。number 和 boolean 是可以相互转换的!不像 Java 要求的严格 ~
因此使用if (body.isYourBlog) {
判断完全可以,因为 非0 就为 true !!!
12.3 实现 BlogDeleteServlet
实现具体的删除动作。
前端代码已经完成了,成功运行后会添加一个 a 标签 (删除)!
点击 “删除” 按钮,即发送一个 GET 请求 (a 标签跳转) ~~
处理这个请求!!!
@WebServlet("/blogDelete")
public class BlogDeleteServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 1. 先判定用户的登录状态, 如果用户未登录, 就不给删除HttpSession session = req.getSession(false);if (session == null) {resp.setStatus(403);return;}User user = (User) session.getAttribute("user");if (user == null) {resp.setStatus(403);return;}// 2. 获取到 blogIdString blogId = req.getParameter("blogId");if (blogId == null || blogId.equals("")) {resp.setContentType("text/html; charset=utf8");resp.getWriter().write("要删除的文章不存在!!");return;}// 3. 删除数据库中的数据BlogDao blogDao = new BlogDao();blogDao.delete(Integer.parseInt(blogId));// 4. 跳转到博客列表页resp.sendRedirect("blog_list.html");}
}