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

news2025/2/26 10:08:12

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

🛰️🛰️系列专栏:【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 {
    @Override
    protected 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;

-- 编写建库建表的 sql

create 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,         -- 文章作者的 id
    postTime 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;//注意加上volatile

    private static DataSource getDataSource() {
        if (dataSource == null) {
            synchronized (DBUtil.class) {//双重校验锁
                if (dataSource == null) {
                    dataSource = new MysqlDataSource();
                    ((MysqlDataSource)dataSource).setUrl(URL);
                    ((MysqlDataSource)dataSource).setUser(USERNAME);
                    ((MysqlDataSource)dataSource).setPassword(PASSWORD);
                }
            }
        }
        return dataSource;
    }

    public static Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }

    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException 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) 执行 SQL
            statement.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();

    // 这个方法用来获取到数据库中的博客列表.
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json; charset=utf8");
        BlogDao blogDao = new BlogDao();
        // 先尝试获取到 req 中的 blogId 参数. 如果该参数存在, 说明是要请求博客详情
        // 如果该参数不存在, 说明是要请求博客的列表.
        String param = req.getParameter("blogId");
        if (param == null) {
            // 不存在参数, 获取博客列表
            List<Blog> blogs = blogDao.selectAll();
            // 把 blogs 对象转成 JSON 格式.
            String respJson = objectMapper.writeValueAsString(blogs);
            resp.getWriter().write(respJson);
        } else {
            // 存在参数, 获取博客详情
            int blogId = Integer.parseInt(param);
            Blog blog = blogDao.selectOne(blogId);
            String respJson = objectMapper.writeValueAsString(blog);
            resp.getWriter().write(respJson);
        }
    }
}

代码写完之后,我们来验证这个代码是否正确,可以使用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, 构造出一个个的 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 = '查看全文 &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.html
                        alert("当前您尚未登录! 请登录后再访问博客列表!");
                        location.assign('blog_login.html');
                    }
                },
                error: function() {
                    alert("当前您尚未登录! 请登录后再访问博客列表!");
                    location.assign('blog_login.html');
                }
            });
        }

        // 判定用户的登录状态
        getUserInfo("blog_detail.html");

        // 从服务器获取一下当前博客的作者信息, 并显示到界面上. 
        // 参数 user 就是刚才从服务器拿到的当前登录用户的信息
        function getAuthorInfo(user) {
            $.ajax({
                type: 'get',
                url: 'authorInfo' + location.search,
                success: function(body) {
                    // 此处的 body, 就是服务器返回的 User 对象, 是文章的作者信息
                    if (body.username) {
                        // 如果响应中的 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();

    @Override
    protected 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");
    }

    // 这个方法用来让前端检测当前的登录状态.
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf8");
        HttpSession session = req.getSession(false);
        if (session == null) {
            // 检测下会话是否存在, 不存在说明未登录!
            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 {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 先找到当前用户的会话,
        HttpSession session = req.getSession(false);
        if (session == null) {
            // 用户没有登录!! 谈不上注销!
            resp.getWriter().write("当前用户尚未登录! 无法注销!");
            return;
        }
        // 然后把这个用户的会话中的信息给删掉就行了!!
        session.removeAttribute("user");
        resp.sendRedirect("blog_login.html");
    }
}

运行实例:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/347538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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

元宇宙基础设施:WEB 3.0 chain33 优势分析

WEB 3.0 chain33 优势分析 一、与以太坊兼容&#xff08;优势&#xff1a;兼容及开发成本低百倍&#xff09; 使用 Solidity 开发智能合约并部署在 EVM 虚拟机上是目前以太坊生态主流的开发 模式&#xff0c;目前看来不兼容以太坊或不能与以太坊跨链都可能是不归路&#xff0c;…

AcWing语法基础课笔记 第一章 C++入门及简单的顺序结构

第一章 C入门及简单的顺序结构 编程是一种控制计算机的方式&#xff0c;和我们平时双击打开文件、关机、重启没有任何区别。 ———闫学灿 C中常用的变量类型 和所占字节大小 输出变量地址符&#xff1a; 软件环境 作业的评测与提交 在线练习地址&#xff1a;www.acwing.com …

数据库设计的基本步骤

分步设计法遵循自顶向下、逐步求精的原则&#xff0c;将数据库设计过程分解为若干相互独立又相互依存的阶段&#xff0c;每一阶段采用不同的技术与工具&#xff0c;解决不同的问题&#xff0c;从而将问题局部化&#xff0c;减少了局部问题对整体设计的影响。目前&#xff0c;此…

aws Distro for OpenTelemetry 可观测性workshop记录

参考资料 https://aws-otel.github.io/docs/introductionhttps://aws-otel.github.io/docs/introduction aws distro for opentelemetry 官方提供了不同语言不同使用场景下完善的使用实例和相关配置。 AWS Distro for OpenTelemetrics 由以下部分组成&#xff0c;用于向后端…

用户认证概述

文章目录一、用户身份认证1.1 单一服务器模式1.2 SSO&#xff08;Single Sign On&#xff09;模式1.3 Token模式二、JWT令牌2.1 JWT 令牌说明2.2 JWT令牌的组成2.3 JWT 问题和趋势2.4 JWT 测试一、用户身份认证 1.1 单一服务器模式 一般过程如下&#xff1a; 用户向服务器发送…

【编程基础之Python】5、安装Python第三方模块

【编程基础之Python】5、安装Python第三方模块安装Python第三方模块为什么需要安装第三方模块Python包管理器介绍pippip installpython -m pip installcondaconda install在Windows环境中安装Python模块安装numpy安装pandas安装matplotlib在Linux环境中安装Python模块在PyCharm…

线程的创建

1. 多线程常用函数 1.1 创建一条新线程pthread_create 对此函数使用注意以下几点&#xff1a; 线程例程指的是&#xff1a;如果线程创建成功&#xff0c;则该线程会立即执行的函数。POSIX线程库的所有API对返回值的处理原则一致&#xff1a;成功返回0&#xff0c;失败返回错误…

NLP实践——知识图谱问答模型FiD

NLP实践——知识图谱问答模型FiD0. 简介1. 模型结构2. 召回3. 问答4. 结合知识的问答0. 简介 好久没有更新了&#xff0c;今天介绍一个知识图谱问答&#xff08;KBQA&#xff09;模型&#xff0c;在此之前我一直在用huggingface的Pipeline中提供的QA模型&#xff0c;非常方便但…

低代码和零代码的有什么不同?如何区分?

低代码开发平台和零代码平台的区别是什么&#xff1f;一个例子就能讲清楚&#xff01; 周末你外出露营&#xff0c;在野外需要搭一个帐篷。有两种方法&#xff1a; 一种是最原始的搭帐篷方法&#xff0c;即有隔水布、外账、内账、营柱骨架等等......另一种是直接“封装好”的…

OpenCV-PyQT项目实战(5)项目案例01:图像模糊

欢迎关注『OpenCV-PyQT项目实战 Youcans』系列&#xff0c;持续更新中 OpenCV-PyQT项目实战&#xff08;1&#xff09;安装与环境配置 OpenCV-PyQT项目实战&#xff08;2&#xff09;QtDesigner 和 PyUIC 快速入门 OpenCV-PyQT项目实战&#xff08;3&#xff09;信号与槽机制 …

PySpark实战一之入门

1、PySpark的编程模型 分三个模块&#xff1a; 数据输入&#xff1a;通过SparkContext对象&#xff0c;完成数据输入 数据处理计算&#xff1a;输入数据后得到RDD对象&#xff0c;对RDD对象的成员方法进行迭代计算 数据输出&#xff1a;最后通过RDD对象的成员方法&#xff0…

互联网行业固定资产智能化解决方案为企业降本增效

互联网行业的固定资产数量和种类往往比较多&#xff0c;来源可能是租赁、购入、调拨等。主要分为&#xff1a;办公设备、电子设备、服务器等。固定资产是互联网企业的重要资产之一&#xff0c;是企业持续经营的物质基础。因此&#xff0c;对于实物资产的管理尤为重要。 互联网…

搭建zookeeper高可用集群详细步骤

目录 一、虚拟机设置 1.新建一台虚拟机并克隆三台&#xff0c;配置自定义 2.修改四台虚拟机的主机名并立即生效 3.修改四台虚拟机的网络信息 4.重启四台虚拟机的网络服务并测试网络连接 5.重启四台虚拟机&#xff0c;启动后关闭四台虚拟机的防火墙 6.在第一台虚拟机的/e…

TripleCross:一款功能强大的Linux eBPF安全研究工具

关于TripleCross TripleCross是一款功能强大的Linux eBPF安全研究工具&#xff0c;该工具提供了后门、C2、代码库注入、执行劫持、持久化和隐蔽执行等功能。 功能介绍 1、使用一个代码库注入模块通过往进程的虚拟内存中写入命令来执行恶意代码&#xff1b; 2、提供了一个行劫…

波卡2022年第四季度报告

本文将介绍Messari最新发布的波卡Polkadot 2022年第四季度报告内容。 1 Messari已经发布关于波卡Polkadot最新的报告&#xff1a;显示了2022年第四季度的日活账户增加了64%&#xff0c;新用户增长49%。 2 Messari指出&#xff0c;波卡中继链在2022第四季度的环比增长令人印象…

JavaScript 保留关键字

文章目录JavaScript 保留关键字JavaScript 标准JavaScript 保留关键字JavaScript 对象、属性和方法Java 保留关键字Windows 保留关键字HTML 事件句柄非标准 JavaScriptJavaScript 保留关键字 在 JavaScript 中&#xff0c;一些标识符是保留关键字&#xff0c;不能用作变量名或函…

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

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

部门新来个00后卷王,太让人崩溃了,想离职了....

在职场上&#xff0c;什么样的人最让人反感&#xff1f; 是技术不好的人吗&#xff1f; 并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗&#xff1f; 也不是。技术很强的同事&#xff0c;可遇不可求&#xff0c;向他学习还来不及呢。 真正让人反感的…

【uniapp】getOpenerEventChannel().once 接收参数无效的解决方案

uniapp项目开发跨平台应用常会遇到接收参数无效的问题&#xff0c;无法判断是哪里出错了&#xff0c;这里是讲替代的方案&#xff0c;现有三种方案可选。 原因 一般我们是这样处理向另一个页面传参&#xff0c;代码是这样写的 //... let { title, type, rank } args; uni.n…