博客系统(前后端分离版)

时间:2022-10-16 01:08:08

博客系统的具体实现

在正式写后端程序之前,我已经将博客系统的前端最基本的页面写好,详情可以见我的gitee

https://gitee.com/dengchuanfei/vscode_demo/tree/master/blog_system

软件开发的基本流程

  • 可行性分析
  • 需求分析
  • 概要设计
  • 详细设计
  • 编码
  • 测试
  • 发布

具体实现的八大功能

  • 实现博客列表的展示功能
  • 实现博客详情的展示功能
  • 登录功能
  • 强制用户登录
  • 显示用户的信息
  • 实现注销
  • 发布博客
  • 删除博客

数据库设计

写之前首先要进行规划,要做到“谋定而后动”

首先进行“数据库设计”,也就是想清楚需要几个库,几张表,每个表长啥样(属性是干什么的,是什么类型)

需要找到实体,然后分析实体之间的关联关系,再思考表的属性

这里业务比较简单,所以只需要两张表

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

用户表user (userId username password)

创建数据库

先建一个数据库,往里面添加数据

-- 拿到的数据库很可能并不干净,所以创建之前要确保之前没有同名的,还要删除同名的,注意:这是十分危险的操作,所以务必谨慎使用!!!
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是文章作者的ID
    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,"这是第一篇博客","从今天开始我要好好写代码,好好上课",now(),1);
insert into blog values(null,"这是第二篇博客","我要好好写代码,好好上课",now(),1);

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

这里设计的content是text类型的,text能放64KB的内容,一般是够博客使用的了,博客中的截图和博客文字不是存储在一起的,所以不用担心截图放不下

注意: 在SQL中的注释是 “-- ”,在–后面还有一个空格!

操作数据库

引入依赖

首先要先引入maven依赖,在.xml的中引入依赖

去*仓库中,搜索servlet API(3.1.0),mysql connect Java(5.1.49), jackson Databind(2.13.4.2)(将JSON格式进行转换)

封装DataSource

由于DataSource只有一份,所以使用单例模式来实现会比较好

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

//使用这个类来封装DataSource,使用单例模式(懒汉模式 + 多线程判断)
//实现数据库的连接和断开
public class DBUtil {
    private static volatile  DataSource dataSource = null;
    public static DataSource getDataSource() {
        //第一次判断是否需要加锁
        if (dataSource == null) {
            synchronized (DBUtil.class) { //针对类对象加锁
                //第二次判断是会否需要new对象
                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("1111");
                }
            }
        }
        return dataSource;
    }

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

    //关闭连接
    //建立连接的顺序是connection statement resultSet,所以关闭的顺序是反着的
    private static void close(Connection connection, Statement statement, ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

博客系统(前后端分离版)

在关闭资源的时候, 要是像这样直接使用依次try catch就会导致一旦上面的抛出异常,下面的就不会被执行到了,此时就会导致资源泄漏,很严重

要是使用的是throws相当于上面的情况,后面的代码还是不会执行,导致资源泄漏

所以还是应该 细分一下,多用几个try catch,来保证3个对象都关闭了

创建实体类

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

表中的属性怎么写,实体类就这么写

这里需要创建2个实体类 User 和 Blog

里面需要有属性和getter和setter方法, IDEA快捷键是alt + fn + insert

import java.security.Timestamp;

public class Blog {
    private int blogId;
    private String tittle;
    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 getTittle() { 
        return tittle;
    }

    public void setTittle(String tittle) {
        this.tittle = tittle;
    }

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

将JDBC增删改查封装起来

​ 这里创建的是BlogDao和UserDao类,这里的DAO是 Data Acess Object 数据访问对象

也就是说访问数据库的操作就可以使用这几个DAO对象来进行

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
//封装关于博客的相关操作
public class BlogDao {
    //插入博客--发布博客
    public void insert(Blog blog) {
        Connection connection = null;
        PreparedStatement statement = null;
        //此处只要判断改变的行数是不是1就行了,所以没有resultSet
        try {
            //1.建立连接
            connection = DBUtil.getConnection();
            //2.构造SQL
            //sql对应着blog的属性
            //blogId tittle content postTime userId
            String sql = "insert into blog values (null, ? , ?, now(), ?)";
            statement = connection.prepareStatement(sql);
            statement.setString(1, blog.getTittle());
            statement.setString(2, blog.getContent());
            statement.setInt(3, blog.getUserId());
            //3.执行sql
            int ret = statement.executeUpdate();//executeUpdate的返回值是修改的行数
            if (ret != 1) {
                System.out.println("博客插入失败!");
            } else {
                System.out.println("博客插入成功!");
            }
            //4.释放相关的资源--但是这里还是不适合,要是上面代码抛异常了,这里就会导致资源没有释放,资源泄露
        } catch (SQLException e) {
            e.printStackTrace();
        } finally{
            //方法哦finally就一定会执行到了,但是connection和statement是局部变量,所以就将这两个放到最外面,先置为null
            DBUtil.close(connection, statement, null);//这里没有涉及到resultSet,所以填null
        }
    }

    //查询一个博客--博客详情页
    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();
            //遍历结果集合
            if (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTittle(resultSet.getString("tittle"));
                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;//要是没有找到直接返回null就行
    }

    //查询所有博客--博客展示页
    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();
            //遍历结果集合,这里使用的是while循环来寻找
            while(resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTittle(resultSet.getString("tittle"));
                blog.setContent(resultSet.getString("content"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserId(resultSet.getInt("userId"));
                blogs.add(blog);//将所有搜到的blog都添加到blogs中
            } 
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //4.关闭资源
            DBUtil.close(connection,statement,resultSet);
        }
        return blogs;
    }

    //删除博客
    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();//executeUpdate的返回值是修改的行数
            if (ret != 1) {
                System.out.println("博客删除失败!");
            } else {
                System.out.println("博客删除成功!");
            }
            //4.释放相关的资源--但是这里还是不适合,要是上面代码抛异常了,这里就会导致资源没有释放,资源泄露
        } catch (SQLException e) {
            e.printStackTrace();
        } finally{
            //方法哦finally就一定会执行到了,但是connection和statement是局部变量,所以就将这两个放到最外面,先置为null
            DBUtil.close(connection, statement, null);//这里没有涉及到resultSet,所以填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 blog where username = ?";
            statement = connection.prepareStatement(sql);
            statement.setString(1, username);
            //3.执行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 {
            //1.建立连接
            connection = DBUtil.getConnection();
            //2.构造SQL
            String sql = "select * from blog where userId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, userId);
            //3.执行
            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 {
            //4.关闭资源
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }
}

将上面的几个函数的实现都看明白,其实JDBC的操作都是差不多的

下面主要是服务端和客户端的代码实现,由于服务端的代码比较长,就只有贴出核心功能的客户端代码

详细的客户端 服务端代码将会在文章最后给出

实现博客列表页

将之前写的前端代码复制到webapp目录下

博客系统(前后端分离版)

web.xml的配置文件

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

实现博客展示,是从数据库中读取数据,然后写到前端网页上

在博客列表页,需要做一个很重要的事情,页面在加载的时候通过ajax发起HTTP请求,从服务端获取到博客列表的数据

所以需要实现想好发什么样的请求,返回什么样的响---->约定前后端接口

[请求]
GET /blog

[响应]
[
    {
        blogId: 1,
        title: "第一篇博客",
        content: "博客正文",
        userId: 1,
        postTime: "2021-07-07 12:00:00"
    },
    {
        blogId: 2,
        title: "第二篇博客",
        content: "博客正文",
        userId: 1,
        postTime: "2021-07-07 12:10:00"
    },
    ...
]

由页面发起请求,后服务端进行响应

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 {
        BlogDao blogDao = new BlogDao();
        List<Blog> blogs = blogDao.selectAll();
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(objectMapper.writeValueAsString(blogs));
    }
}

在之前已经写过blog_list.html页面上进行修改


<!-- 发送ajax从服务端上获取数据 -->
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script>
    //在页面加载的时候,通过ajax给服务端发送消息,获取到博客列表信息,并且显示在页面上
    function getBlog(){
        $.ajax({
            type:'get',
            url:'blog',
            success:function(body){
                //获取到的body就是一个js对象数组,每个元素就是一个js对象,根据这个对象来构造一个div
                //1.把原来.right里面原有的内容清空,替换成从数据库服务端拿到的
                let rightDiv=document.querySelector('.container-right');
                rightDiv.innerHTML='';//清空原有数据
                //2.遍历body,构造出一个个blogDiv
                for(let blog of body){
                    let blogDiv=document.createElement('div');
                    //针对blogDiv设置一个属性,类名设为blog
                    blogDiv.className='blog';
                    //构造内部元素
                    //构造标题
                    let titleDiv=document.createElement('div');
                    titleDiv.className='title';
                    titleDiv.innerHTML=blog.title;
                    //作为子元素添加进去
                    blogDiv.appendChild(titleDiv);
                    //构造发布时间
                    let dateDiv=document.createElement('div');
                    dateDiv.className='date';
                    dateDiv.innerHTML=blog.postTime;
                    blogDiv.appendChild(dateDiv);
                    //构造摘要
                    let descDiv=document.createElement('div');
                    descDiv.className='desc';
                    descDiv.innerHTML=blog.content;
                    blogDiv.appendChild(descDiv);
                    //链接 查看全文(这里用的是a标签)
                    let a=document.createElement('a');
                    a.innerHTML='查看全文 &gt;&gt;';
                    //希望点击后能跳转到博客详情页
                    //跳转要告知哪个博客的详情页
                    a.href='blog_detail.html?blogId='+blog.blogId;
                    blogDiv.appendChild(a);
                    //把blogDiv挂到dom树上
                    rightDiv.appendChild(blogDiv);
                }
            },
            error:function(){
                alert("获取博客列表失败");
            }
        });
    }
    getBlog();
</script>

博客系统(前后端分离版)

这里的时间很明显就是一个时间戳,并不直观,所以还是要改的,通过fiddler可以看到返回的响应就是时间戳的格式,所以也就是要将get方法的返回值变成String类型的时间格式

此处就要使用SimpleDateFormat类了

public String getPostTime() {
        //使用IDEA提供的原生的,返回的是时间戳,所以需要改一下返回值
        //使用SimpleDateFormat来将时间戳转换成指定的时间格式
        //这里的参数标准格式建议查一下,因为在不同的语言中表示放方式是不一样的
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return simpleDateFormat.format(postTime);
    }

博客系统(前后端分离版)

时间的格式确实改好了,但是发现最上面的博客确实比较老的博客,不符合常规的博客展示思路

此时只要在BlogDao中将selectAll的搜索改成select * from blog order by postTime desc 按时间降序排列就行了

要是正文很长,在博客展示页就应该显示一部分文章,此时就对内容进行截断

在BlogDao 中进行content的判断和截断:

String content = resultSet.getString("content");
if(content.length() > 100){
    content = content.substring(0,100) + "......";
}
blog.setContent(content);

实现博客系统的展示功能

在展示的时候,发送ajax请求来访问服务端,获取到服务端返回的响应之后,填充到博客的详情页面中

@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();
            resp.getWriter().write(objectMapper.writeValueAsString(blogs));
        }else{
            //说明是博客详情页发起的请求
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            resp.getWriter().write(objectMapper.writeValueAsString(blog));
        }
    }
}

客户端核心代码:

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>.blog-date');
                    dateDiv.innerHTML = body.postTime; 
                     
                    //方法1:
                     //let contentDiv=  document.querySelector('#content');
                     //contentDiv.innerHTML = body.content;
					
                    //方法2:
                    //此处使用editor.md来进行渲染,主要是后面实现博客编辑的时候使用
                    editormd.markdownToHTML('content', {markdown: body.content}); 
                }
            });
        }
        getBlog();

关于前端代码:

  1. 要是想显示具体的哪篇博客,就要知道具体的博客id,所以这里写URL的时候将location.search添加上去了
  2. 为什么这里要使用方法2,而不是方法1,主要是因为博客是以markdown的形式来写的,所以渲染的时候也要以markdown格式来渲染

登录功能

所谓的登录功能就是在登录之后跳转到博客列表页

服务端代码:

其实登录操作就是先根据请求获取到用户输入的账号密码,之后进行判断,看看用户输入的账号密码是不是符合要求,要是符合的话,再查询数据库,要是账号密码都是正确的,就创建出一个会话,重定向到博客列表页

@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;
        }
        //3.要是登录成功,就创建一个会话
        HttpSession session = req.getSession(true);
        //在会话中保存user,方便后面知道当前的user是谁
        session.setAttribute("user", user);

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

客户端核心代码:

 <div class="login-container">
        <form action="login" method="post">
            <!-- 实现登录对话框 -->
        <div class="dialog">
            <h3>登录</h3>
            <div class="row">
                <span>用户名</span>
                <input type="text"id="username" name = "username">
            </div>
            <div class="row">
                <span>密码</span>
                <input type="password" id ="password" name = "password">    
            </div>
            <div class="row">
                <input class="login-btn"  value="登录" type = "submit"> 
            </div>
        </div>
        </form>

    </div>

强制要求用户登录

在博客系统中要求要先登录之后才能查看博客和编辑博客,所以要检查用户的登录状况,要是没有登录的话就自动跳转到登录页面

服务端核心代码:

首先要判断当前有没有创建出会话,要是当前存在session并且user也存在,说明已经登录了,就不进行操作,返回一个状态码200就行了,要是没有session或者user就说明当前没有登录,重定向到登录页面

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.首先先获取一下会话
        //getSession的参数是false,就是说要是没有会话,也不用创建会话
        HttpSession session =req.getSession(false);
        if(session == null){
            //当前没有会话,说明没有登录
            resp.setStatus(403);
            return;
        }
        //为什么要判断user是否存在,其实还是会存在"有session但是没有user对象"这种情况的,这种情况会在后面退出登录的时候出现,所以此处一定要判断user对象是否存在
        User user = (User) session.getAttribute("user");
        if (user == null) {
            //说明此时虽然有会话,但是没有user对象,还是不行
            resp.setStatus(403);
            return;
        }
        //当前已经是登录状态,其实这行代码不写也行,但是写上更清楚
        resp.setStatus(200);
    }

客户端核心代码 :

function checkLogin() {
    $.ajax({
        type:'get',
        url:'login',
        success: function(body) {
            // 成功不做处理
        },
        error: function() {
            // 失败就会强行跳转到登录页面
            location.assign('login.html');
        }
    })
}

显示用户信息

当李四登录进去之后看到张三的文章,之后点了进去,此时就要修改一下用户的信息

这里的服务端逻辑要分成两种情况:

  1. 要是当前用户是在博客列表页,登录的信息就在session中
  2. 要是当前用户是在博客详情页中,就要求数据库中查询文章作者的信息
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");//getParameter是用来获取表单中的值的,返回值是String,所以此时的blogId就变成了String类型
        if (blogId == null) {
            //博客ID为0说明当前是在列表页,登录的用户信息在session里面,所以从session中拿
                getUserInfoFromSession(req,resp);
        }else{
            //存在博客ID说明当前是在博客展示页,要获取作者信息,就要查询数据库,不能从session中拿,因为文章作者可能不是自己,所以要从数据库中拿
            getUserInfoFromDB(req,resp, Integer.parseInt(blogId));//将blogId转换成int类型
        }
    }

    private void getUserInfoFromDB(HttpServletRequest req, HttpServletResponse resp, int blogId) throws IOException {
        //先根据blogId查询blog对象,获取到userId(作者是谁)
        // 根据user查询对应的User对象即可
        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;
        }
        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("user不存在");
            return;
        }
        //排除了上面两种错误情况,接下来只有将user以JSON的形式返回就行了
        user.setPassword("");
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(objectMapper.writeValueAsString(user));
    }

    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 = (User) session.getAttribute("user");
        if (user == null) {
            resp.setStatus(403);
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前未登录!");
            return;
        }
        //在获取到user对象之后为了安全起见,将密码设置为空
        //以JSON的形式返回响应,所以要提前创建objectMapper
        user.setPassword("");
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(objectMapper.writeValueAsString(user));
    }
}

服务端核心代码:

//针对博客列表页,获取到当前用户的信息
    function getUserInfo(){
        $.ajax({
            type:'get',
            url:'userInfo',
            success : function(body){
                //修改头像
                let h3 = document.querySelector('.container-left>.card>h3')
                h3.innerHTML = body.username;
            }
        })

    }
    getUserInfo();

退出登录状态

在页面上一个注销按钮,要求实现点击一下按钮就能退出当前的账号

服务端代码:

后端服务端在收到请求之后,将当前会话的信息删除,然后重定向到登录页面即可

//实现注销功能(退出登录状态)
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;
        }
        //删除user对象,并重定向到登录页面
        session.removeAttribute("user");
        resp.sendRedirect("login.html");
    }
}

这里需要注意的是: 在登录状态下,既有session又有user属性,要是想要删除用户的信息,只要删除其中一个就行了,但是这里选择的是删除user属性,因为没有提供删除session的api,并不是很好删除session

服务端代码:

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

只要将注销按钮变成一个链接形式即可

发布博客

发布博客的意思是在用户写完博客之后,点击提交按钮,服务端收到请求之后,在数据库中添加一条记录,并且在最后还会跳转到博客列表页

有一个前提: 用户要写上了标题和正文之后点击提交按钮才能提交

所以还要判断一下是否用户写了标题和正文

服务端代码:

接着在原本的BlogServlet类中写

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //使用这个post方法来实现创建新的博客
    //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 = (User) session.getAttribute("user");
    if (user == null) {
        resp.setStatus(403);
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("当前未登录,不能创建博客!");
        return;
    }
    //2.当前已经登录,获取请求的参数(博客的标题和内容)
    req.setCharacterEncoding("utf8");//将请求变成utf8的格式,防止乱码
    String title = req.getParameter("title");
    String content = req.getParameter("content");
    //3.构造Blog对象,并插入到数据库中
    Blog blog = new Blog();
    blog.setTitle(title);
    blog.setContent(content);
    //博客的blogId是自增主键,所以不用专门指定设置,postTime是now()函数,所以也不用指定
    blog.setUserId(user.getUserId());
    BlogDao blogDao = new BlogDao();
    blogDao.insert(blog);

    //4.插入新的博客之后重定向到博客列表页
    resp.sendRedirect("blog_list.html");

    //有一个疑问:前面已经实现了未登录状态下是不能进入博客编辑页的,那么此时为什么还要先判断一下是否登录呢?
    //1.要是有人使用postman直接构造post请求,直接绕开了登录,插入数据怎么办?
    //2.在上面的代码中,要想给新的博客设置userId需要使用user.getUserId(),所以前面要保证存在user对象
    //结合上面的两点理由,加上登录判断是很有必要的
}

客户端核心代码:

使用form表单来提交请求

<!-- 这个编辑页的版心 -->
    <div class="blog-edit-container" style="height: 100%">
        <!-- 套上一个form标签 -->
        <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">
            <!-- editor.md规定,要想使用form表单来提交数据,就要按照下面的标准来写 -->
            <textarea name="content" style="display: none"></textarea>
        </div>

        </form>
    </div>

删除博客

要是想要删除博客,在不考虑管理员的情况下,只能由博客的作者自己来删除

所以就要判断当前登录的用户是不是博客的作者,要是不是的话,他是没有权限删除别人的博客的

在删除之后还要重定向到博客列表页

服务端代码:

主要的思路就是看看博客作者id和登陆者id是不是一样的,要是一样的,就有权限去删除博客

但是在此之前,还有很多要考虑的

  1. 当前用户是否已经登录?(这个问题是要考虑的,前面已经解释过)
  2. 客户端传过来的blogId是否可能是null(前端没有传过来blogId)
  3. 传过来的blogId是否可能不存在(客户端传了blogId,但是在服务端这边根本就没有)

在经历了上面的错误情况之后,就是客户端传来正确的blogId,服务端也有对应的博客作者的id,此时只要对比一下登录用户id和博客作者id即可

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为空,这种情况是前端没传blogId
            resp.setStatus(403);
            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,但是它并没有对应的文章,这种情况是前端传了blogId,但是后端没有对应的数据
            resp.setStatus(404);
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您当前要删除的博客不存在! blogId = " + blogId);
            return;
        }
        //观察上面的四个if可以感受到,这就是在排错,先将错误的情况都写出来,起到过滤的作用.
        //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.真正执行删除操作
        //将String类型的blogId转换成int类型的,准备删除
        blogDao.delete(Integer.parseInt(blogId));
        //6.返回302,重定向到博客列表页,要是失败就提示失败
        resp.sendRedirect("blog_list.html");
    }
}

客户端核心代码:

 function upDateDeleteURL(){
    // 这里的location.search就是'?blogId=?'
    //可以在网页开发者工具的console中输入查看
    let deleteBtn = document.querySelector('#delete-btn');
    deleteBtn.href = 'blog_delete' + location.search;
 }
 upDateDeleteURL();

在Linux上部署博客系统

在完成了博客系统之后,只是可以在自己的电脑上查看博客系统, 别人还是看不到的,所以要借助公网ip来让所有人都能访问博客系统

要是想要部署java web程序,首先要配置环境

jdk tomcat mysql

安装jdk

推荐的方法是使用yum直接安装openjdk(开源的,与官方的jdk功能差不多),目前使用的最多的就是jdk8系列

yum list | grep jdk

在源上搜索所有关于jdk的文件

博客系统(前后端分离版)

devel表示development的意思,就是软件开发包

后面的x86_64的意思是支持64位的系统

选中之后按ctrl + insert来复制,之后使用yum install 粘贴(shift + insert ) 就下载好了

安装tomcat

当前的程序使用的是tomcat8.5的版本,但是使用yum list | grep tomcat 并没有8.5版本(当前的centos7系统有点老)

此时有一个好方法,找到之前的tomcat压缩包,直接拖到Linux上就能安装,主要是因为tomcat是java写的,能跨平台

注意: 在拖压缩包之前,要确认压缩包是不是zip格式的,因为Linux默认不支持rar格式的压缩包,只支持zip格式

可能会出现拖不过去的情况,此时就要安装一个lrzsz, yum insatll lrzsz

之后就能将tomcat的压缩包拖过去了

博客系统(前后端分离版)

之后进行解压,需要使用unzip ,所以要先安装unzip, yum insatll unzip

之后进行解压 unzip apache apache-tomcat-8.5.85.zip(后面的文件可以用TAB来补全)

进入到tomcat的bin目录下,就可以看到启动的脚本

博客系统(前后端分离版)

在Windows上使用的是startup.bat,但是在Linux上使用的是startup.sh

要想启动这里的程序,首先要赋予他们可执行权限,也就是chmod + x *.sh, 之后后面的文件就会变成绿色,表示可以执行

博客系统(前后端分离版)

博客系统(前后端分离版)

启动tomcat的过程:

  1. 下载安装包(要是zip格式的压缩包)
  2. 上传压缩包到Linux上(要使用到 lszrz命令)
  3. 解压缩(要使用到unzip 命令)
  4. 进入Tomcat目录的bin目录
  5. 给启动脚本增加可执行权限(chmod +x *.sh) --此时.sh的文件就会变成绿色
  6. 使用 sh startup.sh来启动 tomcat

验证tomcat是否启动成功

  • 通过ps aux | grep tomcat来查看进程

  • 通过nststat -anp | grep 8080 查看端口号是否被绑定

博客系统(前后端分离版)

博客系统(前后端分离版)

tomcat也是用java写的,所以此时8080已经被tomcat绑定了

还有一种验证tomcat是否启动的方式,就是直接访问tomcat的欢迎页面 公网IP地址:8080

但是大概率是不能访问的,这是因为云服务器的防火墙或者安全组没有对8080允许访问,所以要去云服务器那边手动添加一个8080端口号的规则

博客系统(前后端分离版)

添加完8080端口号的规则之后就能正常访问tomcat的欢迎页面

博客系统(前后端分离版)

但是在平时的时候还是不要开启端口号规则,防止服务器被攻击

安装mysql

linux安装mysql有很多的方式,最简单的一种安装方式是使用yum 安装mariadb(开源的,是mysql的孪生兄弟)

使用yum安装必要的包

直接执行这几个命令就行了

yum install -y mariadb-server
yum install -y mariadb
yum install -y mariadb-libs
yum install -y mariadb-devel

启动

启动mariadb服务

systemctl start mariadb

设置服务自启动

systemctl enable mariadb

查看服务的状态

systemctl status mariadb

验证是否连接上

mysql -uroot

博客系统(前后端分离版)

这样子就是连接成功了

创建数据库的时候为了支持中文,这里统一使用utf8mb4字符集

create database demo_db charset utf8mb4;

此时就将mariadb安装连接好了

正式部署

在安装好了jdk tomcat mysql之后,就可以开始将博客系统部署到云服务器上

  1. 首先要理清楚 博客系统的依赖,先将依赖的内容打通

  2. 将博客系统的程序打包, 将war包上传到云服务器的webapps目录下即可

    在我的博客系统中的依赖就是mysql,必须要知道的是,本地的电脑上的数据库与云服务器上的数据库是两台不一样的电脑,所以本地的数据库有数据,但是云服务器上的数据库还没有数据

    所以此时要将SQL指令在云服务器上输入(粘贴)一下

进入mariadb 的命令: mysql -uroot,退出mariadb: ctrl + c

在开始打war包之前要先调整一下DBUtil的代码,主要就是要调整一下连接数据库的代码

博客系统(前后端分离版)

需要将这里的密码设置成云服务器的数据库的密码,要是没有单独设置过云服务器上的密码,那么就是空字符串

博客系统(前后端分离版)

这里的setURL也基本上不用改,因为云服务器也是在我主机上的,所以还是可以使用环回IP, 后面的3306端口号也是不用改的

打war包的时候

博客系统(前后端分离版)

改好之后双击maven的package就好了

博客系统(前后端分离版)

打好war包之后找到war包

在Linux上进入到apache tomcat目录中,在进入webapps目录中

博客系统(前后端分离版)

将之前打好的war包拖到webapp下面

博客系统(前后端分离版)

之后就会自动进行解压缩和部署,变成蓝色的了

要是之后修改了代码,务必要重新打包,重新上传

部署(也叫上线)是一个很重要,很有仪式感的事情,所以一定要小心谨慎!

此时就已经是部署完毕了!

注意: 要是发现网页打不卡,先检测一下tomcat的欢迎页面能不能打开,要是不能打开说明是tomcat8080端口被阻止了,就要去云服务器那边将防火墙新增8080端口的规则

要知道本地电脑上能运行,不能说明部署到云服务器上就能成功

此时我出现了一个问题,那就是mariadb上的中文乱码,但是我在建库的时候已经指定了utf8mb4字符集,最后我发现还要在建表的时候也要指定字符集

博客系统(前后端分离版)

要是修改了任何代码都要重新使用maven双击package重新打war包

再把之前Linux上的war删除,再把新的war包拖进去,就能重新部署

此时,就正式完成了博客系统的简单功能实现和部署

博客系统的源码(服务端+客户端)