我的博客系统[Servlet]

news2024/11/20 14:20:37

目录

后端程序

1. 需求分析

2. 概要设计

3. 编写数据库操作的代码

3.1.使用maven,引入依赖

3.2 封装 数据库的 DataSource

3.2.1 使用单例模式,把数据库的 DataSource 和 建立连接 还有 断开连接也给封装进去

3.2.2 创建实体类

3.2.3 针对这两个实体类涉及到的 增删改查 进行进一步的封装,也就是把 jdbc 代码给封装一下

4. 进行前后端交互

4.1 实现博客列表页(blog_list.html)

4.2 实现博客详情页(blog_datail.html) 

4.3 实现登录页面(login.html)

4.4 实现在博客列表页/博客详情页/博客编辑页中,必须强制登录才可以访问

4.5 实现在博客列表页和博客详细页中显示用户信息

4.6 实现注销(退出登录状态)

4.7 实现发布博客(blog_edit.html)

4.8 实现删除博客


gitee代码链接https://gitee.com/lei-xiaoheng/java-ee/tree/master/blog_system/src/main

后端程序

1. 需求分析

软件开发基本流程

  1. 可行性分析

  2. 需求分析

  3. 概要设计

  4. 详细设计

  5. 编码

  6. 测试

  7. 发布

但在实际上也不需要每个步骤都要详细的设置,这里我们进行[博客系统]设计就先主要进行需求分析

需求分析:也就是明确程序要解决什么问题,做成啥样子.实现什么功能

下面进行简单需求分析

1. 实现博客列表的展示功能

2. 实现博客详情的展示功能

3. 登录功能 (暂时不实现注册功能)

4. 限制用户权限 (强制要求用户要登录)

5. 显示用户的信息

6. 实现注销 (退出登录)

7. 发布博客

8. 删除博客 

总结起来8个功能,而这除了这8个功能外,还可以加一些别的.这里就不再赘述了

1. 实现博客列表的展示功能

2. 实现博客详情的展示功能

3. 登录功能 (暂时不实现注册功能)

4. 限制用户权限 (强制要求用户要登录)

5. 显示用户的信息

6. 实现注销 (退出登录)

7. 发布博客

8. 删除博客 


2. 概要设计

下面进行概要设计中的 [数据库设计]

数据库设计:也就是要想清楚,有几个库,几个表,每个表什么样子的

当前这个 博客系统 的业务比较简单,只需要两个表

 下面进行创建数据库等一系列的操作

create database if not exists java_blog_system;

use java_blog_system;

drop table if exists blog;
create table blog (
    blogId int primary key auto_increment,
    title varchar(256),
    content text,
    postTime datetime,
    userId int
);

drop table if exists user;
create table user (
    userId int primary key auto_increment,
    username varchar(50),
    password varchar(50)
);

insert into blog values (null, "这是第一篇博客", "这是认真写博客的第一天", "2022-11-25 20:00:00", 1);
insert into blog values (null, "这是第二篇博客", "这是认真写博客的第二天", "2022-11-26 20:00:00", 1);

insert into user values (null, "zhangsan", "123");
insert into user values (null, "lisi", "123");

这个是两个表中的测试数据 


3. 编写数据库操作的代码

3.1.使用maven,引入依赖

3.2 封装 数据库的 DataSource

3.2.1 使用单例模式,把数据库的 DataSource 和 建立连接 还有 断开连接也给封装进去

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 volatile DataSource dataSource = null;

    //创建数据源
    public static DataSource getDataSource() {
        if(dataSource == null) {
            synchronized (DBUtil.class) {
                if(dataSource == null) {
                    dataSource = new MysqlDataSource();
                    ((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/java_blog_system?characterEncoding=utf8&&useSSL=false");
                    ((MysqlDataSource)dataSource).setUser("root");
                    ((MysqlDataSource)dataSource).setPassword("111111");
                }
            }
        }
        return dataSource;
    }

    //建立连接
    public static Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }

    //断开连接
    public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet) {
        if(resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if(connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

3.2.2 创建实体类

一个实体类的对象就对应表里的一条记录

表结构怎么写, 实体类就怎么写

创建两张表对应的实体类  User Blog

import java.sql.Timestamp;

public class Blog {
    private int blogId;
    private String title;//博客标题
    private String content;//博客正文
    // mysql 里的 datetime 和 timestamp 类型都是在 java 中使用 Timestamp 表示的
    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 getPostTime() {
        return postTime;
    }

    public void setPostTime(Timestamp postTime) {
        this.postTime = postTime;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }
}
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;
    }
}

3.2.3 针对这两个实体类涉及到的 增删改查 进行进一步的封装,也就是把 jdbc 代码给封装一下

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) {
        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
            int ret = statement.executeUpdate();
            if(ret != 1) {
                System.out.println("博客插入失败!");
            }else {
                System.out.println("博客插入成功!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //4. 释放对应资源
            DBUtil.close(connection,statement,null);
        }
    }

    //2. 根据博客 id 来查询指定博客 -- 博客详情页
    public Blog selectOne(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. 遍历结果集合
            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 e) {
            e.printStackTrace();
        } finally {
            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";
            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"));
                blog.setContent(resultSet.getString("content"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserId(resultSet.getInt("userId"));
                blogs.add(blog);
            }
        } catch (SQLException e) {
            e.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
            int ret = statement.executeUpdate();
            if(ret != 1) {
                System.out.println("博客删除失败!");
            }else {
                System.out.println("博客删除成功!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,null);
        }
    }
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

//封装了针对用户表的操作
public class UserDao {
    //根据用户名来查询用户的详情 -- 登陆
    // 隐含约束: 用户名需要唯一
    public User selectByName(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 e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

    // 根据用户 id 来查询用户详情 -- 在获取用户信息的时候,需要用到
    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 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 e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }
}

4. 进行前后端交互

让页面发起 http 请求,服务器返回 http 响应

需要约定好请求是啥样的,响应是啥样的

4.1 实现博客列表页(blog_list.html)

之前这里是固定的数据,现在想要让页面从数据库这里来拿当前的博客列表

下面将前面写的前端页面文件拷贝到 idea 这里的 webapp下

在博客列表页中,需要做一个重要的事情

页面在加载的时候,通过 ajax 发起 HTTP 请求, 从服务器获取到博客列表数据

考虑好要发个啥样的请求, 返回啥样的响应 ====> 约定前后端交互接口

(1) 先写后端的代码   BlogServlet

import com.fasterxml.jackson.databind.ObjectMapper;

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;
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 {
        // 按照约定的接口格式返回数据
        resp.setContentType("application/json; charset=utf8");
        BlogDao blogDao = new BlogDao();
        List<Blog> blogs = blogDao.selectAll();//查询博客列表
        //writeValueAsString 把 java 对象转成 json 格式的字符串
        resp.getWriter().write(objectMapper.writeValueAsString(blogs));
    }
}

(2) 下面修改一下,前端blog_list.html 的代码

注意 通过ajax 从服务器获取数据后,别忘了进行函数调用

<!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.css">
</head>
<body>
    <div class="nav">
        <img src="img/暂无消息.png" alt="">
        <span class="title">我的博客系统</span>
        <!-- spacer只是占位 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="#">注销</a>
    </div>

    <!-- 这个div表示页面的主区域(版心) -->
    <div class="container">
        <!-- 左侧用户信息 -->
        <div class="container-left">
            <div class="card">
                <!-- 用户头像 -->
                <img src="./img/头像.png" alt="">
                <h3>XXX</h3>
                <a href="https://gitee.com/lei-xiaoheng">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">
            <!-- 每个.blog用来表示一篇博客 -->
            <!-- <div class="blog">
                <div class="title">我的第一篇博客</div>
                <div class="date">2022-11-07 12:00:00</div>
                <div class="desc">
                    Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quae at esse eum nam in dicta impedit expedita, id voluptas eius labore velit ipsa sapiente. Voluptatum quasi quis odio debitis recusandae?
                    Lorem ipsum dolor sit, amet consectetur adipisicing elit. Magni non ea et dignissimos, dolor neque, maiores modi minima laborum iure totam atque quaerat earum dicta necessitatibus omnis quo aperiam hic.
                </div>
                <a href="blog_datail.html">查看全文 &gt; &gt;</a>
            </div> -->

        </div>
    </div>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
        //发送 ajax 从服务器获取数据
        function getBlogs() {
            $.ajax({
                type: 'get',
                url: 'blog',
                success: function(body) {
                    //获取成功, 则 body 就是一个 js 对象数组,每个元素就是一个博客
                    let container = document.querySelector('.container-right');
                    for(let blog of body) {
                        // 构造 blogDiv
                        let blogDiv = document.createElement('div');
                        blogDiv.className = 'blog';

                        // 构造博客标题
                        let titleDiv = document.createElement('div');
                        titleDiv.className = 'title';
                        titleDiv.innerHTML = blog.title;

                        // 构造博客日期
                        let dateDiv = document.createElement('div');
                        dateDiv.className = 'date';
                        dateDiv.innerHTML = blog.postTime

                        // 构造博客的摘要
                        let descDiv = document.createElement('div');
                        descDiv.className = 'desc';
                        descDiv.innerHTML = blog.content;

                        // 构造查看全文的按钮
                        let a = document.createElement('a');
                        //加上一个 query string, blogId 用这个区分,要跳到哪个博客的页面
                        a.href = 'blog_datail.html?blogId=' + blog.blogId;
                        a.innerHTML = '查看全文 &gt;&gt;'

                        //拼装最终结构
                        blogDiv.appendChild(titleDiv);
                        blogDiv.appendChild(dateDiv);
                        blogDiv.appendChild(descDiv);
                        blogDiv.appendChild(a);
                        container.appendChild(blogDiv);
                    }
                }
            });
        }
        //要进行函数调用
        getBlogs();
    </script>
</body>
</html>

(3) 需要注意前面的postTime返回的响应是一个时间戳(这里可以通过抓包进一步分析),而我们需要的是一个格式化的时间

Blog.java 的代码

import java.sql.Timestamp;
import java.text.SimpleDateFormat;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 28463
 * Date: 2022—11—25
 * Time: 15:17
 */
public class Blog {
    private int blogId;
    private String title;//博客标题
    private String content;//博客正文
    // mysql 里的 datetime 和 timestamp 类型都是在 java 中使用 Timestamp 表示的
    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 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;
    }
}

 (4) 正常情况下博客列表展示,最上面的也就是刚发布的,所以在数据库查询时要根据时间进行降序排列,这就要修改 BlogDao.java 中的代码了

(5) 还需要考虑的一个是, 博客如果内容很多,就会导致博客列表展示这个页面,放不下几个博客,所以最好的是当这个博客的内容超过多少字时,只显示前面,比如说只显示100字("摘要"的信息,也就是进行内容的截断) 

比如说这里新插入一条博客,可以看到这个博客列表的展示页面这里内容显示太多了,不合理 

 BlogDao.java

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) {
        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
            int ret = statement.executeUpdate();
            if(ret != 1) {
                System.out.println("博客插入失败!");
            }else {
                System.out.println("博客插入成功!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //4. 释放对应资源
            DBUtil.close(connection,statement,null);
        }
    }

    //2. 根据博客 id 来查询指定博客 -- 博客详情页
    public Blog selectOne(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. 遍历结果集合
            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 e) {
            e.printStackTrace();
        } finally {
            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.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 e) {
            e.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
            int ret = statement.executeUpdate();
            if(ret != 1) {
                System.out.println("博客删除失败!");
            }else {
                System.out.println("博客删除成功!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,null);
        }
    }
}

运行代码后


4.2 实现博客详情页(blog_datail.html) 

在博客列表页,点击查看全文,跳转到详情页,并且看到详情页里博客的正文

处理流程:

流程分析完毕后,就要做这样三件事

(1) 约定前后端交互接口

(2) 实现服务器代码

(3) 实现客户端代码

  (1) 约定前后端交互接口

在博客详情页再次发起 ajax 请求, 获取到具体博客的内容

注意: 在博客列表页中,已经使用 BlogServlet.doGet 方法了

而在博客详情页中也想使用,那就要做出区分,可以使用 query string 来区分

如果请求中带有 query string, 有 blogId这个参数,就认为是博客详情页的请求

如果请求中不带有 query string,就认为是博客列表页的请求

(2) 实现服务器代码

BlogServlet,java 的代码 

import com.fasterxml.jackson.databind.ObjectMapper;

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;
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 {
        // 按照约定的接口格式返回数据
        resp.setContentType("application/json; charset=utf8");
        BlogDao blogDao = new BlogDao();

        String blogId = req.getParameter("blogId");

        if(blogId == null) {
            //在博客列表页发起的请求
            List<Blog> blogs = blogDao.selectAll();//查询博客列表
            //writeValueAsString 把 java 对象转成 json 格式的字符串
            resp.getWriter().write(objectMapper.writeValueAsString(blogs));
        } else {
            //博客详情页发起的请求
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            resp.getWriter().write(objectMapper.writeValueAsString(blog));
        }


    }
}

 (3) 实现客户端代码

让页面发起 ajax 请求,获取到博客数据

<!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">
</head>
<body>
    <div class="nav">
        <img src="img/暂无消息.png" alt="">
        <span class="title">我的博客系统</span>
        <!-- spacer只是占位 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="#">注销</a>
    </div>

       <!-- 这个div表示页面的主区域(版心) -->
    <div class="container">
        <!-- 左侧用户信息 -->
        <div class="container-left">
            <div class="card">
                <!-- 用户头像 -->
                <img src="./img/头像.png" alt="">
                <h3>XXX</h3>
                <a href="https://gitee.com/lei-xiaoheng">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 class="blog-detail">
                <h3></h3>
                <div class="date"></div>
                <!-- #content 这个 div 表示用来存放博客正文的部分-->
                <div id="content">

                </div>
            </div>
        </div>
    </div>
        <!-- 通过 ajax 请求,获取到博客详情页数据 -->
        <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
        <script>
            function getBlog() {
                $.ajax({
                    type: 'get',
                    url: 'blog' + location.search,
                    success: function(body) {
                        let h3 = document.querySelector('.blog-detail>h3');
                        h3.innerHTML = body.title;
                        let dateDiv = document.querySelector('.blog-detail>.date');
                        dateDiv.innerHTML = body.postTime;
                        let contentDiv = document.querySelector('#content');
                        contentDiv.innerHTML = body.content;
                    }
                });
            }
            getBlog();
        </script>
</body>
</html>

运行程序

(4) 现在还有个问题是,博客的正文希望是 markdown 格式

(因为博客编辑页,就是一个 markdown 的编辑器)

所以提交的数据就是 markdown,数据库里存的也是 markdown,所以最终也要显示一个 markdown格式的内容.

但是现在这个只是以文本的格式来显示内容,这个不太符合我们的要求

现在插入一条 markdown结构的内容,可以看到还是以纯文本内容显示的

 所以,可以使用 editor.md 这个库,来完成渲染

<!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="https://cdn.staticfile.org/jquery/1.10.2/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="img/暂无消息.png" alt="">
        <span class="title">我的博客系统</span>
        <!-- spacer只是占位 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="#">注销</a>
    </div>

       <!-- 这个div表示页面的主区域(版心) -->
    <div class="container">
        <!-- 左侧用户信息 -->
        <div class="container-left">
            <div class="card">
                <!-- 用户头像 -->
                <img src="./img/头像.png" alt="">
                <h3>XXX</h3>
                <a href="https://gitee.com/lei-xiaoheng">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 class="blog-detail">
                <h3></h3>
                <div class="date"></div>
                <!-- #content 这个 div 表示用来存放博客正文的部分-->
                <div id="content">

                </div>
            </div>
        </div>
    </div>
            <!-- 通过 ajax 请求,获取到博客详情页数据 -->
        <script>
            function getBlog() {
                $.ajax({
                    type: 'get',
                    url: 'blog' + location.search,
                    success: function(body) {
                        let h3 = document.querySelector('.blog-detail>h3');
                        h3.innerHTML = body.title;
                        let dateDiv = document.querySelector('.blog-detail>.date');
                        dateDiv.innerHTML = body.postTime;
                        // let contentDiv = document.querySelector('#content');
                        // contentDiv.innerHTML = body.content;

                        // 此处使用 editer.md 来进行渲染
                        editormd.markdownToHTML('content', { markdown: body.content});
                    }
                });
            }
            getBlog();
        </script>
</body>
</html>

下面再次运行代码,可以看到刚刚插入的那个纯文本格式的转为了, markdown格式了

(5) 也可以把这个显示内容的背景,设置成半透明的效果

可以在这里加上这个属性 


4.3 实现登录页面(login.html)

处理流程:

用户访问 login.html , 输入用户名和密码. 点击登录按钮

发起一个请求,把用户名和密码提交给服务器

服务器对身份进行验证,验证成功,就让页面跳转到 博客列表页

(1) 约定前后端交互接口

(2) 实现客户端前端代码

前面都是用 ajax构造请求,这里试一下用form表单构造请求

ajax 是各种http方法都可以构造,并且默认发起的请求不会进行跳转(也可以手动设置跳转)

而 form 表单 只能构造 Get 和 POST请求,并且一定会触发页面跳转

我们这里是登陆成功后,跳转到 博客登录页,所以 使用 from表单 没啥问题

(3) 实现服务器后端代码 

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 {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1. 先去从请求中获取到用户名和密码
        req.setCharacterEncoding("utf-8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if(username == null || username.equals("") || password == null || password.equals("")) {
            // 用户名密码为空,直接返回登录失败
            resp.setContentType("type/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);
        //在会话中保存一下 user,后面再访问其他页面
        //就可以直接通过会话拿到当前是哪个用户在访问了
        session.setAttribute("user",user);

        //4. 构造 302 响应报文(重定向)
        resp.sendRedirect("blog_list.html");
    }
}


4.4 实现在博客列表页/博客详情页/博客编辑页中,必须强制登录才可以访问

业务逻辑:

博客列表页/博客详情页/博客编辑页,都在页面加载后,发起一个 ajax

这个 ajax 就从服务器获取一下当前的登录状态

如果当前是从未登录的情况,就直接重定向到 登录页

如果是已经登录,则不做任何处理

(1) 前后端交互接口

(2) 前端代码

判定状态码,是 200 还是 403 ,如果是 200 没啥影响,如果是403,强行跳转

因为是要在三个页面:博客列表页/博客详情页/博客编辑页 都要判定,所以可以单独写一个函数,然后在这三个页面中引用

// 创建新的函数
function getLoginStatus() {
    $.ajax({
        type: 'get',
        url: 'login',
        success: function(body) {
            // 得到 200,此处不做任何工作
            console.log("当前已经登录过!");
        },
        error: function() {
            // 得到 403[没有访问权限],(非 2 开头的状态码都会触发这个 error 分支)
            // 让页面强制跳转到 login.html
            console.log("当前还未登录!");
            location.assign('login.html');
        }
    });
}

(3) 后端代码

只是获取一下会话,看能不能拿到会话以及里面的 user 对象,能拿到说明已经登录过了,返回200即可

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 {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1. 先去从请求中获取到用户名和密码
        req.setCharacterEncoding("utf-8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if(username == null || username.equals("") || password == null || password.equals("")) {
            // 用户名密码为空,直接返回登录失败
            resp.setContentType("type/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);
        //在会话中保存一下 user,后面再访问其他页面
        //就可以直接通过会话拿到当前是哪个用户在访问了
        session.setAttribute("user",user);

        //4. 构造 302 响应报文(重定向)
        resp.sendRedirect("blog_list.html");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 使用这个方法来 针对当前登录状态进行判断
        //1. 需要获取一下当前的会话
        HttpSession session = req.getSession(false);
        if (session == null) {
            // 没有会话,当前是未登录状态
            resp.setStatus(403);
            return;
        }
        User user = (User) session.getAttribute("user");
        if(user == null) {
            // 虽然有会话,但是里面没有 user 对象,也认为是未登录状态
            resp.setStatus(403);
            return;
        }
        
        //2. 返回 200 这样的响应即可,这行代码不写也可,默认状态码200
        resp.setStatus(200);
    }
}

4.5 实现在博客列表页和博客详细页中显示用户信息

 业务逻辑

在博客列表页,加载页面的同时,从服务器获取到当前登陆的用户信息,把这个信息显示到页面上

在博客详情页中,加载页面的同时,从服务器获取到博客的作者用户信息,把这个信息显示到页面上

(1) 前后端交互接口

(2) 前端代码

 

<!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.css">
</head>
<body>
    <div class="nav">
        <img src="img/暂无消息.png" alt="">
        <span class="title">我的博客系统</span>
        <!-- spacer只是占位 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="login.html">登录</a>
        <a href="#">注销</a>
    </div>

    <!-- 这个div表示页面的主区域(版心) -->
    <div class="container">
        <!-- 左侧用户信息 -->
        <div class="container-left">
            <div class="card">
                <!-- 用户头像 -->
                <img src="./img/头像.png" alt="">
                <h3></h3>
                <a href="https://gitee.com/lei-xiaoheng">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">
            <!-- 每个.blog用来表示一篇博客 -->
            <!-- <div class="blog">
                <div class="title">我的第一篇博客</div>
                <div class="date">2022-11-07 12:00:00</div>
                <div class="desc">
                    Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quae at esse eum nam in dicta impedit expedita, id voluptas eius labore velit ipsa sapiente. Voluptatum quasi quis odio debitis recusandae?
                    Lorem ipsum dolor sit, amet consectetur adipisicing elit. Magni non ea et dignissimos, dolor neque, maiores modi minima laborum iure totam atque quaerat earum dicta necessitatibus omnis quo aperiam hic.
                </div>
                <a href="blog_datail.html">查看全文 &gt; &gt;</a>
            </div> -->

        </div>
    </div>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script src="JS/app.js"></script>
    <script>
        //发送 ajax 从服务器获取数据
        function getBlogs() {
            $.ajax({
                type: 'get',
                url: 'blog',
                success: function(body) {
                    //获取成功, 则 body 就是一个 js 对象数组,每个元素就是一个博客
                    let container = document.querySelector('.container-right');
                    for(let blog of body) {
                        // 构造 blogDiv
                        let blogDiv = document.createElement('div');
                        blogDiv.className = 'blog';

                        // 构造博客标题
                        let titleDiv = document.createElement('div');
                        titleDiv.className = 'title';
                        titleDiv.innerHTML = blog.title;

                        // 构造博客日期
                        let dateDiv = document.createElement('div');
                        dateDiv.className = 'date';
                        dateDiv.innerHTML = blog.postTime

                        // 构造博客的摘要
                        let descDiv = document.createElement('div');
                        descDiv.className = 'desc';
                        descDiv.innerHTML = blog.content;

                        // 构造查看全文的按钮
                        let a = document.createElement('a');
                        //加上一个 query string, blogId 用这个区分,要跳到哪个博客的页面
                        a.href = 'blog_datail.html?blogId='+ blog.blogId;
                        a.innerHTML = '查看全文 &gt;&gt;'

                        //拼装最终结构
                        blogDiv.appendChild(titleDiv);
                        blogDiv.appendChild(dateDiv);
                        blogDiv.appendChild(descDiv);
                        blogDiv.appendChild(a);
                        container.appendChild(blogDiv);
                    }
                }
            });
        }
        //要进行函数调用
        getBlogs();

        getLoginStatus();

        // 针对博客列表页, 获取到当前用户的登录信息
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'userInfo',
                success: function(body) {
                    //获取成功, body 就是一个 User 对象
                    //把 user 对象里的内容填写到页面上即可
                    //此处主要是填用户名
                    let h3 = document.querySelector('.container-left>.card>h3');
                    h3.innerHTML = body.username;
                }
            });
        }
        getUserInfo();
    </script>
</body>
</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="https://cdn.staticfile.org/jquery/1.10.2/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="img/暂无消息.png" alt="">
        <span class="title">我的博客系统</span>
        <!-- spacer只是占位 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="login.html">登录</a>
        <a href="#">注销</a>
    </div>

       <!-- 这个div表示页面的主区域(版心) -->
    <div class="container">
        <!-- 左侧用户信息 -->
        <div class="container-left">
            <div class="card">
                <!-- 用户头像 -->
                <img src="./img/头像.png" alt="">
                <h3>雷晓恒</h3>
                <a href="https://gitee.com/lei-xiaoheng">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 class="blog-detail">
                <h3></h3>
                <div class="date"></div>
                <!-- #content 这个 div 表示用来存放博客正文的部分-->
                <div id="content" style="background-color: transparent">

                </div>
            </div>
        </div>
    </div>
            <!-- 通过 ajax 请求,获取到博客详情页数据 -->
            <script src="JS/app.js"></script>
        <script>
            function getBlog() {
                $.ajax({
                    type: 'get',
                    url: 'blog' + location.search,
                    success: function(body) {
                        let h3 = document.querySelector('.blog-detail>h3');
                        h3.innerHTML = body.title;
                        let dateDiv = document.querySelector('.blog-detail>.date');
                        dateDiv.innerHTML = body.postTime;
                        // let contentDiv = document.querySelector('#content');
                        // contentDiv.innerHTML = body.content;

                        // 此处使用 editer.md 来进行渲染
                        editormd.markdownToHTML('content', { markdown: body.content});
                    }
                });
            }
            getBlog();
            getLoginStatus();

            function getUserInfo() {
                $.ajax({
                    type: 'get',
                    url: 'userInfo' + location.search,
                    success: function(body) {
                    let h3 = document.querySelector('.container-left>.card>h3');
                    h3.innerHTML = body.username;
                    }
                });
            }
            getUserInfo();
        </script>
</body>
</html>

(3) 后端代码

import com.fasterxml.jackson.databind.ObjectMapper;

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("/userInfo")
public class UserInfoServlet extends HttpServlet {
    ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取用户信息
        String blogId = req.getParameter("blogId");
        if(blogId == null) {
            // 列表页,获取当前登陆用户的信息
            // 直接从 session 中获取
            getUserInfoFromSession(req,resp);
        } else {
            // 详情页,获取文章作者的信息
            // 查询数据库
            getUserInfoFromDB(req,resp,Integer.parseInt(blogId));
        }
    }

    private void getUserInfoFromSession(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setStatus(403);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录");
            return;
        }
        User user = (User) session.getAttribute("user");
        if(user == null) {
            resp.setStatus(403);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录");
            return;
        }
        // user 获取到了,把 user 中的 password 给赋值为空字符串,然后返回
        user.setPassword("");

        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(objectMapper.writeValueAsString(user));
    }

    private void getUserInfoFromDB(HttpServletRequest req, HttpServletResponse resp, int blogId) throws IOException {
        // 1. 先根据 blogId 查询 Blog 对象,获取到 userId (作者是谁)
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectOne(blogId);
        if(blog == null) {
            //如果参数传来的这个 blogId 是随便写的,数据库中是没有的
            resp.setStatus(404);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("blogId 不存在");
            return;
        }
        // 2. 根据 userId 查询对应的 User 对象即可
        UserDao userDao = new UserDao();
        User user = userDao.selectById(blog.getUserId());
        if(user == null) {
            resp.setStatus(404);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("blogId 不存在");
            return;
        }
        // 把这个 user 对象返回给浏览器
        user.setPassword("");
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(objectMapper.writeValueAsString(user));
    }
}

4.6 实现注销(退出登录状态)

在博客列表页/博客详情页/博客编辑页,导航栏里有注销按钮

注销按钮是一个 a 标签,点击的时候就会给服务器发送一个 http 请求(这个不是 ajax,如果用 ajax 做也可以)

发送的 http 请求就告诉服务器,现在要退出登陆了

服务器就会把会话中的 user 对象,给删除了,同时重定向跳转到 登录页

需要注意的是: 此处删除的是 user 对象,不是 HttpSession 对象, HttpSession 没有一个直接的用于删除的方法(直接删除会话,Servlet没提供),虽然可以通过设置过期时间的方式来删,但这不是一个好选择

(1) 前后端交互接口

(2) 前端代码

只需要给 a 标签里的 href 属性设置个值就可以了,

(3) 后端代码

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.setStatus(403);
            return;
        }
        // 直接把 session 中之间的 user 对象给删掉即可
        session.removeAttribute("user");
        // 重定向到登录页面
        resp.sendRedirect("login.html");
    }
}

4.7 实现发布博客(blog_edit.html)

业务逻辑:

在博客编辑页中,能够把用户提交的博客数据给获取到,保存到数据库中,

用户在博客编辑页中,填写博客标题和正文,点击发布博客

(1) 前后端交互接口

(2) 前端代码

 

<!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="https://cdn.staticfile.org/jquery/1.10.2/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="img/暂无消息.png" alt="">
        <span class="title">我的博客系统</span>
        <!-- spacer只是占位 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="login.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="blog-title" placeholder="在这里输入博客标题" name="title">
            <input id="submit" value="发布文章" type="submit">       
    </div>
        <!-- 正文的编辑区 -->
        <div id="editor">
            <textarea name="content" style="display:none"></textarea>
        </div>
        </form>
    </div>

    <script src="JS/app.js"></script>
    <script>
        //初始化编辑器
        var editor = editormd("editor", {
            //这里的尺寸必须在这里设置,设置样式会被 editormd 自动覆盖掉
            width: "100%",
            //设置编辑器的高度
            height: "calc(100% - 50px)",
            //编辑器中的初始内容
            markdown: "# 在这里写下一篇博客",
            //指定 editor.md 依赖的插件路径
            path: "editor.md/lib/",
            saveHTMLToTextarea: true
        });

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

(3) 后端代码

import com.fasterxml.jackson.databind.ObjectMapper;

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;

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

        String blogId = req.getParameter("blogId");

        if(blogId == null) {
            //在博客列表页发起的请求
            List<Blog> blogs = blogDao.selectAll();//查询博客列表
            //writeValueAsString 把 java 对象转成 json 格式的字符串
            resp.getWriter().write(objectMapper.writeValueAsString(blogs));
        } else {
            //博客详情页发起的请求
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            resp.getWriter().write(objectMapper.writeValueAsString(blog));
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 使用这个方法,来提交新博客
        //1. 先检查一下用户的登录状态,获取到会话和用户信息
        //   如果未登录,显然不能提交博客
        HttpSession session = req.getSession(false);
        if(session == null) {
            resp.setStatus(403);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录,不能发布博客!");
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            resp.setStatus(403);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录,不能发布博客!");
            return;
        }

        //2. 获取请求中的参数(博客的标题和正文)
        req.setCharacterEncoding("utf8");
        String title = req.getParameter("title");
        String content = req.getParameter("content");

        //3. 构造 Blog 对象,并且插入到数据库中
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        blog.setUserId(user.getUserId());
        //blogId 是自增主键,不需要设置,postTime 直接在数据库操作中,已经使用 now 来设置了
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);

        //4. 构造 重定向报文, 回到 博客列表页
        resp.sendRedirect("blog_list.html");

    }
}

运行程序,下面来试着写一篇试试看 


4.8 实现删除博客

业务逻辑

作者能删除自己的文章,不能删除别人的

暂时没有管理员这个角色

在博客详情页导航栏上,加个 删除 按钮

点击删除按钮,就会触发删除操作,通过 a 标签. href 属性发起一个 HTTP GET 请求

但是删除的时候,会做一个判断,如果当前登录的用户就是文章作者,才能真正删除,否则就提示不能删除

(1) 约定前后端交互接口

(2) 前端代码

<!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="https://cdn.staticfile.org/jquery/1.10.2/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="img/暂无消息.png" alt="">
        <span class="title">我的博客系统</span>
        <!-- spacer只是占位 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="#" id="delete-btn">删除</a>
        <a href="logout">注销</a>
    </div>

       <!-- 这个div表示页面的主区域(版心) -->
    <div class="container">
        <!-- 左侧用户信息 -->
        <div class="container-left">
            <div class="card">
                <!-- 用户头像 -->
                <img src="./img/头像.png" alt="">
                <h3>雷晓恒</h3>
                <a href="https://gitee.com/lei-xiaoheng">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 class="blog-detail">
                <h3></h3>
                <div class="date"></div>
                <!-- #content 这个 div 表示用来存放博客正文的部分-->
                <div id="content" style="background-color: transparent">

                </div>
            </div>
        </div>
    </div>
            <!-- 通过 ajax 请求,获取到博客详情页数据 -->
            <script src="JS/app.js"></script>
        <script>
            function getBlog() {
                $.ajax({
                    type: 'get',
                    url: 'blog' + location.search,
                    success: function(body) {
                        let h3 = document.querySelector('.blog-detail>h3');
                        h3.innerHTML = body.title;
                        let dateDiv = document.querySelector('.blog-detail>.date');
                        dateDiv.innerHTML = body.postTime;
                        // let contentDiv = document.querySelector('#content');
                        // contentDiv.innerHTML = body.content;

                        // 此处使用 editer.md 来进行渲染
                        editormd.markdownToHTML('content', { markdown: body.content});
                    }
                });
            }
            getBlog();
            getLoginStatus();

            function getUserInfo() {
                $.ajax({
                    type: 'get',
                    url: 'userInfo' + location.search,
                    success: function(body) {
                    let h3 = document.querySelector('.container-left>.card>h3');
                    h3.innerHTML = body.username;
                    }
                });
            }
            getUserInfo();

            function updateDeleteURL() {
                let deleteBtn = document.querySelector('#delete-btn');
                deleteBtn.href = 'blog_delete' + location.search;
            }

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

(3) 后端代码 

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;

// 8. 删除博客
@WebServlet("/blog_delete")
public class BlogDeleteServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1. 先判定用户登录状态
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setStatus(403);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录,不能删除博客!");
            return;
        }
        User user = (User) session.getAttribute("user");
        if(user == null) {
            resp.setStatus(403);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录, 不能删除博客!");
            return;
        }

        //2. 获取到 blogId
        String blogId = req.getParameter("blogId");
        if(blogId == null) {
            // 这个 blogId 参数不存在,无法删除
            // 比如本身 blogId 只有 1-10,现在要删除100,当前就不行了
            resp.setStatus(404);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("您当前删除的 blogId 有误!");
            return;
        }

        //3. 查询出这个 blogId 对应的 Blog 对象
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
        if(blog == null) {
            resp.setStatus(404);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("您当前删除的博客不存在! blogId=" + blogId);
            return;
        }

        //4. 判定登录用户是否是文章作者
        if(blog.getUserId() != user.getUserId()) {
            // blog.getUserId 文章的作者
            // user.getUserId 从 session 里拿的登录的用户是谁
            // 不一样.说明你想要删除别人的文章,那当然是不行了  直接返回403
            resp.setStatus(403);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前您不能删除别人的博客!");
            return;
        }

        //5. 执行删除操作
        blogDao.delete(Integer.parseInt(blogId));

        //6. 返回 302 重定向
        resp.sendRedirect("blog_list.html");
    }
}

运行代码,插入两篇博客测试一下

删除一篇试试看

如果删除一篇不是自己的博客,就会这样提示

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

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

相关文章

近期Java杂项问题

Data不会为static变量提供get/set。 Data不会为final变量提供set。 ConfigurationProperties加载static静态属性为null的问题 解决&#xff1a;ConfigurationProperties默认是调用非static Setter方法&#xff0c;把静态的Setter方法的static去掉就可以了 Value注入静态属性 解…

RK3568平台开发系列讲解(系统优化篇)排查卡顿的工具介绍

🚀返回专栏总目录 文章目录 一、Traceview二、Nanoscope三、systrace四、Simpleperf沉淀、分享、成长,让自己和他人都能有所收获!😄 📢Android底层基于 Linux 内核,像 systrace、Simpleperf 也是利用 Linux 提供的机制实现,因此学习一些 Linux 的基础知识,对于理解这…

D-Wave最新调研!量子计算商业活动和投资不断增长

&#xff08;图片来源&#xff1a;网络&#xff09; 加拿大量子计算公司委托高性能计算分析机构Hyperion Research进行了一项研究&#xff0c;以更好地了解量子计算 (QC) 的商业早期采用者所面临的挑战和机遇。这项研究调查了全球范围内的300家目前接触量子计算的企业&#xff…

计算机组成大题分析(六)

假定计算机的主频为 500MHz &#xff0c;CPI 为4。现有设备A 和 B&#xff0c;其数据传输率分别为2MB/s 和 40MB/s &#xff0c;对应 I/O 接口中各有一个 32 位数据缓冲寄存器。请回答下列问题&#xff0c;要求给出计算过程。 (1) 若设备 A 采用定时查询 I/O 方式&#xff0c;每…

为什么说函数组件要比类组件好呢,函数组件和类组件的区别你都清楚吗

一、类组件 类组件&#xff0c;顾名思义&#xff0c;也就是通过使用ES6类的编写形式去编写组件&#xff0c;该类必须继承React.Component 如果想要访问父组件传递过来的参数&#xff0c;可通过this.props的方式去访问 在组件中必须实现render方法&#xff0c;在return中返回…

详解c++---类和对象(四)

这里写目录标题const成员再谈构造函数为什么会有初始化列表第一个问题第二个问题第三个问题初始化列表的使用方式即注意事项explicit关键字static成员static修饰类中的变量一些性质static修饰成员函数友元友元函数友元类内部类匿名对象拷贝对象时的一些编译器优化const成员 在…

【知识图谱】(task2)知识图谱表示

note 知识图谱的符号表示方法&#xff1a; 属性图是工业界最常见的图谱建模方法&#xff0c;属性图数据库充分利用图结构特点做了性能优化&#xff0c;实用度高&#xff0c;但不支持符号推理。RDF是W3C推动的语义数据交换标准与规范&#xff0c;有更严格的语义逻辑基础&#x…

dev_III笔记补充

1.在客户端显示form 思路&#xff1a; 想要在客户端显示form必须先把form放在 menu item中&#xff0c;然后通过url地址的方式打开form 步骤&#xff1a; 1.建立form 2.建立Menu Item &#xff08;特别注意&#xff1a;是display类型&#xff0c; 不是action类型 不是action类型…

【分布式技术专题】「架构设计方案」盘点和总结秒杀服务的功能设计及注意事项技术体系

秒杀应该考虑哪些问题 超卖问题 分析秒杀的业务场景&#xff0c;最重要的有一点就是超卖问题&#xff0c;假如备货只有100个&#xff0c;但是最终超卖了200&#xff0c;一般来讲秒杀系统的价格都比较低&#xff0c;如果超卖将严重影响公司的财产利益&#xff0c;因此首当其冲…

python爱心源代码集锦

python爱心源代码集锦 本文目录&#xff1a; 一、代码效果预览索引图 二、爱心源代码集锦 &#xff08;1&#xff09;、爱心图形1&#xff08;弧线型&#xff09;&#xff08;显示的文字写在代码里&#xff09; &#xff08;2&#xff09;、爱心图形2&#xff08;直线型&am…

MySQL 如何优化慢查询?

一、前言 在日常开发中&#xff0c;我们往往会给表加各种索引&#xff0c;来提高 MySQL 的检索效率。 但我们有时会遇到明明给字段加了索引&#xff0c;并没有走索引的Case。 进而导致 MySQL 产生慢查询。 严重场景下&#xff0c;甚至出现主从延迟、数据库拖垮的极端事故。 本…

微服务框架 SpringCloud微服务架构 28 数据同步 28.4 发送mq 消息

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构28 数据同步28.4 发送mq 消息28.4.1 直接开干28 数据同步 28.4 发送mq 消…

《Linux运维实战:使用Percona Backup for MongoDB逻辑备份与恢复Mongodb数据》

一、备份与恢复方案 Percona Backup for MongoDB 是一个开源、分布式和低影响的解决方案&#xff0c;用于MongoDB分片集群和副本集的一致备份。从版本1.7.0开始&#xff0c;Percona Backup for MongoDB支持物理和逻辑备份和恢复&#xff0c;仅支持对逻辑备份进行时间点恢复。 …

UIAutomator测试框架介绍

uiautomator简介 UiAutomator是Google提供的用来做安卓自动化测试的一个Java库&#xff0c;基于Accessibility服务。功能很强&#xff0c;可以对第三方App进行测试&#xff0c;获取屏幕上任意一个APP的任意一个控件属性&#xff0c;并对其进行任意操作&#xff0c;但有两个缺点…

软件测试面试笔试习题参考,你都会了吗?

目录 一、 简答题和应用题&#xff1a; 二、 填空题&#xff1a; 三、 判断题&#xff1a; 四、 选择题&#xff1a; 总结 重点&#xff1a;配套学习资料和视频教学 一、 简答题和应用题&#xff1a; 1. 什么是软件测试&#xff1f; 2. 比较软件测试过程和软件开发过程&a…

基于JavaScript中AES和MD5加密,以及简单二维码的生成

一、加密技术 1、对称加密&#xff1a;单密钥加密。一个密钥可以用来加密也可以用来解密 —- AES 2、非对称加密&#xff1a;有两把密码&#xff0c;公钥(用于加密)&#xff0c;私钥(用于解密) 3、摘要算法&#xff1a;把任意长度的输入&#xff0c;根据算法生成一串固定长度…

有环链表入口问题

有环链表入口问题 当快慢指针相遇时&#xff0c;我们可以判断到链表中有环&#xff0c;这时重新设定一个新指针指向链表的起点&#xff0c;且步长与慢指针一样为1&#xff0c;则慢指针与“新”指针相遇的地方就是环的入口。 图片来源:黑马程序员 证明: 设a为起点位置&#xff…

百变郁锦香,开创新典范,深化全球战略布局成就国际高端酒店品质之选

随着消费需求的不断升级&#xff0c;酒店消费场景也进行着多元化的发展&#xff0c;城市高端度假品牌正积极溯源消费需求&#xff0c;寻得品牌文化延伸的可靠路径。同时&#xff0c;各大酒店品牌也加快在市场布局的脚步&#xff0c;希望通过布局城市核心区域获得可持续发展的更…

Python绘制正二十面体

文章目录正二十面体的顶点绘制棱绘制面正二十面体的顶点 正20面体的12个顶点刚好可以分为三组&#xff0c;每一组都是一个符合黄金分割比例的长方形&#xff0c;而且这三个长方形是互相正交的。 所以&#xff0c;想绘制一个正二十面体是比较容易的 import numpy as np from …

大环配体配合物1407166-70-4,NODA-GA-NHS ester,NODA-GA-NHS 酯

●外观以及性质&#xff1a; NODA-GA-NHS ester产物呈固体或粘性液体&#xff0c;取决于PEG分子量&#xff0c;一般为白色固体&#xff0c;双功能大环化合物&#xff0c;大环配体配合物是指由多齿配体与环骨架上的O、N、P、S等多个配位原子形成的环配合物。 NODA-GA-NHS ester …