博客系统[基于Tomcat部署 servlet框架]

前言

        在本博客中,我将详细介绍整个博客系统的设计与实现过程,包括Smart Tomcat的部署和servlet框架的使用。无论你是初学者还是有一定经验的开发者,我相信这篇博客都能带给你一些收获。在编写本文时,我尽量采用通俗易懂的语言,希望让大家都能轻松理解和学习。


博客系统简要拆解分析

  1. 包含四个界面:博客列表页,博客详情页,博客编辑页以及博客登录页
  2. 包含以下八个功能:
  • 博客列表页展示
  • 博客详情页展示
  • 博客登录页展示
  • 博客编辑页展示(发布博客功能)
  • 限制用户权限(必须登录才可以访问其他页面)
  • 用户信息与作者信息显示分离
  • 注销功能(退出登录)
  • 删除博客

 一、数据库的设计

1.1 设计分析:

  • 设计表结构,有几张表,每个表里面都有啥
  • 要存储的有2个部分,博客数据、用户数据
  • 博客表:blog{blogID,title,content,userID,postTime}
  • 用户表:user{userID,username,password}

1.2 代码实现(sql语句):

create database if not exists Blog_System charset=utf8;

use Blog_System;

drop table if exists blog;
create table blog (
    blogID int primary key auto_increment,
    title varchar(128),
    content varchar(4096),
    userID int,
    postTime datetime
);

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

insert into user values(null,'Pharaoh','123456'),(null,'xiaoming','123');

insert into blog values(null,'Pharaoh的第一篇博客','这是博客正文',1,'2023-7-20 12:30:00');
insert into blog values(null,'Pharaoh的第二篇博客','这是博客正文',1,'2023-7-21 12:30:00');

注意此处博客表中的blogID和用户表中userID设置了自增主键,所以下面进行insert插入操作时,设置null即可。


二、封装数据库(JDBC)

2.1 准备工作

1.创建Maven项目

2.引入相关依赖(pom.xml)

可以从Maven中央仓库中查找相关依赖

    <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/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.0</version>
        </dependency>

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

    </dependencies>

3.创建必要的目录

在src下创建webapp目录,然后再创建子目录WEB-INF,在子目录中创建web.xml

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

4.配置smartTomcat

2.2 封装数据库的连接操作

此处使用 DataSource引入单例模式中的懒汉模式,对连接、关闭操作进行封装。

package model;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.*;

//---------------------------------封装数据库的连接操作---------------------------------

//创建dataSource
public class DBUtil {
    //DataSource对于一个项目,有一个就可以了(单例)--懒汉模式
    //加上volatile和synchronized以保证线程安全
    private static volatile DataSource dataSource = null;

    private 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("123456");
                }
            }
        }
        return dataSource;
    }

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

    //关闭资源
    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
        //注意释放的顺序,谁后创建谁先释放
        if(resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

1.为什么datasource前后要判定两次是否为空?       

        先判断null如果为null再尝试获取锁(提高性能,如果已经创建就不用加锁判断了),获取锁后还不能直接创建,因为之前可能也有判断为null的已经获取过锁并创建对象,所以锁内需要再次检测。两次的if判断与synchronized称为双重校验锁。

2.为什么close操作中不使用thorws抛异常而使用try/catch语句?

        因为如果前面的语句如果发生异常,会导致后面的资源没释放从而导致资源泄漏! 

2.3 对两个表创建对应的实体类

package model;

//这个类表示数据库中的Blog表的内容
//每个Blog对象,就对应着Blog表中的一条记录

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

public class Blog {
    private int blogID;
    private String title;
    private String content;
    private int userID;
    private Timestamp postTime;

    public int getBlogID() {
        return blogID;
    }

    public void setBlogID(int blogID) {
        this.blogID = blogID;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getUserID() {
        return userID;
    }

    public void setUserID(int userID) {
        this.userID = userID;
    }

    public String getPostTime() {
        SimpleDateFormat format = new SimpleDateFormat();
        return format.format(postTime);
    }

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

//这个类表示数据库中的User表的内容
//每个User对象,就对应着User表中的一条记录

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

这里就是设置相关的属性以及提供对应的getter和setter方法。

注意此处更改了getPostTime方法,需要用到SimpleDataFormat这个类,使返回数据不再是时间戳而是一个正常格式的时间,此处格式具体如何填写需要参考官方文档,不同语言需要提供的格式不同。

2.4 封装两个表对应的增删改查操作

package model;

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

//DAO => Data Access Object (对象访问数据)--通过这样的对象来访问数据
public class BlogDao {
    //插入一个Blog对象
    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,?,?,?,?)";
            statement = connection.prepareStatement(sql);
            statement.setString(1,blog.getTitle());
            statement.setString(2,blog.getContent());
            statement.setInt(3,blog.getUserID());
            //如果数据库表中的datetime类型,插入数据的时候,按照Timestamp来插入或者按照格式化时间来插入均可!
            statement.setString(4,blog.getPostTime());
            //3.执行sql
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,null);
        }
    }

    //查询Blog表中的所有数据内容
    public List<Blog> selectAll() {
        List<Blog> blogs = new ArrayList<>();
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            connection = DBUtil.getConnection();
            //此处对于博客编写时间通过倒序排列更加合理,使新的博客展示在页面最上边。
            String sql = "select * from blog order by postTime desc";
            statement = connection.prepareStatement(sql);
            resultSet = statement.executeQuery();

            while(resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogID(resultSet.getInt("blogID"));
                blog.setTitle(resultSet.getString("title"));
                //提取出一部分摘要用于列表页的显示
                String content = resultSet.getString("content");
                if(content.length() > 100) {
                    content = content.substring(0,100) + "...";
                }
                blog.setContent(content);
                blog.setUserID(resultSet.getInt("userID"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blogs.add(blog);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return blogs;
    }

    //指定一个博客ID来查询对应博客
    public Blog selectOne(int blogID) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from blog where blogID = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogID);
            resultSet = statement.executeQuery();

            if(resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogID(resultSet.getInt("blogID"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setUserID(resultSet.getInt("userID"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                return blog;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

    //指定一个博客ID来删除对应博客
    public void delete(int blogID) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "delete from blog where blogID = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,blogID);
            statement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(connection,statement,null);
        }
    }
}
package model;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

//DAO => Data Access Object (对象访问数据)--通过这样的对象来访问数据
public class UserDao {
    public User selectUserByID(int userID) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from user where userID = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,userID);
            resultSet = statement.executeQuery();

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

    public User selectUserByName(String username) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from user where username = ?";
            statement = connection.prepareStatement(sql);
            statement.setString(1,username);
            resultSet = statement.executeQuery();

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

1.此处为什么取名DAO?

        DAO => Data Access Object 通过这样的对象来访问数据

        此处即为通过这两个类操作数据库中表的数据


三、实现博客列表页 

3.1 设计分析

        在博客列表页页面加载的时候,需要在前端代码中使用ajax构造HTTP请求,从服务器中获取到博客数据再返回到页面上,所以此处要预先约定好构造怎么样的请求,返回怎么样的响应。

效果如下:

3.2 约定前后端交互接口

 此处的响应以json格式的数组来创建

3.3 实现前端代码(ajax部分)

// 通过 ajax 给服务器发请求, 获取到所有的博客数据. 并且构造到页面上. 
        function getBlogs() {
            $.ajax({
                type: 'get',
                url: 'blog',
                success: function(body) {
                    // 根据返回的数据, 构造出页面中对应的元素
                    let containerRight = document.querySelector('.container-right');
                    for (let blog of body) {
                        let blogDiv = document.createElement("div");
                        blogDiv.className = 'blog';
                        let titleDiv = document.createElement("div");
                        titleDiv.className = 'title';
                        titleDiv.innerHTML = blog.title;
                        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");
                        a.href = 'blog_detail.html?blogID=' + blog.blogID;
                        a.innerHTML = '查看全文 &gt;&gt;';

                        // 把上述标签构造好了之后, 还需要组合起来. 
                        blogDiv.appendChild(titleDiv);
                        blogDiv.appendChild(dateDiv);
                        blogDiv.appendChild(descDiv);
                        blogDiv.appendChild(a);
                        containerRight.appendChild(blogDiv);
                    }
                }
            });
        }

3.4 实现后端代码(BlogServlet类)

//通过该类,来实现一些后端接口

//约定前后端接口为:     请求:Get /blog
//                   响应: json格式来存储每一条博客,用json数组的形式
//
//每个Servlet都对应了一个路径,如果路径确定了,对应的Servlet也明确了.
@WebServlet("/blog")
public class BlogServlet extends HttpServlet{
    //用这个类转换成json形式的字符串
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //从query String中获取blogID,如果有则视为查询指定博客,没有则查询全文
        String blogID = req.getParameter("blogID");
        BlogDao blogDao = new BlogDao();
        if(blogID == null) {
            List<Blog> blogs = blogDao.selectAll();
            String respString = objectMapper.writeValueAsString(blogs);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
        }
        else {
            Blog blog = blogDao.selectOne(Integer.parseInt(blogID));
            String respString = objectMapper.writeValueAsString(blog);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
        }
    }
}

四、实现博客详情页

4.1 设计分析

博客列表页中点击查看全文,跳转到博客详情页

点击了查看全文之后就在blog_list.html中发起一个get请求,此时需要告诉服务器你查找的是哪一篇博客,于是就要在url中加上query String。

let a = document.createElement("a");
a.href = 'blog_detail.html?blogID=' + blog.blogID;
a.innerHTML = '查看全文 &gt;&gt;';

此时需要博客详情页再发送一个ajax请求到服务器中去,向服务器获取当前的博客全文数据,再展示到页面上

效果如下:

4.2 约定前后端交互接口 

 4.3 实现前端代码(ajax部分)

function getBlog() {
            $.ajax({
                type: 'get',
                url: 'blog' + location.search,
                success: function(body) {
                    // 设置博客的标题
                    let h3 = document.querySelector('.container-right h3');
                    h3.innerHTML = body.title;
                    // 设置发布时间
                    let dateDiv = document.querySelector('.container-right .date');
                    dateDiv.innerHTML = body.postTime;
                    // 设置正文. 正文内容应该是 markdown 格式的数据. 
                    // 此处要显示的应该是渲染过的 markdown 的内容, 而不是 markdown 的原始字符串. 
                    // 第一个参数, 是一个 html 元素的 id, 接下来渲染的结果会放到对应的 元素中. 
                    editormd.markdownToHTML('content', {markdown: body.content});
                }
            });
        }

注意: 

  • 此处的location.search 即为 query String,location是一个全局变量 
  • 由于当前响应的Content-Type返回的是application/json格式,此时json就会把这个body直接转成js对象。
  • 数据库中存储的是markdown原始的字符串,最终需要展示渲染后的效果,这里就需要在博客详情页中,针对markdown字符串进行渲染(直接借助editor.md这个库即可)

4.4 实现后端代码

        与博客列表页代码一致,此处先判断blogID是否为空,为空则展示博客列表页,不为空则判定具体blogID等于多少,返回相应的博客详情页。


五、实现博客登录页

5.1 设计分析 

        用户在登录页输入用户名和密码,点击登录按钮,发送一个POST请求给服务器,服务器对用户名和密码进行核验,如果正确则跳转到博客列表页,如果错误则返回相应的错误信息。

效果如下: 

5.2 约定前后端交互接口 

form表单和ajax构造请求的区别是:

  1. from会跳转页面,而ajax不会
  2. form只支持get和post请求,而ajax可以支持其他更多请求

5.3 实现前端代码(form部分)

            <!-- 使用 form 包裹一下下列内容, 便于后续给服务器提交数据 -->
            <form action="login" method="post">
                <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 type="submit" id="submit" value="登录">
                </div>
            </form>

 注意:

  1. form表单此处的action和method要和约定的接口相匹配
  2. input标签中的name属性就是构造请求的键值对中的“键”,用户输入的内容即为“值”

  3. 必须使用submit来提交form表单

5.4 实现后端代码 

package api;

import com.fasterxml.jackson.databind.ObjectMapper;
import model.User;
import model.UserDao;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.从请求中获取用户名和密码
        req.setCharacterEncoding("utf8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if(username == null || username.equals("") || password == null || password.equals("")) {
            String html = "<h3>用户名或密码不得为空!</h3>";
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(html);
            return;
        }
        //2.从数据库中读取用户名和密码进行比对
        UserDao userDao = new UserDao();
        User user = userDao.selectUserByName(username);
        if(user == null) {
            String html = "<h3>用户名或密码错误!</h3>";
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(html);
            return;
        }
        if(!password.equals(user.getPassword())) {
            String html = "<h3>用户名或密码错误!</h3>";
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(html);
            return;
        }
        //3.用户名和密码都正确,登录成功,设置会话!
        //会话可以理解为一个Hash表
        //每个用户(客户端)对应表中一个键值对,其中键是一个字符串:sessionID(唯一字符串),值是HttpSession对象
        HttpSession session = req.getSession(true);
        session.setAttribute("user",user);
        //4.返回一个重定向响应,跳转回博客列表页
        resp.sendRedirect("blog_list.html");

    }
}

六、实现强制登录逻辑 

6.1 设计分析

        当用户访问博客列表页、详情页、编辑页页面时必须是登录状态,如果未登录,则返回到登录页面。

        在博客列表页、详情页、编辑页页面加载时,发起一个ajax请求,通过这个请求访问服务器并获取到当前登录状态,如果未登录则返回到登录页,如果已登录则不作任何操作。

6.2 约定前后端交互接口

 6.3 实现前端代码

function getLoginStatus() {
    $.ajax({
        type: 'get',
        url: 'login',
        success: function(body) {
            // 响应 200 的时候, 执行 success 回调. 
            // 用户已经登录, 啥都不用干. 
            console.log("用户已经登录了. ");
        },
        error: function(body) {
            // 响应 403 的时候, 执行 error 回调.  (只要返回不是 2 开头的, 都会触发 error)
            // 跳转到 login.html 主页. 
            location.assign("login.html");
        }
    })
}

由于此处在博客列表页、详情页、编辑页都要判定是否登录,于是我们可以将这段代码分装到一个.js文件里中,然后只需要在各个页面中直接调用即可

<script src="js/app.js"></script>

6.4 实现后端代码

//通过这个方法判定用户登录状态
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession(false);
        //会话不存在,拒绝查看请求,返回403,未登录状态
        if(session == null) {
            resp.setStatus(403);
            return;
        }
        User user = (User)session.getAttribute("user");
        //虽然会话存在,但是用户对象不存在,未登录状态,依旧返回403无权限查看
        if(user == null) {
            resp.setStatus(403);
            return;
        }
        resp.setStatus(200);
        resp.setContentType("application/json;charset=utf8");
        user.setPassword("");
        String respjson = objectMapper.writeValueAsString(user);
        resp.getWriter().write(respjson);
    }

七、实现用户信息与作者信息分离 

7.1 设计分析

效果如下:

 7.2 约定前后端交互接口

1.针对博客列表页

2.针对博客详情页

 7.3 实现前端代码

1.博客列表页:

function getLoginStatus() {
                $.ajax({
                    type: 'get',
                    url: 'login',
                    success: function(body) {
                    // 响应 200 的时候, 执行 success 回调. 
                    // 用户已经登录, 啥都不用干. 
                    console.log("用户已经登录了. ");
                    let h3 = document.querySelector('.card h3');
                    h3.innerHTML = body.username;
                },
                error: function(body) {
                    // 响应 403 的时候, 执行 error 回调.  (只要返回不是 2 开头的, 都会触发 error)
                    // 跳转到 login.html 主页. 
                    location.assign("login.html");
                }
            })
        }
        // 判定用户的登录状态
        getLoginStatus();

2.博客详情页 :

        function getAuthor() {
            $.ajax({
                type: 'get',
                url: 'user' + location.search,
                success: function(body) {
                    // 把响应中得到的 user 对象的数据, 给构造到页面上. 
                    if (body.userId == 0) {
                        // 服务器没有找到匹配的用户. 
                        alert("当前未找到作者信息!");
                        return;
                    }
                    // body 是一个合法的 user 对象
                    let h3 = document.querySelector('.card h3');
                    h3.innerHTML = body.username;
                }
            });
        }
        getAuthor();

 此处即为插入一个h3标签把当前用户名称加上去,其他内容需要更改也是类似如此。

7.4 实现后端代码 

1.博客列表页

在BlogServlet后面加上这段代码即可

注意此处返回给前端的响应最好是不带密码的,保证用户密码安全性(掩耳盗铃)

resp.setStatus(200);
resp.setContentType("application/json;charset=utf8");
user.setPassword("");
String respjson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respjson);

2. 博客详情页

package api;

import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
import model.User;
import model.UserDao;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf8");
        String blogID = req.getParameter("blogID");
        //1.直接返回一个userID为0的对象,方便前端进行处理
        if(blogID == null || blogID.equals("")) {
            String respjson = objectMapper.writeValueAsString(new User());
            resp.getWriter().write(respjson);
            System.out.println("blogID为空!");
            return;
        }
        //2.查询数据库,查询对应的Blog对象
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectOne(Integer.parseInt(blogID));
        if(blog == null) {
            String respjson = objectMapper.writeValueAsString(new User());
            resp.getWriter().write(respjson);
            System.out.println("blogID不存在!");
            return;
        }
        //3.根据blog中的blogID,查询对应的作者信息
        UserDao userDao = new UserDao();
        User user = userDao.selectUserByID(blog.getUserID());
        if(user == null) {
            String respjson = objectMapper.writeValueAsString(new User());
            resp.getWriter().write(respjson);
            System.out.println("该博客对应的作者不存在!");
            return;
        }
        //4.把user对象返回给页面
        String respjson = objectMapper.writeValueAsString(user);
        resp.getWriter().write(respjson);
    }
}

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

8.1 设计分析

        注销按钮为一个a标签,这个标签在点击的时候会发送一个GET请求,服务器收到这个请求之后就会把会话中的user对象给删除掉。

注意:

此处其实能删除整个sessionID:session1是更好的,但是Servlet中没有提供对应的API(其实提供了但是并不好用,提供的不是删除方法而是设置过期时间)。

8.2 约定前后端交互接口

8.3 实现前端代码

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

只需要在列表页中插入一个a标签即可,属性与约定接口相对应。 

8.4 实现后端代码

package api;

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.sendRedirect("login.html");
            return;
        }
        session.removeAttribute("user");
        resp.sendRedirect("login.html");
    }
}

九、实现发布博客功能(博客编辑页)

9.1 设计分析

        在博客编辑页中,用户点击提交,就会发送一个POST请求,把用户写的博客标题、正文、时间、用户名一起上传给服务器,服务器则可以保存上述数据到数据库中,接下来,就可以在博客列表页和详情页中看到新的博客数据了。

效果如下:

9.2 约定前后端交互接口

9.3 实现前端代码

<form action="blog" method="post">
            <!-- 标题编辑区 -->
            <div class="title">
                <input type="text" id="title-input" name="title">
                <input type="submit" id="submit">
            </div>
            <!-- 博客编辑器 -->
            <!-- 把 md 编辑器放到这个 div 中 -->
            <div id="editor">
                <textarea name="content" style="display: none;"></textarea>
            </div>
        </form>

9.4 实现后端代码

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        //1.先从请求中拿到标题和正文
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        if(title == null || title.equals("") || content == null || content == "") {
            String html = "<h3>标题或者正文不得为空!</h3>";
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(html);
            return;
        }
        //2.从会话中拿到userID
        HttpSession session = req.getSession(false);
        if(session == null) {
            String html = "<h3>当前用户未登录,无权限发布博客!</h3>";
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(html);
            return;
        }
        User user = (User)session.getAttribute("user");
        if(user == null) {
            String html = "<h3>当前用户未登录,无权限发布博客!</h3>";
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(html);
            return;
        }
        //3.构造Blog对象
        Blog blog = new Blog();
        blog.setUserID(user.getUserID());
        blog.setTitle(title);
        blog.setContent(content);
        blog.setPostTime(new Timestamp(System.currentTimeMillis()));
        //blogID为自增主键,不需要手动设定
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
        //4.跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }

在BlogServlet类中重写doPost方法即可。


十、实现删除博客功能

10.1 设计分析

        在博客详情页中点击a标签删除博客,会发送一个GET请求给服务器,服务器会判定只有当你是登录状态且你是本文作者时你才有权删除。

10.2 约定前后端交互接口

10.3 实现前端代码

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

        updateDeleteURL();

10.4 实现后端代码

package api;

import model.Blog;
import model.BlogDao;
import model.User;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/blog_delete")
public class BlogDelete extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession(false);
        resp.setContentType("text/html;charset=utf8");
        if(session == null) {
            resp.setStatus(403);
            resp.getWriter().write("您当前未登录,无法删除!");
            return;
        }
        User user = (User)session.getAttribute("user");
        if(user == null) {
            resp.setStatus(403);
            resp.getWriter().write("您当前未登录,无法删除!");
            return;
        }
        // 2. 获取到 blogId
        String blogID = req.getParameter("blogID");
        if(blogID == null) {
            // 这个 blogId 参数不存在, 无法删除
            resp.setStatus(404);
            resp.getWriter().write("您当前删除的blogID有误");
            return;
        }
        //3.查询出这个blog对应的blog对象
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectOne(Integer.parseInt(blogID));
        if(blog == null) {
            resp.setStatus(404);
            resp.getWriter().write("您当前删除的博客不存在");
            return;
        }
        //4.判定当前用户是否为博客作者
        if(blog.getUserID() != user.getUserID()) {
            resp.setStatus(403);
            resp.getWriter().write("您不是当前博客的作者,无权删除");
            return;
        }
        //5.执行删除操作
        blogDao.delete(Integer.parseInt(blogID));
        //6.返回重定向操作
        resp.sendRedirect("blog_list.html");
    }
}

十一、总结

        怎么说呢,这是第一个手动+看各种指导完成的第一个项目也是编写的第一篇博客,中途也遇到了很多问题,还是花了很多心血希望能吃透这个项目的,博客结构及内容有所参考,也欢迎各位大佬多多与我交流指导,共勉~

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Pharaoh413

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

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

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

打赏作者

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

抵扣说明:

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

余额充值