JavaWeb项目 -- 博客系统

JavaWeb项目 -- 博客系统

  • 前言:页面展示
  • 一、创建 Maven 项目
  • 二、设计数据库
  • 三、封装数据库的操作
    • 3.1 创建 DBUtil 类
    • 3.2 创建 Blog 类
    • 3.3 创建 User 类
    • 3.4 创建类 BlogDao
    • 3.5 创建类 UserDao
  • 四、导入准备好的前端代码
  • 五、实现博客列表界面
    • 5.1 约定好前后端交互接口
    • 5.2 实现 BlogServlet
    • 5.3 实现前端代码
  • 六、实现博客详情界面
    • 6.1 约定好前后端交互接口
    • 6.2 实现 BlogServlet
    • 6.3 实现前端代码
  • 七、实现登录界面
    • 7.1 约定好前后端交互接口
    • 7.2 实现 LoginServlet
    • 7.3 实现前端代码
  • 八、强制用户登录 (列表页和详情页)
    • 8.1 约定好前后端交互接口
    • 8.2 实现 LoginServlet
    • 8.3 实现前端代码
  • 九、实现显示用户信息
    • 9.1 约定好前后端交互接口
      • 9.1.1 博客列表页
      • 9.1.2 博客详情页
    • 9.2 实现 UserInfoServlet
    • 9.3 实现前端代码
      • 9.3.1 博客列表页
      • 9.3.2 博客详情页
  • 十、实现注销
    • 10.1 约定好前后端交互接口
    • 10.2 实现 LogoutServlet
    • 10.3 实现前端代码
  • 十一、实现发布博客
    • 11.1 约定好前后端交互接口
    • 11.2 实现 BlogServlet
    • 11.3 实现前端代码
  • 十二、删除博客
    • 12.1 约定好前后端交互接口
    • 12.2 修改 UserInfoServlet
    • 12.4 实现前端代码
    • 12.3 实现 BlogDeleteServlet

前言:页面展示

效果图:

登录页:

博客列表页:

博客详情页:

博客编辑页:

一、创建 Maven 项目

参考博客:https://blog.csdn.net/yyhgo_/article/details/128468738?spm=1001.2014.3001.5501
按步骤创建出 Maven 项目并引入依赖创建目录
(引入 Servlet 依赖、Jackson 依赖、mysql 依赖)

pom.xml:


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>org.examplegroupId>
    <artifactId>blog_systemartifactId>
    <version>1.0-SNAPSHOTversion>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
    properties>

    <dependencies>
        
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>3.1.0version>
            <scope>providedscope>
        dependency>

        
        <dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-databindartifactId>
            <version>2.13.4version>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.49version>
        dependency>

    dependencies>
project>

二、设计数据库

本系统要存入博客文章的信息和用户的信息
1)创建博客表:

博客的 id,博客的标题,博客的内容,博客的日期,博文的博主 id

2)创建用户表:

用户 id 用户名 用户密码

在 main 目录下创建文件 db.sql:

create database if not exists yyhgo charset utf8mb4;

use yyhgo;

drop table if exists blog;
create table blog (
    blogId int primary key auto_increment,
    title varchar(1024), -- 博客标题
    content mediumtext,  -- 博客正文
    userId int,    -- 作者的 id
    postTime datetime  -- 发布时间
);

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

insert into blog values(null, "这是第一篇博客", "从今天开始, 我要认真写代码", 1, '2022-11-24 20:00:00');
insert into blog values(null, "这是第二篇博客", "从今天开始, 我要认真写代码", 1, '2022-11-25 20:00:00');
insert into blog values(null, "这是第三篇博客", "从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码", 1, '2022-11-26 20:00:00');

insert into user values(null, "张三", "123");
insert into user values(null, "李四", "12345");

同时在数据库中复制粘贴这段代码。

建议把 sql 写到一个文件中。后续如果需要把数据库往别的主机上部署,就可以直接复制粘贴建库建表语句完成 ~~

三、封装数据库的操作

把一会需要用到的数据库操作的 jdbc 代码封装起来,以备后用 ~~

创建包 model 用来存放数据库的代码!

3.1 创建 DBUtil 类

用于创建数据源、建立网络连接、释放资源。

package model;

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

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

// 通过这个类, 来封装数据库建立连接/断开连接操作
// 懒汉模式要考虑线程安全问题~~
// Servlet 程序天然就是运行在多线程环境中的. 每个请求都可能对应着一个线程(Tomcat 是通过多线程的方式来处理很多请求)
public class DBUtil {
    private volatile static 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/yyhgo?characterEncoding=utf8&useSSL=false");
                    ((MysqlDataSource)dataSource).setUser("root");
                    ((MysqlDataSource)dataSource).setPassword("");
                }
            }
        }
        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();
            }
        }
    }
}

3.2 创建 Blog 类

是实体类,代表一篇博客。

实体类:对应数据库表里的一条记录 ~~

package model;

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

    // 把这个方法魔改一下!! 在方法里面把时间戳构造成格式化时间. 以 String 的方式来返回.
    public String getPostTime() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return simpleDateFormat.format(this.postTime);
    }

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

细节:
JavaWeb项目 -- 博客系统_第1张图片

3.3 创建 User 类

是实体类,代表一个用户。

package model;

// 这个类的对象表示一个用户
public class User {
    private int userId;
    private String username;
    private String password;
    private int isYourBlog = 0;

    public int getIsYourBlog() {
        return isYourBlog;
    }

    public void setIsYourBlog(int isYourBlog) {
        this.isYourBlog = isYourBlog;
    }

    public int getUserId() {
        return userId;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

3.4 创建类 BlogDao

主要是针对博客表进行增删改查操作。

Dao:Data Access Object
用来访问数据的类。这是习惯命名方式 ~

涉及到大量的 jdbc 操作,参考博客:https://blog.csdn.net/yyhgo_/article/details/128061324?spm=1001.2014.3001.5501

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;

// 针对博客要实现的功能:
// 1. 新增博客 (博客编辑页)
// 2. 查询出博客列表 (博客列表页)
// 3. 查询出指定博客的详情 (博客详情页)
// 4. 删除指定的博客 (可以在博客详情页中加入)
public class BlogDao {
    // 下列代码都是 JDBC 操作. 代码相似性非常高的!!

    // 此处的 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, ?, ?, ?, now())";
            statement = connection.prepareStatement(sql);
            statement.setString(1, blog.getTitle());
            statement.setString(2, blog.getContent());
            statement.setInt(3, blog.getUserId());
            // 3. 执行 SQL
            int ret = statement.executeUpdate();
            if (ret == 1) {
                System.out.println("插入成功!");
            } else {
                System.out.println("插入失败!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, null);
        }
    }

    // 当前这个方法, 是给博客列表页使用的.
    // 博客列表页里面, 不需要显示博客的完整正文, 只需要有一小部分即可 (作为一个用来预览的摘要)
    public List<Blog> selectAll() {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        List<Blog> blogs = new ArrayList<>();
        try {
            // 1. 和数据库建立连接
            connection = DBUtil.getConnection();
            // 2. 构造 SQL
            // 排序:新插入的博客在上面!
            String sql = "select * from blog order by postTime desc";  
            statement = connection.prepareStatement(sql);
            // 3. 执行 SQL
            resultSet = statement.executeQuery();
            // 4. 遍历结果集合.
            while (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                String content = resultSet.getString("content");
                // 只截取前 100 个字符作为摘要. 注意! 此处的 100 是拍脑门的!!
                // 具体设置成几~~ 没关系, 只要最终的效果正确好看即可!
                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 throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return blogs;
    }

    public Blog selectOne(int blogId) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            // 1. 和数据库建立连接
            connection = DBUtil.getConnection();
            // 2. 构造 SQL
            String sql = "select * from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogId);
            // 3. 执行 SQL
            resultSet = statement.executeQuery();
            // 4. 遍历结果集. 由于是按照 blogId 来查询. blogId 是自增主键, 不能重复.
            //    此处的查询结果不可能是多条记录. 只能是 1 条或者 0 条.
            if (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserId(resultSet.getInt("userId"));
                return blog;
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            // 5. 关闭资源
            DBUtil.close(connection, statement, resultSet);
        }

        return null;
    }

    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);
            int ret = statement.executeUpdate();
            if (ret == 1) {
                System.out.println("删除成功!");
            } else {
                System.out.println("删除失败!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, null);
        }
    }
}

请关注代码注释 ~~

3.5 创建类 UserDao

主要是针对用户表进行增删改查操作。

package model;

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

// 关于 User 表, 涉及到的操作
// 1. 根据用户名来查询用户信息(实现登录的时候)
// 2. 根据用户的 id 来查询用户信息 (获取文章的时候, 根据博客的 userId 拿到作者的信息)
public class UserDao {
    public User selectByName(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 throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }

    public User selectById(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 throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }
}

四、导入准备好的前端代码

JavaWeb项目 -- 博客系统_第2张图片

只要把前面的静态页面拷贝到项目的 webapp 目录中,就可以启动 tomcat 通过浏览器访问了 ~~

五、实现博客列表界面

5.1 约定好前后端交互接口

JavaWeb项目 -- 博客系统_第3张图片
JavaWeb项目 -- 博客系统_第4张图片

5.2 实现 BlogServlet

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf8");
        BlogDao blogDao = new BlogDao();
        List<Blog> blogs = blogDao.selectAll();
        String jsonString = objectMapper.writeValueAsString(blogs);
        resp.getWriter().write(jsonString);
    }
}

5.3 实现前端代码

在 blog_list.html 中 实现 ajax
注意:引入依赖 (jquery)

参考代码:

        
        <div class="container-right">
            
            
            
        div>

实现:

    <script src="https://code.jquery.com/jquery-3.6.1.min.js">script>
    <script src="js/app.js">script>
    <script>
        // 通过这个函数, 来从服务器获取到博客列表的数据
        function getBlogs() {
            $.ajax({
                type: 'get',
                url: 'blog',
                success: function(body) {
                    // 根据返回的 json 数据, 来构造出页面内容, div.blog
                    // jquery ajax 会自动的把响应得到的 body 按照响应的 Content-Type 进行转换格式. 
                    // 如果响应的 Content-Type 是 json, 此时就会自动把 body 转成 js 的对象
                    let container = 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;
                        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);
                        // 创建查看全文按钮
                        let a = document.createElement('a');
                        a.innerHTML = '查看全文 >>';
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        blogDiv.appendChild(a);
                        // 把 blogDiv 加入外层元素
                        container.appendChild(blogDiv);
                    }
                }
            });
        }

        // 获取博客列表
        getBlogs();
    script>

六、实现博客详情界面

在这里插入图片描述

点击"查看全文",先让页面跳转到博客详情页 (blog_detail.html)。跳转的过程中,给 URL 带上当前要获取的博客 id。在 blog_detail.html 页面中,再通过 ajax 从服务器获取博客详情内容 ~~

JavaWeb项目 -- 博客系统_第5张图片
前面设计跳转时,已经在 query string 里设置了 博客 id !!!

6.1 约定好前后端交互接口

JavaWeb项目 -- 博客系统_第6张图片

6.2 实现 BlogServlet

约定请求的路径是 /blog,代码中当前已经有了一个 /blog 的 Servlet 了,就在之前的 Servlet 基础上做出修改即可!
基于同一个 Servlet,同一个 doGet 方法,让它既可以处理获取博客列表,又能获取博客详情:

  • 获取博客列表,请求是 /blog
  • 获取博客详情,请求是 /blog?blogld=1

在 doGet 里面就可以根据这个参数 是否存在 来决定 是返回博客列表还是博客详情!!!

@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");
        String blogId = req.getParameter("blogId");
        BlogDao blogDao = new BlogDao();
        if (blogId == null) {
            // 不存在 blogId 这个参数, 这就是获取博客列表.
            List<Blog> blogs = blogDao.selectAll();
            String jsonString = objectMapper.writeValueAsString(blogs);
            resp.getWriter().write(jsonString);
        } else {
            // 存在 blogId 参数, 就是获取博客详情.
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            String jsonString = objectMapper.writeValueAsString(blog);
            resp.getWriter().write(jsonString);
        }
    }
}

6.3 实现前端代码

参考代码:

        
        <div class="container-right">
            
            <div class="blog-content">
                
                <h3>我的第一篇博客h3>
                
                <div class="date">2022-09-21 12:00:00div>
                
                <div id="content" style="opacity: 80%">
                    
                div>
            div>
        div>

实现:

    <script src="https://code.jquery.com/jquery-3.6.1.min.js">script>
    <script src="js/app.js">script>
    <script>
        function getBlog() {
            $.ajax({
                type: 'get',
                url: 'blog' + location.search,
                success: function(body) {
                    // body 就是得到的一个 json 格式的 博客数据. 由于响应的 Content-Type 是 application/json
                    // 因此 jquery 就会自动把响应数据转成 js 对象. 
                    let h3 = document.querySelector('.blog-content h3');
                    h3.innerHTML = body.title;
                    let divDate = document.querySelector('.blog-content .date');
                    divDate.innerHTML = body.postTime;
                    // 刚才这里是直接把正文内容设置到 innerHTML 中了, 没有渲染的过程的. 
                    // let divContent = document.querySelector('#content');
                    // divContent.innerHTML = body.content;
                    // 靠谱的做法, 应该是先使用 editor.md 进行渲染. 
                    // [重要] 这个方法就是 editor.md 提供的一个方法把 markdown 字符串转成格式化的效果. 
                    // 第一个参数是一个 div 的 id, 表示要把渲染结果放到哪个 div 中. 
                    // 第二个参数是一个 js 对象, 把正文内容传入即可. (还支持很多别的参数属性, 此处暂时不涉及)
                    editormd.markdownToHTML('content', {
                        markdown: body.content
                    });
                }
            });
        }

        // 在页面加载之后, 要调用代码. 
        getBlog();
	script>

细节:

1)url: 'blog' + location.search,
在 js 中,可以通过这样的方式得到当前页面的 query string,即 ?blogId=1
(location 是 js 中特殊的全局变量)

2)运行后在页面控制台会有这样的警告:
在这里插入图片描述
这里的 404 没事 ~ (favicon 是页面的图标,当前没这个图标就报错了)

类似于 b站 的:
在这里插入图片描述

3)博客编辑页是一个 markdown 格式的数据。就希望当获取到博客详情的时候,也能按照 markdown 来渲染!
1.在页面中引入 editor.md:
JavaWeb项目 -- 博客系统_第7张图片
2.通过 editormd.markdownToHTML('content', { markdown: body.content });渲染。
(关注代码注释)

七、实现登录界面

7.1 约定好前后端交互接口

JavaWeb项目 -- 博客系统_第8张图片

7.2 实现 LoginServlet

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 告诉服务器如何解析请求.
        req.setCharacterEncoding("utf8");
        // resp.setCharacterEncoding("utf8");
        // 1. 从请求中拿到用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null || password == null || username.equals("") || password.equals("")) {
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您当前的用户名或者密码为空!");
            return;
        }

        // 加上一个打印, 看看服务器读取的用户名密码到底是什么鬼!!
        System.out.println("username=" + username + ", password=" + password);

        // 2. 查询数据库, 看密码是否匹配
        UserDao userDao = new UserDao();
        User user = userDao.selectByName(username);
        if (user == null) {
            // 用户不存在.
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您的用户名或者密码错误!");
            return;
        }
        if (!user.getPassword().equals(password)) {
            // 密码错误!
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您的用户名或者密码错误!");
            return;
        }
        // 3. 登录成功之后, 构造会话.
        HttpSession session = req.getSession(true);
        // 把刚才获取到的 user 对象给存到 session 里, 方便后续使用.
        session.setAttribute("user", user);
        // 4. 返回一个重定向报文, 跳转到博客列表页.
        resp.sendRedirect("blog_list.html");
    }

7.3 实现前端代码

form 表单是可以搭配 302 进行跳转的!
如果是使用 ajax,其响应是不能处理 302,不会有跳转的 (需要使用别的方法来跳转)!

修改代码,放到 form 表单里面去!!!

    
    <div class="login-container">
        <div class="login-dialog">
            <form action="login" method="post">
                <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 type="submit" value="提交" id="submit">
                div>
            form>
        div>
    div>

JavaWeb项目 -- 博客系统_第9张图片

八、强制用户登录 (列表页和详情页)

在博客列表页 / 详情页里,访问页面的时候验证用户的登录状态。

  • 如果用户是已经登录了,自然允许访问;
  • 如果用户未登录,则强制跳转到博客登录页面!

我们登录之后,服务器会在内存中保存 session 对象 (维护了用户的信息)
一旦服务器重启,此时内存中的数据就没了,自然登录状态就丢失了!

8.1 约定好前后端交互接口

当用户进入博客列表 / 博客详情页的时候,先发起一个单独的 ajax 请求,通过这个请求来验证用户登录状态!
服务器这边根据登录 / 未登录,返回不同的结果。客户端就可以根据返回的响应,来决定是否要强制跳转到登录页!!!

一个页面是可以发送多个 ajax 的,想发几个都行 ~~

JavaWeb项目 -- 博客系统_第10张图片

8.2 实现 LoginServlet

在原有 LoginServlet 的基础上添加代码!

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 告诉服务器如何解析请求.
        req.setCharacterEncoding("utf8");
        // resp.setCharacterEncoding("utf8");
        // 1. 从请求中拿到用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null || password == null || username.equals("") || password.equals("")) {
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您当前的用户名或者密码为空!");
            return;
        }

        // 加上一个打印, 看看服务器读取的用户名密码到底是什么鬼!!
        System.out.println("username=" + username + ", password=" + password);

        // 2. 查询数据库, 看密码是否匹配
        UserDao userDao = new UserDao();
        User user = userDao.selectByName(username);
        if (user == null) {
            // 用户不存在.
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您的用户名或者密码错误!");
            return;
        }
        if (!user.getPassword().equals(password)) {
            // 密码错误!
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您的用户名或者密码错误!");
            return;
        }
        // 3. 登录成功之后, 构造会话.
        HttpSession session = req.getSession(true);
        // 把刚才获取到的 user 对象给存到 session 里, 方便后续使用.
        session.setAttribute("user", user);
        // 4. 返回一个重定向报文, 跳转到博客列表页.
        resp.sendRedirect("blog_list.html");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 验证登录状态.
        // 直接去取登录状态. 看能不能取到.
        HttpSession session = req.getSession(false);
        if (session == null) {
            // 未登录, 直接设置状态码 403 即可. body 都不需要~
            resp.setStatus(403);
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            // 未登录, 直接设置状态码 403 即可. body 都不需要~
            resp.setStatus(403);
            return;
        }
        // 已登录!
        resp.setStatus(200);
    }
}

8.3 实现前端代码

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

location.assign()是前端代码实现页面跳转的方式!!!

博客列表页和详情页都需要去完成这样一件事,所以把代码写进 app.js 文件,放进 js 目录里 ~~
在 blog_list.html 和 blog_detail.html 文件中引入 app.js:

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

并且调用函数!:

        checkLogin();

ajax 是异步的:发起请求的主体,不负责接受结果,而是由别人主动推送过来!
所以多个 ajax 之间执行间隔极短,就可以近似看作是同时发送!
所以不必关注多个 ajax 之间的先后顺序!!!

九、实现显示用户信息

JavaWeb项目 -- 博客系统_第11张图片
在博客列表页,和博客详情页都有用户信息。
这里的信息不要写死,而是能够从服务器动态获取!

  • 如果是博客列表页,此处显示当前登录的用户信息
  • 如果是博客详情页,此处显示文章的作者信息

图片的话一般是保存在一个单独的位置,在数据库里存图片的路径 ~~

9.1 约定好前后端交互接口

9.1.1 博客列表页

JavaWeb项目 -- 博客系统_第12张图片

9.1.2 博客详情页

JavaWeb项目 -- 博客系统_第13张图片

9.2 实现 UserInfoServlet

@WebServlet("/userInfo")
public class UserInfoServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String blogId = req.getParameter("blogId");
        if (blogId == null) {
            // 请求来自博客列表页, 直接返回登录的用户信息.
	        HttpSession session = req.getSession(false);
	        if (session == null) {
	            resp.setStatus(403);
	            resp.setContentType("text/html;charset=utf8");
	            resp.getWriter().write("当前未登录!");
	            return;
	        }
	        User user = (User) session.getAttribute("user");
	        if (user == null) {
	            resp.setStatus(403);
	            resp.setContentType("text/html;charset=utf8");
	            resp.getWriter().write("当前未登录!");
	            return;
	        }
            user.setPassword("");
            resp.setContentType("application/json; charset=utf8");
            String jsonString = objectMapper.writeValueAsString(user);
            resp.getWriter().write(jsonString);
        } else {
            // 请求来自博客详情页, 返回文章作者信息.
            BlogDao blogDao = new BlogDao();
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            if (blog == null) {
                resp.setStatus(403);
                resp.setContentType("text/html;charset=utf8");
                resp.getWriter().write("当前 blogId 有误!");
                return;
            }
            UserDao userDao = new UserDao();
            User author = userDao.selectById(blog.getUserId());
            if (author == null) {
                resp.setStatus(403);
                resp.setContentType("text/html;charset=utf8");
                resp.getWriter().write("当前博客对应的作者没有找到!");
                return;
            }
            author.setPassword("");    // 为了安全,隐藏密码~~
            resp.setContentType("application/json; charset=utf8");
            String jsonString = objectMapper.writeValueAsString(author);
            resp.getWriter().write(jsonString);
        }
    }
}

9.3 实现前端代码

参考代码:

        
        <div class="container-left">
            
            <div class="card">
                <img src="image/doge.jpg" alt="">
                <h3>小豪h3>
                <a href="#">github 地址a>
                <div class="counter">
                    <span>文章span>
                    <span>分类span>
                div>
                <div class="counter">
                    <span>2span>
                    <span>3span>
                div>
            div>
        div>

9.3.1 博客列表页

实现:

    <script>
        // 获取当前用户的信息
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'userInfo',
                success: function (body) {
                    // 让后端在查询失败的时候, 不要返回 200 , 而是返回 403 . 
                    // 避免在前端触发 success 分支. 
                    let h3 = document.querySelector('.card h3');
                    h3.innerHTML = body.username;
                }
            });
        }
        
        getUserInfo();
    script>

9.3.2 博客详情页

实现:

    <script>
        // 获取用户信息
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'userInfo' + location.search,
                success: function (body) {
                    // 让后端在查询失败的时候, 不要返回 200 , 而是返回 403 . 
                    // 避免在前端触发 success 分支. 
                    let h3 = document.querySelector('.card h3');
                    h3.innerHTML = body.username;
                }
            });
        }

	    getUserInfo();
    script>

十、实现注销

这个功能要做的工作:

  1. 清除当前用户的登录状态 (删除会话)
  2. 跳转到博客登录页

此处的注销就是退出登录。

10.1 约定好前后端交互接口

点击注销的时候,发送一个 GET 请求,并且跳转到博客登录页。

直接借助 a 标签 来实现!
a 标签 点击之后正好是发送了一个 GET 请求,也正好能触发页面跳转 ~~

JavaWeb项目 -- 博客系统_第14张图片

10.2 实现 LogoutServlet

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 注销要做的是删除用户的会话信息. 因此就得先确认用户有没有会话.
        // req 对象没有直接提供一个 删除会话 的操作~~
        // 删除会话有个办法, 就是把过期时间设置成 0. 比较麻烦.
        // 更简单的办法, 虽然保留会话对象, 但是把会话里的 user 给删了.
        HttpSession session = req.getSession(false);
        if (session == null) {
            // resp.setStatus(403);
            resp.sendRedirect("login.html");
            return;
        }
        session.removeAttribute("user");
        resp.sendRedirect("login.html");
    }
}

注销要做的是删除用户的会话信息,因此先确认用户有没有会话。
req 对象没有直接提供一个 删除会话 的操作!删除会话有个办法,就是把过期时间设置成 0,但比较麻烦。
更简单的办法:虽然保留会话对象,但是把会话里的 user 给删了!
JavaWeb项目 -- 博客系统_第15张图片

10.3 实现前端代码

直接通过 a 标签 来进行实现,不需要加上任何的 ajax 请求!

    
    <div class="nav">
        <img src="image/logo2.jpg" alt="">
        <span class="title">我的博客系统span>
        
        <span class="spacer">span>
        
        <a href="blog_list.html">主页a>
        <a href="blog_edit.html">写博客a>
        <a href="logout">注销a>  
    div>

博客列表页、详情页、编辑页都进行修改!!!

十一、实现发布博客

11.1 约定好前后端交互接口

JavaWeb项目 -- 博客系统_第16张图片

11.2 实现 BlogServlet

把请求中的博客数据拿到,同时写入数据库!

前面实现博客列表界面时,已经使用过 blog 路径,有了 BlogServlet 这个类 (doGet 方法)。
在这个类里添加 doPost 方法即可 ~~

@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");
        String blogId = req.getParameter("blogId");
        BlogDao blogDao = new BlogDao();
        if (blogId == null) {
            // 不存在 blogId 这个参数, 这就是获取博客列表.
            List<Blog> blogs = blogDao.selectAll();
            String jsonString = objectMapper.writeValueAsString(blogs);
            resp.getWriter().write(jsonString);
        } else {
            // 存在 blogId 参数, 就是获取博客详情.
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            String jsonString = objectMapper.writeValueAsString(blog);
            resp.getWriter().write(jsonString);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        // 1. 获取到用户的登录状态.
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setStatus(403);
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            resp.setStatus(403);
            return;
        }
        // 2. 读取请求的内容
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        if (title == null || title.equals("") || content == null || content.equals("")) {
            resp.setStatus(400);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("请求中的标题或正文不完整");
            return;
        }
        // 3. 构造 Blog 对象, 并插入到数据库中.
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        // 博客的作者. 作者是谁? 当前谁登录, 作者就是谁!!
        blog.setUserId(user.getUserId());
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
        // 4. 插入成功之后, 跳转到博客列表页.
        resp.sendRedirect("blog_list.html");
    }
}

11.3 实现前端代码

这个地方可以使用 ajax,也可以使用 form 表单。
此处就使用 form 表单 即可 ~~

    <div class="blog-edit-container">
        <form action="blog" method="post" style="height: 100%">
            
            <div class="title">
                <input type="text" id="title" placeholder="请输入文章标题" name="title">
                <input type="submit" id="submit" value="发布文章">
            div>
            
            <div id="editor">
                
                
                <textarea name="content" style="display: none;" >textarea>
            div>
        form>
    div>

初始化编辑器代码也要稍作修改:

    <script>
        // 初始化编辑器
        var editor = editormd("editor", {
            // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. 
            width: "100%",
            // 设定编辑器高度
            height: "calc(100% - 50px)",
            // 编辑器中的初始内容
            markdown: "# 在这里写下一篇博客",
            // 指定 editor.md 依赖的插件路径
            path: "editor.md/lib/",
            // 加上这个属性, 效果就是把编辑器里的内容给自动保存到 textarea 里. 
            saveHTMLToTextArea: true,    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        });
    script>

JavaWeb项目 -- 博客系统_第17张图片

有时页面会触发缓存,这时候要强制刷新!Ctrl + F5

记得修改 form 元素的属性 (设置高度什么的 ~)

十二、删除博客

删除按钮可以放在列表页,也可以放在详情页 ~~
这里我们放在详情页。

  • 如果当前博客作者是登录用户自己,则在详情页导航栏中显示这个删除按钮;
  • 如果当前博客作者不是登录的用户,则不显示删除按钮!

所以此处要先根据当前用户的状态,来判断是否显示删除按钮!!!

12.1 约定好前后端交互接口

12.2 修改 UserInfoServlet

需要修改一下 UserInfoServlet 的 doGet 方法,来判断一下登录用户与博客作者 (从数据库中查) 的关系!

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

        // 先获取一下当前是哪个用户登录的
        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;
        }

        if (blogId == null) {
            // 请求来自博客列表页, 直接返回登录的用户信息.
            user.setPassword("");
            resp.setContentType("application/json; charset=utf8");
            String jsonString = objectMapper.writeValueAsString(user);
            resp.getWriter().write(jsonString);
        } else {
            // 请求来自博客详情页, 返回文章作者信息.
            BlogDao blogDao = new BlogDao();
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            if (blog == null) {
                resp.setStatus(403);
                resp.setContentType("text/html;charset=utf8");
                resp.getWriter().write("当前 blogId 有误!");
                return;
            }
            UserDao userDao = new UserDao();
            // author 是博客的作者
            User author = userDao.selectById(blog.getUserId());
            if (author == null) {
                resp.setStatus(403);
                resp.setContentType("text/html;charset=utf8");
                resp.getWriter().write("当前博客对应的作者没有找到!");
                return;
            }
            author.setPassword("");    // 为了安全,隐藏密码~~
            if (user.getUserId() == author.getUserId()) {
                author.setIsYourBlog(1);
            } else {
                author.setIsYourBlog(0);
            }
            resp.setContentType("application/json; charset=utf8");
            String jsonString = objectMapper.writeValueAsString(author);
            resp.getWriter().write(jsonString);
        }
    }
}

User 类中有一个 isYourBlog 属性 ~~
(使用 int 类型!若使用 boolean 类型可能会与属性名中的 “is” 产生一些冲突,会出问题)

12.4 实现前端代码

在这里复用一下这个接口:JavaWeb项目 -- 博客系统_第18张图片
在响应中返回一个 当前博客是否是登录用户自己的文章,根据这个结果来判断是否显示删除按钮 ~~

参考代码:

    
    <div class="nav">
        <img src="image/logo2.jpg" alt="">
        <span class="title">我的博客系统span>
        
        <span class="spacer">span>
        
        <a href="blog_list.html">主页a>
        <a href="blog_edit.html">写博客a>
        <a href="logout">注销a>
        
        
    div> 

实现:

    <script>
        // 获取用户信息
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'userInfo' + location.search,
                success: function (body) {
                    // 让后端在查询失败的时候, 不要返回 200 , 而是返回 403 . 
                    // 避免在前端触发 success 分支. 
                    let h3 = document.querySelector('.card h3');
                    h3.innerHTML = body.username;

                    if (body.isYourBlog) {
                        // 在导航栏中加个按钮, 用来删除文章. 
                        let deleteA = document.createElement('a');
                        // location.search 就是当前页面 url 的 query string, 也就是
                        // ?blogId=1 这样的结果. 
                        deleteA.href = 'blogDelete' + location.search;
                        deleteA.innerHTML = '删除';

                        let navDiv = document.querySelector('.nav');
                        navDiv.appendChild(deleteA);
                    }
                }
            });
        }
        
        getUserInfo();
    script>

JavaScript 是弱类型。number 和 boolean 是可以相互转换的!不像 Java 要求的严格 ~
因此使用if (body.isYourBlog) {判断完全可以,因为 非0 就为 true !!!

12.3 实现 BlogDeleteServlet

实现具体的删除动作。

前端代码已经完成了,成功运行后会添加一个 a 标签 (删除)!
点击 “删除” 按钮,即发送一个 GET 请求 (a 标签跳转) ~~
处理这个请求!!!
JavaWeb项目 -- 博客系统_第19张图片

@WebServlet("/blogDelete")
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);
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            resp.setStatus(403);
            return;
        }
        // 2. 获取到 blogId
        String blogId = req.getParameter("blogId");
        if (blogId == null || blogId.equals("")) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("要删除的文章不存在!!");
            return;
        }
        // 3. 删除数据库中的数据
        BlogDao blogDao = new BlogDao();
        blogDao.delete(Integer.parseInt(blogId));
        // 4. 跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }
}

你可能感兴趣的:(网络与Web开发,servlet,java,tomcat,网络,服务器)