【Java项目】前后端分离的博客系统

本文详细介绍了如何基于Maven构建一个前后端分离的博客系统,包括创建项目、引入依赖、数据库设计、编写模型层和API层的代码,以及实现登录状态检查和注销功能。通过数据库操作,实现了博客的增删查改,并且利用Ajax实现了前后端交互,确保用户登录后才能发布博客和查看详细内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面我们已经实现了博客系统的前端页面
本篇博客是基于前端页面,编写的后端服务器,实现前后端分离
接下来我们开始编写博客系统

准备工作

打开idea,创建maven项目

1)new Project
(1)

2)选择创建maven项目
(2)

3)给maven项目取个名字,我取的是blog_system
(3)

引入依赖

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

点击官网: maven中央仓库

servlet依赖引入:

1)打开链接,搜索框搜索servlet,进入 Java Servlet API
servlet

2)鼠标滚轮向下滑动,找到3.0.1版本(Servlet 的版本要和 Tomcat 匹配)
查询版本对应关系: Tomcat

3.1.0版本

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

<!-- 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>

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

jackson依赖引入:

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.2</version>
</dependency>

mysql依赖引入:

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

点击刷新

刷新进行依赖的加载

如下图所示,不报红,说明成功引入三个依赖!

创建相关目录

1)点开我们项目下的 src 文件,找到 main ,然后右键创建一个新的目录,webapp ;在 webapp下创建一个目录叫做 WEB-INF,然后在 WEB-INF 下面创建一个新的xml文件叫做web.xml;创建好之后如下图所示

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>

编写代码

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

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("helloservlet");
    }
}

打包部署

使用idea自带的插件smart tomcat来完成;

在浏览器验证程序

编写服务器代码

我们需要把上篇博客的前端代码全部粘贴到webapp下,这样就把代码集成到我们的项目上了;后续打包部署的时候,这些资源会被一并打包部署,就可以在浏览器访问到了。

粘贴过来的目录结构;

这里需要存储博客,点击发布的时候,博客会被发表到服务器上,需要被存储起来。
在博客详情页和博客列表页我们可以拿到博客的内容。所以我们需要用数据库存储 blog表 和 user表。

开始编写数据库代码
1)在main下创建一个File,叫做db.sql(方便我们保存和使用)

项目中我们会用到两个表:

博客表blog(blogId,title,content,postTime,userId)

用户表user (userId,username,password);

-- 这个文件主要用来写建库建表语句.

create database if not exists BloggingSystem charset utf8;
use BloggingSystem;

-- 删除旧表, 重新创建新表. 删除旧表是为了防止之前的残留数据对后续的程序有负面影响.
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');
insert into user values(3, 'yangjing', '123');
insert into user values(4, 'fire-yang', '123');
//1数据库设计实现 2博客列表页 3博客详情页 4登录页实现 5强制要求登录 6显示用户信息 7退出登陆状态 8发布博客

auto_increment:主键自增长 数据库系统根据定义自动赋值。每增加一条记录,主键会自动以相同的步长进行增长。

2)编写完成后,复制上面的SQL语句,直接粘贴到我们的 MySQL 客户端;

3)查询数据库和表

封装数据库操作

model层

在java文件夹下创建 model 文件夹,我们将操作数据库所用到的代码都存放到这个文件夹中

我们先将数据库建立和断开操作的代码进行封装,这样以后我们在使用的时候比较方便

1)首先在 model 文件夹中创建一个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 DataSource dataSource = new MysqlDataSource();

    static {
        ((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/BloggingSystem?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource) dataSource).setUser("root");
//        数据库无密码 设置""即可
        ((MysqlDataSource) dataSource).setPassword("");

//        ((MysqlDataSource) dataSource).setPassword("111111");
    }

    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();
            }
        }
    }
}

2)创建 Blog类 和 User类 ;

User类:表示一个用户

package model;

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;
    }
}

针对用户表来设计博客表;
Blog类:表示一篇博客

package model;

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;
    }
}

封装针对数据增删改查;

创建两个类:BlogDao、UserDao。
这两个类主要包含对博客表和用户表的增删改查操作

创建Blog_Dao:
在BlogDao类中我们要实现以下几点功能:
1.新增博客(博客编辑页)
2.查询出博客列表(博客列表页)
3.查询出指定博客的详细内容(博客详情页)

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. 构造 SQL
            String 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. 执行 sql
            statement.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. 构造 SQL
            String sql = "select * from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogId);
            // 3. 执行 SQL
            resultSet = 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. 执行 SQL
            resultSet = 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. 构造 SQL
            String sql = "delete from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogId);
            // 3. 执行 SQL
            statement.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            // 4. 关闭
            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;

// 针对用户表提供的基本操作.
// 由于此处没写注册功能, 也就不必搞 add
// 也没有用户删号功能. 也就不必 delete
public class UserDao {
    // 1. 根据 userId 来查用户信息
    public User selectById(int userId) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            // 1. 和数据库建立连接.
            connection = DBUtil.getConnection();
            // 2. 构造 SQL
            String sql = "select * from user where userId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, userId);
            // 3. 执行 SQL
            resultSet = statement.executeQuery();
            // 4. 遍历结果集合
            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. 根据 username 来查用户信息 (登录的时候)
    public User selectByUsername(String username) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            // 1. 和数据库建立连接.
            connection = DBUtil.getConnection();
            // 2. 构造 SQL
            String sql = "select * from user where username = ?";
            statement = connection.prepareStatement(sql);
            statement.setString(1, username);
            // 3. 执行 SQL
            resultSet = statement.executeQuery();
            // 4. 遍历结果集合
            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;
    }
}

接下来开始编写服务器中的api层;

api层

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

1)从博客列表页开始;

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

效果如下:

  1. 约定

请求:GET/blog

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

[{blogId:1,title:‘这是第一篇博客’,
content:‘这是博客正文’,
userId:1,postTime:‘2023-06-05 16:00:00’},
{blogId:2,title:‘这是第二篇博客’,
content:‘这是博客正文’,
userId:1,
postTime:‘2023-06-05 16:00:00’},]

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

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

package api;

import com.fasterxml.jackson.databind.ObjectMapper;
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.sql.Timestamp;
import java.util.List;

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 尝试获取一下 queryString 中的 blogId 字段.
        BlogDao blogDao = new BlogDao();
        String blogId = req.getParameter("blogId");
        if (blogId == null) {
            // queryString 不存在, 说明这次请求是获取博客列表
            List<Blog> blogs = blogDao.selectAll();
            // 需要把 blogs 转成符合要求的 json 格式字符串.
            String respJson = objectMapper.writeValueAsString(blogs);
            resp.setContentType("application/json; charset=utf8");
            resp.getWriter().write(respJson);
        } else {
            // 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);
        }
    }

    @Override
    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、编写客户端代码

这里我们想要实现的是在页面加载的时候让页面通过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.jpg" alt="">
        <span class="title">我的博客系统</span>
        <!-- 这个标签仅仅用于占位, 把这几个 a 标签挤到右侧去 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="logout">注销</a>
    </div>
    <!-- 页面主体部分 -->
    <div class="container">
        <!-- 左侧信息 -->
        <div class="container-left">
            <!-- 使用这个 .card 表示用户信息 -->
            <div class="card">
                <!-- 用户头像 -->
                <img src="image/touxiang.jpg" alt="">
                <!-- 用户名 -->
                <h3>fire-yang</h3>
                <a href="#">gitee 地址</a>
                <div class="counter">
                    <span>文章</span>
                    <span>分类</span>
                </div>
                <div class="counter">
                    <span>2</span>
                    <span>1</span>
                </div>
            </div>
        </div>
        <!-- 右侧信息 -->
        <div class="container-right">
            <!-- 表示一篇博客 (样例, 最后删除) -->
            
            
        </div>
    </div>

    <script src="./js/jquery.min.js"></script>
    <script>
        // 在页面加载时, 向服务器发起请求, 获取博客列表数据
        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 代码
                        // 构造整个博客 div
                        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;';
                        // 期望点击之后能跳转到博客详情页. 为了让博客详情页知道是点了哪个博客, 把 blogId 给传过去
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        blogDiv.appendChild(a);

                        // 把 blogDiv 加到父元素中
                        containerRight.appendChild(blogDiv);
                    }
                }
            });
        }

        // 要记得调用
        getBlogs();

        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();
    </script>
</body>
</html>

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

http://127.0.0.1:8080/blog_system/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">
    <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
</head>
<body>
    <!-- 导航栏 -->
    <div class="nav">
        <img src="image/logo.jpg" alt="">
        <span class="title">我的博客系统</span>
        <!-- 这个标签仅仅用于占位, 把这几个 a 标签挤到右侧去 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="logout">注销</a>
    </div>
    <!-- 页面主体部分 -->
    <div class="container">
        <!-- 左侧信息 -->
        <div class="container-left">
            <!-- 使用这个 .card 表示用户信息 -->
            <div class="card">
                <!-- 用户头像 -->
                <img src="image/touxiang.jpg" alt="">
                <!-- 用户名 -->
                <h3>fire-yang</h3>
                <a href="#">gitee 地址</a>
                <div class="counter">
                    <span>文章</span>
                    <span>分类</span>
                </div>
                <div class="counter">
                    <span>2</span>
                    <span>1</span>
                </div>
            </div>
        </div>
        <!-- 右侧信息 -->
        <div class="container-right">
            <!-- 博客标题 -->
            <h3 class="title"></h3>
            <!-- 博客发布时间 -->
            <div class="date"></div>
            <!-- 博客正文, 为了配合 editormd 进行格式转换, 此处务必改成 id  -->
            <div id="content">
                
            </div>
        </div>
    </div>

    <script src="js/jquery.min.js"></script>
    <!-- 要保证这几个 js 的加载在 jquery 之后. editor.md 依赖了 jquery -->
    <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>
    <script>
        $.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. 更新博客正文
                // 此处不应该直接把博客正文填充到这个标签里~~
                editormd.markdownToHTML('content', { markdown: body.content });
            }
        })

        function checkLogin() {
            $.ajax({
                type: 'get',
                url: 'login',
                success: function(body) {
                    if (body.userId && body.userId > 0) {
                        // 登录成功!!
                        console.log("当前用户已经登录!!");
                    } 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();
    </script>
</body>
</html>

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/login.css">
</head>
<body>
    <!-- 导航栏 -->
    <div class="nav">
        <img src="image/logo.jpg" alt="">
        <span class="title">我的博客系统</span>
        <!-- 这个标签仅仅用于占位, 把这几个 a 标签挤到右侧去 -->
        <div class="spacer"></div>
        <a href="#">主页</a>
        <a href="#">写博客</a>
    </div>
    <!-- 正文部分 -->
    <!-- 贯穿整个页面的容器 -->
    <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" placeholder="手机号/邮箱" name="username">
                </div>
                <div class="row">
                    <span>密码</span>
                    <input type="password" id="password" name="password">
                </div>
                <div class="row">
                    <input type="submit" id="submit" value="登录">
                </div>
            </form>
        </div>
    </div>
</body>
</html>

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

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

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

package api;

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 {
        // 设置请求的编码. 告诉 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");
    }

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


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

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

注销功能;

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

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

请求:GET/logout

响应:HTTP/1.1 302

Location:login.html

注销逻辑的服务器代码:

在api包下创建一个新的类LogoutServlet

package api;

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;

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected 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");
    }
}

运行实例:

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">
    <!-- <script src="js/jquery.min.js"></script> -->
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
    <!-- 引入 editor.md 的依赖 -->
    <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
    <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.jpg" alt="">
        <span class="title">我的博客系统</span>
        <!-- 这个标签仅仅用于占位, 把这几个 a 标签挤到右侧去 -->
        <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" id="title" placeholder="输入文章标题" name="title">
                <input type="submit" id="submit" value="发布文章">
            </div>
            <!-- 博客编辑器, 这里用 id 是为了和 markdown 编辑器对接, 而设置的 -->
            <div id="editor">
                <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/"
        });

        function checkLogin() {
            $.ajax({
                type: 'get',
                url: 'login',
                success: function(body) {
                    if (body.userId && body.userId > 0) {
                        // 登录成功!!
                        console.log("当前用户已经登录!!");
                    } else {
                        // 当前未登录
                        // 强制跳转到登录页. 
                        location.assign('login.html');
                    }
                }
            });
        }

        checkLogin();
    </script>
</body>
</html>

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

最终效果如下

OK,以上就是前后端分离博客系统的全部代码+解析过程了,我们下期再见!

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值