【Java】博客系统——详细解释+代码+详细注释(课设必过)

目录

前言

博客系统简要分析

一、数据库的设计

1.1 分析

1.2 代码实现(创建数据库和表)

二、封装数据库(JDBC代码的编写)

2.1、首先通过创建Maven项目,基于Small Tomcat部署 servlet;

2.2、封装数据库的DataSource

三、实现博客列表页

3.1、业务逻辑及解释

3.2、约定前后端交互接口

3.3实现客户端代码代码

3.4、实现服务器代码

四、实现博客详情页

4.1、业务逻辑及解释

4.2 约定前后端交互接口

4.3、实现服务器代码

4.4、实现客户端代码

五、实现博客登录页

5.1、业务逻辑及解释

5.2、约定前后端交互接口

 5.3、实现客户端代码

5.4、实现服务器代码

六、实现强制登录逻辑

6.1、业务逻辑及解释

6.2、前后端交互接口

 6.3、实现客户端代码

6.4、实现服务器代码

七、博客列表页中的显示用户信息

7.1、业务逻辑及解释

7.2、前后端交互接口

 7.3实现服务器代码

7.4、实现客户端代码

八、实现注销功能(退出登录)

8.1、业务逻辑及解释

8.2、前后端交互接口

 8.3、实现客户端代码

8.4、实现服务器代码

九、实现发布博客功能

9.1、业务逻辑及解释

9.2、前后端交互接口

9.3、实现服务器代码

9.4、实现客户端代码

十、实现删除博客功能

10.1、业务逻辑及解释

10.2、约定前后端接口

10.3、实现服务器代码

10.4、实现客户端代码

十一、结语


前言

        这里所要介绍的博客系统,是一个简单的网站程序,如果你能吃透这个博客系统,也是可以作为一个项目写到简历上的;


博客系统简要分析

        咱们将要实现的博客系统,分为四个网页:博客列表页、博客详情页、博客登录页、博客编辑页;要实现的功能有:实现博客列表的展示功能、实现博客详情的展示功能、登录功能、限制用户权限(强制要求用户登录后查看信息)、显示用户信息、实现注销(退出登录)、发布博客、删除博客;

接下来就来带大家实现一下~


一、数据库的设计

1.1 分析

        实际上,咱们的业务逻辑较为简单,只需要一个库,两张表即可;

两张表分别是:

        1. 博客表 :blog(blogId, title, content, postTime, userId);  ——>  (博客ID,博客标题,博客正文,发布时间,用户ID);

        2. 用户表 user(userId, username, password); ——> (用户ID,用户名,用户密码)

1.2 代码实现(创建数据库和表)

create database if not exists blog_system;
use blog_system;
-- 这里删除是为了以防以前过相同的表有干扰
drop table if exists blog;

-- 这是博客table
create table blog (
    blogId int primary key auto_increment, -- 博客id
    title varchar(256), -- 标题
    content text,  -- 正文
    postTime datetime, -- 发布时间
    userId int -- 用户Id
);

drop table if exists user;
-- 这是用户table
create table user (
    userId int primary key auto_increment, -- 用户id
    username varchar(50) unique, -- 用户名(类似于账户不可以重复)
    password varchar(50) -- 密码
);
-- 插入数据用于测试

insert into blog values(null, "这是第一篇博客", "努力敲代码,走上人生巅峰", '2022-11-24 18:00:00', 1);
insert into blog values(null, "这是第二篇博客", "努力敲代码,走上人生巅峰", '2022-11-24 18:00:00', 1);

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

二、封装数据库(JDBC代码的编写)

2.1、首先通过创建Maven项目,基于Small Tomcat部署 servlet;

这里如果不了解的,可以去看看我的这篇博客:

http://t.csdn.cn/QSt3L

所需要引入的依赖:

    <dependencies>
        <!-- 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>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.4.1</version>
        </dependency>
    </dependencies>

2.2、封装数据库的DataSource

        因为我们的博客系统网站是要给很多人用的,所以在数据库的创建数据源、建立连接、断开连接这里,可以引入单例模式中的懒汉模式,对以上所说步骤进行封装;

第一步:在java目录下创建类DBUtil,对数据库一些操作进行封装(如下代码)

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/blog_system?characterEncoding=utf8&useSSL=false");
                    ((MysqlDataSource)dataSource).setUser("root");
                    ((MysqlDataSource)dataSource).setPassword("1111");
                }
            }
        }
        return dataSource;
    }
    //建立连接
    public static Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }
    //关闭连接
    public void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
        //[注意1]这里不要直接用throws抛异常,因为一旦哪一个发生了异常,
        // 就有可能存在后面的还有需要释放的资源没释放掉的情况;
        //[注意2]注意释放顺序,和创建顺序是相反的
        if(resultSet != null) {//这里判断是因为有的jdbc操作不需要他,但却要close其他的
            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();
            }
        }
    }
}

【对以上代码的解释】:为什么close方法中采用try catch的方式处理异常而不是throws?

        这里不要直接用throws抛异常,因为一旦哪一个发生了异常,就有可能存在后面的还有需要释放的资源没释放掉的情况;

【对以上代码的解释】:为什么close方法中需要判断是否为null才执行close操作?

        这里是将所有的close操作放在一起了,所以一旦执行close操作,就会对所以资源进行close,但实际上,有时候可能有的数据源我们都没有创建(例如,有时不用创建ResultSet),这时,对没有创建的数据进行close,就会导致null异常,所以这里需要判断是否为空,再释放;

第二步:在java目录下创建Blog类,用来表示实体类(如下代码)

import java.sql.Timestamp;

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

【对以上代码的解释】:

        简单来说,就是你的数据库中创建的blog表有什么属性,就写什么就ok;

【对以上代码的解释】:对getPostTime方法的解释

        这里不能通过getter直接返回日期数据,直接返回的话,是一个时间戳(如下图)

         这里需要通过SimpleDateFormat这个类将时间戳格式化成时间日期,初始化这个类的时候就需要填写的格式,具体怎么填写,建议大家不要背,因为不同语言之间这个大小写格式差异很大,所以建议用的时候查一下官方文档就好;(使用此类后,效果如下)

第三步:在java目录下创建类BlogDao,用数据库来操作博客数据

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

//对博客信息的相关操作
// +----------+--------------+------+-----+---------+----------------+
// | Field    | Type         | Null | Key | Default | Extra          |
// +----------+--------------+------+-----+---------+----------------+
// | blogId   | int(11)      | NO   | PRI | NULL    | auto_increment |
// | title    | varchar(256) | YES  |     | NULL    |                |
// | content  | text         | YES  |     | NULL    |                |
// | postTime | datetime     | YES  |     | NULL    |                |
// | userId   | int(11)      | YES  |     | NULL    |                |
// +----------+--------------+------+-----+---------+----------------+
public class BlogDao {
    //1.插入一个博客到数据库中 -- 发布博客
    public void insert(Blog blog) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            //建立连接
            connection = DBUtil.getConnection();
            //构造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.getBlogId());
            //执行sql
            int ret = statement.executeUpdate();
            if(ret == 1) {
                System.out.println("用户" + blog.getUserId() + "插入1个博客!");
            } else {
                System.out.println("用户" + blog.getUserId() + "博客插入失败!");
            }
            //[注意]:不可以在这里close,上面代码一旦抛出异常,这里就无法执行close;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭数据源
            DBUtil.close(null, statement, connection);
        }

    }
    //2.根据博客id查询博客 -- 博客详情页
    public Blog selectOne(int blogId) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //建立连接
            connection = DBUtil.getConnection();
            //创建sql
            String sql = "select * from Blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogId);
            //执行sql
            resultSet = statement.executeQuery();
            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(resultSet, statement, connection);
        }
        return null;
    }
    //3.查询博客列表 -- 博客列表页
    public List<Blog> selectAll() {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        List<Blog> blogList = new ArrayList<>();
        try {
            //建立连接
            connection = DBUtil.getConnection();
            //构造sql
            String sql = "select * from blog order by postTime desc";
            statement = connection.prepareStatement(sql);
            //执行sql
            resultSet = statement.executeQuery();
            while(resultSet.next()) {//遍历结果集
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                String content = resultSet.getString("content");
                //在博客列表页,正文一旦超出100个字,就截断,用...代替
                if(content.length() > 100) {
                    content = content.substring(0, 100) + "...";
                }
                blog.setContent(content);
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserId(resultSet.getInt("userId"));
                blogList.add(blog);
            }
        } catch(SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(resultSet, statement, connection);
        }
        return blogList;
    }
    //4.删除指定博客 -- 删除博客
    public void delete(String blogId) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            //建立连接
            connection = DBUtil.getConnection();
            //构造sql
            String sql = "delete from Blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setString(1, blogId);
            //执行sql
            int ret = statement.executeUpdate();
            if(ret != 1) {
                System.out.println("博客删除成功!");
            } else {
                System.out.println("博客删除失败!");
            }
        } catch(SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(null, statement, connection);
        }
    }
}

【对以上代码的解释】:BlogDao类名这样叫什么用意?

        DAO是 Data Access Object 的缩写,也就是说访问数据库操作,就可以通过后缀含有DAO的类来操作;

【对以上代码的解释】:为什么使用try catch finally,而不用throws?

        以防代码还没有关闭数据库连接就发生了异常,导致内存泄漏;

【对以上代码的解释】:为什么要对博客正文内容进行substring?

        此处是博客列表页,不因该把博客正文全部显示出来,而是因该只显示文章摘要,所以这里就对摘要部分进行截断,长度超出限制(这里限制自己根据需求定义),就取出一部分子串,剩下部分用"..."代替;

第四步:在java目录下创建User类,用来实体话对象(和Blog类同理)

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

第五步:在java目录下创建UserDao,用数据库来操作用户数据(和BlogDao类同理)

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 {
            //建立连接
            connection = DBUtil.getConnection();
            //构造sql
            String sql = "select * from User where username = ?";
            statement = connection.prepareStatement(sql);
            statement.setString(1, username);
            //执行sql
            resultSet = statement.executeQuery();
            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 {
            //建立连接
            connection = DBUtil.getConnection();
            //构造sql
            String sql = "select * from user where userId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, userId);
            //执行sql
            resultSet = statement.executeQuery();
            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;
    }
}

三、实现博客列表页

3.1、业务逻辑及解释

        在博客列表页中,在页面加载的时候,需要通过ajax发起HTTP请求,从服务器获取到博客列表数据,所以这里我们就要提前约定好前后端交换接口:考虑好发什么样的请求,返回什么样的响应。

3.2、约定前后端交互接口

在blog_list.html页面(前端HTML)中,发起如下请求:

在java目录下创建BlogServlet类,用来处理get请求,并返回如下http响应

3.3实现客户端代码代码

    <script src="http://libs.baidu.com/jquery/2.0.0/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');
                        //这里href加上了一个query string,是为了查看全文的内容可以通过ID对应到对应的文章
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        a.innerHTML = "查看全文 &gt;&gt;";

                        //拼装
                        container.appendChild(blogDiv);
                        blogDiv.appendChild(titleDiv);
                        blogDiv.appendChild(dateDiv);
                        blogDiv.appendChild(descDiv);
                        blogDiv.appendChild(a);
                    }
                }
            });
        }
        getBlogs();
    </script>

3.4、实现服务器代码

这里创建了一个新的类BlogServlet;

import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class BlogServlet extends HttpServlet {
    //json格式转换
    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> blogList = blogDao.selectAll();
        resp.getWriter().write(objectMapper.writeValueAsString(blogList));
    }
}

四、实现博客详情页

4.1、业务逻辑及解释

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

        点击查看全文,就在blog_html页面发起一个get请求,这里就需要告诉服务器,是请求的哪一个博客,那么就要约定,在请求页面url中加上query string(这里之前已经加好了,如下图)

进入详情页后,就需要让博客详情页再次发送ajax请求,向服务器获取当前blogId对应的博客内容,再由博客详情也把数据展示到页面中;

4.2 约定前后端交互接口

(例如获取博客ID为1的博客)

4.3、实现服务器代码

这里实际上对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 {
    //json数据格式的转换
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //博客列表页中已经使用了过了/blog下的doGet请求,但是
        //博客详情页还想继续用,这里就需要用query string来区分了
        //若请求中带有query string,也就是含有blogId这个参数,就说明是博客详情页发起的请求
        //若没有,则说明是博客列表页发起的请求
        resp.setContentType("application/json; charset=utf8");
        BlogDao blogDao = new BlogDao();
        String blogId = req.getParameter("blogId");
        if(blogId == null) { //说明是博客列表页
            List<Blog> blogList = blogDao.selectAll();
            resp.getWriter().write(objectMapper.writeValueAsString(blogList));
        } else { //说明是博客详情页
            Blog blog = blogDao.selectOne(Integer.valueOf(blogId));
            resp.getWriter().write(objectMapper.writeValueAsString(blog));
        }
    }
}

4.4、实现客户端代码

    <!-- 通过ajax请求获取详细博客详细 -->
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script>
        function getBlog() {
            $.ajax({
                type: 'get',
                url: 'blog' + location.search, //location.search 是用来获取当前url中的query string
                success: function(body) {//这里成功的话,会获取到一个blog
                    let h3 = document.querySelector('.container-right>.blog-detail>h3');
                    h3.innerHTML = body.title;
                    let dateDiv = document.querySelector('.blog-detail>.date');
                    dateDiv.innerHTML = body.postTime;
                    //content部分需要使用editor.md来渲染,否则就是一个普通文本格式(注意引入editor.md依赖)
                    //以下写法是官方约定的写法:
                    editormd.markdownToHTML('content', { markdown: body.content });
                }
            });
        }
        getBlog();
    </script>

 【对以上代码的解释】:location.search是干什么的?

        这是用来获取当前url里的query string;

        例如:假设当前url为: 127.0.0.1:8080/blog?blogId = 1;通过location.search就可以获取到

"?blogId = 1";

【对以上代码的解释】:editormd.markdownToHTML是干什么的?

        由于content部分的内容是使用markdown写的,如果直接通过body.content得到正文内容,就是一个简单的文本格式,所以这里需要editor.md渲染文本;这里的这段代码是官方文档上的固定写法;

注意:这里用editor.md渲染文本,需要引入依赖(如下)

        <!-- 引入 editor.md 的依赖 -->
        <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
        <script src="https://apps.bdimg.com/libs/jquery/2.1.4/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>

五、实现博客登录页

5.1、业务逻辑及解释

        用户访问login.html,输入用户名和密码,点击登录按钮,发起一个请求,将用户名和密码交给服务器;服务器对身份进行验证,若验证成功,就让页面跳转到博客列表页,若验证失败,就返回错误信息;

5.2、约定前后端交互接口

 5.3、实现客户端代码

    <div class="login-container">
        <form action="login" method="post">
            <!-- 登录对话框 -->
            <div class="dialog">
                <h3>登录</h3>
                <div class="row">
                    <span>用户名</span>
                    <!-- 这两个框很关键, 后面还要用, 就起个 id 作为标识 -->
                    <input type="text" id="username" name="username">
                </div>
                <div class="row">
                    <span>密码</span>
                    <input type="password" id="password" name="password">
                </div>
                <div class="row">
                    <input type="submit" id="login-btn" value="登录">
                </div>
            </div>
        </form>
    </div>

【对以上代码的解释】:

        1.注意action和method和约定的请求格式是匹配的;

        2.input标签中的name属性就是构造请求的键值对中的“键”,用户输入的内容则是“值”;

        3.form约定,必须使用 input type="submit"来提交表单;

5.4、实现服务器代码

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("text/html; charset=utf8");
            resp.getWriter().write("用户名或密码为空,登录失败!");
            return;
        }
        //2.查询数据库,检验用户名和密码是否正确
        UserDao userDao = new UserDao();
        User user = userDao.selectByName(username);//用户名获取用户信息
        if(user == null || !user.getPassword().equals(password)) {
            //用户名不存在,或者密码错误
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("用户名或密码错误,登录失败!");
            return;
        }
        //4.若正确,创建一个会话
        HttpSession session = req.getSession(true);
        //会话中保存user信息,为了后续访问其他页面,就可以直接通过会话拿到当前页面是哪一个用户在访问
        session.setAttribute("user", user);
        //5.构造重定向302报文
        resp.sendRedirect("blog_list.html");
    }
}

六、实现强制登录逻辑

6.1、业务逻辑及解释

        在博客列表页、详情页、编辑页、都在页面加载后、发起一个ajax;这个ajax就从服务器获取当前登陆状态,若当下是未登录,就直接重定向到登录页,若已登录,不做任何处理;

6.2、前后端交互接口

 6.3、实现客户端代码

         function getLoginStatus() {
            $.ajax({
                type: 'get',
                url: 'login',
                success: function() {
                    //状态码是200,就不用做任何事情
                    console.log("当前页面已经登录过~");
                },
                error: function() {
                    //只要是非2开头的状态码,就会触发这个error
                    //就让页面强制跳转到登录页面
                    console.log("当前还未登录");
                    location.assign('login.html');
                }
            });
        }
        getLoginStatus();

【对以上代码的解释】:

        判断状态码:若状态码是200则没有事情发生,若状态码是403,则强制跳转;

6.4、实现服务器代码

    @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对象是否存在
        //已经判断了session,为什么还要判断user?
        //因为当点击“退出登录”之后,我们实现的逻辑是删除user对象,session并没有删除
        //所以这里需要判断
        User user = (User)session.getAttribute("user");
        if(user == null) {
            //有会话,但是没有user对象,也认为是未登录状态
            resp.setStatus(403);
            return;
        }
        //2.返回状态码200(也可以不返回,因为默认状态码是200)
        resp.setStatus(200);
    }

【对以上代码的解释】:已经判断了session,为什么还要判断user?

        我们实现退出登录的逻辑是只删除user对象,不删除session,那么当点击退出登录之后,就会出现session存在,而user对象不存在的情况,所以这里仍需要判断user,即使有会话,user不存在,也认为是未登录状态;


七、博客列表页中的显示用户信息

7.1、业务逻辑及解释

        博客列表页中:加载页面时,从服务器获取当前登录用户信息,将信息显示到页面上;

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

7.2、前后端交互接口

 7.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 {
    private 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.valueOf(blogId));
        }
    }

    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("userId不存在");
            return;
        }
        //2.获取到user后,将其中的password删除(保护用户信息安全),并返回
        user.setPassword("");
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(objectMapper.writeValueAsString(user));
    }

    private void getUserInfoFromSession(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //1.获取session会话
        HttpSession session = req.getSession(false);
        if(session == null) {
            resp.setStatus(404);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录");
        }
        User user = (User) session.getAttribute("user");
        if(user == null) {
            resp.setStatus(404);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录");
        }
        //2.获取到user后,将其中的password删除(保护用户信息安全),并返回
        user.setPassword("");
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(objectMapper.writeValueAsString(user));
    }
}

7.4、实现客户端代码

博客详情页:

        //博客详情页,对当前作者信息进行获取
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'userInfo' + location.search,//注意这里要加上qurey string,才能得到当前作者信息
                success: function(body) {
                    //这里主要改用户名,其他内容要改逻辑都是一样的
                    let h3 = document.querySelector('.container-left>.card>h3');
                    h3.innerHTML = body.username;
                }
            });
        }
        getUserInfo();

博客列表页:

        //博客列表页,对当前用户信息进行获取
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'userInfo',
                success: function(body) {
                    //这里主要改用户名,其他内容要改逻辑都是一样的
                    let h3 = document.querySelector('.container-left>.card>h3');
                    h3.innerHTML = body.username;
                }
            });
        }
        getUserInfo();

八、实现注销功能(退出登录)

8.1、业务逻辑及解释

        注销按钮是 a标签 ,点击他就会给服务器发送一个http请求,发送的http请求会告诉服务器要退出登录了,服务器就会把user对象删除(此处不是删除HttpSession对象的是因为HttpSession没有一个直接用于删除的方法,即使可以通过过期时间来删除,但并不是一个很好的办法;这里就呼应了前写判断登录状态的条件是HttpSession&&user存在),并且重定向到 登录页;

8.2、前后端交互接口

 8.3、实现客户端代码

这里只需要修改前端代码中的注销a标签的href属性即可;

<a href="logout">注销</a>

8.4、实现服务器代码

这里需要重新创建一个类LogoutServlet,用来服务注销;

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 {
        //1.获取session会话
        HttpSession session = req.getSession(false);
        if(session == null) {
            resp.setStatus(404);
            return;
        }
        //2.直接删除session中的user对象
        session.removeAttribute("user");
        //3.重定向到登录界面
        resp.sendRedirect("login.html");
    }
}

九、实现发布博客功能

9.1、业务逻辑及解释

        用户在博客编辑页里,填写博客标题和正文,点击发布博客,发起HTTP请求,服务器接收到这些数据,构造Blog对象保存到数据库中;

9.2、前后端交互接口

9.3、实现服务器代码

在BlogServlet类下,创建doPost方法

    @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();
        //blogId是自增主键,不用设置
        blog.setTitle(title);
        blog.setContent(content);
        blog.setUserId(user.getUserId());
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
        //4.返回主页
        resp.sendRedirect("blog_list.html");
    }

9.4、实现客户端代码

    <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: "## hello world",
            // 指定 editor.md 依赖的插件路径
            path: "editor.md/lib/",

            //用户输入markdown内容想要被textarea获取到,就需要如下写法(官方文档规定)
            saveHTMLToTextarea: true
        });
        //强制登录逻辑
        getLoginStatus();

【对以上代码的解释】:saveHTMLToTextarea是什么?

        当用户使用markdown输入内容时,要想让textarea获取到被渲染的内容,就需要这样写,这是官方文档规定;


十、实现删除博客功能

10.1、业务逻辑及解释

        点击删除按钮,会触发删除操作;通过a标签的href属性发起一个HTTP GET请求,但是删除的时候会做出一个判定,只有当前登录用户时文章作者,才能执行删除;

10.2、约定前后端接口

10.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("/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 参数不存在, 无法删除
            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) {
            // 这个 blogId 参数不存在, 无法删除
            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(blogId);
        // 6. 返回 302 重定向
        resp.sendRedirect("blog_list.html");
    }
}

【对以上代码的解释】:

        大部分逻辑都是判定非法情况,这是一个好意识,这些情况都尽可能进行处理;

10.4、实现客户端代码

1.加上一个a标签即可

<a href="#" id="delete-btn">删除</a>

2.初始化的时候给href赋值上对应的url加上blogId,就能识别出当前是谁在删博客

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

十一、结语

        想要前端HTML代码可以私信我哦~实际上大家也可以自己尝试一下,做一个大体的网页框架,本文已经详细给出了前后端交互的具体实现,希望可以帮助到你!


  • 26
    点赞
  • 114
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Golang是一种高效的编程语言,因此使用它来开发博客系统是一个不错的选择。首先,我们可以利用Golang的并发特性来提高博客系统的性能,实现更高的并发访问量和更快的响应速度。其次,Golang的内置网络库和丰富的第三方库可以帮助我们轻松实现博客系统的各种功能,如用户认证、文章管理、评论系统等。另外,Golang还提供了良好的标准库和极低的内存消耗,可以有效地减少系统的资源占用,提高系统的稳定性和可靠性。 对于博客系统的前端开发,我们可以选择使用Golang的Web框架,比如Gin或者Beego,这些框架都提供了强大的路由和中间件功能,可以帮助我们快速搭建出美观、高效的界面。同时,Golang还支持模板引擎,我们可以利用模板引擎来实现页面的动态渲染和数据展示。 在数据库方面,Golang也有丰富的支持。我们可以选择使用Golang的SQL库来操作数据库,也可以使用ORM框架来简化数据库的操作流程。另外,Golang还支持各种类型的数据库,包括MySQL、PostgreSQL、MongoDB等,可以根据实际需求选择合适的数据库。 总之,利用Golang来开发博客系统,可以获得高性能、高可靠性和高效率的优势,是一个非常值得考虑的选择。同时,Golang社区也非常活跃,有丰富的资源和优秀的开源项目可以借鉴和使用。希望有更多的开发者能够利用Golang来开发出更加强大和优秀的博客系统
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值