博客系统
- 前言
- 一.准备工作
- 1.1 准备好前端文件
- 1.2 设计数据库
- 1.3 编写基本的数据库代码
- 1.4 封装好数据库的连接操作
- 1.5 根据设计的表创建实体类
- 1.6 根据实体类,提供一些简单的增删改查操作
- 二.博客要实现的功能
- 2.1 博客列表页功能
- 2.2 博客详情页
- 2.3 博客登录页
- 2.4 页面强制登录功能
- 2.5 显示用户功能
- 2.6 退出登录功能
- 2.7 发布博客功能
前言
这篇博客相当于是,根据前面的所学的知识,来做一个综合练习
一.准备工作
1.1 准备好前端文件
登录页:
列表详情页:
博客详情页
博客编辑页
1.2 设计数据库
因为是博客管理系统,我们涉及数据库的话,这个两个表的结构如下:
1.3 编写基本的数据库代码
列出数据库的基本代码.
-- 这个文件主要用来写建库建表语句.
-- 一般都建议大家, 在建表的时候把建表 sql 保留下来. 以备后续部署其他机器的时候就方便了.create database if not exists java_blog_system;
use java_blog_system;-- 删除旧表, 重新创建新表. 删除旧表是为了防止之前的残留数据对后续的程序有负面影响.
drop table if exists user;
drop table if exists blog;-- 真正创建表.
create table blog (blogId int primary key auto_increment,title varchar(128),content varchar(4096),postTime datetime,userId int
);create table user (userId int primary key auto_increment,username varchar(20) unique, -- 要求用户名和别人不重复~~password varchar(20)
);-- 构造测试数据
insert into blog values(1, '这是我的第一篇博客', '从今天开始我要认真敲代码', now(), 1);
insert into blog values(2, '这是我的第二篇博客', '从昨天开始我要认真敲代码', now(), 1);
insert into blog values(3, '这是我的第三篇博客', '从前天开始我要认真敲代码', now(), 1);-- 构造测试数据
insert into user values(1, 'zhangsan', '123');
insert into user values(2, 'lisi', '123');
1.4 封装好数据库的连接操作
我们会对数据库有基本的操作.
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;/*** @author <a href="mailto:1065043594@qq.com">ChenJiaYi</a>* @CreateDate 2023/7/7* @ProjectDetails [项目简述]*/
public class DBUtil {private static DataSource dataSource = new MysqlDataSource();static {((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java_blog_system?characterEncoding=utf8&useSSL=false");((MysqlDataSource) dataSource).setUser("root");((MysqlDataSource) dataSource).setPassword("123456");}public static Connection getConnection() throws SQLException {return dataSource.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();}}}
}
1.5 根据设计的表创建实体类
Blog表 =>Blog类对应的Blog的一个对象,就对应表中的一个记录
User表 => User类对应的User的一个对象,就对应表中的一个记录
实体类有哪些属性都跟表中的东西一一对应的
实体类如下:
Blog类
package model;/*** @author <a href="mailto:1065043594@qq.com">ChenJiaYi</a>* @CreateDate 2023/7/7* @ProjectDetails [项目简述]*/import java.sql.Timestamp;
import java.text.SimpleDateFormat;public class Blog {private int blogId;private String title;private String content;private Timestamp postTime;private int userId;public int getBlogId() {return blogId;}public void setBlogId(int blogId) {this.blogId = blogId;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public Timestamp getPostTimestamp() {return postTime;}public String getPostTime() {// 把时间戳转成 格式化 时间.// 这个类的用法千万不要背, 一定要去查一下.SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");return simpleDateFormat.format(postTime);}public void setPostTime(Timestamp postTime) {this.postTime = postTime;}public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}
}
User类
package model;
/*** @author <a href="mailto:1065043594@qq.com">ChenJiaYi</a>* @CreateDate 2023/7/7* @ProjectDetails [项目简述]*/
public class User {private int userId;private String username;private String password;public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}
1.6 根据实体类,提供一些简单的增删改查操作
首先我们对blog博客类的操作
- 新增一个博客.
- 根据博客 id 来查询指定博客 (博客详情页中)
- 直接查询出数据库中所有的博客列表 (用于博客列表页)
- 删除指定博客
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 add(Blog blog) {Connection connection = null;PreparedStatement statement = null;try {// 1. 和数据库建立连接connection = DBUtil.getConnection();// 2. 构造 SQLString sql = "insert into blog values(null, ?, ?, ?, ? )";statement = connection.prepareStatement(sql);statement.setString(1, blog.getTitle());statement.setString(2, blog.getContent());statement.setTimestamp(3, blog.getPostTimestamp());statement.setInt(4, blog.getUserId());// 3. 执行 sqlstatement.executeUpdate();} catch (SQLException e) {e.printStackTrace();} finally {DBUtil.close(connection, statement, null);}}// 2. 根据博客 id 来查询指定博客 (博客详情页中)public Blog selectById(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 在 blog 表中是唯一的. (主键)// 此时的查询结果, 要么是没有查到任何数据, 要么只有一条记录!!// 此处可以不使用 while, 直接 if 判定即可.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;}// 3. 直接查询出数据库中所有的博客列表 (用于博客列表页)public List<Blog> selectAll() {List<Blog> blogs = new ArrayList<>();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. 执行 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");if (content == null) {content = "";}if (content.length() >= 100) {content = content.substring(0, 100) + "...";}blog.setContent(content);blog.setPostTime(resultSet.getTimestamp("postTime"));blog.setUserId(resultSet.getInt("userId"));blogs.add(blog);}} catch (SQLException throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return blogs;}// 4. 删除指定博客public void delete(int blogId) {Connection connection = null;PreparedStatement statement = null;try {// 1. 和数据库建立连接.connection = DBUtil.getConnection();// 2. 构造 SQLString sql = "delete from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);// 3. 执行 SQLstatement.executeUpdate();} catch (SQLException throwables) {throwables.printStackTrace();} finally {// 4. 关闭DBUtil.close(connection, statement, null);}}
}
二.博客要实现的功能
这里所实现的功能基本我们思路都是这样
1.实际上就是借助ajax给服务器发送一个请求
2.服务器查找数据库获取到博客列表数据,返回给浏览器
3.浏览器再根据数据构造页面内容
基本步骤如下:
1)约定前后端交互接口
2)编写后端代码
3)编写前端代码
2.1 博客列表页功能
这里列表页的逻辑如下.
我们具体的步骤如下:
-
约定前后端接口
我们在约定前后端接口之前,我们先回答几个问题?
前端要发什么样子的请求?
后端要返回什么样子的响应?
大概就是如下所示:
-
编写后端代码
后端代码入下:
-
编写前端代码
function getBlogs() {$.ajax({type: 'get',url: 'blog',success: function(body) {// 响应的正文 是一个 json 字符串, 此处已经被 jquery 自动解析成 js 对象数组了. // 直接 for 循环遍历即可. let containerRight = document.querySelector('.container-right');for (let blog of body) {// 构造页面内容, 参考之前写好的 html 代码// 构造整个博客 divlet 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 = '查看全文 >>';// 期望点击之后能跳转到博客详情页. 为了让博客详情页知道是点了哪个博客, 把 blogId 给传过去a.href = 'blog_detail.html?blogId=' + blog.blogId;blogDiv.appendChild(a);// 把 blogDiv 加到父元素中containerRight.appendChild(blogDiv);}}});}// 要记得调用getBlogs();
2.2 博客详情页
具体的逻辑如下:
点击”查看全文”按钮,就能够跳转到博客详情页中.
跳转过去之后,在博客详情页中发起一个ajax,从服务器获取当前博客的具体内容.
再显示出来
1)约定前后端接口
2) 编写后端代码
BlogDao blogDao = new BlogDao();String blogId = req.getParameter("blogId");private ObjectMapper objectMapper = new ObjectMapper();// queryString 存在, 说明本次请求获取的是指定 id 的博客.Blog blog = blogDao.selectById(Integer.parseInt(blogId));if (blog == null) {System.out.println("当前 blogId = " + blogId + " 对应的博客不存在!");}String respJson = objectMapper.writeValueAsString(blog);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);
3)编写前端代码
$.ajax({type: 'get',url: 'blog' + location.search,success: function(body) {// 处理响应结果, 此处的 body 就是表示一个博客的 js 对象. // 1. 更新标题let titleDiv = document.querySelector('.container-right .title');titleDiv.innerHTML = body.title;// 2. 更新日期let dateDiv = document.querySelector('.date');dateDiv.innerHTML = body.postTime;// 3. 更新博客正文// 此处不应该直接把博客正文填充到这个标签里~~// let contentDiv=document.querySelector('#content');// contentDiv.innerHTML=body.content;editormd.markdownToHTML('content', { markdown: body.content });}})
在 JavaScript 中,location 是用于获取或设置窗口的 URL,并可以用来对 URL 进行解析和操作。
location.search 属性用于获取 URL 的查询字符串部分,也就是问号 (?) 后的内容。
2.3 博客登录页
具体思路:
1.在此处输入用户名和密码
2.点击登录就会触发一个http请求
3.服务器验证结果,判定是否登录成功
4.如果成功就跳转到博客列表页
1) 约定前后端交互接口
2) 编写后端代码
private ObjectMapper objectMapper = new ObjectMapper();protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 设置请求的编码. 告诉 servlet 按照啥格式来理解请求req.setCharacterEncoding("utf8");// 设置响应的编码. 告诉 servlet 按照啥格式来构造响应// resp.setCharacterEncoding("utf8");resp.setContentType("text/html;charset=utf8");// 1. 读取参数中的用户名和密码// 注意!! 如果用户名密码包含中文, 此处的读取可能会乱码.String username = req.getParameter("username");String password = req.getParameter("password");if (username == null || "".equals(username) || password == null || "".equals(password)) {// 登录失败!!String html = "<h3>登录失败! 缺少 username 或者 password 字段</h3>";resp.getWriter().write(html);return;}// 2. 读数据库, 看看用户名是否存在, 并且密码是否匹配UserDao userDao = new UserDao();User user = userDao.selectByUsername(username);if (user == null) {// 用户不存在.String html = "<h3>登录失败! 用户名或密码错误</h3>";resp.getWriter().write(html);return;}if (!password.equals(user.getPassword())) {// 密码不对String html = "<h3>登录失败! 用户名或密码错误</h3>";resp.getWriter().write(html);return;}// 3. 用户名密码验证通过, 登录成功, 接下来就创建会话. 使用该会话保存用户信息.//这里就是创建信息HttpSession session = req.getSession(true);session.setAttribute("user", user);// 4. 进行重定向. 跳转到博客列表页resp.sendRedirect("blog_list.html");}
3)编写前端代码
这是里是通过from表单构造请求.
2.4 页面强制登录功能
当用户访问 博客列表页 和 博客详情页 时, 如果用户当前尚未登陆, 就自动跳转到登陆页面.
在页面加载的时候,专门发起一个新的ajax.
举个例子
以博客列表页为例子,会先发送一个请求获取博客列表,再发一个ajax获取用户的登录状态.
如果用户已经登录相安无事,如果未登录,就跳转登录页
具体思路:
首先判断登录状态
- 看是否能查到http Session对象
- 看session 对象里有没有user
然后如果有这种状态就直接可以访问
如果没有就强制登录即可
1)约定前后端交互接口
2)编写后端代码
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json; charset=utf8");// 使用这个方法来获取到用户的登录状态.// 如果用户未登录, 这里的会话就拿不到!!HttpSession session = req.getSession(false);if (session == null) {// 未登录, 返回一个空的 user 对象User user = new User();String respJson = objectMapper.writeValueAsString(user);resp.getWriter().write(respJson);return;}User user = (User) session.getAttribute("user");if (user == null) {user = new User();String respJson = objectMapper.writeValueAsString(user);resp.getWriter().write(respJson);return;}// 确实成功取出了 user 对象, 就直接返回即可.String respJson = objectMapper.writeValueAsString(user);resp.getWriter().write(respJson);}
3)编写前端代码
function checkLogin() {$.ajax({type: 'get',url: 'login',success: function(body) {if (body.userId && body.userId > 0) {// 登录成功!!console.log("当前用户已经登录!!");} else {// 当前未登录// 强制跳转到登录页. location.assign('login.html');}}});}checkLogin();
这里的前端代码,要在每一个页面中都要检查.
2.5 显示用户功能
具体思路:
- 如果是博客列表页,此处显示登录用户的信息
- 如果是博客详情页,此时显示的是该文章的作者.
这里的工作就是根据实际的用户,来生成响应的用户
1)约定前后端交互接口
博客列表页
博客详情页
2)编写后端代码
博客列表的后端代码
这里是利用之前的的强制登录的一个逻辑
@WebServlet("/login")
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json; charset=utf8");// 使用这个方法来获取到用户的登录状态.// 如果用户未登录, 这里的会话就拿不到!!HttpSession session = req.getSession(false);if (session == null) {// 未登录, 返回一个空的 user 对象User user = new User();String respJson = objectMapper.writeValueAsString(user);resp.getWriter().write(respJson);return;}User user = (User) session.getAttribute("user");if (user == null) {user = new User();String respJson = objectMapper.writeValueAsString(user);resp.getWriter().write(respJson);return;}// 确实成功取出了 user 对象, 就直接返回即可.String respJson = objectMapper.writeValueAsString(user);resp.getWriter().write(respJson);}
博客详情页的后端代吗
@WebServlet("/author")
public class AuthorServlet 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) {resp.setContentType("text/html; charset=utf8");resp.getWriter().write("参数非法, 缺少 blogId");return;}// 根据 blogId 查询 Blog 对象BlogDao blogDao = new BlogDao();Blog blog = blogDao.selectById(Integer.parseInt(blogId));if (blog == null) {// 博客不存在.resp.setContentType("text/html; charset=utf8");resp.getWriter().write("没有找到指定博客: blogId = " + blogId);return;}// 根据 blog 中的 userId 找到对应的用户信息UserDao userDao = new UserDao();User author = userDao.selectById(blog.getUserId());String respJson = objectMapper.writeValueAsString(author);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);}
}
3)编写前端代码
博客列表页
function checkLogin() {$.ajax({type: 'get',url: 'login',success: function(body) {if (body.userId && body.userId > 0) {// 登录成功!!console.log("当前用户已经登录!!");//加上一个功能,把当前用户界面加载到界面上let h3 = document.querySelector('.container-left .card h3');h3.innerHTML = body.username;} else {// 当前未登录// 强制跳转到登录页. location.assign('login.html');}}});}checkLogin();
博客详情页
// 函数定义function getAuthor() {$.ajax({type: 'get',url: 'author' + location.search,success: function(body) {// 把 username 设置到界面上let h3 = document.querySelector('.container-left .card h3');h3.innerHTML = body.username;}});}// 函数调用getAuthor();
2.6 退出登录功能
基本思路:
思路:
首先判断登录状态
- 看是否能查到http Session对象
- 看session 对象里有没有user
- 实现退出登录
要么把HttpSession干掉
要么把user干掉.
这里的请求就是a标签了,就不是ajax了
我们这里选择把user干掉
1) 约定前后端交互接口
2) 编写后端代码
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HttpSession httpSession = req.getSession(false);if (httpSession == null) {// 未登录状态, 就直接提示出错.resp.setContentType("text/html; charset=utf8");resp.getWriter().write("当前未登录!");return;}httpSession.removeAttribute("user");resp.sendRedirect("login.html");}
}
3)编写前端代码
2.7 发布博客功能
具体思路
1.点击发布文章就会向服务器发布一个请求
2.服务器做出相关操作
3.浏览器跳转到指定页面
我们这里使用from构造请求.
1)约定前后端交互接口
2)编写后端代码
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 发布博客// 读取请求, 构造 Blog 对象, 插入数据库中即可!!HttpSession httpSession = req.getSession(false);if (httpSession == null) {resp.setContentType("text/html; charset=utf8");resp.getWriter().write("当前未登录, 无法发布博客!");return;}User user = (User) httpSession.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 blog = new Blog();blog.setTitle(title);blog.setContent(content);blog.setUserId(user.getUserId());// 发布时间, 在 java 中生成 / 数据库中生成 都行blog.setPostTime(new Timestamp(System.currentTimeMillis())); // 插入数据库BlogDao blogDao = new BlogDao();blogDao.add(blog);// 跳转到博客列表页resp.sendRedirect("blog_list.html");}
3)编写前端代码
<div class="blog-edit-container"><form action="blog" method="post"><!-- 标题编辑区 --><div class="title"><input type="text" id="title-input" name="title"><input type="submit" id="submit" value="发布文章"></div><!-- 博客编辑器 --><!-- 把 md 编辑器放到这个 div 中 --><div id="editor"><textarea name="content" style="display: none;"></textarea></div></form>