【JavaEE进阶】图书管理系统(未完待续)

目录

用户登录

添加图书

图书列表

修改图书

删除图书

批量删除

拦截器

🍃前言

什么是拦截器?

拦截器的基本使用

自定义拦截器

注册配置拦截器

拦截路径

拦截器执行流程

项目实现统一拦截

定义拦截器

注册配置拦截器



前⾯图书管理系统, 咱们只完成了⽤⼾登录和图书列表, 并且数据是模拟的. 接下来我们把其他功能进⾏完善.

这里没图书管理系统之前模板的可以参考我之前博客[SpringBoot]Spring MVC(6.0)----图书管理系统(初)-CSDN博客

在那基础上,我们继续进行代码书写

用户登录

第一步引入依赖,以及配置yml文件

复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  configuration: # ???? MyBatis ??? SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true  #??????
# ???? MyBatis ??? SQL
logging:
  file:
    name: logs/springboot.log

引入依赖需要我们加上一个插件

然后在pom.xml文件中右键

然后进行选择即可,这里我们需要选择我右面的依赖

其次,我们书写Controller--Service--Mapper结构

复制代码
package com.example.demo.controller;

import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/login")
    public boolean  login(String userName, String password, HttpSession session) {
        //校验用户信息是否合法.
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
            return false;
        }
        UserInfo userInfo=userService.queryUserByName(userName);
        if(userInfo==null)
        {
            return false;
        }
        //判断用户名和密码是否正确
        //理论上应该从数据库中获取, 但是目前还没学习 mybatis, 所以先这么写.
        if(userInfo!=null&&password.equals(userInfo.getPassword())) {
            return true;
        }
        userInfo.setPassword("");
        session.setAttribute("userName",userName);
        return false;
    }
}

为什么要把userInfo的密码设置为空再设置session呢?

因为存储到session中了.你如果不将密码设置为空的话,在获取到session就能看到密码

复制代码
import com.example.demo.model.UserInfo;
import com.example.demo.model.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

@Service
public class UserService{
    @Autowired
    private UserInfoMapper userInfoMapper;
    public UserInfo queryUserByName(String name) {
        return userInfoMapper.queryUserByName(name);
    }
}

package com.example.demo.model;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserInfoMapper {
    @Select("select * from user_info where delete_flag=0 and user_name = #{name}")
    UserInfo queryUserByName(String name);
}

这样我们就可以完成一个登录,数据库中存储信息

同时我们需要修改前端代码(因为我们将返回类型改变了)

复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/login.css">
    <script type="text/javascript" src="js/jquery.min.js"></script>
</head>

<body>
    <div class="container-login">
        <div class="container-pic">
            <img src="pic/computer.png" width="350px">
        </div>
        <div class="login-dialog">
            <h3>登陆</h3>
            <div class="row">
                <span>用户名</span>
                <input type="text" name="userName" id="userName" class="form-control">
            </div>
            <div class="row">
                <span>密码</span>
                <input type="password" name="password" id="password" class="form-control">
            </div>
            <div class="row">
                <button type="button" class="btn btn-info btn-lg" onclick="login()">登录</button>
            </div>
        </div>
    </div>
    <script src="js/jquery.min.js"></script>
    <script>
        function login() {
            $.ajax({
                url : "/user/login",
                type : "post",
                data : {
                    userName : $("#userName").val(),
                    password : $("#password").val(),
                },
                success:function(result) {
                    if(result==false) {
                        alert("用户名或密码错误,请重新输入");
                    }else {
                        location.href = "book_list.html";
                    }
                }
            });
        }
    </script>
</body>

</html>

添加图书

控制层:

复制代码
@RequestMapping("/addBook")
    public String addBook(BookInfo bookInfo){
        System.out.println("添加图书"+bookInfo);
        if (!StringUtils.hasLength(bookInfo.getBookName())
                || !StringUtils.hasLength(bookInfo.getAuthor())
                || bookInfo.getCount()==null
                || bookInfo.getPrice()==null
                || !StringUtils.hasLength(bookInfo.getPublish())
                || bookInfo.getStatus() ==null
        ){
            return "输⼊参数不合法, 请检查⼊参!";
        }
        try {
            bookService.addBook(bookInfo);
            return "";
        }
        catch (Exception e){
            System.out.println("增添错误");
          return e.getMessage();
        }
    }

业务层

复制代码
public void addBook(BookInfo bookInfo) {
        bookInfoMapper.insertBook(bookInfo);
    }

数据层:

复制代码
@Mapper
public interface BookInfoMapper {
    @Insert("insert into book_info (book_name,author,count,price,publish,status) " + "values (#{bookName},#{author},#{count},#{price},#{publish},#{status})")
    Integer insertBook(BookInfo bookInfo);
}

前端修改代码

复制代码
   function add() {
           $.ajax({
                type: "post",
                url: "/book/addBook",
                data: $("#addBook").serialize(),
                 success: function (result) {
                 if (result == "") {
                    location.href = "book_list.html"
                 } else {
                        console.log(result);
                        alert("添加失败:" + result);
                        }
                    },
                    error: function (error) {
                        console.log(error);
                     }
                });
        }

然后页面就可以正常访问了

前后对比:

图书列表

可以看到, 添加图书之后, 跳转到图书列表⻚⾯, 并没有显⽰刚才添加的图书信息, 接下来我们来实现图 书列表
我们之前做的表⽩墙查询功能,是将数据库中所有的数据查询出来并展⽰到⻚⾯上,试想如果数据库 中的数据有很多(假设有⼗⼏万条)的时候,将数据全部展⽰出来肯定不现实,那如何解决这个问题呢?

使⽤分⻚解决这个问题。每次只展⽰⼀⻚的数据,⽐如:⼀⻚展⽰10条数据,如果还想看其他的数
据,可以通过点击⻚码进⾏查询

1.添加数据

复制代码
INSERT INTO `book_info` ( book_name, author, count, price, publish )
VALUES
( '图书2', '作者2', 29, 22.00, '出版社2' ),( '图书3', '作者2', 29, 22.00, '出版社
3' ),
( '图书4', '作者2', 29, 22.00, '出版社1' ),( '图书5', '作者2', 29, 22.00, '出版社
1' ),
( '图书6', '作者2', 29, 22.00, '出版社1' ),( '图书7', '作者2', 29, 22.00, '出版社
1' ),
( '图书8', '作者2', 29, 22.00, '出版社1' ),( '图书9', '作者2', 29, 22.00, '出版社
1' ),
( '图书10', '作者2', 29, 22.00, '出版社1'),( '图书11', '作者2', 29, 22.00, '出版 社1'),
( '图书12', '作者2', 29, 22.00, '出版社1'),( '图书13', '作者2', 29, 22.00, '出版 社1'),
( '图书14', '作者2', 29, 22.00, '出版社1'),( '图书15', '作者2', 29, 22.00, '出版 社1'),
( '图书16', '作者2', 29, 22.00, '出版社1'),( '图书17', '作者2', 29, 22.00, '出版 社1'),
( '图书18', '作者2', 29, 22.00, '出版社1'),( '图书19', '作者2', 29, 22.00, '出版 社1'),
( '图书20', '作者2', 29, 22.00, '出版社1'),( '图书21', '作者2', 29, 22.00, '出版 社1');

2.了解分页的sql原理

查找第1到10条

复制代码
SELECT * FROM book_info LIMIT 0,10

3.算法可知:开始索引 = (当前⻚码 - 1) * 每⻚显⽰条数

4.前端在发起查询请求时,需要向服务端传递的参数

currentPage 当前⻚码 //默认值为1
pageSize 每⻚显⽰条数 //默认值为10
5后端响应时, 需要响应给前端的数据
records 所查询到的数据列表(存储到List 集合中)
total 总记录数 (⽤于告诉前端显⽰多少⻚, 显⽰⻚数为: (total + pageSize -1)/pageSize
显⽰⻚数totalPage 计算公式为 : total % pagesize == 0 ? total / pagesize : (total /
pagesize)+1;)

6.翻⻚请求和响应部分, 我们通常封装在两个对象中

翻页请求对象

复制代码
@Data
public class PageRequest {
 private int currentPage = 1; // 当前⻚
 private int pageSize = 10; // 每⻚中的记录数 }

我们需要根据currentPage 和pageSize ,计算出来开始索引

所以PageRequest修改为

复制代码
package com.example.demo.model;

import lombok.Data;

@Data
public class PageRequest {
    private int currentPage = 1; // 当前⻚
    private int pageSize = 10; // 每⻚中的记录数
    private int offset=1;
    public int getOffset() {
        return (currentPage-1) * pageSize;

    }
}

翻⻚列表结果类:

复制代码
import lombok.Data;
import java.util.List;
@Data
public class PageResult<T> {
 private int total;//所有记录数
 private List<T> records; // 当前⻚数据
 public PageResult(Integer total, List<T> records) {
 this.total = total;
 this.records = records;
 }
}

7.约定前后端交互接⼝


我们约定, 浏览器给服务器发送⼀个 /book/getListByPage 这样的 HTTP 请求, 通过
currentPage 参数告诉服务器, 当前请求为第⼏⻚的数据, 后端根据请求参数, 返回对应⻚的数据
第⼀⻚可以不传参数, currentPage默认值为 1
7. 创建enmus⽬录, 创建BookStatus类:

复制代码
package com.example.demo.model;

public enum BookStatus {
    DELETED(0,"⽆效"),
    NORMAL(1,"可借阅"),
    FORBIDDEN(2,"不可借阅");
    private Integer code;
    private String name;
    BookStatus(int code, String name) {
        this.code = code;
        this.name = name;
    }
    public static BookStatus getNameByCode(Integer code){
        switch (code){
            case 0: return DELETED;
            case 1: return NORMAL;
            case 2: return FORBIDDEN;
        }
        return null;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

8.完善PageResult类

复制代码
package com.example.demo.model;

import lombok.Data;
import java.util.List;
@Data
public class PageResult<T> {
    public int total;//所有记录数
    private List<T> records; // 当前⻚数据
    private PageRequest pageRequest;
    public PageResult(Integer total, PageRequest pageRequest, List<T> records)
    {
        this.total = total;
        this.pageRequest = pageRequest;
        this.records = records;
    }
}

将PageRequest类引入,便于后面的分页插件获取信息
分⻚组件需要提供⼀些信息: totalCounts: 总记录数, pageSize: 每⻚的个数, visiblePages: 可视⻚数 ,currentPage: 当前⻚码
这些信息中, pageSize 和 visiblePages 前端直接设置即可. totalCounts 后端已经提供, currentPage 也 可以从参数中取到, 但太复杂了, 咱们直接由后端返回即可.

9.完善 BookController,BookService,BookInfoMapper

复制代码
  @RequestMapping("/getListByPage")
    public PageResult<BookInfo> getListByPage(PageRequest pageRequest) {
        log.info("获取图书列表{}",pageRequest);
        PageResult<BookInfo> pageResult = bookService.getBookListByPage(pageRequest);
        return pageResult;
    }

public PageResult<BookInfo> getBookListByPage(PageRequest pageRequest) {
        Integer count = bookInfoMapper.count();
        List<BookInfo> books = bookInfoMapper.queryBookListByPage(pageRequest);
        for (BookInfo book:books){
            book.setStatusCN(BookStatus.getNameByCode(book.getStatus()).getName());
        }
        return new PageResult<>(count,pageRequest, books);
    }

@Mapper
public interface BookInfoMapper {
    @Insert("insert into book_info (book_name,author,count,price,publish,status) " + "values (#{bookName},#{author},#{count},#{price},#{publish},#{status})")
    Integer insertBook(BookInfo bookInfo);
    @Select("select count(1) from book_info where status<>0")
    Integer count();
    @Select("select * from book_info where status !=0 order by id desc limit #{offset}, #{pageSize}")
    List<BookInfo> queryBookListByPage(PageRequest pageRequest);
}

10.接下来处理分⻚信息

jQuery 分页插件 : jqPaginator | 菜鸟教程

使用教程,修改里面的部分值即可


onPageChange :回调函数,当换⻚时触发(包括初始化第⼀⻚的时候),会传⼊两个参数:
1、"⽬标⻚"的⻚码page,Number类型
2、触发类型type,可能的值:"init"(初始化),"change"(点击分⻚)

11.编写前端代码(前后端交互,个人认为最为难人的部分,需要重点理解)

复制代码
<!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/bootstrap.min.css">

    <link rel="stylesheet" href="css/list.css">
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript" src="js/bootstrap.min.js"></script>
    <script src="js/jq-paginator.js"></script>

</head>

<body>
    <div class="bookContainer">
        <h2>图书列表展示</h2>
        <div class="navbar-justify-between">
            <div>
                <button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button>
                <button class="btn btn-outline-info" type="button" onclick="batchDelete()">批量删除</button>
            </div>
        </div>

        <table>
            <thead>
                <tr>
                    <td>选择</td>
                    <td class="width100">图书ID</td>
                    <td>书名</td>
                    <td>作者</td>
                    <td>数量</td>
                    <td>定价</td>
                    <td>出版社</td>
                    <td>状态</td>
                    <td class="width200">操作</td>
                </tr>
            </thead>
            <tbody>

            </tbody>
        </table>

        <div class="demo">
            <ul id="pageContainer" class="pagination justify-content-center"></ul>
        </div>
        <script>
           
            function getQueryParam(name) {
                const urlParams = new URLSearchParams(window.location.search);
                return urlParams.get(name);
            }

            // 修复:设置默认参数currentPage = 1,避免初始调用时参数缺失
            function getBookList(currentPage=1) {
                $.ajax({
                    type: "get",
                    // 修复:确保参数正确拼接,避免出现undefined
                    url: "/book/getListByPage?currentPage=" + currentPage,
                    success: function (result) {
                        console.log(result);
                        if (result != null && result.records) { // 修复:字段名records(原resords拼写错误)
                            var finalHtml = "";
                            // 遍历图书数据(修复字段名resords -> records)
                            for (var book of result.records) {
                                finalHtml += '<tr>';
                                finalHtml += '<td><input type="checkbox" name="selectBook" value="' + book.id + '" class="book-select"></td>';
                                finalHtml += '<td>' + book.id + '</td>';
                                finalHtml += '<td>' + book.bookName + '</td>';
                                finalHtml += '<td>' + book.author + '</td>';
                                finalHtml += '<td>' + book.count + '</td>';
                                finalHtml += '<td>' + book.price + '</td>';
                                finalHtml += '<td>' + book.publish + '</td>';
                                finalHtml += '<td>' + book.statusCN + '</td>';
                                finalHtml += '<td><div class="op">';
                                finalHtml += '<a href="book_update.html?bookId=' + book.id + '">修改</a>'; // 修复:链接中去除空格(bookId后无空格)
                                finalHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + book.id + ')">删除</a>';
                                finalHtml += '</div></td>';
                                finalHtml += "</tr>";
                            }
                            $("tbody").html(finalHtml);

                            // 初始化分页插件
                            $("#pageContainer").jqPaginator({
                                totalCounts: result.total, // 总记录数(需与后端返回字段一致)
                                pageSize: 10,             // 每页条数
                                visiblePages: 5,          // 可见页码数
                                currentPage: result.pageRequest.currentPage, // 修复:从返回结果中获取当前页码(根据后端实际字段名调整)
                                first: '<li class="page-item"><a class="page-link">首页</a></li>',
                                prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页</a></li>',
                                next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页</a></li>',
                                last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页</a></li>',
                                page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}</a></li>',
                                // 修复:分页切换时直接调用getBookList加载数据,而非跳转页面
                               onPageChange: function (Page, type) {
                                    if (type != 'init') {
                                         location.href = "book_list.html?currentPage=" + Page;
                                      //  getBookList(Page);
                                    }
                                }
                            });
                        }
                    },
                    error: function (xhr) {
                        console.error("请求失败:", xhr.responseText); // 增加错误日志,便于调试
                    }
                });
            }

            // 初始调用时无需传参(函数已设置默认值)
             const initialPage = parseInt(getQueryParam("currentPage")) || 1;
            getBookList(initialPage);
           // getBookList();


            function deleteBook(id) {
                var isDelete = confirm("确认删除?");
                if (isDelete) {
                    //删除图书
                    alert("删除成功");
                }
            }
            function batchDelete() {
                var isDelete = confirm("确认批量删除?");
                if (isDelete) {
                    //获取复选框的id
                    var ids = [];
                    $("input:checkbox[name='selectBook']:checked").each(function () {
                        ids.push($(this).val());
                    });
                    console.log(ids);
                    alert("批量删除成功");
                }
            }

        </script>
    </div>
</body>

</html>

修改图书

进⼊修改⻚⾯, 需要显⽰当前图书的信息

复制代码
[请求]
/book/queryBookById?bookId=25
[参数] ⽆[响应] {
 "id": 25,
 "bookName": "图书21",
 "author": "作者2",
 "count": 999,
 "price": 222.00,
 "publish": "出版社1",
 "status": 2,
 "statusCN": null,
 "createTime": "2023-09-04T04:01:27.000+00:00",
 "updateTime": "2023-09-05T03:37:03.000+00:00"
}

点击修改按钮, 修改图书信息

复制代码
[请求]
/book/updateBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
id=1&bookName=图书1&author=作者1&count=23&price=36&publish=出版社1&status=1
[响应]
"" //失败信息, 成功时返回空字符串

我们约定, 浏览器给服务器发送⼀个 /book/updateBook 这样的 HTTP 请求, form表单的形式来 提交数据
服务器返回处理结果, 返回""表⽰添加图书成功, 否则, 返回失败信息

实现服务器代码

BookController代码

复制代码
   @RequestMapping("/queryBookById")
    public BookInfo queryBookById(Integer bookId) {
        if (bookId == null || bookId <= 0) {
            return new BookInfo();
        }
        BookInfo bookInfo = bookService.queryBookById(bookId);
        return bookInfo;
    }
    @RequestMapping("/updateBook")
    public String updateBook(BookInfo bookInfo) {
        log.info("修改图书:{}", bookInfo);
        try {
            bookService.updateBook(bookInfo);
            return "";
        } catch (Exception e) {
            log.error("修改图书失败", e);
            return e.getMessage();
        }
    }

BookService代码

复制代码
  public BookInfo queryBookById(Integer bookId) {
        return bookInfoMapper.queryBookById(bookId);
    }

    public void updateBook(BookInfo bookInfo) {
        bookInfoMapper.updateBook(bookInfo);
    }

BookInfoMapper代码

复制代码
@Select("select id, book_name, author, count, price, publish, `status`, 
create_time, update_time " +
 "from book_info where id=#{bookId} and status<>0")
BookInfo queryBookById(Integer bookId);

前端代码

复制代码
<!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/bootstrap.min.css">
    <link rel="stylesheet" href="css/add.css">
</head>

<body>

    <div class="container">
        <div class="form-inline">
            <h2 style="text-align: left; margin-left: 10px;"><svg xmlns="http://www.w3.org/2000/svg" width="40"
                    fill="#17a2b8" class="bi bi-book-half" viewBox="0 0 16 16">
                    <path
                        d="M8.5 2.687c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z" />
                </svg>
                <span>修改图书</span>
            </h2>
        </div>

        <form id="updateBook">

            <input type="hidden" class="form-control" id="bookId" name="id">
            <div class="form-group">
                <label for="bookName">图书名称:</label>
                <input type="text" class="form-control" id="bookName" name="bookName">
            </div>
            <div class="form-group">
                <label for="bookAuthor">图书作者</label>
                <input type="text" class="form-control" id="bookAuthor" name="author" />
            </div>
            <div class="form-group">
                <label for="bookStock">图书库存</label>
                <input type="text" class="form-control" id="bookStock" name="count" />
            </div>
            <div class="form-group">
                <label for="bookPrice">图书定价:</label>
                <input type="number" class="form-control" id="bookPrice" name="price">
            </div>
            <div class="form-group">
                <label for="bookPublisher">出版社</label>
                <input type="text" id="bookPublisher" class="form-control" name="publish" />
            </div>
            <div class="form-group">
                <label for="bookStatus">图书状态</label>
                <select class="custom-select" id="bookStatus" name="status">
                    <option value="1" selected>可借阅</option>
                    <option value="2">不可借阅</option>
                </select>
            </div>
            <div class="form-group" style="text-align: right">
                <button type="button" class="btn btn-info btn-lg" onclick="update()">确定</button>
                <button type="button" class="btn btn-secondary btn-lg" onclick="javascript:history.back()">返回</button>
            </div>
        </form>
    </div>
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script>
        $.ajax({
            type: "get",
            url: "/book/queryBookById" + location.search,
            success: function (book) {
                if (book != null) {
                    $("#bookId").val(book.id);
                    $("#bookName").val(book.bookName);
                    $("#bookAuthor").val(book.author);
                    $("#bookStock").val(book.count);
                    $("#bookPrice").val(book.price);
                    $("#bookPublisher").val(book.publish);
                    $("#bookStatus").val(book.status);
                }
            }
        });
        function update() {
            $.ajax({
                type: "post",
                url: "/book/updateBook",
                data: $("#updateBook").serialize(),
                success: function (result) {
                    if (result == "") {
                        location.href = "book_list.html"
                    } else {
                        console.log(result);
                        alert("修改失败:" + result);
                    }
                },
                error: function (error) {
                    console.log(error);
                }
            });
        }
    </script>
</body>

</html>

我们修改图书信息, 是根据图书ID来修改的, 所以需要前端传递的参数中, 包含图书ID
在form表单中, 再增加⼀个隐藏输⼊框, 存储图书ID, 随 $("#updateBook").serialize()
⼀起提交到后端

  1. 获取url中参数的值(⽐较复杂, 需要拆分url)
  1. 在form表单中, 再增加⼀个隐藏输⼊框, 存储图书ID, 随 $("#updateBook").serialize()
    ⼀起提交到后端
    我们采⽤第⼆种⽅式
    在form表单中,添加隐藏输⼊框

    <form id="updateBook">
    <label for="bookName">图书名称:</label>

hidden 类型的 <input> 元素
隐藏表单, ⽤⼾不可⻅、不可改的数据,在⽤⼾提交表单时,这些数据会⼀并发送出
使⽤场景: 正被请求或编辑的内容的 ID. 这些隐藏的 input 元素在渲染完成的⻚⾯中完全不可⻅,⽽ 且没有⽅法可以使它重新变为可⻅
⻚⾯加载时, 给该hidden框赋值

复制代码
     $("#bookId").val(book.id);

删除图书

删除分为 逻辑删除 和物理删除
逻辑删除
逻辑删除也称为软删除、假删除、Soft Delete,即不真正删除数据,⽽在某⾏数据上增加类型 is_deleted的删除标识,⼀般使⽤UPDATE语句
物理删除
物理删除也称为硬删除,从数据库表中删除某⼀⾏或某⼀集合数据,一般用DELETE语句
数据是公司的重要财产, 通常情况下, 我们采⽤逻辑删除的⽅式, 当然也可以采⽤[物理删除+归档]
实现客⼾端代码

复制代码
            function deleteBook(id) {
                var isDelete = confirm("确认删除?");
                if (isDelete) {
                    //删除图书
                    $.ajax({
                        type: "post",
                        url: "/book/updateBook",
                        data: {
                            id: id,
                            status: 0
                        },
                        success: function () {
                            //重新刷新⻚⾯
                            location.href = "book_list.html"
                        }
                    });
                }
            }

批量删除

批量删除, 其实就是批量修改数据

点击[批量删除]按钮时, 只需要把复选框选中的图书的ID,发送到后端即可
多个id, 我们使⽤List的形式来传递参数
实现服务器代码

复制代码
@RequestMapping("/batchDeleteBook")
    public boolean batchDeleteBook(@RequestParam List<Integer> ids){
        log.info("批量删除图书, ids:{}",ids);
        try{
            bookService.batchDeleteBook(ids);
        }catch (Exception e){
            log.error("批量删除异常,e:",e);
            return false;
        }
        return true;
    }
  public void batchDeleteBook(List<Integer> ids) {
        bookInfoMapper.batchDeleteBook(ids);

    void batchDeleteBook(List<Integer> ids);

xml

复制代码
<update id="batchDeleteBook">
        update book_info set status=0 where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </update>

拦截器

🍃前言

前面我们基本实现了图书股管理系统的功能,但是我们依旧存在一个问题。

就是我们不论是否登录,我们直接访问图书列表。也可以进行访问及修改

而我们希望达到的效果是,必须要进行登录后才能进行一系列操作

这里我们使用拦截器来完成着一系列操作

什么是拦截器?

拦截器是Spring框架提供的核⼼功能之⼀,主要⽤来拦截⽤⼾的请求,在指定⽅法前后,根据业务需要执⾏预先设定的代码.

也就是说,允许开发⼈员提前预定义⼀些逻辑,在⽤⼾的请求响应前后执⾏.也可以在⽤⼾请求前阻⽌其执⾏.

在拦截器当中,开发⼈员可以在应⽤程序中做⼀些通⽤性的操作,⽐如通过拦截器来拦截前端发来的请求,判断Session中是否有登录⽤⼾的信息.如果有就可以放⾏,如果没有就进⾏拦截.

就⽐如我们去银⾏办理业务,在办理业务前后,就可以加⼀些拦截操作,办理业务之前,先取号,如果带⾝份证了就取号成功。业务办理结束,给业务办理⼈员的服务进⾏评价.这些就是"拦截器"做的⼯作

拦截器的基本使用

拦截器的使⽤步骤分为两步:

  1. 定义拦截器
  2. 注册配置拦截器

自定义拦截器

⾃定义拦截器:实现HandlerInterceptor接⼝,并重写其所有⽅法

复制代码
package com.example.demo.configuration;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("执行目标方法前的代码");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("目标执行完后的代码");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("目标试图渲染后的代码");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

这里涉及到的三个方法

preHandle()⽅法:⽬标⽅法执⾏前执⾏. 返回true:继续执⾏后续操作;返回false:中断后续操作.

postHandle()⽅法:⽬标⽅法执⾏后执⾏

afterCompletion()⽅法:视图渲染完毕后执⾏,最后执⾏(后端开发现在⼏乎不涉及视图,暂不了解)

注册配置拦截器

注册配置拦截器:实现WebMvcConfigurer接⼝,并重写addInterceptors⽅法

WebMvcConfigurer这个接口并不是只给拦截器使用的,而是WebMVC相关的配置都在这里

复制代码
package com.example.demo.configuration;

import com.example.demo.model.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                 .addPathPatterns("/**");
    }
}

启动服务,试试访问任意请求,观察后端⽇志

们把拦截器中preHandle⽅法的返回值改为false,再观察运⾏结果

运行结果:可以看到,拦截器拦截了请求,没有进⾏响应

拦截路径

关于注册配置拦截器的拦截路劲,拦截路径是指我们定义的这个拦截器,对哪些请求⽣效.我们在注册配置拦截器的时候,通过addPathPatterns() ⽅法指定要拦截哪些请求.也可以通过
excludePathPatterns() 指定不拦截哪些请求
.上述代码中,我们配置的是 /** ,表⽰拦截所有的请求.

拦截器中除了可以设置 /** 拦截所有资源外,还有⼀些常⻅拦截路径设置,比如在该项目中

拦截器执行流程

我们先来看一下正常的执行流程

当我们有了拦截器以后,我们的执行流程为

1.添加拦截器后,执⾏Controller的⽅法之前,请求会先被拦截器拦截住.执⾏ preHandle() ⽅法,这个⽅法需要返回⼀个布尔类型的值.如果返回true,就表⽰放⾏本次操作,继续访问controller中的⽅法.如果返回false,则不会放⾏(controller中的⽅法也不会执⾏).

2.controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 这个⽅法以及afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据.

项目实现统一拦截

定义拦截器

首先定义一个常量类来存放我们的sessionid常量等等

复制代码
package com.example.demo.model;

public class Constants {
    public static final String SESSION_USER_KEY="userName";
}

再来就是拦截器(逻辑简单,查看有没有现在的这个session即可)

复制代码
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("登录拦截器校验...");

        HttpSession session = request.getSession();
        UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
        if (userInfo!=null && userInfo.getId()>=0){
            return true;
        }
        response.setStatus(401);//401 表示未认证登录
        return false;
    }
}

代码解释如下:

  1. 对服务中所存在的session进行判断,如果存在,则返回true,放行
  2. 若不存在,则使用setStatus()方法设置http状态码为401,前端收到响应进行跳转到登录页面

当前按照上述配置拦截器的代码来看,会拦截所有的路径,那么此时在没有登录的情况下,访问每个接口都会进行拦截,包括登录接口

所以我们需要把上述配置拦截器中的拦截路径重新配置一下

注册配置拦截器

注意:拦截器也会拦截前端请求

复制代码
 @Configuration
 public class WebConfig implements WebMvcConfigurer {
     //⾃定义的拦截器对象 
     @Autowired
     private LoginInterceptor loginInterceptor;
 
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
        //注册⾃定义拦截器对象
         registry.addInterceptor(loginInterceptor)
                 .addPathPatterns("/**")//设置拦截器拦截的请求路径(/**表⽰拦截所有请求 
                 .excludePathPatterns("/user/login")//设置拦截器排除拦截的路径
                 .excludePathPatterns("/**/*.js") //排除前端静态资源
                .excludePathPatterns("/**/*.css")
                .excludePathPatterns("/**/*.png")
                .excludePathPatterns("/**/*.html");
     }
 }

访问用户登录接口:此时就可以访问了

希望你们可以从这一个简单项目中学到一个项目的基础思路知识

相关推荐
JiaJZhong13 分钟前
力扣.最长回文子串(c++)
java·c++·leetcode
Xy91023 分钟前
开发者视角:App Trace 一键拉起(Deep Linking)技术详解
java·前端·后端
一个混子程序员38 分钟前
Mockito不常用的方法
java
敏叔V5871 小时前
SpringBoot实现MCP
java·spring boot·后端
小袁拒绝摆烂1 小时前
SpringCache整合SpringBoot使用
java·spring boot·后端
水果里面有苹果1 小时前
19-C#静态方法与静态类
java·开发语言·c#
BUG批量生产者1 小时前
[746] 使用最小花费爬楼梯
java·开发语言
慕y2742 小时前
Java学习第二十四部分——JavaServer Faces (JSF)
java·开发语言·学习
JosieBook2 小时前
【Java编程动手学】深入剖析Java网络编程:原理、协议与应用
java·udp·tcp
black_blank2 小时前
st表 && csp37 第四题 集体锻炼
java·数据结构·算法