【Java】Servlet实现前后端分离的博客系统

数据库

数据库的设计

系统中需要设计几张表?
用户表:用户Id 用户名 密码 是否作者
博客表:标题 内容 发布时间 作者ID(与用户表关联)

不能被数据库或编程语言中的基本数据类型表示的,都可以考虑定义成一个类或者一张表。

数据库的创建

# 创建数据库
drop database if exists blog_db;
# 排序时忽略大小写
create database blog_db character set utf8mb4 collate utf8mb4_general_ci;

#选择数据库
use blog_db;
#创建表
drop table if exists user;
create table user(
	id bigint primary key auto_increment comment '用户ID,自增',
    username varchar(50) unique not null comment '用户名',
    password varchar(50) not null comment '密码'
);

#博客表
drop table if exists blog;
create table blog(
	id bigint primary key auto_increment comment 'ID,自增',
    title varchar(1024) not null comment '标题',
    content text not null comment '内容',
    createTime datetime not null comment '发布时间',
    userId bigint not null
);
 # 初始化两个用户
insert into user values(null,'魈','123456');
insert into user values(null,'迪卢克','123456');
 #初始化两篇文章
 insert into blog values(null,'单元测试','内容',now(),1);
 insert into blog values(null,'单元测试2','内容',now(),2);

准备工作

1.创建工程
2.设置编码和其他项设置
3.引入依赖

    <dependencies>
        <!-- servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <!-- json解析 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.2</version>
        </dependency>
    </dependencies>

创建表

根据数据库中的表,创建对应的Java类

用户表

public class User {
    private Long id;
    private String username;
    //删除文章进行权限判断时添加
    //private boolean isAuthor;
    @JsonIgnore  //不参与Json序列化
    private String password;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

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

    //public void setAuthor(boolean author) {
    //    isAuthor = author;
    //}
    //public boolean isAuthor() {
    //    return isAuthor;
    //}

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

博客表

public class Blog {
    private Long id;
    private String title;
    private String content;
    //发布日期,使用当前的系统时间
    private Timestamp createTime;
    private Long userId;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

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

    public String getContent() {
        return content;
    }

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

    public Timestamp getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Timestamp creatTime) {
        this.createTime = creatTime;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", creatTime=" + createTime +
                ", userId=" + userId +
                '}';
    }
}

封装工具类

1.与数据库建立连接的方法需要用到很多次,所以将其抽出来单独封装成一个类,减少重复编码。

public class DBUtils {
    //定义数据源
    private static DataSource dataSource;

    private static final String URL = "jdbc:mysql://localhost:3306/blog_db?characterEncoding=utf8&useSSL=false";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";
    //完成初始化
    static {
        MysqlDataSource mysqlDataSource = new MysqlDataSource();
        mysqlDataSource.setURL(URL);
        mysqlDataSource.setUser(USER);
        mysqlDataSource.setPassword(PASSWORD);
        dataSource = mysqlDataSource;
    }

    //构造方法私有化
    private DBUtils (){}
    //获取数据库连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    public static void close (ResultSet resultSet, PreparedStatement statement,Connection connection){
        //依次关闭并释放资源
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if(statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

2.封装校验要传入的字符串是否为空的工具类。

public class StringUtils {
    /**
     * 校验要传入的字符串是否为空
     * @param value
     * @return true 空 <br/> false 非空
     */
    public static boolean isEmpty(String value){
        if(value == null || value.equals("")){
            return true;
        }
        return false;
    }
}

3.由于博客系统中的主要功能都需要用户登录,所以可以把登录校验抽取出来,做成一个方法。

public class UserUtils {
    public static User checkUserLoginStatus (HttpServletRequest request){
        //判断request是否为null
        if(request == null){
            return null;
        }
        //获取session
        HttpSession session = request.getSession(false);
        if(session == null){
            return null;
        }
        //获取用户信息
        User user = (User) session.getAttribute(AppConfig.USER_SESSION_KEY);
        return user;
    }
}

封装公共类

1.定义操作成功或者失败的统一返回格式。当状态码为1000时,定义为操作失败。当状态码为0时,定义操作成功。

public class AppResult<T> {
    // 状态码
    private int code;
    // 描述信息
    private String message;
    // 相关的数据
    private T data;

    // 三个参数的构造方法
    public AppResult(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    /**
     * 操作成功
     * @return
     */
    public static AppResult success() {
        return new AppResult(0, "操作成功", null);
    }

    public static AppResult success (String message) {
        return new AppResult(0, message, null);
    }

    public static <T> AppResult success (T data) {
        return new AppResult(0, "操作成功", data);
    }

    public static <T> AppResult success (String message, T data) {
        return new AppResult(0, message, data);
    }

    /**
     * 操作失败
     * @return
     */
    public static AppResult failed () {
        return new AppResult(1000, "操作失败", null);
    }

    public static AppResult failed (String message) {
        return new AppResult(1000, message, null);
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

2.定义全局的配置变量,保存用户session信息的key值。

/**
 * 定义全局的配置变量
 */
public class AppConfig {
    public static final String USER_SESSION_KEY = "USER_SESSION_KEY";
}

登录功能

约定前后端交互接口

[请求]
POST /login
Content-Type : application/x-www-form-urlencoded

username=test&password=123456

[响应]
{
  "code":0,
  "message" : "登录成功",
  "data" : null
}

服务端实现

1.用户输入用户名和密码并提交到服务器;
2.服务器接收到用户数据,并校验是否为null;
3.根据用户名去数据库中查询用户信息;
4.如果可以查到用户对象,判断数据库中查到的密码与用户输入的密码是否相等;
5.返回登录是否成功。

1.数据库访问的实现(UserDao):提供一个selectByUsername的数据库访问方法,根据用户名去数据库中查询用户信息

public class UserDao {
/**
     * 根据用户名查询用户信息
     * @param username
     * @return User对象
     */
    public User selectByUsername (String username) {
        // 非空校验
        if (StringUtils.isEmpty(username)) {
            return null;
        }

        // 进行数据库操作, 定义数据库访问的相关对象
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            // 1. 获取数据库连接
            connection = DBUtils.getConnection();
            // 2. 定义SQL语句
            String sql = "select id, username, password from user where username = ?";
            // 3. 对SQL进行预处理
            statement = connection.prepareStatement(sql);
            // 4. 设置占位符的值
            statement.setString(1, username);
            // 5. 执行SQL,获取结果
            resultSet = statement.executeQuery();
            // 6. 构造对象
            if (resultSet.next()) {
                User user = new User();
                user.setId(resultSet.getLong(1));
                user.setUsername(resultSet.getString(2));
                user.setPassword(resultSet.getString(3));
                // 返回结果
                return user;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            DBUtils.close(resultSet, statement, connection);
        }

        // 没有查询到结果,返回null
        return null;
    }
}

2.Servlet实现,用于用户登录。
此时用户要提交数据,需要重写doPost 方法。

@WebServlet("/login")
public class LoginServlet extends HttpServlet {

    // 创建DAO
    private UserDao userDao = new UserDao();

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置编码集
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json; charset=utf-8");

        // 接收参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        // 非空校验
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            // 使用统一的数据返回格式
            AppResult appResult = AppResult.failed("用户名和密码不能为空");
            String json = objectMapper.writeValueAsString(appResult);
            resp.getWriter().write(json);
            return;
        }
        // 校验通过,调用DAO
        User user = userDao.selectByUsername(username);
        if (user == null) {
            // 使用统一的数据返回格式
            AppResult appResult = AppResult.failed("用户名密码不正确");
            resp.getWriter().write(objectMapper.writeValueAsString(appResult));
            return;
        }
        // 校验密码是否正确
        if (!user.getPassword().equals(password)) {
            AppResult appResult = AppResult.failed("用户名密码不正确");
            resp.getWriter().write(objectMapper.writeValueAsString(appResult));
            return;
        }
        // 保存用户信息到session中
        HttpSession session = req.getSession(true);
        // 使用全局定义的SESSION_KEY
        session.setAttribute(AppConfig.USER_SESSION_KEY, user);
        // 使用同一个数据返回格式
        AppResult appResult = AppResult.success("登录成功");
        resp.getWriter().write(objectMapper.writeValueAsString(appResult));
        // 打印日志
        System.out.println("登录成功 : " + user.getId());
    }
}

服务端测试

通过postman测试接口是否正确。使用表单提交的方式来测试。

1.在不输入用户名的情况下进行测试,返回了用户名和密码不能为空,符合预期。 在这里插入图片描述

2.输入错误的用户名或密码,返回了用户名和密码不正确,符合预期。

在这里插入图片描述

3.输入正确的用户名和密码,返回了登录成功,测试完成。 在这里插入图片描述

客户端实现(blog_login)

在前端页面,使用ajax来提交数据,根据服务端返回的数据,在回调方法中处理前端逻辑。当获取到返回的状态码为0时,让界面从登录界面(blog_login)跳转到博客的列表页(blog_list),代表恩登录成功。当登录失败时弹窗,提示用户错误信息。

 <div class="row">
      <input type="button" class="btn_submit" id="btn_login_submit" value="提交">
 </div>

核心代码: 当form表单没有指定action时,那么数据就会提交到当前的URL。为了不执行表单的提交动作,input type 改为button,表示他是一个普通的按钮。这样就会执行给按钮绑定的事件。

<script>
    //在页面加载完成之后再执行JS
    $(function(){
        //为登陆按钮绑定事件
        $('#btn_login_submit').click(function(){
            //1.获取输入的用户名并校验
            let usernameEl = $('#username');
            if (!usernameEl.val()){    
                alert('用户名不能为空');
                //让用户名输入框获取焦点
                usernameEl.focus();
                return;
            }
            //2.获取输入密码并做校验
            let passwordEl = $('#password');
            if(!passwordEl.val()){
                alert('请输入密码');
                passwordEl.focus();
                return;
            }
            //构造发送的数据
            let postData = {
                username : usernameEl.val(),
                password : passwordEl.val()
            };
            //发送ajax
            $.ajax({
                //请求的方法
                type : 'post',
                url : 'login',
                contentType : 'application/x-www-form-urlencoded',
                data : postData,
                //成功回调
                success : function(respData){
                    if(respData.code == 0){
                        //登录成功,跳转到blog_list_html界面
                        location.assign('blog_list.html');
                    }else{
                        //登录失败
                        alert(respData.message); 
                    }
                },
                error : function(){
                    console.log('访问出现问题')
                }
            });
        });
        
    })
</script>

在这里插入图片描述

客户端测试

1.不输入用户名点击提交时,显示用户名不能为空,符合预期。 在这里插入图片描述

2.只输入用户名不输入密码,显示请输入密码,符合预期。
在这里插入图片描述

3.输入正确的用户名和密码,跳转到博客列表页。
在这里插入图片描述

获取当前用户的登录信息

服务端实现

当用户登录成功之后,已经把用户信息保存到了session中。用户发送请求到指定的URL,服务器从session中取出用户信息,转换成JSON字符串并返回。

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    private UserDao userDao = new UserDao();
    private BlogDao blogDao = new BlogDao();

    //获取用户信息
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置编码格式
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json; charset=utf-8");
        //定义统一返回的数据类型
        AppResult appResult;
        //获取session对象
        HttpSession session = req.getSession(false);
        //判断session
        if(UserUtils.checkUserLoginStatus(req) == null){
            //设置HTTP的状态码
            resp.setStatus(403);
            //错误描述
            appResult = AppResult.failed("用户没有登录,请登陆后再试");
            //表示用户没有登录
            resp.getWriter().write(objectMapper.writeValueAsString(appResult));
            //中断代码的流程
            return;
        }
        user = (User) session.getAttribute(AppConfig.USER_SESSION_KEY);
        
        jsonString = objectMapper.writeValueAsString(AppResult.success(user));
        //返回json
        resp.getWriter().write(jsonString);
    }
}

服务端测试

1.当用户没有登陆时,通过postman发送GET请求,返回了用户没有登录,请登录后再试,并将状态码置为了403,符合预期。
在这里插入图片描述2.当用户登录成功之后再发送GET请求,返回了作者信息。注意,用户的敏感信息,比如密码,在json序列化时就应当排除这个字段。只需要在给password这个属性加入注解即可。
序列化:把Java对象转换成可以在网络上传输的数据类型。比如java->json。
在这里插入图片描述
在这里插入图片描述

客户端实现

首先要为相应的标签起个名字或ID,然后引入依赖,在资源页面加载成功之后直接发送请求,获取用户信息,在回调方法里面处理响应。
核心代码:

<script>
    $(function(){
        //直接发送ajax请求,获取当前登录的用户信息
        $.ajax({
            //请求的方法
            type : 'get',
            url : 'user',
            success : function(respData){
                if(respData.code == 0){
                    //请求成功
                    let user = respData.data;
                    //设置用户名
                    $('#h3_list_username').html(user.username);
                }else{
                    //失败
                }
            },
            error : function(){
                console.log('访问出现问题.')
            },
            statusCode : {
                //可以为不同的HTTP状态码定义不同的方法
                403 : function() {
                    //打开日志并跳转到登录界面
                    console.log('用户无权访问,强制跳转到登录界面.');
                    location.assign('blog_login.html');

                }
            }
        });
    })
</script>

在这里插入图片描述

获取所有的的文章列表

约定前后端接口

[请求]
GET/blog

[响应]
{
 "code": 0,
 "message": "操作成功",
 "data": [
     {
         "blogid": 2,
         "title": "单元测试2",
         "content": "内容",
         "createTime":"xxxx-xx-xx xx:xx:xx" ,
         "userId": 2
     },
     {
         "blogid": 1,
         "title": "单元测试",
         "content": "内容",
         "createTime": "xxxx-xx-xx xx:xx:xx",
         "userId": 1
     }
 ]
}

服务端实现

根据以上的响应格式,对于DAO中的SQL,select语句查询出来的是一个集合,用Java的List对象包装结果集。
1.操作数据库(Blog Dao),获取所有文章。

public class BlogDao {
/**
     * 获取所有文章
     */
    public List<Blog> selectAll(){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            connection = DBUtils.getConnection();
            //定义SQL
            String sql = "select id, title, content, createTime, userId from blog order by  createTime desc";
            statement = connection.prepareStatement(sql);
            resultSet = statement.executeQuery();
            //遍历结果集
            List<Blog> blogList = null;
            while (resultSet.next()){
                //判断是否为空
                if(blogList == null){
                    blogList = new ArrayList<>();
                }
                //获取数据
                Blog blog= new Blog();
                blog.setId(resultSet.getLong(1));
                blog.setTitle(resultSet.getString(2));
                blog.setContent(resultSet.getString(3));
                //判断长度是否大于100
                if(blog.getContent().length() > 100){
                    //调用String对象的截取字符串的方法
                    blog.setContent(blog.getContent().substring(0,100) + "...");
                }
                blog.setCreateTime(resultSet.getTimestamp(4));
                blog.setUserId(resultSet.getLong(5));
                //把blog对象加到集合中
                blogList.add(blog);
            }
            //返回结果集
            return blogList;
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(resultSet,statement,connection);
        }
        return null;
    }
}

注意这里有一个BUG: 当写入的内容太长,会使得列表里的博客长度太长,因此此时需要只显示一部分,其余用省略号代替。

2.Servlet实现,用于获取文章列表。
重写doGet方法。

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    //用于数据库访问
    private BlogDao blogDao = new BlogDao();
    //转换json
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置编码格式
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json; charset=utf-8");
        //定义返回的统一对象
        AppResult<List<Blog>> appResult;
        //校验用户登录状态
        if(UserUtils.checkUserLoginStatus(req) == null){
            //设置HTTP的状态码
            resp.setStatus(403);
            //错误描述
            appResult = AppResult.failed("用户没有登录,请登陆后再试");
            //表示用户没有登录
            resp.getWriter().write(objectMapper.writeValueAsString(appResult));
            //中断代码的流程
            return;
        }

        //调用DAO
        List<Blog> blogs = blogDao.selectAll();
        // 为空时
        if (blogs == null) {
            blogs = new ArrayList<>();
        }
        // 序列化
        jsonString = objectMapper.writeValueAsString(AppResult.success(blogs));

        // 返回JSON
        resp.getWriter().write(jsonString);
    }
}

服务端测试

当登录之后发送GET请求,返回了博客的详情,符合预期在这里插入图片描述

客户端实现(blog_list)

核心代码:

<script>
    $(function(){
        //发送请求,获取文章列表
        $.ajax({
            type : 'get',
            url : 'blog',
            //回调
            success : function(respData){
                //根据自定义状态码处理
                if(respData.code == 0){
                    //成功时,构造文章列表
                    buildArticleList(respData.data);
                } else {
                    //出现错误
                    alert (respData.message);
                }
            },
            error : function(){
                //打印日志
                console.log('访问出现错误。');
            },
            statusCode : {
                403 : function(){
                    //未登录,强制跳转到登录界面
                    location.assign('blog_login.html');

                }
            }
        });

        // 构建文章列表
        function buildArticleList(data) {
            
            // 当文章列表为空时
            if (!data || data.length == 0) {
                let htmlString = '<h3>没有文章, 快发布一篇吧.</h3>'
                $('.container-right').html(htmlString);
                return;
            }
            data.forEach(blog => {
                let htmlString = '<div class="blog-content"> '
                    + ' <div class="blog-title"> '
                    + blog.title 
                    + ' </div> '
                    + ' <div class="blog-datetime"> '
                    + formatDate(blog.createTime)
                    + ' </div> '
                    + ' <div class="content"> '
                    + blog.content
                    + ' </div> '
                    + ' <div class="aEl"> '
                    + ' <a href="./blog_details.html?blogId=' + blog.id + '">查看全文 &gt;&gt; </a> '
                    + ' </div> '
                    + ' <hr> '
                    + ' </div> ';
                // 追加到列表区域
                $('.container-right').append(htmlString);            
            });          
        }
</script>

注意:这里需要解决日期的显示问题,需要将日期格式化。

function formatDate(time) {
    var date = new Date(time);

    var year = date.getFullYear(),
        month = date.getMonth() + 1,//月份是从0开始的
        day = date.getDate(),
        hour = date.getHours(),
        min = date.getMinutes(),
        sec = date.getSeconds();
    var newTime = year + '-' +
        (month < 10 ? '0' + month : month) + '-' +
        (day < 10 ? '0' + day : day) + ' ' +
        (hour < 10 ? '0' + hour : hour) + ':' +
        (min < 10 ? '0' + min : min) + ':' +
        (sec < 10 ? '0' + sec : sec);

    return newTime;
}

客户端测试

在这里插入图片描述

查看文章详情

点击查看全文的按钮会跳转到blog_details页面。在这个页面里需要查询到两个信息,一个是指定的文章信息,一个是指定文章的作者信息。

服务端实现

1.根据文章Id去查询文章详情(Blog Dao),要去查询数据库。

public class BlogDao {
/**
     * 根据博客Id查询博客信息
     * @param id 博客Id
     * @return 博客记录
     */

    public Blog selectById(Long id){
        //非空校验
        if(id == null || id <= 0){
            return null;
        }
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            connection = DBUtils.getConnection();
            //定义SQL
            String sql = "select id, title, content, createTime, userId from blog where id = ?";
            statement = connection.prepareStatement(sql);
            //替换占位符
            statement.setLong(1,id);
            resultSet = statement.executeQuery();
            //遍历结果集

            if (resultSet.next()){
                //获取数据
                Blog blog= new Blog();
                blog.setId(resultSet.getLong(1));
                blog.setTitle(resultSet.getString(2));
                blog.setContent(resultSet.getString(3));
                blog.setCreateTime(resultSet.getTimestamp(4));
                blog.setUserId(resultSet.getLong(5));
                //返回结果
                return blog;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(resultSet,statement,connection);
        }
        return null;
    }
}

2.根据作者Id查询作者的信息(User Dao),也要去查询数据库。

public class UserDao {
/**
     * 根据用户ID查询用户信息
     * @param id 用户Id
     * @return 对应的用户记录
     */
    public User selectById (Long id){
        // 非空校验
        if (id == null || id <= 0) {
            return null;
        }
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet= null;

        try {
            connection = DBUtils.getConnection();
            //定义SQL
            String sql = "select id, username, password from user where id = ?";
            statement = connection.prepareStatement(sql);
            statement.setLong(1,id);
            //获取结果集
            resultSet= statement.executeQuery();
            //处理结果集
            if(resultSet.next()){
                User user = new User();
                user.setId(resultSet.getLong(1));
                user.setUsername(resultSet.getString(2));
                user.setPassword(resultSet.getString(3));
                //返回结果
                return user;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtils.close(resultSet,statement,connection);
        }
        return null;
    }
}

3.Servlet实现查看文章的详情
这里可以有两种实现方式,第一种是重新创建一个新的Servlet,并接收blogId参数,返回对应的博客内容。第二种是修改现有的获取文章列表里实现过的doGet方法,并对是否传入了blogId参数进行判断,然后执行相应的逻辑。我们来实现第二种方式。

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    //用于数据库访问
    private BlogDao blogDao = new BlogDao();
    //转换json
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置编码格式
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json; charset=utf-8");
        //定义返回的统一对象
        AppResult<List<Blog>> appResult;
        //校验用户登录状态
        if(UserUtils.checkUserLoginStatus(req) == null){
            //设置HTTP的状态码
            resp.setStatus(403);
            //错误描述
            appResult = AppResult.failed("用户没有登录,请登陆后再试");
            //表示用户没有登录
            resp.getWriter().write(objectMapper.writeValueAsString(appResult));
            //中断代码的流程
            return;
        }
        // 获取blogId参数
        String blogId = req.getParameter("blogId");

        // 定义返回的JSON变量
        String jsonString = null;

        // 判断参数是否为空
        if (StringUtils.isEmpty(blogId)) {
            // 没有传blogId 获取博客列表
            List<Blog> blogs = blogDao.selectAll();
            // 为空时
            if (blogs == null) {
                blogs = new ArrayList<>();
            }
            // 序列化
            jsonString = objectMapper.writeValueAsString(AppResult.success(blogs));
        } else {
            // 传入了blogId 根据blogId去查询指定的博客详情
            Blog blog = blogDao.selectById(Long.valueOf(blogId));
            // 序列化
            jsonString = objectMapper.writeValueAsString(AppResult.success(blog));
        }
        // 返回JSON
        resp.getWriter().write(jsonString);
    }
}

4.Servlet设置作者信息:先通过blogId获取到博客信息,再通过博客Blog中的userId字段,去用户表中查询出对应的用户信息。 这里也有两种方式,我们通过修改UserServlet的doGet方法来实现。

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    private UserDao userDao = new UserDao();
    private BlogDao blogDao = new BlogDao();

    //获取用户信息
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置编码格式
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json; charset=utf-8");
        //定义统一返回的数据类型
        AppResult appResult;
        //获取session对象
        HttpSession session = req.getSession(false);
        //判断session
        if(UserUtils.checkUserLoginStatus(req) == null){
            //设置HTTP的状态码
            resp.setStatus(403);
            //错误描述
            appResult = AppResult.failed("用户没有登录,请登陆后再试");
            //表示用户没有登录
            resp.getWriter().write(objectMapper.writeValueAsString(appResult));
            //中断代码的流程
            return;
        }
        // 获取传的参数blogId
        String blogId = req.getParameter("blogId");
        // 返回的JSON
        String jsonString = null;
        User user = null;
        // 不传blogId时从session中获取当前登录的用户信息
        if (StringUtils.isEmpty(blogId)) {
            // 获取当前登录的用户
            user = (User) session.getAttribute(AppConfig.USER_SESSION_KEY);
            // 返回JSON
            jsonString = objectMapper.writeValueAsString(AppResult.success(user));
        } else {
            // 查询博客信息
            Blog blog = blogDao.selectById(Long.valueOf(blogId));
            // 查询到的结果为空
            if (blog == null) {
                resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("没有找到对应博客")));
                return;
            }
            // 根据userId查询用户,这个用户就是当前博客的作者
            user = userDao.selectById(blog.getUserId());
            // 判断User是否为空
            if (user == null) {
                resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("用户不存在")));
                return;
            }
            // 返回作者
            jsonString = objectMapper.writeValueAsString(AppResult.success(user));
        }
        // 返回结果
        resp.getWriter().write(jsonString);
    }
}

服务端测试

1.当指定博客Id并发送GET请求时,返回了博客的详细信息以及作者的Id,符合预期。 在这里插入图片描述

2.当指定博客Id并发送GET请求时,返回了发布该博客的作者信息。 在这里插入图片描述

客户端实现(blog_details)

前端可以通过JS中的location对象获取浏览器地址栏query string 中的参数列表。只保留页面的结构,并起一个ID,在ajax成功回调中,设置对应ID的内容。
核心代码

<script>
    $(function () {
        //获取当前博客的详细信息
        $.ajax({
            //请求方法
            type: 'get',
            //请求路径
            url: 'blog' + location.search,
            //回调
            success: function (respData) {
                if (respData.code == 0) {
                    let blog = respData.data;
                    //成功,为页面中的相应标签赋值
                    $('#div_details_title').html(blog.title);
                    $('#div_details_createTime').html(formatDate(blog.createTime));
                    // $('#div_details_content').html(blog.content);
                    //格式化内容
                    editormd.markdownToHTML('div_details_content', { markdown: blog.content });
                 
                } else {
                    //错误
                    alter(respData.message);
                }
            },
            error: function () {
                //打印日志
                console.log('访问出现错误');
            },
            statusCode: {
                403: function () {
                    //强制跳转到登陆界面
                    location.assign('blog_login.html');
                }
            }

        });
        //获取作者详情信息
        $.ajax({
            type: 'get',
            url: 'user' + location.search,
            //回调
            success: function (respData) {
                if (respData.code == 0) {
                    let user = respData.data;
                    //成功,设置作者名
                    $('#h_datails_username').html(user.username);

                } else {
                    //错误
                    alter(respData.message);
                }
            },
            error: function () {
                //打印日志
                console.log('访问出现错误');
            },
            statusCode: {
                403: function () {
                    //强制跳转到登陆界面
                    location.assign('blog_login.html');
                }
            }
        });
    })
</script>

注意,这里有一个bug: 通过MarkDown编辑之后详情界面仍然显示的是字符串格式而不是MarkDown格式,因此需要格式化。

客户端测试

通过点击不同的帖子,在显示不同的作者信息时,显示不同的作者信息。

发布文章

1.提供一个插入数据的方法;
2.Servlet对提交的参数做非空校验,并且从session中获取用户;
3.前端发送post请求,把用户页面上的数据提交到Servlet;
4.成功之后,前端跳转到blog_list.html界面,展示出博客列表。

约定前后端交互接口

[请求]
POST/blog
Content-type : application/x-www-form-urlencoded

title=标题&content=正文...

[响应]

"code": 0,
"message":发布成功,

服务端实现

1.提供插入数据的方法

public class BlogDao {
 /**
     * 发布新的博客
     * @param blog
     * @return 受影响的行数
     */
    public int insert(Blog blog){
        //非空校验
        if(blog == null || StringUtils.isEmpty(blog.getTitle()) || StringUtils.isEmpty(blog.getContent()) ||
            blog.getUserId() == null || blog.getCreateTime() == null){
            return 0;
        }
        //定义操作数据库过程中使用的对象
        Connection connection = null;
        PreparedStatement statement = null;

        try {
            connection = DBUtils.getConnection();
            //定义sql
            String sql = "insert into blog values(null, ?, ?, ?, ?)";
            //预处理sql
            statement  = connection.prepareStatement(sql);
            statement.setString(1,blog.getTitle());
            statement.setString(2,blog.getContent());
            statement.setTimestamp(3,blog.getCreateTime());
            statement.setLong(4,blog.getUserId());

            //执行sql
            int row = statement.executeUpdate();
            return row;

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(null,statement,connection);
        }
        return 0;
    }
}

2.Servlet实现:这里是提交参数,通过重写doPost方法实现。

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
/**
     * 发布博客
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置编码格式
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json; charset=utf-8");
        //获取当前登陆的用户信息
        User user = UserUtils.checkUserLoginStatus(req);
        //校验用户登录状态
        if(user == null){
            //设置HTTP的状态码
            resp.setStatus(403);
            //表示用户没有登录
            resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("用户没有登录,请登陆后再试")));
            //中断代码的流程
            return;
        }
        //接收用户提交的参数
        String title =  req.getParameter("title");
        String content = req.getParameter("content");
        if(StringUtils.isEmpty(title) || StringUtils.isEmpty(content)){
            resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("标签或正文不能为空")));
            return;
        }
        //构造blog对象
        Blog blog = new Blog();
        blog.setTitle(title); //标题
        blog.setContent(content);//内容
        blog.setCreateTime(new Timestamp(System.currentTimeMillis()));//发布时间
        blog.setUserId(user.getId());//当前用户就是作者

        //调用DAO,写入数据库
        int row = blogDao.insert(blog);
        if(row <= 0){
            resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("保存文章失败,请联系管理员。")));
            return;
        }
        //返回成功
        resp.getWriter().write(objectMapper.writeValueAsString((AppResult.success("发布成功"))));
    }
}

服务端测试

1.当没有填写文章的title或内容时,返回了标签或正文不能为空,符合预期。 在这里插入图片描述

2.填写完标签和内容,返回了发布成功,并在Blog表中查到了插入的数据。 在这里插入图片描述
在这里插入图片描述

客户端实现(blog_edit)

前端要集成一个编辑器插件。要定义样式和插件的JS依赖。初始化编辑器:editormd会在页面中找到指定ID的DIV,在DIV中完成编辑器的初始化。在DIV中添加一个文本域,编辑器会自动把用户输入的内容设置在到这个文本域。这个文本域本身就是一个表单标签,可以通过.val()的形式拿到当中的值。

<!-- 创建一个div,起一个id -->
<div id="blog_edit">
    <textarea id="text_edit_content" style="display : none;"></textarea>
</div>

首先给提交按钮绑定事件,在事件内先校验title和content的值,校验通过之后构造要发送的数据,发送ajax请求,然后进行回调,发布成功则跳转到博客列表页面,失败则给用户显示错误信息。
核心代码:

<!-- 初始化编辑器 -->
<script type="text/javascript">
    $(function () {
        var editor = editormd("blog_edit", {
            width: "100%",
            height: "100%",
            // theme : "dark",
            // previewTheme : "dark",
            // editorTheme : "pastel-on-dark",
            codeFold: true,
            //syncScrolling : false,
            saveHTMLToTextarea: true,    // 保存 HTML 到 Textarea
            searchReplace: true,
            //watch : false,                    // 关闭实时预览
            htmlDecode: "style,script,iframe|on*",            // 开启 HTML 标签解析,为了安全性,默认不开启    
            // toolbar  : false,             //关闭工具栏
            // previewCodeHighlight : false, // 关闭预览 HTML 的代码块高亮,默认开启
            emoji: true,
            taskList: true,
            tocm: true,         // Using [TOCM]
            tex: true,                     // 开启科学公式TeX语言支持,默认关闭
            flowChart: true,               // 开启流程图支持,默认关闭
            sequenceDiagram: true,         // 开启时序/序列图支持,默认关闭,
            placeholder: '开始创作...',     // 占位符
            path: "./editor.md/lib/"
        });

       // 绑定按钮的点击事件
       $('#submit').click(function() {
            // 获取用户输入
            let titleEl = $('#title');
            if (!titleEl.val()) {
                alert('请输入文章标题');
                titleEl.focus();
                return;
            }

            let contentEl = $('#text_edit_content');
            if (!contentEl.val()) {
                alert('请输入文章内容');
                return;
            }

            // 构选发送的数据
            let postData = {
                title : titleEl.val(),
                content: contentEl.val()
            }

            // 提交请求
            $.ajax ({
                // 请求方法
                type : 'post',
                url: 'blog',
                contentType : 'application/x-www-form-urlencoded',
                data : postData,
                // 回调
                success : function(respData) {
                    if (respData.code == 0) {
                        alert('发布成功.');
                        // 发送成功,跳转到blog_list.html
                        location.assign('blog_list.html');
                    } else {
                        alert(respData.message);
                    }
                }, 
                error : function () {
                    // 打印日志
                    console.log('访问出现错误。');
                },
                statusCode : {
                    403 : function () {
                        // 未登录,强制跳转到登录页面
                        location.assign('blog_login.html');
 
                    }
                }
            });
        });
    });
</script>

客户端测试

1.当只写博客标题而不写内容时,页面弹出请输入文章。
在这里插入图片描述

2.写完博客点击提交,弹出发布成功,然后跳转到博客列表页面。 在这里插入图片描述

删除文章

服务端实现

1.首先要定义SQL语句,然后提供相应的接口 。

public class BlogDao {
/**
     * 删除一条博客
     * @param id
     * @return
     */
    public int deleteById (Long id) {
        // 非空校验
        if (id == null || id <= 0) {
            return -1;
        }

        Connection connection = null;
        PreparedStatement statement = null;

        try {
            connection = DBUtils.getConnection();
            String sql = "delete from blog where id = ?";
            statement = connection.prepareStatement(sql);
            // 替换占位符
            statement.setLong(1, id);
            // 执行SQL
            int row = statement.executeUpdate();
            return row;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(null , statement, connection);
        }

        return -1;
    }
}

2.修改UserServlet中的doGet方法中获取用户信息的代码:
通过传入的参数blodId查询文章详情,然后获取到Blog对象中的userId的值,通过这个值和和当前登录的UserId作比较,相同则是作者,不同则不是。

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    private UserDao userDao = new UserDao();
    private BlogDao blogDao = new BlogDao();

    //获取用户信息
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置编码格式
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json; charset=utf-8");
        //定义统一返回的数据类型
        AppResult appResult;
        //获取session对象
        HttpSession session = req.getSession(false);
        //判断session
        if(UserUtils.checkUserLoginStatus(req) == null){
            //设置HTTP的状态码
            resp.setStatus(403);
            //错误描述
            appResult = AppResult.failed("用户没有登录,请登陆后再试");
            //表示用户没有登录
            resp.getWriter().write(objectMapper.writeValueAsString(appResult));
            //中断代码的流程
            return;
        }
        // 获取传的参数blogId
        String blogId = req.getParameter("blogId");
        // 返回的JSON
        String jsonString = null;
        User user = null;
        // 不传blogId时从session中获取当前登录的用户信息
        if (StringUtils.isEmpty(blogId)) {
            // 获取当前登录的用户
            user = (User) session.getAttribute(AppConfig.USER_SESSION_KEY);
            // 返回JSON
            jsonString = objectMapper.writeValueAsString(AppResult.success(user));
        } else {
            // 查询博客信息
            Blog blog = blogDao.selectById(Long.valueOf(blogId));
            // 查询到的结果为空
            if (blog == null) {
                resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("没有找到对应博客")));
                return;
            }
            // 根据userId查询用户,这个用户就是当前博客的作者
            user = userDao.selectById(blog.getUserId());
            // 判断User是否为空
            if (user == null) {
                resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("用户不存在")));
                return;
            }
            // 判断当前登录的用户是不是文章作者
            User currentUser = (User) session.getAttribute(AppConfig.USER_SESSION_KEY);
            if (currentUser.getId() == blog.getUserId()) {
                // 表示当前登录用户就是作者
                user.setAuthor(true);
            }
            // 返回作者
            jsonString = objectMapper.writeValueAsString(AppResult.success(user));
        }
        // 返回结果
        resp.getWriter().write(jsonString);
    }
}

3.Servlet实现删除博客,重写doDelete方法。这是再次判断是否是作者本人,否则无权删除文章。

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    //用于数据库访问
    private BlogDao blogDao = new BlogDao();
    //转换json
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置编码格式
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json; charset=utf-8");

        // 获取参数
        String blogId = req.getParameter("blogId");
        // 非空校验
        if (StringUtils.isEmpty(blogId)) {
            resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("没有找到文章编号")));
            return;
        }
        // 获取当前登录的用户
        // 3. 判断session
        User user = UserUtils.checkUserLoginStatus(req);
        if (user == null) {
            String jsonString = objectMapper.writeValueAsString(AppResult.failed("用户没有登录, 请登录后再试"));
            // 表示没用户没有登录
            resp.getWriter().write(jsonString);
            // 设置HTTP的状态码
            resp.setStatus(403);
            // 中断代码的流程
            return;
        }

        // 获取文章详情
        Blog blog = blogDao.selectById(Long.valueOf(blogId));
        // 校验文章是否存在
        if (blog == null ) {
            resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("文章不存在")));
            return;
        }
        // 判断是不是作者
        if (user.getId() != blog.getUserId()) {
            resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("无权删除别人的文章")));
            return;
        }

        // 调用DAO,进行删除操作
        int row = blogDao.deleteById(Long.valueOf(blogId));
        if (row != 1) {
            resp.getWriter().write(objectMapper.writeValueAsString(AppResult.failed("删除失败")));
            return;
        }
        // 删除成功
        resp.getWriter().write(objectMapper.writeValueAsString(AppResult.success("删除成功")));
    }
}

服务端测试

1.当删除别人的文章,返回了无权删除别人的文章,符合预期。 在这里插入图片描述

2.当删除自己的文章之后,返回了删除成功,此时blog表中也删除了这篇文章。
在这里插入图片描述
在这里插入图片描述

客户端实现

前端根据author是否为true来初始化一个删除按钮,并绑定事件。删除文章之后跳转到列表页面。
核心代码:

<script>
    $(function () {
        //获取作者详情信息
        $.ajax({
            type: 'get',
            url: 'user' + location.search,
            //回调
            success: function (respData) {
                if (respData.code == 0) {
                    let user = respData.data;
                    //成功,设置作者名
                    $('#h_datails_username').html(user.username);
                    //生成删除按钮
                    let htmlStr = '<a href="javascript:void(0);">删除</a>';
                    //把html转成jQuery对象并追加到页面上
                    let deleteEl = $(htmlStr);
                    //获取到要追加元素的父标签
                    $('.opts').append(deleteEl);
                    //绑定事件
                    deleteEl.click(deleteBlog);

                } else {
                    //错误
                    alter(respData.message);
                }
            },
            error: function () {
                //打印日志
                console.log('访问出现错误');
            },
            statusCode: {
                403: function () {
                    //强制跳转到登陆界面
                    location.assign('blog_login.html');
                }
            }
        });

        //删除文章事件
        function deleteBlog() {
            if (!confirm('是否删除?')) {
                return;
            }
            //发送请求
            $.ajax({
                type: 'delete',
                url: 'blog' + location.search,
                //回调
                success: function (respData) {
                    if (respData.code == 0) {
                        //跳转到列表页
                        location.assign('blog_list.html')

                    } else {
                        //错误
                        alter(respData.message);
                    }
                },
                error: function () {
                    //打印日志
                    console.log('访问出现错误');
                },
                statusCode: {
                    403: function () {
                        //强制跳转到登陆界面
                        location.assign('blog_login.html');
                    }
                }
            });

        }
    })


</script>

客户端测试

在文章详情页,点击删除文章,弹出是否删除,点击确认文章被删除,符合预期。
在这里插入图片描述

注销登录

服务端实现

在服务器把用户对应的session销毁。

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置编码格式
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json; charset=utf-8");
        //获取session
        HttpSession session = req.getSession(false);
        //判断session是否有效
        if(session != null){
            //销毁session
            session.invalidate();
        }
        resp.getWriter().write(objectMapper.writeValueAsString(AppResult.success("注销成功")));
    }
}

服务端测试

在这里插入图片描述

客户端实现

在文章列表页面生成注销按钮,当点击注销之后,页面跳转到登录界面。
核心代码:

<script>
    $(function(){
        // 为注销按钮绑定事件
        $('#a_list_logout').click(function () {
            // 发送请求
            $.ajax({
                type : 'get',
                url : 'logout',
                success : function (respData) {
                    if (respData.code == 0) {
                        // 成功,跳转到登录页面
                        location.assign('blog_login.html');

                    } else {
                        // 失败
                        alert(respData.message);
                    }
                },
                error : function() {
                    console.log('访问出现问题.');
                }
            });
        });
    })
</script>

总结
1.前端怎么发送数据;
2.后端怎么接收数据;
3.前后端交互过程中参数的解析方式 (前后端要约定好);
4.后端接收到参数之后要做哪些校验;
5.数据库如何操作;
6.后端返回什么样的结果给前端;
7.前端拿到服务器的响应之后做什么样的处理;


继续加油~

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值