[Java EE进阶] 图书管理系统(2)

图书管理系统(1)

https://blog.csdn.net/Boop_wu/article/details/158656231?fromshare=blogdetail&sharetype=blogdetail&sharerId=158656231&sharerefer=PC&sharesource=Boop_wu&sharefrom=from_linkhttps://blog.csdn.net/Boop_wu/article/details/158656231?fromshare=blogdetail&sharetype=blogdetail&sharerId=158656231&sharerefer=PC&sharesource=Boop_wu&sharefrom=from_link

本次更新添加了数据库,以及完善了相关功能

准备工作 :

前置基础

  • 整体架构 : 前端页面+Ajax+SpringBoot 后端+MySQL+Mybatis
  • 前后端交互 : 前端通过 Ajax 向后端接口发送轻轨 , 后端处理数据库操作后 , 返回数据给前端 ; 前端解析数据 , 渲染页面 , 跳转窗口
  • 登录 : 登录成功后后端会把用户信息存入 HttpSession , 浏览器自动溴代 Cookie , 后续操作维持登录状态
  • 统一返回 : 后端用 Result 类 , 统一返回 , 前端固定逻辑

SQL

sql 复制代码
-- 创建数据库
DROP DATABASE IF EXISTS book_test;

CREATE DATABASE book_test DEFAULT CHARACTER SET utf8mb4;

-- 用户表
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (
        `id` INT NOT NULL AUTO_INCREMENT,
        `user_name` VARCHAR ( 128 ) NOT NULL,
        `password` VARCHAR ( 128 ) NOT NULL,
        `delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
        `create_time` DATETIME DEFAULT now(),
        `update_time` DATETIME DEFAULT now() ON UPDATE now(),
        PRIMARY KEY ( `id` ),
UNIQUE INDEX `user_name_UNIQUE` ( `user_name` ASC )) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '用户表';

-- 图书表
DROP TABLE IF EXISTS book_info;
CREATE TABLE `book_info` (
        `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
        `book_name` VARCHAR ( 127 ) NOT NULL,
        `author` VARCHAR ( 127 ) NOT NULL,
        `count` INT ( 11 ) NOT NULL,
        `price` DECIMAL (7,2 ) NOT NULL,
        `publish` VARCHAR ( 256 ) NOT NULL,
        `status` TINYINT ( 4 ) DEFAULT 1 COMMENT '0-无效, 1-正常, 2-不允许借阅',
        `create_time` DATETIME DEFAULT now(),
        `update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;

-- 初始化数据
INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "admin", "admin" );
INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "zhangsan", "123456" );

-- 初始化图书数据
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('活着', '余华', 29, 22.00, '北京文艺出版社');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('平凡的世界', '路遥', 5, 98.56, '北京十月文艺出版社');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('三体', '刘慈欣', 9, 102.67, '重庆出版社');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('金字塔原理', '麦肯锡', 16, 178.00, '民主与建设出版社');

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');

引入 如下两个依赖

配置文件

配置数据库和日志以及 Mapper.xml

XML 复制代码
spring:
  application:
    name: MybatisDemo
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
  mapper-locations: classpath:mapper/**Mapper.xml


logging:
  pattern:
    date-format: HH:mm:ss.SSS

定义两个实体类对应数据库

java 复制代码
package com.boop.book.model;

import lombok.Data;

import java.math.BigDecimal;
import java.sql.Date;

@Data
public class BookInfo {

    private Integer id;//图书ID
    private String bookName;//书
    private String author;//作者
    private Integer count;//数量
    private BigDecimal price;//价格
    private String publish;//出版社
    private Integer status;//状态 0无效,1允许借阅,2不可借阅
    private String statusCN;

    private Date createTime;//创建时间
    private Date updataTime;//更新时间
}
java 复制代码
package com.boop.book.model;

import lombok.Data;

import java.sql.Date;

@Data
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;

}

项目结构 :

功能 1 : 用户登录

需求 :

从数据库中查询用户 , 如果能查到且密码一致就登录成功

接口定义:

实现服务端代码

UserController
java 复制代码
package com.boop.book.controller;

import com.boop.book.Mapper.UserInfoMapper;
import com.boop.book.model.UserInfo;
import com.boop.book.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;
    @RequestMapping("/login")
    public boolean login(String name, String password, HttpSession session){
        //账号或密码为空
        if(!StringUtils.hasLength(name)||!StringUtils.hasLength(password)){
            return false;
        }
        UserInfo userInfo = userService.queryUserByName(name);
        if(userInfo == null){
            return false;
        }
        //模拟验证数据 , 账号密码正确
        if("admin".equals(name)&&"admin".equals(password)){
            userInfo.setPassword("");
            session.setAttribute("userName",name);
            return true;
        }
        //账号或密码错误
        return false;
    }
}
UserService
java 复制代码
package com.boop.book.service;

import com.boop.book.Mapper.UserInfoMapper;
import com.boop.book.model.UserInfo;
import jakarta.annotation.Resource;

@Service
public class UserService {
    @Resource
    private UserInfoMapper userInfoMapper;

    public UserInfo queryUserByName(String name){
        return userInfoMapper.queryUserBtName(name);
    }
}
UserInfoMapper
java 复制代码
package com.boop.book.Mapper;

import com.boop.book.model.UserInfo;
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 = #{username} ")
    UserInfo queryUserBtName(String username);
}

前端代码

html 复制代码
<!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({
                type:"post",
                url:"/user/login",
                data:{
                    name:$("#userName").val(),
                    password:$("#password").val()
                },
                success:function(result){
                    if(result){
                        location.href = "book_list.html";
                    }else {
                        alert("账号或密码不正确");
                    }
                }
            });
        }
    </script>
</body>

</html>

原理 :

Ajax 发起请求后 , 后端 return true/false , 会传入前端 success 函数的参数中

测试:

测试 1:

http://127.0.0.1:8080/user/login? name=admin&password=admin

为 true

测试 2:

http://127.0.0.1:8080/login.html

输入密码 :

①admin 123

②admin admin

正常跳转

功能 2 : 添加图书

需求分析 :

点击添加图书,并填写图书信息点击确定后返回图书列表

接口定义

服务器代码

BookInfoMapper
java 复制代码
package com.boop.book.Mapper;

import com.boop.book.model.BookInfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface BookInfoMapper {
    @Insert("insert into book_info (book_name,author,count,price,publish,status) values " +
            "(#{bookName},#{author},#{count},#{price},#{publish},#{status})")
    Integer inserBook(BookInfo bookInfo);
}
BookService
vb 复制代码
`package com.boop.book.service;

import com.boop.book.Mapper.BookInfoMapper;
import com.boop.book.dao.BookDao;
import com.boop.book.model.BookInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;
@Component
public class BookService {
    @Autowired
    private BookDao bookDao ;
    public List<BookInfo> getList(){
        //获取数据
        List<BookInfo> books = bookDao.mockData() ;
        //处理页面展示
        for(BookInfo book:books){
            if(book.getStatus() == 1){
                book.setStatusCN("可借阅");
            }else{
                book.setStatusCN("不可借阅");
            }
        }
        return books;
    }

    @Autowired
    private BookInfoMapper bookInfoMapper;
    public void addBook(BookInfo bookInfo){
        bookInfoMapper.inserBook(bookInfo);
    }
}`
BookController
java 复制代码
package com.boop.book.controller;

import com.boop.book.Constants.Constants;
import com.boop.book.model.*;
import com.boop.book.service.BookService;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/book")
public class BookController {
    @Autowired
    private BookService bookService;
    @RequestMapping("/addBook")
    public String addBook(BookInfo bookInfo){
        log.info("添加图书 : {}"+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){
            log.error("添加如数失败{}",e);
            return e.getMessage();
        }
    }
}

前端代码

补全 book_add.html 中的 add 方法

html 复制代码
<!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="addBook">
            <div class="form-group">
                <label for="bookName">图书名称:</label>
                <input type="text" class="form-control" placeholder="请输入图书名称" id="bookName" name="bookName">
            </div>
            <div class="form-group">
                <label for="bookAuthor">图书作者</label>
                <input type="text" class="form-control" placeholder="请输入图书作者" id="bookAuthor" name="author" />
            </div>
            <div class="form-group">
                <label for="bookStock">图书库存</label>
                <input type="text" class="form-control" placeholder="请输入图书库存" id="bookStock" name="count"/>
            </div>

            <div class="form-group">
                <label for="bookPrice">图书定价:</label>
                <input type="number" class="form-control" placeholder="请输入价格" id="bookPrice" name="price">
            </div>

            <div class="form-group">
                <label for="bookPublisher">出版社</label>
                <input type="text" id="bookPublisher" class="form-control" placeholder="请输入图书出版社" 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="add()">确定</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>
        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);
                }
            });
            // alert("添加成功");
            // location.href = "book_list.html";
        }
    </script>
</body>

</html>

原理 :

前端

每个输入框的属性必须和后端一致,否则拿不到数据

作用是将表单中的数据,自动拼接成后端能解析的字符串

后端

接收图书信息并插入到数据库中

测试

添加成功

注意 :

2.SQL 中的字段名称要和数据库中的字段名称一致,否则会产生异常

功能 3 : 图书列表

由于图书列表还没有关联数据库,跳转到图书列表时显示的还是之前 mock 的信息

需求分析

要求 : 使用分页操作,每页只显示 10 条数据

先往数据库插入更多的数据

sql 复制代码
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');

查询第一页的信息

SELECT * FROM book_info LIMIT 0,10

前后端参数分析 :

  1. 前端发起查询时 , 需要向服务器传递参数 : currentPage (当前页码,默认为 1) ; pageSize(每页显示条数,默认为 10)
  2. 后端响应时 , 需要响应给前端的数据 : records(查询到的数据 List) ; total(中记录条数)

接口定义

服务端代码

BookInfoMapper
java 复制代码
@Select("select * from book_info where status<>0 order by id asc limit #{offset} , #{pageSize}")
List<BookInfo> queryBookListByPage(PageRequest pageRequest);

@Select("select count(1) from book_info where status!=0")
Integer count();
BookService
java 复制代码
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,books,pageRequest);
}
BookController
java 复制代码
@RequestMapping("/getListByPage")
public PageResult<BookInfo> getLIstByPAge(PageRequest pageRequest){
    log.info("获取图书列表,pageRequest:{}",pageRequest);
    PageResult<BookInfo> pageResult = bookService.getBookListByPage(pageRequest);
    return pageResult;
}

前端 补全 getBookList()方法

java 复制代码
getBookList();
function getBookList() {
    $.ajax({
        type: "get",
        url: "/book/getListByPage" + location.search,
        success: function (result) {
            // 1. 严格的非空判断
            if (!result || !result.records) {
                $("tbody").html("<tr><td colspan='9'>暂无图书数据</td></tr>");
                return;
            }

            var books = result.records;
            var finalHtml = "";

            // 2. 循环渲染时,给所有字段加上默认值,防止JS报错
            for (var book of books) {
                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>';
                // statusCN 为空时,显示 "未知",防止JS报错
                finalHtml += '<td>' + (book.statusCN || '未知') + '</td>';
                finalHtml += '<td><div class="op">';
                finalHtml += '<a href="book_update.html?bookId=' + (book.id || '') + '">修改</a>';
                finalHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + (book.id || 0) + ')">删除</a>';
                finalHtml += '</div></td>';
                finalHtml += "</tr>";
            }
            // 3. 一次性写入,保证渲染完成
            $("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">上一页</a></li>',
                next: '<li class="page-item"><a class="page-link">下一页</a></li>',
                last: '<li class="page-item"><a class="page-link">最后一页</a></li>',
                page: '<li class="page-item"><a class="page-link">{{page}}</a></li>',
                onPageChange: function (page, type) {
                    if (type == "change") {
                        location.href = "book_list.html?currentPage=" + page;
                    }
                }
            });
        }
    });

}

分页插件

https://jqpaginator.keenwon.com/

原理 :

执行分页查询的 SQL 后 , 前端拿到后端封装的分页数据 通过分页插件进行渲染

测试 :

http://127.0.0.1:8080/book_list.html?currentPage=1

能够正常分页

功能 4 : 修改图书

接口定义

进入修改页面要显示要修改图书的信息

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

实现服务端代码

BookController
java 复制代码
@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
java 复制代码
public BookInfo queryBookById(Integer bookId) {
    return bookInfoMapper.queryBookById(bookId);
}

public void updateBook(BookInfo bookInfo){
    bookInfoMapper.updateBook(bookInfo);
}
BookInfoMappper
java 复制代码
@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);


void updateBook(BookInfo bookInfo);
BookInfoMapper.xml
XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.boop.book.Mapper.BookInfoMapper">

    <update id="updateBook">
        update book_info
        <set>
            <if test="bookName!=null">
                book_name = #{bookName},
            </if>
            <if test="author!=null">
                author=#{author},
            </if>
            <if test="price!=null">
                price = #{price},
            </if>
            <if test="count!=null">
                count = #{count},
            </if>
            <if test="publish!=null">
                publish = #{publish},
            </if>
            <if test="status!=null">
            status = #{status},
            </if>
        </set>
        where id = #{id};
    </update>
</mapper>

前端代码

javascript 复制代码
// 根据ID查询书籍信息
getBookInfo();
function getBookInfo() {
    $.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);
        }
    });
}

测试 :

点击修改

会显示要修改的图书信息

点击确定会成功修改

功能 5 : 删除图书

需求 :

逻辑删除 , 将图书状态设置为 0

接口定义

前端代码

逻辑删除可以用更新图书的接口,将 status 设置为 0;

直接写前端代码

点击调用该函数

javascript 复制代码
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"
            }
        });
        alert("删除成功");
    }
}

测试 :

点击删除,并确定

功能 6 : 批量删除

接口设计

服务端代码

BookController
java 复制代码
@RequestMapping("/batchDeleteBook")
public boolean batchDeleteBook(@RequestParam List<Integer> ids){
    log.info("批量删除图书,ids:{}",ids);
    try{
        bookService.batchDeleteBook(ids);
    }catch (Exception e){
        log.error("批量删除图书异常");
        return false;
    }
    return true;
}
BookService
java 复制代码
public void batchDeleteBook(List<Integer> ids) {
    bookInfoMapper.batchDeleteBook(ids);
}
BookInfoMapper
java 复制代码
void batchDeleteBook(List<Integer> ids);
BookInfoMapper.xml
XML 复制代码
<update id="batchDeleteBook">
    update book_info set status = 0 where id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
</update>
前端代码
javascript 复制代码
function batchDelete() {
    var isDelete = confirm("确认批量删除?");
    if (isDelete) {
        //获取复选框的id
        var ids = [];
        $("input:checkbox[name='selectBook']:checked").each(function () {
            ids.push($(this).val());
        });
        console.log(ids);
        $.ajax({
            type:'post',
            url:'/book/batchDeleteBook',
            data: {
                ids: ids   
            },
            traditional: true,  //表单序列化
            success:function (result){
                if(result){
                    alert("批量删除成功");
                    //重新刷新页面
                    location.href = "book_list.html"
                }
            }
        });
    }
}

给后端传递 ids 也可以直接放在 url 中 或者是 JSON 请求体传参 contentType:"application/json",data:JSON.stringify(ids)

测试

最后点击确定即可删除成功

功能 7 : 强制登录

需求分析 :

上述的功能都涉及到对数据库的操作 , 而且用户不用登录也可以直接访问图书列表 , 风险很高 , 1

如果 Session 中可以获取到用户的登录信息 , 则可以进行后续操作

如果 Session 中获取不到用户的登录信息 , 则跳转到登录页面

以下重写的方法均加入登录校验

服务端代码

重写 UserController
java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/login")
    public boolean login(String name, String password, HttpSession session){
        //账号或密码为空
        if(!StringUtils.hasLength(name)||!StringUtils.hasLength(password)){
            return false;
        }
        UserInfo userInfo = userService.queryUserByName(name);
        if(userInfo == null){
            return false;
        }

        if(userInfo!=null&&password.equals(userInfo.getPassword())){
            userInfo.setPassword("");
            session.setAttribute(Constants.SESSION_USER_KEY,userInfo);
            return true;
        }
        //账号或密码错误
        return false;
    }
}
重写 getListByPage
java 复制代码
@RequestMapping("/getListByPage")
public Result getListByPAge(PageRequest pageRequest, HttpSession session){
    log.info("获取图书列表,pageRequest:{}",pageRequest);
    if(session.getAttribute(Constants.SESSION_USER_KEY) == null){
        return Result.unlogin();
    }
    UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
    if (userInfo == null||userInfo.getId()<0) {
        return Result.unlogin();
    }
    PageResult<BookInfo> pageResult = bookService.getBookListByPage(pageRequest);
    return Result.success(pageResult);
}
实体类

作用 : 不管返回的是图书列表还是用户信息,统一封装成 Result 对象返回给前端

java 复制代码
package com.boop.book.model;

import com.boop.book.enums.ResultStatus;
import lombok.Data;

@Data
public class Result<T> {
    private ResultStatus status;
    private String errorMessage;
    private T data;

    public static <T> Result success(T data){
        Result result = new Result();
        result.setStatus(ResultStatus.SUCCESS);
        result.setErrorMessage("");
        result.setData(data);
        return result;
    }
    public static Result fail(String errorMessage){
        Result result = new Result();
        result.setStatus(ResultStatus.FAIL);
        result.setErrorMessage(errorMessage);
        result.setData("");
        return result;
    }
    public static Result unlogin(){
        Result result = new Result();
        result.setStatus(ResultStatus.UNLOGIN);
        result.setErrorMessage("用户未登录");
        result.setData(null);
        return result;
    }
}
枚举类
java 复制代码
package com.boop.book.enums;

public enum ResultStatus {
    SUCCESS(200),
    UNLOGIN(-1),
    FAIL(-2);
    private Integer code;

    ResultStatus(int code){
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }

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

前端代码

完整前端代码

html 复制代码
<!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>
        getBookList();
        getBookList();
        function getBookList() {
            $.ajax({
                type: "get",
                url: "/book/getListByPage" + location.search,
                success: function (result) {
                    console.log("返回",result);
                    if (result&&result.status === 'UNLOGIN') {
                        alert("用户未登录,请先登录");
                        location.href = "login.html";
                        return;
                    }

                    var finalHtml = "";
                    var data = result.data;
                    for (var book of data.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>';
                        finalHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + book.id + ')">删除</a>';
                        finalHtml += '</div></td>';
                        finalHtml += "</tr>";
                    }
                    $("tbody").html(finalHtml);

                    $("#pageContainer").jqPaginator({
                        totalCounts: data.total,
                        pageSize: 10,
                        visiblePages: 5,
                        currentPage: data.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>',
                        onPageChange: function (page, type) {
                            if (type != 'init') {
                                location.href = "book_list.html?currentPage=" + page;
                            }
                        }
                    });
                }
            });
        }

        function deleteBook(id) {
            var isDelete = confirm("确认删除?");
            if (isDelete) {
                $.ajax({
                    type: "post",
                    url: "/book/deleteBook?bookId=" + id,
                    success: function (result) {
                        if (result.code == "SUCCESS" && result.data == "") {
                            location.href = "book_list.html?currentPage=1";
                        } else {
                            alert(result.errMsg);
                        }
                    }
                });
            }
        }
        function batchDelete() {
            var isDelete = confirm("确认批量删除?");
            if (isDelete) {
                var ids = [];
                $("input:checkbox[name='selectBook']:checked").each(function () {
                    ids.push($(this).val());
                });
                console.log(ids);
                $.ajax({
                    type: "post",
                    url: "/book/batchDelete?ids="+ids,
                    success: function(result){
                        if(result.code == "SUCCESS" && result.data==true){
                            location.href = "book_list.html?currentPage=1";
                        }else{
                            alert("批量删除失败");
                        }
                    }
                });
            }
        }
    </script>
</div>
</body>
</html>

原理 :

后端 :

① 如果 session.getAttribute()为空 , 则会返回 {data: null, errorMessage: '用户未登录', status: 'UNLOGIN'}

② 把 session 中的用户信息拿出来,判断对象是否为空,或者用户 ID 为非法

前端 :

① 先接收后端返回的 result 并判断 ; 如果是未登录状态 , 弹窗提示 , 并跳转到登录界面

测试 :

未登录之前输入

http://127.0.0.1:8080/book_list.html

跳转登录页面

总结 :

  1. Ajax : 前端异步发起请求 , 不刷新页面和后端交互 , 传参 , 接收返回值
  2. 参数传递 : 普通参数直接键值对 ; 批量删除传 id 数组 ; 表单用 serialize()自动打包
  3. Session : 存登录用户 , 做全局权限校验
  4. Result 统一返回 : 规范前后端约定状态 , 前端不用做多余判断 , 只判断 status 即可
  5. 删除操作 : 逻辑删除 , 只修改状态
相关推荐
小张小张爱学习2 小时前
Mybatis高频面试题
java·spring·mybatis
直奔標竿2 小时前
Java开发者AI转型第十三课!知识库终局方案:Spring AI Vector Store架构演进与ETL全链路入库实战
java·人工智能·后端·spring
XiYang-DING2 小时前
【Java EE】阻塞队列(BlockingQueue)
java·java-ee
星轨zb3 小时前
什么是Spring设计模式:单例、工厂与代理
java·spring·设计模式
悟05153 小时前
设计模式-状态模式
设计模式·状态模式
.柒宇.3 小时前
SpringCloud微服务入门教程
spring·spring cloud·微服务
xuhaoyu_cpp_java3 小时前
Mybatis学习(四)
java·经验分享·笔记·学习·mybatis
.生产的驴3 小时前
SpringBoot 大文件分片上传 文件切片、断点续传与性能优化 切片技术与优化方案 文件高效上传
java·服务器·spring boot·后端·spring·spring cloud·状态模式