当前位置: 首页 > news >正文

【Java项目】基于Java+MySQL+Tomcat+maven+Servlet的个人博客系统的完整分析

✨哈喽,进来的小伙伴们,你们好耶!✨

🛰️🛰️系列专栏:【Java项目】

✈️✈️本篇内容:个人博客系统前后端分离实现!

🚀🚀个人代码托管github:博客系统源码地址!

⛵⛵作者简介:一名双非本科大三在读的Java编程小白,道阻且长,星夜启程!

本篇博客开始之前,需要说明的是前面我们已经实现了博客系统的纯前端页面,详情可以参考这篇博客:博客系统前端页面!或者直接点击本专栏的上一篇博客即可!

那么,本篇博客是基于"个人博客系统"的前端页面,编写一个后端服务器,和之前写的这个页面进行联动,实现前后端分离的功能;

OK,接下来,我们开始编写博客系统;

目录

一、准备工作

1、打开idea,创建maven项目;

2、引入依赖;

3、创建相关的目录;

4、编写代码

5、6:打包部署

7、 在浏览器验证程序;

二、开始正式编写服务器代码

model层;

Controller层;


一、准备工作

1、打开idea,创建maven项目;

1)File->new Project;

 2)选择创建maven项目,然后点击next;

 3)给咱们的maven项目取个名字,博主这里取的是Blog_System2;然后选择文件夹存放代码;

2、引入依赖;

这里我们需要引入的依赖是servlet,jackson,mysql;

引入方法:打开下面这个链接:maven中央仓库;

举例:servlet依赖的引入方式:

1)打开链接,在搜索框内输入servlet,点击search,找到下面箭头指向的这个,点进去;

 2)鼠标滚轮向下滑动,找到3.0.1版本(跟自己电脑的tomcat和jdk的版本对应)

3)找到下面红色框框中的内容,Ctrl+c复制;

 4)粘贴到我们刚刚创建的maven项目下pom.xml里面,注意需要用<dependency>包裹起来;

 博主这里只演示一下servlet是如何引入的,像mysql和Jackson都是类似的,直接根据上面的步骤来引入即可;

3、创建相关的目录;

1)点开我们项目下的src文件,找到main,然后右键创建一个新的目录,webapp;在webapp下创建一个目录叫做WEB-INF,然后在WEB-INF下面创建一个新的xml文件叫做web.xml;创建好之后大概如下,(webapp下有些css、js文件这是后来引入的,这里不用管)

2)在web.xml文件中填写以下代码;

<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name>
</web-app>

4、编写代码

这里我们先简单写个无关的测试代码,方便咱们后续步骤的执行;

1)找到java文件,在java文件下创建一个类叫做HelloServlet;

2)写一个简单的实验代码;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@WebServlet("/hello")
public class HelloServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("你好,博客系统!");}
}

5、6:打包部署

这里打包部署在博主之前的博客已经介绍过,使用idea自带的插件smart tomcat来完成;

7、 在浏览器验证程序;

二、开始正式编写服务器代码

1、开篇已经介绍过,这里我们的前端代码可以直接全部粘贴到webapp下,这样就把代码集成到我们的项目上了;后续打包部署的时候,这些资源会被一并打包部署,也就可以在浏览器访问到了;

粘贴过来之后,对应的目录结构;

 2、先来实现我们MVC结构中的M部分;博主这里在java文件下建一个包"model"负责编写M部分的代码;

这里来介绍一下前面写的博客系统页面:

1、博客列表页:显示博客的列表;

2、博客详情页,点击博客列表页,上面列出的博客篇数,可以跳转到完整的博客内容;

3、登录页面;

4、博客编辑页,基于editor.md整了一个markdown编辑器,根据这个页面来发布博客;

联想到数据库,这里需要存储博客,点击发布的时候,博客被发表到服务器上,需要被存储起来;

获取博客,在博客列表页和博客详情页,能够拿到博客的内容;

即我们需要设计一张博客表,用来存储所有的博客数据;设计一张用户表,用户登录的时候就需要用到这个表;

开始编写数据库代码:

1)在main下创建一个File,叫做db.sql;

-- 编写建库建表的 sqlcreate database if not exists blog_system character set utf8mb4;use blog_system;-- 创建一个博客表.
drop table if exists blog;
create table blog (blogId int primary key auto_increment,title varchar(256),content mediumtext, -- 正文较长,使用mediumtext类型userId int,         -- 文章作者的 idpostTime datetime   -- 发布时间
);-- 创建一个用户表
drop table if exists user;
create table user (userId int primary key auto_increment,username varchar(30) unique,    -- 后续会使用用户名进行登录, 一般用于登录的用户名都是不能重复的.password varchar(20)
);insert into user values(null, 'zhangsan', '123');
insert into user values(null, 'lisi', '123');

2)编写完成之后,打开mysql客户端,然后复制上面的SQL语句,粘贴到我们的mysql客户端;

3)检查数据库和表是否创建完成;

 2、封装数据库;

model层;

1)首先创建一个DBUtil类,用来封装数据库连接操作;

package model;import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;// 使用这个类和数据库建立连接.
public class DBUtil {private static final String URL = "jdbc:mysql://127.0.0.1:3306/blog_system?characterEncoding=utf8&useSSL=false";private static final String USERNAME = "root";private static final String PASSWORD = "";//填写自己电脑mysql数据库密码private static volatile DataSource dataSource = null;//注意加上volatileprivate static DataSource getDataSource() {if (dataSource == null) {synchronized (DBUtil.class) {//双重校验锁if (dataSource == null) {dataSource = new MysqlDataSource();((MysqlDataSource)dataSource).setUrl(URL);((MysqlDataSource)dataSource).setUser(USERNAME);((MysqlDataSource)dataSource).setPassword(PASSWORD);}}}return dataSource;}public static Connection getConnection() throws SQLException {return getDataSource().getConnection();}public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}if (statement != null) {try {statement.close();} catch (SQLException e) {e.printStackTrace();}}if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}
}

 2)创建实体类;

使用实体类来表示数据库中的一条记录;

这里包含了两个类分别是Blog、User;

User:

package model;// 每个 model.User 对象, 期望能够表示 user 表中的一条记录.
public class User {private int userId = 0;private String username = "";private String password = "";public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}

针对用户表来设计博客表;

Blog:

package model;import java.sql.Timestamp;
import java.text.SimpleDateFormat;// 每个 model.Blog 对象, 对应 blog 表里的一条记录
public class Blog {private int blogId;private String title;//正文private String content;//内容private int userId;//博客作者private Timestamp postTime;//发布时间public int getBlogId() {return blogId;}public void setBlogId(int blogId) {this.blogId = blogId;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}

3、封装针对数据增删改查;

这里有个很形象的词叫做DAO,即把提供了对增删改查这样的类的叫法;

创建Blog_Dao:(核心代码都提供了注释)

package model;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;// 这个类用于去封装博客表的基本操作
public class BlogDao {// 1. 往博客表里, 插入一个博客.public void insert(Blog blog) {// JDBC 基本代码Connection connection = null;PreparedStatement statement = null;try {// 1) 和数据库建立连接.connection = DBUtil.getConnection();// 2) 构造 SQL 语句String sql = "insert into blog values(null, ?, ?, ?, now())";statement = connection.prepareStatement(sql);statement.setString(1, blog.getTitle());statement.setString(2, blog.getContent());statement.setInt(3, blog.getUserId());// 3) 执行 SQLstatement.executeUpdate();} catch (SQLException e) {e.printStackTrace();} finally {// 4) 关闭连接, 释放资源DBUtil.close(connection, statement, null);}}// 2. 能够获取到博客表中的所有博客的信息 (用于在博客列表页, 此处每篇博客不一定会获取到完整的正文)public List<Blog> selectAll() {List<Blog> blogs = new ArrayList<>();Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from blog order by postTime desc";statement = connection.prepareStatement(sql);resultSet = statement.executeQuery();while (resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));// 这里需要针对内容进行截断(太长了, 就去掉后面)String content = resultSet.getString("content");// 这个 50 自定义,随便给一个就行if (content.length() > 50) {content = content.substring(0, 50) + "...";}blog.setContent(content);blog.setUserId(resultSet.getShort("userId"));blog.setPostTime(resultSet.getTimestamp("postTime"));blogs.add(blog);}} catch (SQLException throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return blogs;}// 3. 能够根据博客 id 获取到指定的博客内容 (用于在博客详情页)public Blog selectOne(int blogId) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);resultSet = statement.executeQuery();// 此处我们是使用 主键 来作为查询条件的. 查询结果, 要么是 1 , 要么是 0.if (resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));blog.setContent(resultSet.getString("content"));blog.setUserId(resultSet.getShort("userId"));blog.setPostTime(resultSet.getTimestamp("postTime"));return blog;}} catch (SQLException throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return null;}// 4. 从博客表中, 根据博客 id 删除博客.public void delete(int blogId) {Connection connection = null;PreparedStatement statement = null;try {connection = DBUtil.getConnection();String sql = "delete from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);statement.executeUpdate();} catch (SQLException throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, null);}}// 注意, 上述操作是 增删查, 没有改
}

Blog_Dao对应的就是User_Dao:

package model;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;// 提供了针对"用户表"的基本操作
public class UserDao {// 需要实现的操作// 1. 根据用户名来查找用户信息.//    会在登录逻辑中使用public User selectByName(String username) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from user where username = ?";statement = connection.prepareStatement(sql);statement.setString(1, username);resultSet = statement.executeQuery();// 此处 username 使用 unique 约束, 要么能查到一个, 要么一个都查不到.if (resultSet.next()) {User user = new User();user.setUserId(resultSet.getInt("userId"));user.setUsername(resultSet.getString("username"));user.setPassword(resultSet.getString("password"));return user;}} catch (SQLException throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return null;}// 2. 根据用户 id 来找用户信息.//    博客详情页, 就可以根据用户 id 来查询作者的名字, 把作者名字显示出来.public User selectById(int userId) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from user where userId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, userId);resultSet = statement.executeQuery();// 此处 username 使用 unique 约束, 要么能查到一个, 要么一个都查不到.if (resultSet.next()) {User user = new User();user.setUserId(resultSet.getInt("userId"));user.setUsername(resultSet.getString("username"));user.setPassword(resultSet.getString("password"));return user;}} catch (SQLException throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return null;}
}

OK,到这里我们的数据库的操作都已经准备好了,接下来开始编写服务器中的Controller层;

Controller层;

1、首先,来约定前后端交互的接口,分别编写客户端代码,服务器代码;

1)从博客列表页开始;

这个页面的作用就是能够展示出数据库中博客的列表

效果如下:

 2) 约定

请求:GET/blog

响应:这里采用的是json格式来编写代码;在括号里面应该包含:

[{blogId:1,title:'这是第一篇博客',content:'这是博客正文',userId:1,postTime:'2023-01-20 19:00:00'},{blogId:2,title:'这是第二篇博客',content:'这是博客正文',userId:1,postTime:'2023-01-21 19:00:00'},]

由于是博客列表页,博客内容只能展示一部分,所以这里的content可以看成是摘要;即截取正文的一部分内容;

新建一个类BlogServlet:(部分代码)

import model.Blog;
import model.BlogDao;
import model.User;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;// 通过这个类, 来处理 /blog 路径对应的请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();// 这个方法用来获取到数据库中的博客列表.@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json; charset=utf8");BlogDao blogDao = new BlogDao();// 先尝试获取到 req 中的 blogId 参数. 如果该参数存在, 说明是要请求博客详情// 如果该参数不存在, 说明是要请求博客的列表.String param = req.getParameter("blogId");if (param == null) {// 不存在参数, 获取博客列表List<Blog> blogs = blogDao.selectAll();// 把 blogs 对象转成 JSON 格式.String respJson = objectMapper.writeValueAsString(blogs);resp.getWriter().write(respJson);} else {// 存在参数, 获取博客详情int blogId = Integer.parseInt(param);Blog blog = blogDao.selectOne(blogId);String respJson = objectMapper.writeValueAsString(blog);resp.getWriter().write(respJson);}}
}

代码写完之后,我们来验证这个代码是否正确,可以使用postman来访问这个接口的数据;

1)首先打开postman,然后输入对应的请求;输入完成之后点击"send",可以看到结果;

可以看到结果是一个空,说明我们的json数组里面还没有插入数据;我们可以在博客表blog中插入一些数据;

-- 给博客表中插入点数据, 方便测试.
insert into blog values(null, '这是第一篇博客', '从今天开始, 我要认真学 Java', 1, now());
insert into blog values(null, '这是第二篇博客', '从昨天开始, 我要认真学 Java', 1, now());
insert into blog values(null, '这是第三篇博客', '从前天开始, 我要认真学 Java', 1, now());
insert into blog values(null, '这是第一篇博客', '从今天开始, 我要认真学 C++', 2, now());
insert into blog values(null, '这是第二篇博客', '从昨天开始, 我要认真学 C++', 2, now());
insert into blog values(null, '这是第三篇博客', '# 一级标题\n ### 三级标题\n > 这是引用内容', 2, now());

在MySQL终端中写以上数据;

3、编写客户端代码

这里我们想实现的是,在页面加载的时候,让页面通过ajax访问服务器,获取到数据库中的博客数据,并且填写到页面中;

首先编写的是blog_list.html;之前我们已经写过大概的前端代码,这里需要稍作补充;

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客列表</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/blog_list.css">
</head>
<body><!-- 这是导航栏 --><div class="nav"><img src="image/logo.jpeg" alt=""><span>我的博客系统</span><!-- 空白元素, 用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="logout">注销</a></div><!-- 这里的 .container 作为页面的版心 --><div class="container"><!-- 左侧个人信息 --><div class="left"><!-- 表示整个用户信息区域. --><div class="card"><img src="image/头像.jpg" alt=""><h3></h3><a href="#">github 地址</a><div class="counter"><span>文章</span><span>分类</span></div><div class="counter"><span>2</span><span>1</span></div></div></div><!-- 右侧内容详情 --><div class="right"><!-- .blog 就对应一个博客 --><!-- <div class="blog"><div class="title">我的第一篇博客</div><div class="date">2022-05-05 20:52:00</div><div class="desc">从今天起, 我要认真敲代码. Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla alias tenetur ut velit ex voluptatibus consequatur quam exercitationem, assumenda ea blanditiis repudiandae? Repellendus tenetur nostrum asperiores molestias doloremque cupiditate maiores.</div><a href="#">查看全文 &gt;&gt; </a></div> --></div></div></div><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script>// 在页面加载的时候, 通过 ajax 给服务器发送数据, 获取到博客列表信息, 并且显示在界面上. function getBlogList() {$.ajax({type: 'get',url: 'blog',success: function(body) {// 获取到的 body 就是一个 js 对象数组, 每个元素就是一个 js 对象, 根据这个对象构造 div// 1. 先把 .right 里原有的内容给清空let rightDiv = document.querySelector('.right');rightDiv.innerHTML = '';// 2. 遍历 body, 构造出一个个的 blogDivfor (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 = '查看全文 &gt;&gt;';// 此处希望点击之后能够跳转到 博客详情页 !!// 这个跳转过程需要告知服务器要访问的是哪个博客的详情页. a.href = 'blog_detail.html?blogId=' + blog.blogId;blogDiv.appendChild(a);// 把 blogDiv 挂到 dom 树上!rightDiv.appendChild(blogDiv);}}, error: function() {alert("获取博客列表失败!");}});}getBlogList();</script><!-- 在这里引入上述的 js 文件, 就可以执行到里面的代码, 也就进行了登录状态的监测了 --><script src="js/common.js"></script><script>// 针对博客列表页, 调用的时候传入参数getUserInfo('blog_list.html');</script>
</body>
</html>

此时需要在浏览器访问这个url地址,可以观察到我们的博客系统页面,blog_list;

http://127.0.0.1:8080/blog_system2/blog_list.html

接下来编写blog_detail.html文件的内容;

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客详情页</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/blog_detail.css"><!-- 引入 editor.md 的依赖 --><link rel="stylesheet" href="editor.md/css/editormd.min.css" /><script src="js/jquery.min.js"></script><script src="editor.md/lib/marked.min.js"></script><script src="editor.md/lib/prettify.min.js"></script><script src="editor.md/editormd.js"></script>
</head>
<body><!-- 这是导航栏 --><div class="nav"><img src="image/logo.jpeg" alt=""><span>我的博客系统</span><!-- 空白元素, 用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="logout">注销</a></div><!-- 这里的 .container 作为页面的版心 --><div class="container"><!-- 左侧个人信息 --><div class="left"><!-- 表示整个用户信息区域. --><div class="card"><img src="image/doge.png" alt=""><h3></h3><a href="#">github 地址</a><div class="counter"><span>文章</span><span>分类</span></div><div class="counter"><span>2</span><span>1</span></div></div></div><!-- 右侧内容详情 --><div class="right"><!-- 使用这个 div 来包裹整个博客的内容详情 --><div class="blog-content"><!-- 博客标题 --><h3></h3><!-- 博客的时间 --><div class="date"></div><!-- 博客的正文内容 --><div id="content" style="opacity: 80%"></div></div></div></div><script>function getBlogDetail() {$.ajax({type: 'get',// location.search 拿到了形如 '?blogId=5' 这样的一段内容url: 'blog' + location.search,success: function(body) {// 根据 body 中的内容来构造页面// 1. 构造博客标题let h3 = document.querySelector(".blog-content>h3");h3.innerHTML = body.title;// 2. 构造博客发布时间let dateDiv = document.querySelector('.date');dateDiv.innerHTML = body.postTime;// 3. 构造博客正文// 如果直接把 content 设为 innerHTML, 此时展示在界面上的内容, 是原始的 markdown 字符串// 咱们需要的是渲染后的, 带有格式的效果// let content = document.querySelector('#content');// content.innerHTML = body.content;// 第一个参数对应 id=content 的 html 标签. 渲染后得到的 html 片段就会被放到这个 标签下. editormd.markdownToHTML('content', {markdown: body.content});}});}getBlogDetail();// 加上一个逻辑, 通过 GET /login 这个接口来获取下当前的登录状态~function getUserInfo(pageName) {$.ajax({type: 'get',url: 'login',success: function(body) {// 判定此处的 body 是不是一个有效的 user 对象(userId 是否非 0)if (body.userId && body.userId > 0) {// 登录成功!// 不做处理!console.log("当前用户登录成功! 用户名: " + body.username);// 在 getUserInfo 的回调函数中, 来调用获取作者信息getAuthorInfo(body);} else {// 登录失败!// 让前端页面, 跳转到 login.htmlalert("当前您尚未登录! 请登录后再访问博客列表!");location.assign('blog_login.html');}},error: function() {alert("当前您尚未登录! 请登录后再访问博客列表!");location.assign('blog_login.html');}});}// 判定用户的登录状态getUserInfo("blog_detail.html");// 从服务器获取一下当前博客的作者信息, 并显示到界面上. // 参数 user 就是刚才从服务器拿到的当前登录用户的信息function getAuthorInfo(user) {$.ajax({type: 'get',url: 'authorInfo' + location.search,success: function(body) {// 此处的 body, 就是服务器返回的 User 对象, 是文章的作者信息if (body.username) {// 如果响应中的 username 存在, 就把这个值设置到页面上. changeUserName(body.username);if (body.username == user.username) {// 作者和登录的用户是一个人, 则显示 "删除按钮"let navDiv = document.querySelector('.nav');let a = document.createElement('a');a.innerHTML = '删除';// 期望点击删除, 构造一个形如 blogDelete?blogId=6 这样的请求a.href = 'blogDelete' + location.search;navDiv.appendChild(a);}} else {console.log("获取作者信息失败! " + body.reason);}}});}function changeUserName(username) {let h3 = document.querySelector('.card>h3');h3.innerHTML = username;}</script>
</body>
</html>

blog_login.html;

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录页面</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/blog_login.css">
</head>
<body><!-- 这是导航栏 --><div class="nav"><img src="image/logo.jpeg" alt=""><span>我的博客系统</span><!-- 空白元素, 用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><!-- 注销按钮没必要在登录页面展示 --><!-- <a href="#">注销</a> --></div><div class="login-container"><form action="login" method="post"><div class="login-dialog"><h3>登录</h3><div class="row"><span>用户名</span><input type="text" id="username" name="username"></div><div class="row"><span>密码</span><input type="password" id="password" name="password"></div><div class="row"><!-- <button>提交</button> --><input type="submit" id="submit" value="提交"></div></div></form></div>
</body>
</html>

即这里在进入博客列表页/详情页的时候,先检查一下用户的登录状态,如果是用户已经登录,才能够继续使用,如果未登录,则强制跳转到login界面;

 直接点击登录,会出现以下提示;

 

 在controller包下创建一个新的java文件,LoginServlet;

package controller;import com.fasterxml.jackson.databind.ObjectMapper;
import model.User;
import model.UserDao;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;@WebServlet("/login")
public class LoginServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@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");System.out.println("username=" + username + ", password=" + password);if (username == null || "".equals(username) || password == null || "".equals(password)) {// 请求的内容缺失, 肯定是登录失败!!resp.setContentType("text/html; charset=utf8");resp.getWriter().write("当前的用户名或密码为空!");return;}// 2. 和数据库中的内容进行比较UserDao userDao = new UserDao();User user = userDao.selectByName(username);if (user == null || !user.getPassword().equals(password)) {// 用户没有查到或者密码不匹配, 也是登录失败!resp.setContentType("text/html; charset=utf8");resp.getWriter().write("用户名或密码错误!");return;}// 3. 如果比较通过, 就创建会话.HttpSession session = req.getSession(true);// 把刚才的用户信息, 存储到会话中.session.setAttribute("user", user);// 4. 返回一个重定向报文, 跳转到博客列表页.resp.sendRedirect("blog_list.html");}// 这个方法用来让前端检测当前的登录状态.@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json;charset=utf8");HttpSession session = req.getSession(false);if (session == null) {// 检测下会话是否存在, 不存在说明未登录!User user = new User();resp.getWriter().write(objectMapper.writeValueAsString(user));return;}User user = (User) session.getAttribute("user");if (user == null) {// 虽然有会话, 但是会话里没有 user 对象, 也视为未登录.user = new User();resp.getWriter().write(objectMapper.writeValueAsString(user));return;}// 已经登录的状态!!// 注意, 此处不要把密码给返回到前端user.setPassword("");resp.getWriter().write(objectMapper.writeValueAsString(user));}
}

这个代码里面,会在服务器这边拿到了session,并且视为也拿到了里面的user,视为登录成功!

若登录成功,服务器会给客户端返回sessionid,浏览器会保存这个sessionid,下次请求的时候就带上这个id,服务器拿到了sessionid就可以去hash表里查,就可以知道当前的session对象是谁;

注销功能;

这里的注销其实就是退出登录的状态;我们这里设计的是在导航栏里面给一个"注销"按钮;当用户点击注销,就会在服务器上取消登录状态,并且能够跳转到登录页面;

同样,这里需要先约定前后端交互的接口;

请求:GET/logout

响应:HTTP/1.1 302

Location:login.html

注销逻辑的服务器代码:

在controller包下创建一个新的类LogoutServlet;

package controller;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 先找到当前用户的会话,HttpSession session = req.getSession(false);if (session == null) {// 用户没有登录!! 谈不上注销!resp.getWriter().write("当前用户尚未登录! 无法注销!");return;}// 然后把这个用户的会话中的信息给删掉就行了!!session.removeAttribute("user");resp.sendRedirect("blog_login.html");}
}

运行实例:

1)当点击注销按钮时,会直接回到登录页面;

登录状态是啥样的?

这里用户有一个session,同时session有一个属性,两者同时具备,才算登录状态;那么注销的话只要破坏上面任意一个条件即可,这里选择的是把user属性从session中删除掉;

发布博客;

在博客编辑页中,当用户输入了博客标题和正文之后,点击发布,此时就会把博客数据提交到服务器,由服务器存储到数据库中;

blog_edit.html;

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客编辑页</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/blog_edit.css"><!-- 引入 editor.md 的依赖 --><link rel="stylesheet" href="editor.md/css/editormd.min.css" /><script src="js/jquery.min.js"></script><script src="editor.md/lib/marked.min.js"></script><script src="editor.md/lib/prettify.min.js"></script><script src="editor.md/editormd.js"></script>
</head>
<body><!-- 这是导航栏 --><div class="nav"><img src="image/logo.jpeg" alt=""><span>我的博客系统</span><!-- 空白元素, 用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="logout">注销</a></div><!-- 包裹整个博客编辑页内容的顶级容器 --><div class="blog-edit-container"><form action="blog" method="post" style="height:100%"><div class="title"><input type="text" placeholder="在此处输入标题" name="title" id="title"><!-- <button>发布文章</button> --><input type="submit" value="发布文章" id="submit"></div><!-- 放置 md 编辑器 --><div id="editor"><!-- 为了进行 form 的提交, 此处搞一个 textarea 多行编辑框, 借助这个编辑框来实现表单的提交 --><!-- 可以设置 editor.md, 让编辑器把 markdown 内容也同步的保存到这个隐藏的 textarea 中, 从而可以进行 form 提交 --><textarea name="content" style="display:none"></textarea></div></form></div><script>// 初始化编辑器let editor = editormd("editor", {// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. width: "100%",// 设定编辑器高度height: "calc(100% - 50px)",// 编辑器中的初始内容markdown: "# 在这里写下一篇博客",// 指定 editor.md 依赖的插件路径path: "editor.md/lib/",// 此处要加上一个重要的选项, 然后 editor.md 就会自动把用户在编辑器输入的内容同步保存到 隐藏的 textarea 中了!saveHTMLToTextarea: true,});</script>
</body>
</html>

与之对应的在服务器代码中,在BlogServlet中添加一个doPost方法,来处理上述post请求;

最终效果如下:

OK,以上就是个人博客系统的全部分析代码+解析过程了,感谢您的阅读,项目源码已经放在文章开头,如有需要,可自取,我们下期再见!

相关文章:

【Java项目】基于Java+MySQL+Tomcat+maven+Servlet的个人博客系统的完整分析

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【Java项目】 ✈️✈️本篇内容:个人博客系统前后端分离实现&#xff01; &#x1f680;&#x1f680;个人代码托管github&#xff1a;博客系统源码地址&#xff…...

java 程序员怎么做找工作

java 程序员怎么做找工作 在网络招聘网站上搜索职位。在中国&#xff0c;像智联招聘、前程无忧、猎聘网等招聘网站上&#xff0c;有许多公司在招聘JAVA程序员。通过这些网站可以快速找到自己合适的工作。 关注社交媒体和专业网站。 加入一些面向JAVA程序员的社交媒体和专业网…...

S7-1200对于不同项目下的PLC之间进行开放式以太网通信的具体方法示例

S7-1200对于不同项目下的PLC之间进行开放式以太网通信的具体方法示例 如下图所示,打开TIA博途创建一个新项目,并通过“添加新设备”组态 S7-1200 客户端 ,选择 CPU1214C DC/DC/DC (client IP:192.168.0.102),建立新子网; 首先编写客户端程序:打开OB1编程界面,选择指令…...

操作系统(四):磁盘调度算法,先来先服务,最短寻道时间优先,电梯算法

文章目录一、磁盘结构二、先来先服务三、最短寻道时间优先四、电梯算法 SCAN一、磁盘结构 盘面&#xff08;Platter&#xff09;&#xff1a;一个磁盘有多个盘面&#xff1b; 磁道&#xff08;Track&#xff09;&#xff1a;盘面上的圆形带状区域&#xff0c;一个盘面可以有多…...

maven解决包冲突简单方式(插件maven helper | maven指令)

文章目录使用idea插件maven helper使用maven指令在Java开发中&#xff0c;常常会遇到不同jar包之间存在冲突的情况&#xff0c;这可能会导致编译错误、运行时异常等问题。 使用idea插件maven helper 在idea安装插件maven helper 安装重启完之后点击pom文件&#xff0c;有一个De…...

100行Pytorch代码实现三维重建技术神经辐射场 (NeRF)

提起三维重建技术&#xff0c;NeRF是一个绝对绕不过去的名字。这项逆天的技术&#xff0c;一经提出就被众多研究者所重视&#xff0c;对该技术进行深入研究并提出改进已经成为一个热点。不到两年的时间&#xff0c;NeRF及其变种已经成为重建领域的主流。本文通过100行的Pytorch…...

linux操作系统篇

目录 操作系统概述基本特征并发共享虚拟异步进程管理内存管理文件管理设备管理宏内核和微内核宏内核微内核中断分类外中断异常陷入(系统调用)进程管理进程与线程的区别进程状态切换进程调度算法**批处理系统****交互式系统**进程同步临界...

redis+token实现登录校验,前后端分离,及解跨域问题的4种方法

目录 一、使用自定义filter实现跨域 1、客户端向服务端发送请求 2、服务端做登录验证了&#xff0c;并生成登路用户对应的token&#xff0c;保存到redis 3、响应&#xff08;报错&#xff09;-----跨域问题 4、解决跨域问题--------服务器端添加过滤器&#xff0c;设置请求…...

怎么解密MD5,常见的MD5解密方法,一看就会

MD5是一种被广泛使用的密码散列函数&#xff0c;曾在计算机安全领域使用很广泛&#xff0c;但是也因为它容易发生碰撞&#xff0c;而被人们认为不安全。那么&#xff0c;MD5应用场景有哪些&#xff0c;我们怎么解密MD5&#xff0c;本文将带大家了解MD5的相关知识&#xff0c;以…...

Vue3 目录结构

Vue3 目录结构 架构搭建 请确保你的电脑上成功安装 Node.js&#xff0c;本项目使用 Vite 构建工具&#xff0c;需要 Node.js 版本 > 12.0.0。 查看 Node.js 版本&#xff1a; node -v建议将 Node.js 升级到最新的稳定版本&#xff1a; 使用 nvm 安装最新稳定版 Node.js…...

Tsp_nurrec表空间满处理记录20230215

Tsp_nurrec表空间满处理记录20230215 一、问题: 问题:护理病历表空间不足。 二、解决过程:1.查询表空间使用效率 SELECT UPPER(F.TABLESPACE_NAME) “表空间名”, D.TOT_GROOTTE_MB "表空间大小(M)",D.TOT_GROOTTE_MB - F.TOTAL_BYTES "已使用空间(M)"…...

影像测量设备都有什么?有哪些影像仪器?

影像测量仪器是广泛应用于机械、电子、仪表的仪器。主要由机械主体、标尺系统、影像探测系统、驱动控制系统和测量软件等与高精密工作台结构组成的光电测量仪器。一般分为三大类&#xff1a;手动影像仪、自动影像仪和闪测影像仪。测量元素主要有&#xff1a;长度、宽度、高度、…...

Transformer:开启CV研究新时代

来源&#xff1a;投稿 作者&#xff1a;魔峥 编辑&#xff1a;学姐 起源回顾 有关Attention的论文早在上世纪九十年代就提出了。 在2012年后的深度学习时代&#xff0c;Attention再次被翻了出来&#xff0c;被用在自然语言处理任务&#xff0c;提高RNN模型的训练速度。但是由…...

Flink X Hologres构建企业级Streaming Warehouse

摘要&#xff1a;本文整理自阿里云资深技术专家&#xff0c;阿里云Hologres负责人姜伟华&#xff0c;在FFA实时湖仓专场的分享。点击查看>>本篇内容主要分为四个部分&#xff1a; 一、实时数仓分层的技术需求 二、阿里云一站式实时数仓Hologres介绍 三、Flink x Hologres…...

关于 mysql数据库插入中文变空白 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/129048030 红胖子网络科技的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…...

不可错过的SQL优化干货分享-sql优化、索引使用

本文是向大家介绍在sql调优的几个操作步骤&#xff0c;它能够在日常遇到慢sql时有分析优化思路&#xff0c;能够让开发者更好的了解sql执行的顺序和原理。一、前言在日常开发中&#xff0c;我们经常遇到一些数据库相关的问题&#xff0c;比方说&#xff1a;SQL已经走了索引了&a…...

vue3:直接修改reative的值,页面却不响应,这是什么情况?

目录 前言 错误示范&#xff1a; 解决办法&#xff1a; 1.使用ref 2.reative多套一层 3.使用Object.assign 前言&#xff1a; 今天看到有人在提问&#xff0c;问题是这样的&#xff0c;我修改了reative的值&#xff0c;数据居然失去了响应性&#xff0c;页面毫无变化&…...

从Vue2 到 Vue3,这些路由差异你需要掌握!

✨ 个人主页&#xff1a;山山而川~xyj ⚶ 作者简介&#xff1a;前端领域新星创作者&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步&#xff0c;一起加油&#xff01; &#x1f386; 系列专栏&#xff1a; vue系列 &#x1f680; 学习格言&#xff1a;与其临渊羡…...

Maxwell简介、部署、原理和使用介绍

Maxwell简介、部署、原理和使用介绍 1.Maxwell概述简介 1-1.Maxwell简介 ​ Maxwell是由美国Zendesk公司开源&#xff0c;使用Java编写的MySQL变更数据抓取软件。他会实时监控Mysql数据库的数据变更操作&#xff08;包括insert、update、delete&#xff09;&#xff0c;并将变…...

20230215_数据库过程_渠道业务清算过程

----2023-0131-清算过程 zhyw.shc_drop_retable(upper(‘xc_qdcn_pgtx_qsqdtype_sja’),‘SHZC’); SQL_STRING:‘create table shzc.xc_qdcn_pgtx_qsqdtype_sja as select * from shzc.xc_qdcn_pgtx_qdtype a where a.in_time ( select max(a.in_time) from shzc.xc_qdcn_pg…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...