基于Servlet+JDBC实现的基础博客系统>>系列3 -- Servlet后端服务器搭建

news2024/11/24 11:03:19

目录

前言

1. 前期准备

2. Model层

2.1 数据库的设计

2.2 数据库表对应的实体类实现

User类

Blog类

2.3 JDBC 工具类实现

2.4 UserDao 的实现

2.5 BlogDao 的实现

3. Controller 层实现

3.1 博客列表页

3.1.1 约定前后端交互接口

3.1.2 编写后端代码 

3.1.3 编写前端代码

3.2 博客详情页

3.2.1 约定前后端交互接口

3.2.2 编写后端代码

3.2.3 编写前端代码

3.3 登录功能        

3.3.1 约定前后端交互接口

 3.3.2 编写后端代码

3.3.3 编写前端代码

3.4 检查用户登录状态

3.4.1 约定前后端交互接口

3.4.2 编写后端代码

3.4.3 编写前端代码

3.5 显示用户信息

3.5.1 约定前后端交互接口 

3.5.2 编写后端代码

3.5.3 编写前端代码 

3.6 退出登录功能(注销)

3.7 发布博客功能

3.7.2 编写后端代码

3.7.3 编写前端代码

3.8 删除博客功能


前言

        上一篇文章:基于Servlet+JDBC实现的基础博客系统>>系列2 -- 前端基础页面 对整个博客系统进行了简要的构建,接下来将博客系统进行实现前后端的交互. 全文项目的代码链接已经上传到git,欢迎大家访问,也希望得到大家的指点.谢谢.

1. 前期准备

 1. 了解MVC模式

        MVC(Model View Controller)是一种软件设计的框架模式,它采用模型(Model)-视图(View)-控制器(controller)的方法把业务逻辑、数据与界面显示分离。把众多的业务逻辑聚集到一个部件里面,当然这种比较官方的解释是不能让我们足够清晰的理解什么是MVC的。用通俗的话来讲,MVC的理念就是把数据处理、数据展示(界面)和程序/用户的交互三者分离开的一种编程模式。

2. 创建Maven项目,引入相关依赖 

下面是pom.xml的内容,主要是引入了Servlet Jackson MySQL的环境依赖

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>blogSystem</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

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

</project>

3. 整个项目使用idea中的插件SmartTomCat ,方便进行调试.

  

2. Model层

2.1 数据库的设计

 在博客系统中,主要包含登录功能、注销功能、发布博客、删除博客、博客展示的功能。涉及到的实体即博客和用户。数据库表设计如下,具体见代码注释:

-- 一般对于建表的 sql 都会单独搞个 .sql 文件来保存.
-- 后续程序可能需要在不同的主机上部署. 部署的时候就需要在对应的主机上把数据库也给创建好.
-- 把建表 sql 保存好, 方便在不同的机器上进行建库建表

-- 1.创建数据数据库
create database if not exists java107_blog_system character set utf8mb4;

use java107_blog_system;

-- 2. 创建各种表
drop table if exists blog;
-- 2.1 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;
-- 2.2 user表
create table user(
    userId int primary key auto_increment,
    username varchar(50) unique,
    password varchar(50)
);

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

insert into blog values(null, '我的第一篇博客', '这是博客正文', 1, '2023-06-04 12:00:00');
insert into blog values(null, '我的第二篇博客', '这是博客正文', 1, '2023-06-05 12:00:00');
insert into blog values(null, '我的第二篇博客', '如果你想用Java的SimpleDateFormat类来格式化时间,你可以使用以下代码:import java.text.SimpleDateFormat;import java.util.Date;public class Main{public static void main(String[] args){Date date = new Date();String strDateFormat = "yyyy-MM-dd HH:mm:ss";SimpleDateFormat sdf = new SimpleDateFormat(strDateFormat);System.out.println(sdf.format(date));}}', 1, '2023-06-05 12:00:00');

2.2 数据库表对应的实体类实现

User类

每个 user 对象,对应 user 表的一条记录。

package model;

/**
 * Created with IntelliJ IDEA.
 * Description:用户user表的实体
 * User: YAO
 * Date: 2023-06-21
 * Time: 10:47
 */
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;
    }
}

Blog类

每个 blog 对象,对应 blog 表的一条记录。

package model;

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

/**
 * Created with IntelliJ IDEA.
 * Description:博客blog表的实体
 * 一个blog对象的实例就代表表中的一条记录
 * User: YAO
 * Date: 2023-06-21
 * Time: 10:46
 */
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 Timestamp getPostTimeStamp() {
        return postTime;
    }
    public String getPostTime() {
        // TimeStamp 返回的是时间戳
        // 这里要将时间戳进行格式化 使用SimpleDataFormat
        String strDateFormat = "yyyy-MM-dd HH:mm:ss";
        SimpleDateFormat sdf = new SimpleDateFormat(strDateFormat);
         return sdf.format(postTime);
    }

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

 特别关注:针对时间戳在后端进行格式化字符串处理,便于前端的展示。

2.3 JDBC 工具类实现

 DBUtil 封装了用于获取数据库连接和关闭数据库连接资源的方法,便于 各个实体的Dao 使用,降低代码冗余度。

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;

/**
 * Created with IntelliJ IDEA.
 * Description:通过封装处理数据库的连接操作
 * User: YAO
 * Date: 2023-06-21
 * Time: 10:29
 */
public class DBUtil {
    // 这个类要提供DataSource
    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/java107_blog_system?characterEncoding=utf8&useSSL=false");
                    ((MysqlDataSource)dataSource).setUser("root");
                    ((MysqlDataSource)dataSource).setPassword("111111");
                }


            }
        }
        return dataSource;
    }

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

    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) throws SQLException {
        if (resultSet != null){
            resultSet.close();
        }

        if (statement!=null){
            statement.close();
        }
        if (connection!=null){
            connection.close();
        }
    }
}

2.4 UserDao 的实现

DAO: Data Access Object 就是通过这个对象进行访问数据

主要实现方法:

1. selectUserById

2. selectUserById

package model;

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

/**
 * Created with IntelliJ IDEA.
 * Description:user表的增删改查
 * DAO : Data Access Object  通过这样的对象进行访问数据
 * User: YAO
 * Date: 2023-06-21
 * Time: 10:52
 */
public class UserDao {
    public User selectUserById(int userId) throws SQLException {
        // 根据id查询用户对象
        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) throws SQLException {
        // 根据name查询用户对象
        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;
    }
}

2.5 BlogDao 的实现

 该类封装了有关 blog 表的操作。包括插入博客,返回博客列表,返回单一一条博客以及删除博客功能。具体见代码与注释:

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;

/**
 * Created with IntelliJ IDEA.
 * Description:blog表的增删改查
 * User: YAO
 * Date: 2023-06-21
 * Time: 10:51
 */
public class BlogDao {
    public void insert(Blog blog) throws SQLException {
        // 将blog对象插入到数据库
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            // 1. 建立连接
            connection = DBUtil.getConnection();
            // 2. 构建sql语句
            String sql = "insert into blog values(null,?,?,?,?)";
            // 3. 使用preparedStatement 填充信息
            statement = connection.prepareStatement(sql);
            statement.setString(1,blog.getTitle());
            statement.setString(2,blog.getContent());
            statement.setInt(3,blog.getUserId());
            statement.setTimestamp(4,blog.getPostTimeStamp());

            // 4. 执行sql
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            // 5. 关闭所有的连接
            DBUtil.close(connection,statement,null);
        }
    }

    public List<Blog> selectAll() throws SQLException {
        // 查询所有的博客记录
        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 == null){
                    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;
    }

    public Blog selectOne(int blogId) throws SQLException {
        // 查询指定博客id的博客
        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;
    }
    public void delete(int blogId) throws SQLException {
        // 指定博客id进行删除
        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) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,null);
        }
    }
}

3. Controller 层实现

无论是博客详情页、博客列表页还是登录功能诸如此类,其核心逻辑都是一样的,具体分为如下几个步骤:

  • 约定前后端交互的接口;
  • 实现服务器代码,分别为controller层的servlet实现的api,以及model层使用jdbc来操作数据库;
  • 实现客户端代码:form / ajax / a标签跳转等。

3.1 博客列表页

3.1.1 约定前后端交互接口

该页面用于展示数据库中的博客列表。约定请求:GET/blog,响应为 json 格式。

3.1.2 编写后端代码 

 主要内容是根据请求中blogId是否为空,进行返回不同的响应,如果请求中包含指定的blogId就返回单条博客信息,为null就返回数据库中所有的博客信息.

创建BlogServlet.java文件:

/*
   获取博客信息
 */
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {

    private ObjectMapper objectMapper = new ObjectMapper();
    // 创建objectMapper对象用来将数据库中博客的信息转换成json格式
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            BlogDao blogDao = new BlogDao();
            String blogId = req.getParameter("blogId");
            if (blogId == null){
                // 分两种情况:
                // 1.当在请求中查找blogId的为空的时候,默认为查找所有的博客
                List<Blog> blogs = blogDao.selectAll();
                // 将查询到的信息转换成json格式
                String respString = objectMapper.writeValueAsString(blogs);

                // 设置响应内容的格式以及字符集
                resp.setContentType("application/json; charset=utf8");
                resp.getWriter().write(respString);
            }else {
                // 2.请求中blogId不为空的时候,进行查找相应id的博客
                Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
                String respString = objectMapper.writeValueAsString(blog);
                resp.setContentType("application/json; charset=utf8");
                resp.getWriter().write(respString);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

3.1.3 编写前端代码

使用Ajax进行构造请求

列表页需要向后端请求的内容有:

  •  1. 所有的博客内容(右侧栏信息)
  •  2. 用户的登录状态(左侧栏信息) 

 1. 所有的博客内容(右侧栏信息) 

<script>
        // 通过ajas给服务器进行请求,获取所有的博客数据,并且构造到页面上
        function getBlogs(){
            jQuery.ajax({
                type:'get',
                url:'blog',
                success:function(body){
                    // 回调函数
                    // 根据返回的数据,构造出页面中对应的元素
                    let containerRight = document.querySelector('.container-right')
                    
                    for(let blog of body){
                        // 此时body就是一个jsd对象
                        // 1.创建一个div
                        let blogDiv = document.createElement("div");
                        // 2.设置格式类名class
                        blogDiv.className = 'blog';
                        // 3.设置标题div
                        let titleDiv = document.createElement("div");
                        titleDiv.className = 'title';
                        titleDiv.innerHTML = blog.title;
                        // 4.设置时间div
                        let dateDiv = document.createElement("div");
                        dateDiv.className = 'date';
                        dateDiv.innerHTML = blog.postTime;
                        // 5.设置描述div
                        let descDiv = document.createElement("div");
                        descDiv.innerHTML = blog.content;
                        // 6.设置查看全文的标签<a>
                        let a = document.createElement("a")
                        a.href = 'blog_detail.html?blogId='+blog.blogId;
                        a.innerHTML = '查看全文 &gt;&gt;';

                        // 7.将上述创建好的标签进行添加
                        blogDiv.appendChild(titleDiv);
                        blogDiv.appendChild(dateDiv);
                        blogDiv.appendChild(descDiv);
                        blogDiv.appendChild(a);
                        containerRight.appendChild(blogDiv)
                    }
                }
            })
        }
        // 定义完函数必须进行调用
        getBlogs();
        
    </script>

  2. 用户的登录状态(左侧栏信息) 

<script>
        function cheakLogin(){
            jQuery.ajax({
                type:'get',
                url:'login',
                success: function(body){
                    if(body.userId && body.userId > 0){
                        // 登录成功
                        console.log('当前用户登录成功')
                        let h3 = document.querySelector('.container .container-left .card h3')
                        h3.innerHTML = body.username;
                    }else{
                        // 当前未登录
                        // 设置重定向,到登录页
                        location.assign('login.html');
                    }
                }
            })
        }
        cheakLogin();
        
</script>

3.2 博客详情页

3.2.1 约定前后端交互接口

 博客列表中有查看全文的按钮,点击查看全文就可以直接跳转到指定的博客详情页,此时查询的路径中拼接了查询字符串,其中包含blogId属性

3.2.2 编写后端代码

 在BlogServlet.java文件中

3.2.3 编写前端代码

 分别设置标题 时间 以及内容(使用editor.md自带的方法,将内容改成渲染之后的样式显示在HTML页面中)

<script>
        function getBlog(){
            jQuery.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});
                }
            });
        }
        getBlog();
 </script>

3.3 登录功能        

        登录功能就相对简单了,约定前后端接口,请求Post/login,响应为 Http/1.1 302,当用户登录成功后跳转到博客列表页。登录失败,则提示相应的信息。需要注意的是,当用户登录成功后需要创建一个会话 session,用于保存必要的用户信息,便于其他页面使用。例如:验证获取登录状态,展示用户信息等。

3.3.1 约定前后端交互接口

 3.3.2 编写后端代码

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;
import java.sql.SQLException;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: YAO
 * Date: 2023-06-23
 * Time: 16:32
 */
@WebServlet("/login")
public class LoginServlet extends HttpServlet {

    // 用来将数据转换成json的数据格式
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置请求的编码. 告诉 servlet 按照啥格式来理解请求
        req.setCharacterEncoding("utf8");
        // 设置响应的编码. 告诉 servlet 按照啥格式来构造响应
        // resp.setCharacterEncoding("utf8");
        resp.setContentType("text/html;charset=utf8");
        // 1. 读取参数中的用户名和密码
        //    注意!! 如果用户名密码包含中文, 此处的读取可能会乱码.
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null || "".equals(username) || password == null || "".equals(password)) {
            // 登录失败!!
            String html = "<h3>登录失败! 缺少 username 或者 password 字段</h3>";
            resp.getWriter().write(html);
            return;
        }
        // 2. 读数据库, 看看用户名是否存在, 并且密码是否匹配
        UserDao userDao = new UserDao();
        User user = null;
        try {
            user = userDao.selectUserByName(username);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        if (user == null) {
            // 用户不存在.
            String html = "<h3>登录失败! 用户名或密码错误</h3>";
            resp.getWriter().write(html);
            return;
        }
        if (!password.equals(user.getPassword())) {
            // 密码不对
            String html = "<h3>登录失败! 用户名或密码错误</h3>";
            resp.getWriter().write(html);
            return;
        }
        // 3. 用户名密码验证通过, 登录成功, 接下来就创建会话. 使用该会话保存用户信息.
        HttpSession session = req.getSession(true);
        session.setAttribute("user", user);
        // 4. 进行重定向. 跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }

}

3.3.3 编写前端代码

使用form表单进行构造POST请求

<form action="login" method="post">
                <div class="row">
                    <span>用户名</span>
                    <input type="text" id="username" placeholder="手机号/邮箱" 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>

3.4 检查用户登录状态

当用户处于博客列表页和详情页的时候,需要检查用户的登录状态。如果用户未登录,则给用户弹窗提示,并返回到登录页面。

3.4.1 约定前后端交互接口

请求 

响应(返回的是一个user对象包含User的所有信息)

3.4.2 编写后端代码

在loginServlet.java文件中重写doPost方法

1. 获取当前Session会话对象 ,检查Session对象是否为空,如果不为空也要检查是否存在用户对象.

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json");
        // 用来回去用登录的状态

        // 如果用户处于未登录的状态,就不会拿到会话
        HttpSession session = req.getSession(false);
        if (session == null){
            // 未登录
            User user = new User();
            String respJson = objectMapper.writeValueAsString(user);
            resp.getWriter().write(respJson);
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            user = new User();
            String respJson = objectMapper.writeValueAsString(user);
            resp.getWriter().write(respJson);
            return;
        }
        // 确实成功取出了 user 对象, 就直接返回即可.
        String respJson = objectMapper.writeValueAsString(user);
        resp.getWriter().write(respJson);
    }

3.4.3 编写前端代码

将下述代码分别点加到blog_list.html blog_detail.html blog_edit.html 中.执行强制登录,才能访问.

<script>
function cheakLogin(){
            jQuery.ajax({
                type:'get',
                url:'login',
                success: function(body){
                    if(body.userId && body.userId > 0){
                        // 登录成功
                        console.log('当前用户登录成功')
                        let h3 = document.querySelector('.container .container-left .card h3')
                        h3.innerHTML = body.username;
                    }else{
                        // 当前未登录
                        // 设置重定向,到登录页
                        location.assign('login.html');
                    }
                }
            })
        }
cheakLogin();
</script>

3.5 显示用户信息

        对于博客列表页,我们希望展示的是当前登录用户的信息;对于博客详情页,我们希望展示的是文章的作者信息。约定前后端交互接口,请求:get/authorInfo,响应依然为json格式。

因此,我们对登录状态检查的函数进行改动,添加一个参数,用于区分当前页是博客列表页还是博客详情页。

  • 对于博客列表页,我们正常展示用户信息即可;
  • 对于博客详情页,我们只需要判断当前用户和作者是否为同一人,如果不是,则更改当前页用户信息为作者信息即可

3.5.1 约定前后端交互接口 

3.5.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;
import java.sql.SQLException;

@WebServlet("/author")
public class authorServlet 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) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("参数非法, 缺少 blogId");
            return;
        }
        // 根据 blogId 查询 Blog 对象
        BlogDao blogDao = new BlogDao();
        Blog blog = null;
        try {
            blog = blogDao.selectOne(Integer.parseInt(blogId));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        if (blog == null) {
            // 博客不存在.
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("没有找到指定博客: blogId = " + blogId);
            return;
        }
        // 根据 blog 中的 userId 找到对应的用户信息
        UserDao userDao = new UserDao();
        User author = null;
        try {
            author = userDao.selectUserById(blog.getUserId());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        String respJson = objectMapper.writeValueAsString(author);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(respJson);
    }
}

3.5.3 编写前端代码 

<script>
// 从服务器获取当前作者的用户信息, 并显示在页面上
        function getAuthorInfo(user){
            $.ajax({
                type: 'get',
                url: 'author' + location.search,
                success: function(body){
                    // 此处的 body 就是服务器返回的 user 对象
                    if (body.username) {
                        // 如果响应中的 username 存在, 则直接更新到页面上
                        changeUsername(body.username);
                        if (user.username == body.username) {
                            // 如果登录的用户和发布文章的用户为同一个人
                            // 在详情页的导航栏添加一个删除按钮
                            let navDiv = document.querySelector(".nav");
                            // let a = document.createElement("a");
                            // a.innerHTML = "删除博客";
                            // a.href = "blogDelete" + location.search;
                            // navDiv.appendChild(a);
                            // 创建 a 标签元素
                            var link = document.createElement("a");
                            link.href = "blogDelete" + location.search;
                            link.textContent = "删除博客";
                            
                            // 添加点击事件监听器
                            link.addEventListener("click", function(event) {
                                event.preventDefault();
                                var confirmResult = confirm("确认要删除此博客?");
                                if (confirmResult) {
                                    window.location.href = link.href; // 执行原有的功能
                                }
                            });
                            navDiv.appendChild(link);                           
                        }
                    } else {
                        console.log("获取作者信息失败! " + body.reason);
                    }
                }
            });
        }
</script>

3.6 退出登录功能(注销)

注销功能非常简单,定义前后端交互接口,请求:get/logout,响应302跳转到登录页面。请求只需要在用户登录的时候添加一个 a 标签到导航栏即可。

怎样才算登录呢?

  • session 会话存在;
  • session 中包含 user 属性。

因此,实现注销,只需要将 user 属性从 session 中删除即可。

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;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: YAO
 * Date: 2023-06-24
 * Time: 18:55
 */
@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.setContentType("txt/html; charset=utf-8");
            resp.getWriter().write("用户未登录");
        }
        // 将当前User对象进行删除
        session.removeAttribute("user");
        resp.sendRedirect("login.html");
    }
}

3.7 发布博客功能

同样,先约定前后端交互接口。请求:POST/blog,以form表单的形式将title和content正文传给服务器。响应:HTTP/1.1 302 发布成功后跳转到博客详情页。 

3.7.1 约定前后端交互接口

3.7.2 编写后端代码

 在BlogServlet.java中重写doPost方法

@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 发布博客
        // 读取请求, 构造 Blog 对象, 插入数据库中即可!!
        HttpSession httpSession = req.getSession(false);
        if (httpSession == null) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录, 无法发布博客!");
            return;
        }
        User user = (User) httpSession.getAttribute("user");
        if (user == null) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录, 无法发布博客!");
            return;
        }
        // 确保登录之后, 就可以把作者给拿到了.

        // 获取博客标题和正文
        req.setCharacterEncoding("utf8");
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        if (title == null || "".equals(title) || content == null || "".equals(content)) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前提交数据有误! 标题或者正文为空!");
            return;
        }

        // 构造 Blog 对象
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        blog.setUserId(user.getUserId());
        // 发布时间, 在 java 中生成 / 数据库中生成 都行
        blog.setPostTime(new Timestamp(System.currentTimeMillis()));
        // 插入数据库
        BlogDao blogDao = new BlogDao();
        try {
            blogDao.insert(blog);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

        // 跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }

3.7.3 编写前端代码

<div class="blog-edit-container">
        <form action="blog" method="post" style="height: 100%;">
            <!-- 标题编辑区 -->
            <div class="title">
                <input type="text" id="title-input" placeholder="输入标题文章" name="title">
                <input type="submit" id="submit" value="发布文章">
            </div>
            <!-- 博客编辑器 -->
            <!-- 把 md 编辑器放到这个 div 中 -->
            <div id="editor">
                <!-- editor.md对于form表单是支持的,就是在form表单中设置了一个隐藏的textarea, editor.md会将用户输入的填写进去 -->
                <!-- style="display: none 是指的移仓了这个元素 -->
                <textarea name="content" style="display: none;"></textarea>
            </div>
            
        </form>
    </div>

3.8 删除博客功能

         只有作者本人才能删除博客,因此,在进入博客详情页的时候,进行判断。如果是作者本人,则添加删除按钮

我们可以在博客详情页中检查用户的登录信息的同时,检测到如果登陆用户与博客作者相同的时候,添加删除按钮,并且在点击删除按钮之后进行弹窗确认之后在进行执行.

 后端代码

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;
import java.sql.SQLException;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: YAO
 * Date: 2023-06-24
 * Time: 20:25
 */
@WebServlet("/blogDelete")
//@SuppressWarnings({"all"})
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.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录");
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前未登录");
            return;
        }
        // 2. 获取要删除的博客的 blogId
        String blogId = req.getParameter("blogId");
        if (blogId == null || "".equals(blogId)) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("参数不正确!");
            return;
        }
        // 3. 获取要删除的博客信息
        BlogDao blogDao = new BlogDao();
        Blog blog = null;
        try {
            blog = blogDao.selectOne(Integer.parseInt(blogId));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        if (blog == null) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前博客不存在");
            return;
        }
        // 4. 再次校验用户是否为博客的作者
        if (blog.getUserId() != user.getUserId()) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("不是您的博客, 无法删除!");
            return;
        }
        // 5. 删除并跳转
        try {
            blogDao.delete(Integer.parseInt(blogId));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        resp.sendRedirect("blog_list.html");
    }
}

前端代码

<script>
        function getBlog(){
            jQuery.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});
                }
            });
        }
        getBlog();

        // 从服务器获取当前作者的用户信息, 并显示在页面上
        function getAuthorInfo(user){
            $.ajax({
                type: 'get',
                url: 'author' + location.search,
                success: function(body){
                    // 此处的 body 就是服务器返回的 user 对象
                    if (body.username) {
                        // 如果响应中的 username 存在, 则直接更新到页面上
                        changeUsername(body.username);
                        if (user.username == body.username) {
                            // 如果登录的用户和发布文章的用户为同一个人
                            // 在详情页的导航栏添加一个删除按钮
                            let navDiv = document.querySelector(".nav");
                            // let a = document.createElement("a");
                            // a.innerHTML = "删除博客";
                            // a.href = "blogDelete" + location.search;
                            // navDiv.appendChild(a);
                            // 创建 a 标签元素
                            var link = document.createElement("a");
                            link.href = "blogDelete" + location.search;
                            link.textContent = "删除博客";
                            
                            // 添加点击事件监听器
                            link.addEventListener("click", function(event) {
                                event.preventDefault();
                                var confirmResult = confirm("确认要删除此博客?");
                                if (confirmResult) {
                                    window.location.href = link.href; // 执行原有的功能
                                }
                            });
                            navDiv.appendChild(link);                           
                        }
                    } else {
                        console.log("获取作者信息失败! " + body.reason);
                    }
                }
            });
        }

        function getUserInfo(){
            $.ajax({
                type: 'get',
                url: 'login',
                success: function(body){
                    // 判断此处的 body 是不是一个有效的 user 对象(userId = 0无效)
                    if (body.userId && body.userId > 0) {
                        // 登录成功
                        console.log("当前用户登录成功! 用户名:" + body.username);
                        // if (pageName == 'blog_list.html') {
                        //     // 更新用户名
                        //     changeUsername(body.username);
                        // }
                        changeUsername(body.username);
                        // 判断当前登录的用户和作者是否相同
                        getAuthorInfo(body);

                    } else {
                        // 登录失败 提示信息 并跳转到 login.html
                        alert("当前未登录, 请登录后重试!");
                        location.assign('login.html');
                    }
                },
                error: function(){
                    alert("当前未登录, 请登录后重试!");
                    location.assign('login.html');
                }
            });
        }
        // 获取当前用户的登录状态
        getUserInfo();

        // 用于更新用户名信息的方法
        function changeUsername(username){
            let h3 = document.querySelector(".card h3");
            h3.innerHTML = username;
        }
        


        
    </script>

以上就是博客系统的所有功能了.还有其他的细节可以进行完善.

完整的博客系统的代码可以在下方链接进行访问👇👇👇欢迎参考和指正.''😊😊😊

博客系统代码(前端+后端)https://gitee.com/yao-fa/java-ee-elementary/tree/master/blogSystem​​​​​​​谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/685282.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Shell1】shell语法,ssh/build/scp/upgrade,环境变量

文章目录 1.shell语法&#xff1a;shell是用C语言编写的程序&#xff0c;是用户使用Linux的桥梁&#xff0c;硬件>内核(os)>shell>文件系统1.1 变量&#xff1a;readonly定义只读变量&#xff0c;unset删除变量1.2 函数&#xff1a;shell脚本传递的参数中包含空格&…

聊天GPT如何运作?| 景联文科技

什么是聊天 GPT&#xff1f; Chat GPT 被定义为一种生成语言模型。在实践中&#xff0c;它被理解为经过训练和设计以进行自然对话的人工智能聊天。 聊天 GPT 的用途是什么&#xff1f; 1.借助 GPT&#xff0c;您可以生成各种风格、主题和语言的连贯且写得很好的文本。此外&a…

【比赛writeup】2023省赛-Reverse-Re2

2023省赛-Reverse-Re2 一、概要 1、标题&#xff1a;Re2 2、关键字&#xff1a;换表BASE64 3、比赛&#xff1a;2023省赛 4、工具&#xff1a;IDAx64、python 二、开始 1、题目分析 逆向的题目&#xff0c;找到关键字符串&#xff0c;找到关键函数&#xff0c;分析函数逻辑…

ISO15765-2 网络层,通俗易懂,最佳入门

I. 简介 ISO15765-2协议网络 ISO15765-2协议网络是一种用于汽车电子系统中的通信协议。它定义了在控制区域网络&#xff08;CAN&#xff09;上使用的诊断通信的协议规范&#xff0c;包括物理层、数据链路层、网络层和传输层。该协议的主要目的是使汽车制造商和维修技术人员能够…

Vue2,Vue3 computed计算属性用法

计算属性就是当依赖的属性的值发生变化的时候&#xff0c;才会触发他的更改&#xff0c;如果依赖的值&#xff0c;不发生变化的时候&#xff0c;使用的是缓存中的属性值。 computed购物车案例 1.Vue2版 <template><div><div><input v-model"keywo…

linux系统Nginx网站服务

文章目录 一、Nginx简介二、Nginx 相对于 Apache 的优点三、nginx 应用场景1.同步与异步2.阻塞与非阻塞 四、Nginx安装及运行控制1、编译安装2、访问控制1、访问状态统计2、基于授权的访问控制3、基于客户端的访问控制4、基于域名的 Nginx 虚拟主机5、基于IP 的 Nginx 虚拟主机…

2.3ORBSLAM3之相机模型与畸变模型

1.简介 主要内容&#xff1a; 1. 对SLAM中常见的相机模型进行介绍&#xff0c;包括针孔相机模型和鱼眼相机模型 2. 对每种相机模型的畸变模型进行介绍 3. 对VSLAM中常见的几种去畸变方法进行介绍 4. 对常见的几种相机标定方法进行总结对于VSLAM来说关于相机投影模型和畸变模型…

Mybatis如何解决循环依赖问题

1、Mybatis如何解决循环依赖问题 mybatis的循环依赖&#xff0c;即是mapper.xml里面的A查询的resultMap包含了B属性&#xff08;B属性是通过子查询得到的&#xff09;&#xff0c;而B属性中又包含了A&#xff08;B查询的resultMap中又包含了A的查询&#xff09;&#xff0c;就…

【系统架构】第五章-软件工程基础知识(软件测试、净室软件工程、基于构件的软件工程、 软件项目管理)

软考-系统架构设计师知识点提炼-系统架构设计师教程&#xff08;第2版&#xff09; 软件测试 一、测试方法 以测试过程中程序执行状态为依据可分为静态测试&#xff08;ST&#xff09;和动态测试&#xff08;DT&#xff09; 以具体实现算法细节和系统内部结构的相关情况为根据…

【Leetcode60天带刷】day35——452. 用最少数量的箭引爆气球,435. 无重叠区间,763.划分字母区间

​ 题目&#xff1a; 452. 用最少数量的箭引爆气球 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着…

SuperMap GIS基础产品云GIS FAQ集锦(3)

SuperMap GIS基础产品云GIS FAQ集锦&#xff08;3&#xff09; 【iServer】如何自定义iServer服务能力文档中提供者的信息&#xff1f; 【解决办法】操作步骤如下&#xff1a; 1&#xff09;进入iServer管理界面&#xff0c;依次点击服务-服务接口&#xff1b; 2&#xff09;点…

PL SQL结构化编程

第一章、存储过程 1.1、概念 存储过程是一个命名的程序块&#xff0c;包括过程的名称、过程使用的参数&#xff0c;以及过程执行的操作。 &#xff08;类似于java中的一种函数&#xff0c;但是存在区别&#xff09; 可以指定输入参数&#xff0c;和输出参数。 1.2、创建存…

Elasticsearch 地理空间计算

地理位1置搜索&#xff1a;使用查询API进行搜索 1、Geo Distance Query Geo Distance Query是在给定的距离范围内搜索数据的查询。例如&#xff0c;可以搜索给定地理位置附近指定距离内的所有文档。 GET /my_index/_search {"query": {"bool" : {"fi…

基于Python+Django+mysql+html图书管理系统V2.0

基于PythonDjangomysqlhtml图书管理系统V2.0 一、系统介绍二、功能展示1.图书查询2.图书添加3.图书修改4.图书删除5.数据库 三、其它系统四、获取源码 一、系统介绍 该系统实现了图书查询、图书添加&#xff0c;图书修改、图书删除 运行环境&#xff1a;python3.7/mysql5.7以…

STM32单片机(八)DMA直接存储器存取----第二节:DMA直接存储器存取练习1(DMA数据转运)

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

TiDB 7.1资源管控和Oceanbase 4.0多租户使用对比

作者&#xff1a; seiang 原文来源&#xff1a; https://tidb.net/blog/a33d3498 一、背景 TiDB和Oceanbase都是非常优秀的国产分布式数据库&#xff1b;公司从2021年第一套生产业务的TiDB集群落地&#xff0c;随着第一套TiDB生产集群的落地&#xff0c;主要面向不同的业务…

[230607] 阅读TPO69汇总|9:00-10:00

目录 TPO6601 5 事实信息题 7 推理题 9 句子插入题 10 小结题 TPO6602 5 修辞目的题 7 词汇题 tame 10 小结题 TPO6603 TPO6601 5 事实信息题 做题技巧&#xff1a;实词对应 定位在&#xff08;1&#xff09;spread the tines of their tongue apart when they ret…

Greenplum数据库优化器——新Path类型CdbMotionPath

Path表示了一种可能的计算路径&#xff08;比如顺序扫描或哈希关联&#xff09;&#xff0c;更复杂的路径会继承Path结构体并记录更多信息以用于优化。Greenplum为Path结构体(src/include/nodes/relation.h/Path)新加CdbPathLocus locus字段&#xff0c;用于表示结果元组在当前…

ConstraintLayout使用指南

ConstraintLayout ConstraintLayout 可让您使用扁平视图层次结构&#xff08;无嵌套视图组&#xff09;创建复杂的大型布局。它与 RelativeLayout 相似&#xff0c;其中所有的视图均根据同级视图与父布局之间的关系进行布局&#xff0c;但其灵活性要高于 RelativeLayout&#x…

【读书笔记】《软件工程导论》

目录 一、软件工程概述 二、启动阶段 三、计划阶段 四、实施阶段 五、收尾阶段 一、软件工程概述 软件危机&#xff1a;在计算机软件的开发和维护过程中遇到的一系列严重问题。 软件危机的产生与自身的特点有关&#xff0c;还与软件开发、管理的方法不正确有关。 软件危…