【JavaEE】博客系统前后端交互

目录

一、准备工作

二、数据库的表设计

三、封装JDBC数据库操作

1、创建数据表对应的实体类

2、封装增删改查操作

 四、前后端交互逻辑的实现

1、博客列表页

1.1、展示博客列表

1.2、博客详情页 

1.3、登录页面

1.4、强制要求用户登录,检查用户的登录状态

1.5、实现显示用户信息的功能

1.5.1、针对列表页进行处理

1.5.2、针对详情页进行处理

1.6、用户退出登录功能

1.7、实现发布博客功能


一、准备工作

 

1️⃣创建新的maven项目

2️⃣引入依赖

我们需要使用的依赖有servlet、Jackson、MySQL。在中央仓库中搜索Java Servlet API,选择3.1.0版本,将maven中的代码复制到pom.xml中。

 搜索点击Jackson Databind,引入Jackson没有特定的版本,随便选一个版本的maven代码复制到pom.xml的<dependencies></dependencies>标签中。 

引入数据库,搜索MySQL选择5.1版本的maven中的代码,复制到pom.xml的<dependencies></dependencies>标签中。

引入依赖之后可能代码中会出现爆红,这个时候点击刷新,出发一下下载即可。 

3️⃣创建必要的目录

目录创建好之后,需要 给web.xml中写入指定配置。

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

<web-app>
    <display-name>Archetype Created Web Application</display-name>
</web-app>

二、数据库的表设计

我们之前的设计的博客前端页面中有博客详情页,博客编辑页,博客列表页,博客登录页。这些页面中,需要使用数据存储的数据主要有两部分,一个是编写的博客数据,一个是用户数据。这里的建表操作和我们之前的直接在数据库中间表的方式有一些区别。首先我们在blog_system项目的main目录下创建一个.sql文件,用来保存建表的过程。如果我们写的服务器需要部署到不同的机器上,就需要在对应的主机上也将数据库建号。这个时候我们只需要将这里的代码拷贝到数据中就可以了。

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

--表示当前电脑的数据库中不存在blog_sysyem这个库就创建,存在就不创建了。
create database if not exists blog_system;

use blog_system;

--表示如果这个库中有blog表,就先删除这个表
drop table if exists blog;
create table blog(
    blogId int primary key auto_increment,
    title varchar(128),
    content varchar(4096), --正文
    userId int,
    posTime datetime
);

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

上述我们在建表的时候,先删除库中存在的我们要创建的表,是为了清空之前残留的数据。

由于我们的博客系统并没有实现注册功能,所以小编事先在数据库中存入两个用户信息。


三、封装JDBC数据库操作

JDBC中提供了简单的API,但是我们写的类的太多,如果每个类都需要初始化数据源、建立连接、关闭资源,那就太麻烦了。所以这里我们将这些操作封装到一个类中,需要使用的时候,直接调用封装好的方法。

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;

public class DBUtil {
    //这个类中提供DataSource,DataSource对于一个项目来说,有一个就行了(单例)
    //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/blog_system?characterEncoding=utf8&&useSSL=false");
                    ((MysqlDataSource)dataSource).setUser("root");
                    ((MysqlDataSource)dataSource).setPassword("991218zf");
                }
            }
        }
        return dataSource;
    }

    //建立连接
    public static Connection getConnection() throws SQLException {

            //这里调用getDataSource方法的作用就是没有实例化数据源,就会被实例先一下
            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();
            }
        }
    }
}

需要了解的是,我们实例并初始化数据源的时候,使用了单例模型中的懒汉模式。懒汉模式在不加锁的情况下,是多线程不安全的。

单例模型是指我们在一个项目中某个类只能有一个实例,也就是我们这里DataSource,它在我们的项目中只需要出现实例一次,只需要描述一次数据库服务器所在位置。

这里使用懒汉模式有两个线程问题,一个是保证创建MysqlDataSource实例时的原子性,一个是保证内存可见性

  1. 保证原子性,就是让我们在实例化MysqlDataSource时,不会出现创建多个MysqlDataSource对象,针对DBUtil类对象进行加锁,一个线程在创建对象的时候,其他线程阻塞等待。当一个对象创建好之后,下次需要使用的时候,就不需要再创建这个对象了,也就不需要进程加锁了,所以这里在外层添加了一个if判断,如果MysqlDataSource对象存在,直接返回创建好的对象。因为加锁会导致程序执行速度变慢,所以必要的时候加锁,不必要的时候就不需要加锁。
  2. 保证内存可见性,使用volatile关键字,防止在读取数据表中的数据时,从内存中读数据的操作被编译器优化掉,而另一个线程修改数据的时候,读取数据的线程感知不到。

1、创建数据表对应的实体类

这里我们在数据库中已经创建好了两个表,一个用来表示博客信息,一个用来表示用户信息。但是我们还需要在Java代码中,创建对应的类,表示这两个实体,比如创建Blog类,Blog类的每个对象,就代表数据库的一个记录。

1️⃣用户信息类(User)

package model;
/*
* 这个类表示数据库中user表的内容
* 每个user对象,就对应user表中的一条记录
* */

public class User {
    private int userId;
    private String username;
    private String password;

    public int getUserId() {
        return userId;
    }

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

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

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

2️⃣博客信息类(Blog)

package model;
/*
* 这里类表示数据库中Blog表的内容
* 没给Blog对象,就对应blog表中的一条记录
* */

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

public class Blog {
    private int bolgId;
    private String title;
    private String  content;
    private int userId;
    private Timestamp postTime;

    public int getBolgId() {
        return bolgId;
    }

    public void setBolgId(int bolgId) {
        this.bolgId = bolgId;
    }

    public String getTitle() {
        return title;
    }

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

    public String getContent() {
        return content;
    }

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

    public int getUserId() {
        return userId;
    }

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

    public String getPostTime() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //format方法是用来转换时间戳为上述规定的格式。
        return format.format(postTime);
    }

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

2、封装增删改查操作

这里我们创建两个类分别为BlogDao和UserDao,这里的Dao是 Data Access Object的缩写,表示的意思是数据访问对象,通过这个类的对象来访问数据。所以这里我们通过在这两个类的方法中封装JDBC来操作数据库。

1️⃣BlogDao

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;

public class BlogDao {
    //把一个Blog对象插入到数据库中
    public void insert(Blog blog){
        Connection connection = null;
        PreparedStatement statement = null;
        try{
            //1.建立连接
             connection = DBUtil.getConnection();
            //2.构造sql
            String sql = "insert into blog values(null,?,?,?,?)";
            statement = connection.prepareStatement(sql);
            statement.setString(1, blog.getTitle());
            statement.setString(2, blog.getContent());
            statement.setInt(3, blog.getUserId());
            //如果数据库表里面是datetime类型,插入数据的时候,按照TimeStamp来插入或者按照格式化时间来插入都是可以的
            statement.setString(4, blog.getPostTime());
            //3.执行sql
            statement.executeUpdate();
        }catch(SQLException e){
            e.printStackTrace();
        }finally{
            DBUtil.close(connection,statement,null);
        }
    }

    //查询blog表中所有的博客数据
    public List<Blog> selectAll() {
        List<Blog> blogs = new ArrayList<>();
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //1.建立连接
            connection = DBUtil.getConnection();
            //2.构造并执行sql
            String sql = "select * from blog order by postTime desc";
            statement = connection.prepareStatement(sql);
            resultSet = statement.executeQuery();
            //遍历结果
            while(resultSet.next()){//这里的resultSet是一个结果集
                Blog blog = new Blog();
                blog.setBolgId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                //先把博客的正文取出来
                String content = resultSet.getString("content");
                //判断如果正文超过了100,从0到100截取出来。
                if(content.length() > 100){
                    content = content.substring(0,100)+"...";
                }
                //最后将这个content放入到blog对象中。最后通过响应显示在博客简介中
                blog.setContent(content);
                blog.setUserId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                //将每次遍历到的结果都放在list中保存。
                blogs.add(blog);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally{
            DBUtil.close(connection,statement,resultSet);
        }
        return blogs;
    }

    //指定一个博客id来查询对应的博客
    public Blog selectOne(int blogId){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,blogId);
            resultSet = statement.executeQuery();
            if(resultSet.next()){
                Blog blog = new Blog();
                blog.setBolgId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setUserId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                return blog;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally{
           DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

    //指定一个博客id 来删除博客
    public void delete(int blogId){
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "delete from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,blogId);
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally{
            DBUtil.close(connection,statement,null);
        }
    }
}

2️⃣UserDao

package model;

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

public class UserDao {
    //根据用户id进行查询
    public User selectUserById(int userId){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from user where userId = ? ";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,userId);
            resultSet = statement.executeQuery();
            if(resultSet.next()){
                User user = new User();
                user.setUserId(resultSet.getInt("userId"));
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                return user;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally{
            DBUtil.close(connection,statement,resultSet);
        }
        return null;

    }
    //根据用户名进行查询
    public User selectUserByName(String username){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from user where username = ? ";
            statement = connection.prepareStatement(sql);
            statement.setString(1,username);
            resultSet = statement.executeQuery();
            if(resultSet.next()){
                User user = new User();
                user.setUserId(resultSet.getInt("userId"));
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                return user;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally{
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }
}

✨这里需要注意的是执行sql语句时,使用了两个方法executeQuery和executeUpdate方法,这两个方法都表示执行sql语句,但是在使用的时机上存在差异。

  1. executeQuery方法用于执行从数据库中检索某些数据的SQL语句。例如select
  2. executeUpdate方法用于执行更新或修改数据库的sql语句。例如:insert into ,update

 四、前后端交互逻辑的实现

1、博客列表页

1.1、展示博客列表

1️⃣约定前后端交互接口

请求:GET/blog :这里的GET表示的是HTTP请求方法,blog表示路径

响应:由于博客列表页中存在多个博客,所以使用数组,来存放这个博客对象。

[
    {
        blogId:1,
        title:"这是标题",
        content:"这是正文",
        userId:1,
        postTime:"2023-07-27 12:00:00"
    },
    
    {
        blogId:1,
        title:"这是标题",
        content:"这是正文",
        userId:1,
        postTime:"2023-07-27 12:00:00"
    },

    {
        blogId:1,
        title:"这是标题",
        content:"这是正文",
        userId:1,
        postTime:"2023-07-27 12:00:00"
    },
]

2️⃣编写后端代码(BlogServlet类)

/*
*通过这个类,来实现一些后端提供的接口
* */
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    //实例化一个ObjectMapper对象,用来将数据转换为json格式
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BlogDao blogDao = new BlogDao();
        List<Blog> blogs = blogDao.selectAll();
        //将从数据库中查询到的数据转换为json格式
        String respString = objectMapper.writeValueAsString(blogs);
        //使客户端浏览器区分不同种类的数据,并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据。
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(respString);
    }
}

3️⃣修改前端代码

之前写的前端代码的博客列表页,都是写死的,现在我们的博客列表页使用数据库中获取数据,所以就需要我们使用JavaScript根据之前写死的样式来编写标签以及其中的内容。

这是之前写死的博客列表页中的组成一个博客的标签。

根据上述的样式,在回调函数中利用JavaScript编写博客列表页的样式,现在只是对博客列表页的右半部分进行了修改。

        <!-- 右侧信息 -->
        <div class="container-right">
            
        </div>
    </div>
    <script>
        //通过ajax给服务器发请求,获取到所有的博客数据,并且构造到页面上。
        function getBlogs(){
            $.ajax({
                type:'get',  //这是请求的方法
                url:'blog',  //这是请求的路径
                success:function(body){
                    //根据返回的响应数据,构造出页面中对应的元素
                    //由于返回的响应的数据是一个application/json,所以jQuery自动的将字符串转化为了数组对象
                    let containerRight = document.querySelector('.container-right')
                    for(let blog of body){
                        //拿到的body数据可以当作数组来使用的
                        let blogDiv = document.createElement("div");
                        blogDiv.className = 'blog';
                        let titleDiv = document.createElement("div");
                        titleDiv.className = 'title';
                        titleDiv.innerHTML = blog.title;
                        let dateDiv = document.createElement('div');
                        dateDiv.className = 'date';
                        dateDiv.innerHTML = blog.postTime;
                        let descDiv = document.createElement("div");
                        descDiv.className = 'desc';
                        descDiv.innerHTML = blog.content;
                        let a = document.createElement("a");
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        a.innerHTML = '查看全文 &gt;&gt;';
                        //把上述标签构造好了之后,还需要组合起来
                        blogDiv.appendChild(titleDiv);
                        blogDiv.appendChild(dateDiv);
                        blogDiv.appendChild(descDiv);
                        blogDiv.appendChild(a);
                        containerRight.appendChild(blogDiv);
                    }
                }
            });
        }
        getBlogs();
    </script>

 🍂我们博客显示的发布时间,在页面上显示的是时间戳,这里需要我们在Blog类中对getPostTime方法进行修改,使用SimpleDateFormat类的format方法对时间戳转换成我们设置的格式。

 public String getPostTime() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //format方法是用来转换时间戳为上述规定的格式。
        return format.format(postTime);
    }

这里的我们设置的格式中小时的HH,一定要大写,使用大写表示的是24小时制,使用的hh表示的是12小时制。

🍂我们写博客的时候,看见的都是最近写的博客都在最上面,所以我们这里设置在数据库中根据博客发布的时间倒序排序,这样在页面中显示的时候就会将最近写的博客放在最上面。

select * from blog order by postTime desc

🍂我们写的博客中,摘要只出现正文的一部分,所以给我们自己博客系统也实现一个,在BlogDao类中selectAll进行一下改造,让其先获取正文部分,然后再对正文内容的长度进行判断,长度超过100,去正文的前100个字最为摘要。

//先把博客的正文取出来
 String content = resultSet.getString("content");
 //判断如果正文超过了100,从0到100截取出来。
 if(content.length() > 100){
     content = content.substring(0,100)+"...";
  }
 //最后将这个content放入到blog对象中。最后通过响应显示在博客简介中
  blog.setContent(content);

将这三个问题修改完成之后,出现的页面就是这样的。


1.2、博客详情页 

1️⃣约定前后端交互接口

请求:GET /blog?blogId=1;这里后面添加blogId= 1表示的意思就是指定获取某个博客内容

响应:只获取一个博客内容。

{
    blogId:1,
    title:"这是一篇博客",
    content:"这是正文",
    userId:1,
    postTime:"2023-07-27 12:00:00"
    
}

2️⃣编写后端代码

这里是否创建新的类,是根据我们约定前后端接口的时候,请求指定的路径来区分的,由于这里我们约定的路径与博客列表页的请求路径是一样,所以博客详情页的代码继续在BlogServlet类中来编写。

这里就是根据请求的query string中是否有blogId来区分是获取一个博客还是获取所有的博客。

mport java.util.List;

/*
*通过这个类,来实现一些后端提供的接口
* */
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    //实例化一个ObjectMapper对象,用来将数据转换为json格式
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String blogId = req.getParameter("blogId");
        //从query string中查询一下看是否有blogId,如果有就认为是查询指定博客;如果没有就是查询所有博客。
        BlogDao blogDao = new BlogDao();
        if(blogId == null){
            List<Blog> blogs = blogDao.selectAll();
            //将从数据库中查询到的数据转换为json格式
            String respString = objectMapper.writeValueAsString(blogs);
            //使客户端浏览器区分不同种类的数据,并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据。
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
        }else{
            //上述使用getParameter从query string中得到的blogId是一个字符串,这里需要将其转为数字
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            String respString = objectMapper.writeValueAsString(blog);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
        }

    }
}

3️⃣修改前端代码

这里需要在博客列表页点击查看全文跳转,跳转后的博客详情页,是由markdown格式构成的数据,所以在前端页面显示的时候需要引入editor.md的依赖,然后使用markdown官方提供的editormd.markdownToHTML方法来对正文进行渲染,然后显示在页面上。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客详情页</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/blog-detail.css">
    <!-- 引入jquery -->
    <script src="jquery.min.js"></script>
    <!-- 引入editor.md依赖 -->
    <link rel="stylesheet" href="editor.md/css/editormd.min.css">
    <script  src="editor.md/lib/marked.min.js"></script>
    <script src="editor.md/lib/prettify.min.js"></script>
    <script src="editor.md/editormd.js"></script>
</head>
<body>
    <!-- 导航栏 nav 是导航整个次的缩写 -->
    <div class="nav">
        <!-- logo -->
        <img src="image/logo.png" alt="">
        <div class="title">我的博客系统</div>
        <!-- 只是一个空白,用来把后面的链接挤过去 -->
        <!-- 这是一个简单粗暴的写法 -->
        <div class="spancer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <!-- 这里的地址回头在说 -->
        <a href="#">注销</a>
    </div>
    <!-- 页面的主题部分 -->
    <div class="container">
        <!-- 左侧信息 -->
        <div class="container-left">
            <!-- 这个div表示整个用户信息的区域 -->
            <div class="card">
                <!-- 用户的头像 -->
                <img src="image/head_portrait.jpg" alt="">
                <!-- 用户名 -->
                <h3>小张学编程</h3>
                <!-- GitHub地址 -->
                <a href="https://github.com">github 地址</a>
                <!-- 统计信息 -->
                <div class="counter">
                    <span>文章</span>
                    <span>分类</span>
                </div>
                <div class="counter">
                    <span>2</span>
                    <span>1</span>
                </div>
            </div>
        </div>

        <!-- 右侧信息 -->
        <div class="container-right">
            <h3></h3>
            <div class="date"></div>
            <div id="content">
            </div>
        </div>
    </div>
    <script>
        function getBlog(){
            $.ajax({
                type:'get',
                //location.search
                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>
</body>
</html>

上述代码中需要注意的是location.search,这个代码可以获取当前的页面的URL中的查询字符串内容。location与document一样是一个全局变量。


1.3、登录页面

1️⃣约定前后端交互接口

这里提交用户名和密码,可以使用form也可以使用ajax,但是form更简单一点,所以我们这里使用form构造请求

请求:

POST/login

Content-Type:application/x-www-form-urlencoded  这种数据的组织类型就是form专属的类型

响应: 

登录成功,直接跳转到主页,302表示重定向

HTTP/1.1 302

Location:blog_list.html 

✨注意:

如果通过302来跳转页面。前端必须使用form,不能使用ajax.如果使用ajax,当收到302响应,不会触发页面跳转。

2️⃣编写后端代码

这里的前端代码由于我们只是使用费form发送请求,所以改动就非常小。

    <!-- 登录页的版心 -->
    <div class="login-container">
        <!-- 登录对话框 -->
        <div class="login-dialog">
            <h3>登录</h3>
            <!-- 这里使用form包裹一下 下列内容,便于后续给服务器提交数据 -->
            <form action="login" method="post">
                <div class="row">
                    <span>用户名</span>
                    <input type="text" id="username" name="username">
                </div>
                <div class="row">
                    <span>密码</span>
                    <input type="password" id="password" name="password">
                </div>
                <div class="row">
                    <input type="submit" id="submit" value="登录">
                </div>
            </form>
            
        </div>
    </div>

这里我们在每个input标签中添加一个name属性,这个属性的值和id属性值相同。但是他们的作用大不相同。id属性,只是针对html生效,只是用来方便获取到该元素。name属性是针对form表单构造http请求的 

3️⃣编写前端代码

由于我们约定的路径发生了变化,所以我们在编写后端代码的时候,重新创建一个类,用来实现登录页面的后端程序。

package controller;

import model.User;
import model.UserDao;

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

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //设置请求传过来的字符服务器以utf8字符集进行解析
        req.setCharacterEncoding("utf8");
        //1.从请求中,获取到用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if(username == null || username.equals("") || password ==null ||password.equals("")){
            //用户名或者密码不全,登录必然失败
            String html = "<h3>登录失败!缺少用户名或者密码</h3>";
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(html);
            return;
        }
        //2.读取数据库,看这里的用户名和密码,是否和数据库中的匹配
        UserDao userDao = new UserDao();
        User user = userDao.selectUserByName(username);
        if(user == null){
            //用户名不存在
            String html = "<h3>用户名或密码错误!</h3>";
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(html);
            return;
        }
        if(password.equals(user.getPassword())){
            //密码错误
            String html = "<h3>用户名或密码错误!</h3>";
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write(html);
            return;
        }
        //3.用户名和密码都正确,登录成功,需要设置会话
            //先要创建一个会话
        HttpSession session = req.getSession(true);
        //此处就把用户对象存储到session中了,下次用户访问其他页面,就可以直接拿到会话,进一步就能拿到之前的user对象了
        session.setAttribute("user",user);
        //4.返回一个重定向响应,能够跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }
}

1.4、强制要求用户登录,检查用户的登录状态

当用户想要访问博客列表页/详情页/编辑页的时候,必须是登录状态,如果是未登录状态,则直接跳转到登录页,要求用户登录。

在博客列表页/详情页/登录页,页面加载的时候,发起一个ajax请求,通过这个请求,访问服务器,获取到当前的登录状态,如果当前为登录,则跳转到登录页面,如果已登录,则不做任何操作。

1️⃣约定前后端交互接口

  • 请求:

GET/login

  • 响应:

登录成功,就返回一个200这样的响应。body可以不要,登录失败(未登录),就返回403这样的响应。

2️⃣编写后端代码

我们可以直接在之前的LoginServlet类中写一个doGet判定当前的登录状态。

    //通过这个方法,判定用户的登录状态。已登录,返回200.未登录返回403
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //看当前请求是否已经存在会话,并且当前的会话是否包含user对象。
        HttpSession session = req.getSession(false);
        if(session == null){
            //会话不存在,未登录
            resp.setStatus(403);
            return;
        }
        //因为当前的getAttribute返回的是一个Object类型的数值,这里我们使用User将其强转
        User user = (User)session.getAttribute("user");
        if(user == null){
            //虽然会话对象存在,但是用户对象没有,也将其判定为未登录状态
            resp.setStatus(403);
            return;
        }
        //如果会话存在并且用户也存在,这个时候就是已登陆状态
        //200是默认的状态,这里的这句代码也可以不用写
        resp.setStatus(200);
    }
}

3️⃣编写前端代码

由于判定是否登录,每个页面都需要,所以这里我们将这个判定的方法单独取出来,放到一个文件中,其他的页面代码中引用这个外部代码即可。

function getLoginStatus(){
    $.ajax({
        type:'get',
        url:'login',
        success:function(body){
            //响应返回200的时候,执行success回调函数
            //用户已经登录,不用进行任何操作
            console.log("用户已经登录");
        },
        error:function(body){
            //只要返回的不是2开头的状态码,都会触发error回调函数
            //assign方法用来跳转到login.html主页
            location.assign("login.html");
        }
    })
}

其他的页面使用这种样式引用即可,但是需要注意的是引入的路径是否正确

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

//引入完成之后调用这个方法
getLoginStatus();

1.5、实现显示用户信息的功能

我们编写的页面中博客列表页和博客详情页的左边页面中都有显示用户信息,但是这两个地方显示的不是一个用户信息,在博客列表页显示的是登录用户的信息,但是当用户点击到别人写的博客中,也就是博客详情页,这个时候就会显示本篇博客作者的信息。

所以实现显示用户信息的功能,需要获取两部分的用户信息

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

所以针对这两部分的数据,就需要分别进行获取

1.5.1、针对列表页进行处理

 1️⃣约定前后端交互接口

在博客列表页,要获取到登录用户的信息

前端部分可以直接复用getLoginStatus方法,之前登录成功之后什么也没有做,这里将其修改一下,登录成功之后,将响应返回的用户名设置到左侧的用户信息中。

后端代码,之前的LoginServlet类中,使用doGet方法判定用户的登录状态,判定登录成功之后只返回了一个200的状态码,这里我们登录成功之后的用户名,返回给前端。

请求:

GET/login

响应:

HTTP/1.1 200

Content-Type:application/json;

{
    userId:1
    username:zhangsan,
}

2️⃣修改后端代码

3️⃣修改后端代码

只有博客列表页显示登录用户的信息,所以这里我们将之间的getLoginStatus方法,复制出来放在blog_list的代码中,对齐进行修改,之前的这个方法中当服务器返回200的时候,这个代码只是打印了日志,什么都没有做,现在我们通过querySelector方法找到h3标签,然后修改h3标签中的内容。

      function getLoginStatus(){
            $.ajax({
                type:'get',
                url:'login',
                success:function(body){
                    //响应返回200的时候,执行success回调函数
                    //用户已经登录,不用进行任何操作
                    console.log("用户已经登录");
                    //把返回的用户名,设置到页面中。
                    let h3 = document.querySelector('.card h3');
                    //这里的body是一个js对象,就是前面服务器返回的json格式的user对象
                    h3.innerHTML = body.username;
                },
                error:function(body){
                    //只要返回的不是2开头的状态码,都会触发error回调函数
                    //assign方法用来跳转到login.html主页
                    location.assign("login.html");
                }
            })
        }

1.5.2、针对详情页进行处理

这里的详情页,用户信息这里显示的是当前文章的作者,首先我们需要根据blogId查询到文章对象,然后拿到文章作者的id,在根据作者id查询对应的作者名字,显示到页面上。

1️⃣约定前后端交互接口

请求

GET/user?blogId=1

响应:

HTTP/1.1 200

Content-Type:application/json

{
    userId:1,
    username:"zhangsan"
}

2️⃣编写后端代码

由于我们约定的时候,指定了新的路径,所以我们需要创建新的Servlet类来处理请求。


package controller;

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

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

@WebServlet("/user")
public class UserServlet extends HttpServlet {

    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1、先读取出blogId,由于我们的blogId是通过query string 传递的,所以我们使用getParameter获取
        String blogId = req.getParameter("blogId");
        if(blogId == null || blogId.equals("")){
            //直接返回一个userId为0的对象,因为最终返回的是一个Json数据
            //此时也是返回json格式比较好,如果返回一个html,前端处理的时候还需要判断
            //这里new的user对象,是一个空的对象,也就满足了返回一个userId为0的对象
            String respJson = objectMapper.writeValueAsString(new User());
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respJson);
            System.out.println("参数给定的blog为空");
            return;
        }
        //2.查询数据库,查询对应的Blog对象
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
        if(blog == null){
            String respJson = objectMapper.writeValueAsString(new User());
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respJson);
            System.out.println("参数给定的blog不存在");
            return;
        }
        //3.根据blog中的userId,查询作者信息
        UserDao userDao = new UserDao();
        User user = userDao.selectUserById(blog.getUserId());
        if(user == null){
            String respJson = objectMapper.writeValueAsString(new User());
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respJson);
            System.out.println("该博客对应的作者不存在");
        }
        //4.把user对象返回给页面
        String respJson = objectMapper.writeValueAsString(user);
        resp.getWriter().write(respJson);
        return;
    }
}

3️⃣修改前端代码

在前端的博客详情页(blog_detail)中添加getAuthor方法。将之前左侧的用户信息框中的h3标签中写死的内容删除掉。

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

                }
            });
        }
        getAuthor();

1.6、用户退出登录功能

之前我们在导航栏中写了注销,这是一个a标签,点击这个标签的时候就会触发一个GET请求,服务器收到这个get请求,就可以把当前用户的会话中的user对象给删除掉。这里为什么不直接删除会话session,但是由于servlet中没有提供直接删除会话的操作,只是提供了设置过期时间的方法,在我们的现在的业务需求上不适用,所以这里直接删除session中的user对象,也就实现了退出登录。

1️⃣约定前后端交互接口

  • 请求:

GET/logout

  • 响应:

HTTP/1.1 302

Lcation:login.html

2️⃣编写后端代码

这里我们约定的请求使用新的路径,所以我们需要创建新的Servlet,来实现业务。由于之前的代码中用来判定用户是否登录的时候,判断user对象是否存在,所以这里我们直接将user对象删除,并且重定向到登录页。

package controller;

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

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession(false);
        if(session == null){
            //session不存在,说明用户本来就没有登录,这里直接跳转到登录页
            //重定向
            resp.sendRedirect("login.html");
            return;
        }
        //拿到session之后 删除session中的user对象即可
        //这里也不要判断session中的user对象存在不存在,存在删除,不存在也不影响
        session.removeAttribute("user");
        //重定向
        resp.sendRedirect("login.html");
    }
}

3️⃣编写后端代码

由于我们的a标签本身就可以发送http请求,所以我们只需要在注销所在a标签的href属性设置为我们约定的路径即可


1.7、实现发布博客功能

1️⃣约定前后端交互接口

前端使用form提交博客

  • 请求:

POST/blog

Content-Type:application/x-www-form-urlencoded

body: title=标题&content=......

  • 响应:

HTTP/1.1 302

Location:blog_list.html

发布成功直接跳转到列表页

2️⃣编写后端代码

从请求中拿到标题和正文,从会话中拿到用户的登录状态(作者id)、获取到系统时间。将这些进行拼接,就构成了一个blog对象,然后将这个对象插入到数据库中。这里由于我们约定的时候,路径为blog,所以我们在BlogServlet类中,添加一个doPost方法来处理博客发布的请求。

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        //1.先从请求中拿到标题和正文
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        if(title == null || title.equals("") || content == null || content.equals("")){
            //只要其中有一个符合条件进入判断,就认为内容是不全的。没有标题或者内容,博客发布失败
            String html = "<h3>title 或者 content 为空!新增博客是失败!</h3>";
            resp.setContentType("text/html;charset=uft8");
            resp.getWriter().write(html);
            return;
        }
        //2.从会话中拿到作者的id
       HttpSession session = req.getSession(false);
        if(session == null){
            String html = "<h3>当前用户未登录!新增博客是失败!</h3>";
            resp.setContentType("text/html;charset=uft8");
            resp.getWriter().write(html);
            return;
        }
        User user = (User)session.getAttribute("user");
        if(user == null){
            String html = "<h3>当前用户未登录!新增博客是失败!</h3>";
            resp.setContentType("text/html;charset=uft8");
            resp.getWriter().write(html);
            return;
        }
        //3.构造Blog对象
        Blog blog = new Blog();
        blog.setUserId(user.getUserId());
        blog.setTitle(title);
        blog.setContent(content);
        blog.setPostTime(new Timestamp(System.currentTimeMillis()));
        //4.插入blog对象到数据库中
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
        //5.跳转到博客列表页(重定向)
        resp.sendRedirect("blog_list.html");
    }
}

3️⃣编写前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客编辑页</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/blog-edit.css">
    <!-- 引入依赖 -->
    <script src="jquery.min.js"></script>
    <link rel="stylesheet" href="editor.md/css/editormd.min.css">
    <script  src="editor.md/lib/marked.min.js"></script>
    <script src="editor.md/lib/prettify.min.js"></script>
    <script src="editor.md/editormd.js"></script>
</head>
<body>
    <!-- 导航栏 nav 是导航整个次的缩写 -->
    <div class="nav">
        <!-- logo -->
        <img src="image/logo.png" alt="">
        <div class="title">我的博客系统</div>
        <!-- 只是一个空白,用来把后面的链接挤过去 -->
        <!-- 这是一个简单粗暴的写法 -->
        <div class="spancer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <!-- 这里的地址回头在说 -->
        <a href="#">注销</a>
    </div>
    <!-- 博客编辑页的版心 -->
    <div class="blog-edit-container">
        <form action="blog" method="post">
            <!-- 标题编辑区 -->
            <div class="title">
                <input type="text" id="title-input" name="title">
                <input type="submit" id="submit">
            </div>
            <!-- 博客编辑器 -->
            <!-- 把 mackdown编辑器放到这个div中-->
            <div id="editor">
                <textarea name="content" system="display:none;"></textarea>
            </div>
        </form>
        
    </div>
    <script src="js/app.js"></script>
    <!-- 针对editor.md初始化,创建一个编辑器对象,并关联到页面的某个元素中 -->
    <script>
        var editor =editormd("editor",{
            // 设置编辑器的宽度和高度
            width:"100%",
            height:"calc(100% - 50px)",
            // 这是编辑器中的初始内容
            markdowm:"# 在这里写下一篇博客",
            // 指定editor.md依赖的插件路径
            path:"editor.md/lib/",
            saveHTMLToTextarea:true
        });
        getLoginStatus();
    </script>
</body>
</html>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JVM】详解JVM的五大内存模型、可能出现的异常以及堆栈引用易错点

文章目录 1、堆(线程共享)2、方法区(线程共享)3、虚拟机栈&#xff08;线程私有&#xff09;4、本地方法栈(线程私有)5、程序计数器(线程私有)6、易错点 源自&#xff1a;深入理解Java虚拟机&#xff1a;JVM高级特性与最佳实践&#xff08;第3版&#xff09; 周志明 1、堆(线程…

使用克拉默法则进行三点定圆(二维)

目录 1.二维圆2.python代码3.计算结果 本文由CSDN点云侠原创&#xff0c;爬虫网站请自重。 1.二维圆 已知不共线的三个点&#xff0c;设其坐标为 ( x 1 , y 1 ) (x_1,y_1) (x1​,y1​)、 ( x 2 , y 2 ) (x_2,y_2) (x2​,y2​)、 ( x 3 , y 3 ) (x_3,y_3) (x3​,y3​)&#xf…

Ubuntu-文件和目录相关命令一

&#x1f52e;linux的文件系统结构 ⛳目录结构及目录路径 &#x1f9e9;文件系统层次结构标准FHS Filesystem Hierarchy Standard(文件系统层次结构标准&#xff09; Linux是开源的软件&#xff0c;各Linux发行机构都可以按照自己的需求对文件系统进行裁剪&#xff0c;所以众多…

Python - OpenCV实现摄像头人脸识别(亲测版)

要使用Python 3和OpenCV进行摄像头人脸识别&#xff0c;您可以按照以下步骤进行操作&#xff1a; 0.安装OpenCV软件 去官网直接下载安装即可,如果是C使用OpenCV&#xff0c;需要使用编译源码并配置环境变量。 1.安装OpenCV库 在命令行中输入以下命令&#xff1a; pip inst…

渗透测试基础知识(1)

渗透基础知识一 一、Web架构1、了解Web2、Web技术架构3、Web客户端技术4、Web服务端组成5、动态网站工作过程6、后端存储 二、HTTP协议1、HTTP协议解析2、HTTP协议3、http1.1与http2.0的区别4、HTTP协议 三、HTTP请求1、发起HTTP请求2、HTTP响应与请求-HTTP请求3、HTTP响应与请…

具有电动驱动的四足机器人模型研究(SimulinkMatlab代码)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

[NLP]LLM高效微调(PEFT)--LoRA

LoRA 背景 神经网络包含很多全连接层&#xff0c;其借助于矩阵乘法得以实现&#xff0c;然而&#xff0c;很多全连接层的权重矩阵都是满秩的。当针对特定任务进行微调后&#xff0c;模型中权重矩阵其实具有很低的本征秩&#xff08;intrinsic rank&#xff09;&#xff0c;因…

ajax axios json

目录 一、ajax概述 1. 概念 2. 实现方式 &#xff08;1&#xff09;原生的JS实现方式&#xff08;了解&#xff09; &#xff08;2&#xff09; JQeury实现方式 二、axios 介绍 三、axios使用 1. axios 发送get/post请求 2. axios验证用户名称是否存在 四、json 1. …

2023牛客暑期多校-J-Qu‘est-ce Que C‘est?(DP)

题意&#xff1a; 给定长度为n的数列,要求每个数都在的范围&#xff0c;且任意长度大于等于2的区间和都大于等于0&#xff0c;问方案数。。 思路&#xff1a; 首先要看出是dp题&#xff0c;用来表示遍历到第i位且后缀和最小为x的可行方案数&#xff08;此时的后缀可以只有最…

[golang gin框架] 42.Gin商城项目-微服务实战之后台Rbac微服务角色增删改查微服务

一.重构后台Rbac用户登录微服务功能 上一节讲解了后台Rbac微服务用户登录功能以及Gorm数据库配置单独抽离&#xff0c;Consul配置单独抽离&#xff0c;这一节讲解后台Rbac微服务角色增删改查微服务功能&#xff0c;Rbac微服务角色增删改查微服务和后台Rbac用户登录微服务是属于…

苍穹外卖day09——历史订单模块(用户端)+订单管理模块(管理端)

查询历史订单——需求分析与设计 产品原型 业务规则 分页查询历史订单 可以根据订单状态查询 展示订单数据时&#xff0c;需要展示的数据包括&#xff1a;下单时间、订单状态、订单金额、订单明细&#xff08;商品名称、图片&#xff09; 接口设计 查询历史订单——代码开…

AI聊天GPT三步上篮!

1、是什么&#xff1f; CHATGPT是OpenAI开发的基于GPT&#xff08;Generative Pre-trained Transformer&#xff09;架构的聊天型人工智能模型。也就是你问它答&#xff0c;根据网络抓去训练 2、怎么用&#xff1f; 清晰表达自己诉求&#xff0c;因为它就是一个AI助手&#…

Java书签 #解锁MyBatis的4种批量插入方式及ID返回姿势

1. 今日书签 项目开发中&#xff0c;我们经常会用到单条插入和批量插入。但是实际情况可能是&#xff0c;项目初期由于种种原因&#xff0c;在业务各处直接使用单条插入SQL进行开发&#xff08;未开启批处理&#xff09;&#xff0c;在后面的迭代中&#xff0c;系统性能问题渐…

无涯教程-jQuery - Ajax Tutorial函数

AJAX是用于创建交互式Web应用程序的Web开发技术。如果您了解JavaScript,HTML,CSS和XML,则只需花费一个小时即可开始使用AJAX。 为什么要学习Ajax? AJAX代表 A 同步 Ja vaScript和 X ML。 AJAX是一项新技术,可借助XML,HTML,CSS和Java Script创建更好,更快,更具交互性的Web应用…

解决Font family [‘sans-serif’] not found问题

序言 以下测试环境都是在 anaconda3 虚拟环境下执行。 激活虚拟环境 conda activate test_python_env 或 source activate test_python_env工具&#xff1a; WinSCP Visual Studio Code 这里笔者使用 WinSCP 工具连接&#xff0c;编辑工具是 Visual Studio Code 一、字体…

基于fpga_EP4CE6F17C8实现的呼吸灯

文章目录 前言实验手册&#xff08;EP4CE6F17C8&#xff09;一、实验目的二、实验原理理论原理 三、系统架构设计四、模块说明1&#xff0e;模块端口信号列表2&#xff0e;状态转移图3&#xff0e;时序图 五、仿真波形图六、引脚分配七、代码实现八、仿真代码九、板级验证效果 …

【论文阅读】Feature Inference Attack on Shapley Values

摘要 研究背景 近年来&#xff0c;解释性机器学习逐渐成为一个热门的研究领域。解释性机器学习可以帮助我们理解机器学习模型是如何进行预测的&#xff0c;它可以提高模型的可信度和可解释性。Shapley值是一种解释机器学习模型预测结果的方法&#xff0c;它可以计算每个特征对…

视频标注是什么?和图像数据标注的区别?

视频数据标注是对视频剪辑进行标注的过程。进行标注后的视频数据将作为训练数据集用于训练深度学习和机器学习模型。这些预先训练的神经网络之后会被用于计算机视觉领域。 自动化视频标注对训练AI模型有哪些优势 与图像数据标注类似&#xff0c;视频标注是教计算机识别对象…

springboot整合myabtis+mysql

一、pom.xml <!--mysql驱动包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--springboot与JDBC整合包--><dependency><groupId>org.springframework.b…

hcip——路由策略

要求&#xff1a; 基础配置 AR1 [R1]int g 0/0/0 [R1-GigabitEthernet0/0/0]ip add 12.0.0.1 24[R1-GigabitEthernet0/0/0]int g 0/0/1 [R1-GigabitEthernet0/0/1]ip add 14.0.0.1 24[R1]int loop0 [R1-LoopBack0]ip add 1.1.1.1 24[R1]rip 1 [R1-rip-1]vers 2 [R1-rip-1]net…