【JavaEE进阶】——利用框架完成功能全面的图书管理系统

目录

🚩项目所需要的技术栈

🚩项目准备工作

🎈环境准备

🎈数据库准备

🚩前后端交互分析

🎈登录

📝前后端交互

📝实现服务器代码

📝测试前后端代码是否正确

🎈添加图书

📝前后端交互

📝实现服务器代码

📝测试前后端代码是否正确

🎈图书列表

📝前后端交互

📝实现服务器代码

📝测试前后端代码是否正确

🎈修改图书

📝前后端交互

📝实现服务器代码 

📝测试前后端代码是否正确

🎈删除图书

📝约定前后端交互接⼝

📝实现服务器代码 

📝测试前后端代码是否正确

🎈批量删除图书

📝约定前后端交互接⼝

📝实现服务器代码 

📝测试前后端代码是否正确


🚩项目所需要的技术栈

 该项目是一个针对于SpringBoot+Mybatis+SpringMVC的基础运用项目适合初学者来检验水平测试能力,该项目所需技术栈如下:
>* SpringBoot:作为项目的框架,使用Maven托管代码
>* Mybatis:使用Mybatis框架操纵数据库,其中使用了xml和注解两种方式去操作数据库
>* 前端ajax:前后端的交互使用的是ajax作为前端为后端发送数据以及接收数据
>* 项目分层:项目分为前端页面+control(与前端建立连接的控制层)+Service(服务层供control层进行调用)+Mapper(操纵数据库实现数据与后端代码的 交互)+model(需要实现的主类)。


🚩项目准备工作

🎈环境准备

项目的创建需要选好项目名,项目路径,语言为java,type是基于maven构建,jdk可以选择17以上的(切记最好不要用jdk8),packing是打成jar包。

此时项目创建成功。MySQL Driver和MyBatis Framework引⼊MyBatis 和 MySQL驱动依赖

也可以手动引入依赖,上面只是更简单。

这是围绕整个项目的配置文件,没有该配置文件,是无法运行成功的,没有它们你就完成不了一个项目。我们依赖该pom.xml文件,让我们能完成该项目。


SpringBookt配置文件,统一使用yml格式 application.yml

很多项⽬或者框架的配置信息也放在配置⽂件中, ⽐如:
  • 项⽬的启动端⼝
  • 数据库的连接信息(包含⽤⼾名和密码的设置)
  • ⽤于发现和定位问题的普通⽇志和异常⽇志等
server:
  port: 8080
#配置数据库
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/book_system?characterEncoding=utf8&useSSL=false
    username: root
    password: 
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  configuration:
    #配置驼峰自动转换
    map-underscore-to-camel-case: true
    #sql日志(打印出来让我们可以清楚自己的sql语句是否正确)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #Spring Boot可以正确找到并加载位于 classpath:mapper/目录下的 xxxxMapper XML 文件,
    #从而在应用程序中使用这些 Mapper 进行数据库操作。(因为有时候sql语句需要动态)
  mapper-locations: classpath:mapper/BookMapper.xml
#将日志记录到一个文件可以通过在配置文件中指定日志文件的位置和名称来实现。
logging:
  file:
    name: spring-book.log


🎈数据库准备

  • 数据库表的设计是应用程序开发的一个重要的环节。设计数据库表是根据业务需求相关的。
  • 数据库表通常分成两种:实体表和关系表,就如图书管理系统来说,图书馆里系统相对是简单的,只有两个实体:用户和图书,并且用户和图书之间没有关联关系。
  • 表的具体字段设计,也与需求相关。

           ⽤⼾表:有⽤⼾名和密码即可(复杂的业务可能还涉及昵称, 年龄等资料)

           图书表:有哪些字段, 也是参考需求⻚⾯(通常不是⼀个⻚⾯决定的, ⽽是要对整个                    系统进⾏全⾯分析观察后定的)


创建数据库book_system  用户表user_info  图书表 book_info

DROP DATABASE IF EXISTS book_system;
CREATE DATABASE book_system DEFAULT CHARACTER SET utf8mb4;
use book_system;

-- ⽤⼾表
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, status)
VALUES
('深入理解计算机系统', 'Randal E. Bryant', 18, 98.00, '机械工业出版社', 1),
('现代操作系统', 'Andrew S. Tanenbaum', 10, 89.00, '清华大学出版社', 1),
('计算机网络:自顶向下方法', 'James Kurose', 14, 85.00, '电子工业出版社', 1),
('数据库系统概念', 'Abraham Silberschatz', 7, 110.00, '机械工业出版社', 1),
('算法导论', 'Thomas H. Cormen', 5, 130.00, '机械工业出版社', 1),
('机器学习', '周志华', 20, 120.00, '清华大学出版社', 1),
('Python编程:从入门到实践', 'Eric Matthes', 25, 65.00, '电子工业出版社', 1),
('深度学习', 'Ian Goodfellow', 12, 180.00, '人民邮电出版社', 1),
('计算机图形学', 'John F. Hughes', 9, 95.00, '机械工业出版社', 1),
('操作系统真相还原', '史蒂文·穆查', 15, 80.00, '电子工业出版社', 1);
 Model创建   BookInfo UserInfo
package com.example.cl.model;

import lombok.Data;

import java.math.BigDecimal;
import java.util.Date;

@Data
public class BookInfo {
    //图书ID
    private Integer id;
    //书名
    private String bookName;
    //作者
    private String author;
    //数量
    private Integer count;
    //定价
    private BigDecimal price;
    //出版社
    private String publish;
    //状态 0-⽆效 1-允许借阅 2-不允许借阅
    private Integer status;
    private String statusCN;
    //创建时间
    private Date createTime;
    //更新时间
    private Date updateTime;
}


package com.example.cl.model;

import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
    private Integer id;
    private String userName;
    private String password;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

🚩前后端交互分析

🎈登录

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

前端登录有两个表单,输入用户名和密码,表单中name属性是用于表单提交时用于标识表单数据的属性,服务器端通过‘name’属性来获取提交的数据。此时前端的id属性来获取客户端输入的用户名和密码给后端。


📝前后端交互

     function login() {
            // var userName=  $("#userName").val();
            // var password = $("#password").val()
            $.ajax({
                url: "/user/login",
                type: "post",
                data:{
                    userName: $("#userName").val(),
                    password: $("#password").val()
                }
        }

前端发出请求:

url:/user/login

type:post

参数:

data:userName=$("#userName").val()【admin】

           password=$("#password").val()【admin】

响应:

true (密码或者用户名正确,返回true)

        function login() {
            // var userName=  $("#userName").val();
            // var password = $("#password").val()
            $.ajax({
                url: "/user/login",
                type: "post",
                data:{
                    userName: $("#userName").val(),
                    password: $("#password").val()
                },
                success:function(result){
                    if(result.code=="SUCCESS" && result.data ==""){
                        //密码正确
                        location.href = "book_list.html?pageNum=1";
                    }else{
                        alert(result.errMsg);
                    }
                }
            });
        }

因为登录页面基本上错误出现在用户名和密码上,如果用户名和密码出现问题,其实并没有必要返回error信息,如果服务器返回的结果码是“SUCCESS”和返回的结果数据是“”,那么就表明密码正确,否则就弹窗 结果的errMsg错误信息。


📝实现服务器代码

我们要知道三层架构,mapper——service——controller

control(与前端建立连接的控制层)

Service(服务层供control层进行调用)

Mapper(操纵数据库实现数据与后端代码的 交互)

model(需要实现的主类)。


我们该如何实现服务器代码呢?首先,我们需要获取到delet_flag=0对应的userName因为,如果已经删除了,那么就也得返回null,如果没有找到服务器收到的userName那么返回null,如果找到了并且并没有删除该用户,那么就成功。


🎓数据层

package com.example.cl.mapper;

import com.example.cl.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=#{name}")
    UserInfo queryUserByName(String name);
}
访问数据库, 使⽤MyBatis来实现, 所以把之前dao路径下的⽂件可以删掉, ⽤mapper⽬录来代替, 创
建UserInfoMapper ,当然, 继续使⽤dao⽬录也可以,此处为建议 ,dao和mapper通常都被认为是数据库层

🎓业务层

package com.example.cl.service;

import com.example.cl.mapper.UserInfoMapper;
import com.example.cl.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

🎓控制层

我们需要进行前后端交互
我们需要创建一个类Result,里面包含errMsg(显示什么导致错误的信息),code(业务码), data(服务器返回给前端的数据)。
而code业务码,是需要通过键值对的形式创建,比如我们设定200标识succss,-1表示fail,-2表示未登录功能,我们需要创建一个一个枚举类。
创建一个枚举类StatusResult,枚举三种状态。
🍭Result类
package com.example.cl.model;

import com.example.cl.enums.ResultStatus;
import lombok.Data;

@Data
public class Result<T> {
    private ResultStatus code; //业务码  不是Http状态码  200-成功  -2 失败  -1 未登录
    private String errMsg; //错误信息, 如果业务成功, errMsg为空
    private T data;
    public static <T> Result success(T data){
        Result result=new Result<>();
        result.setCode(ResultStatus.SUCCESS);
        result.setData(data);
        return  result;
    }
    public static Result fail(String msg){
        Result result=new Result<>();
        result.setCode(ResultStatus.Fail);
        result.setErrMsg(msg);
        return result;
    }
}
🍭StatusResult枚举类
枚举类不能使用@Data注解
package com.example.cl;

public enum StatusResult {
    SUCCESS(200),//成功返回200
    FAIL(-1),//错误返回-1
    NOLOGIN(-2)//未登录返回-2
    ;
    private int code;
    StatusResult(int code) {
        this.code=code;
    }

    public int getCode() {
        return code;
    }

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

package com.example.cl.controller;

import com.example.cl.constant.constants;
import com.example.cl.model.Result;
import com.example.cl.model.UserInfo;
import com.example.cl.service.UserInfoService;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserInfoController {
    @Autowired
    private UserInfoService userInfoService;
    @RequestMapping(value = "/login",produces = "application/json")
    public Result login(String userName, String password, HttpSession session){
        //1.效验参数
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
            //如果长度其中一个为空,
            return Result.fail("用户名或者密码为空");
        }
        //账号和密码是否正确
        //获取查找当前userName的用户所有信息
        UserInfo userInfo=userInfoService.queryUserByName(userName);
        if(userInfo==null){
            return Result.fail("用户不存在");
        }
        if(!password.equals(userInfo.getPassword())){
            return Result.fail("密码错误");
        }
        //3.正确情况
        session.setAttribute(constants.USER_SESSION_KEY,userInfo);
        return Result.success("");//成功返回空串
    }
}

📝测试前后端代码是否正确

后端通过postman进行检查,最后返回的结果正确,说明后端代码是没有问题的, 如果后端代码有问题会返回505错误码。

我们可以看到如果成功,errMsg为空,data返回给前端也是空,code是success

检查前端代码

🎈添加图书

📝前后端交互

前端发出请求:

url:/book/addBook

type:post

参数:

date:传给服务器的数据 ,用到的$("#addBook").serialize() 表示form表单中所有的数据

响应:

""  //失败信息,成功返回空串

  function add() {
            $.ajax({
                url: "/book/addBook",
                type: "post",
                data: $("#addBook").serialize(),
                success: function (result) {
                    if (result.code == "SUCCESS" && result.data == "") {
                        //添加成功
                        location.href = "book_list.html";
                    } else {
                        alert(result.data);
                    }
                }, 
                error: function (error) {
                    //用户未登录
                    if (error.code=="NOLOGIN"&&error.data==null) {
                        location.href = "login.html";
                    }
                }
            });
        }

如果没有返回错误,那么就判断result的data是否为空,如果为空,我们就跳转到博客列表页,如果不等于“”,我们就弹框

如果返回错误,说明是用户未登录,那么就要判断error中的数据是否是未登录页码


📝实现服务器代码

插入图书,还是之前的三层架构方式,再mapper层中,我们要插入book_name, author, `count`, price, publish, `status`,这几个字段,因为创建时间和更新时间是以当时时间为准的,id是自增的。
🎓数据层
package com.example.cl.mapper;

import com.example.cl.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 insertBook(BookInfo bookInfo);
}

🎓业务层

package com.example.cl.service;

import com.example.cl.mapper.BookInfoMapper;
import com.example.cl.mapper.UserInfoMapper;
import com.example.cl.model.BookInfo;
import com.example.cl.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookInfoService {
    @Autowired
    private BookInfoMapper bookInfoMapper;
    public Integer insertBook(BookInfo bookInfo){
        return bookInfoMapper.insertBook(bookInfo);
    }
}

🎓控制层

添加@Slf4j可以让我们再运行的时候,控制台会进行报出日志消息。

在后面我们是对图书进行增删改查的功能,这些功能的前提是我们需要用户必须是登录的,所以我们在效验参数之前,我们需要进行判断是否用户登录,如果没有登录返回error信息,设置状态码为NOLOGIN,并且错误信息就是“用户未登录”。
public static<T> Result nologin(String errMsg){
        Result result=new Result();
        result.setErrMsg(errMsg);
        result.setCode(StatusResult.NOLOGIN);
        return result;
    }
如果用户登录了,就按照正常的流程进行添加图书,由于添加图书的返回值是int类型,如果添加图书的数量是>0的我们就表示添加成功,然后我们返回success成功码,如果没有返回成功,就会报错。
   /**
     * 增加图书
     */
    @RequestMapping(value = "/addBook",produces = "application/json")
    public Result<String> addBook(BookInfo bookInfo, HttpSession session){
        Result<String> ans=new Result<>();
        //1.判断用户是否登录
        //如果用户信息为空, 说明用户未登录
        UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);
        if (loginUserInfo==null || loginUserInfo.getId()<=0){
            return Result.nologin("用户未登录");
        }
        //1.效验参数
        log.info("添加图书,接收到的参数bookInfo:{}",bookInfo);
        if (!StringUtils.hasLength(bookInfo.getBookName())
                || !StringUtils.hasLength(bookInfo.getAuthor())
                || bookInfo.getCount()==null
                || bookInfo.getPrice()==null
                || !StringUtils.hasLength(bookInfo.getPublish())
                || bookInfo.getStatus()==null){
            ans.setData("输入错误");
            ans.setCode(StatusResult.FAIL);
            return ans;
        }
        //添加图书
        try {
            Integer result=bookInfoService.insertBook(bookInfo);
            if(result>0){
                ans.setData("");
                ans.setCode(StatusResult.SUCCESS);
                return ans;
            }
        }catch (Exception e){
            log.error("添加图书失败");
        }
        ans.setCode(StatusResult.FAIL);
        ans.setData("添加失败");
        return ans;
    }

📝测试前后端代码是否正确

我们先看用户登录状态,首先用postman进行发出登录请求
然后通过postman进行发出添加图书请求,返回SUCCESS状态码

我们再来看用户未登录状态:返回的错误信息“errMsg”,code为LOGIN


检查前端代码

此时查看数据库中的数据,此时增加了一条。


🎈图书列表

可以看到, 添加图书之后, 跳转到图书列表⻚⾯, 并没有显⽰刚才添加的图书信息, 接下来我们来实现图书列表。
需求分析
我们之前做的表⽩墙查询功能,是将数据库中所有的数据查询出来并展⽰到⻚⾯上,试想如果数据库中的数据有很多(假设有⼗⼏万条)的时候,将数据全部展⽰出来肯定不现实,那如何解决这个问题呢?
使⽤分⻚解决这个问题。每次只展⽰⼀⻚的数据,⽐如:⼀⻚展⽰10条数据,如果还想看其他的数
据,可以通过点击⻚码进⾏查询

分⻚时, 数据是如何展⽰的呢
第1⻚: 显⽰1-10 条的数据
第2⻚: 显⽰11-20 条的数据
第3⻚: 显⽰21-30 条的数据
以此类推...
要想实现这个功能, 从数据库中进⾏分⻚查询,我们要使⽤ LIMIT 关键字,格式为:
limit 开始索引每⻚显⽰的条数(开始索引从0开始)

我们先增加一些数据,让每一页都更加的明显显示出来。

第一页的开始索引是0  (1-0)*10

第二页的开始索引是10,(2-1)*10

观察以上SQL语句,发现: 开始索引⼀直在改变, 每⻚显⽰条数是固定的
开始索引的计算公式: 开始索引 = (当前⻚码 - 1) * 每⻚显⽰条数

我们继续基于前端⻚⾯, 继续分析, 得出以下结论:
前端发出请求,需要向服务器传递参数
  • currentPage 当前⻚码 //默认值为1
  • pageSize 每⻚显⽰条数 //默认值为10
为了项⽬更好的扩展性, 通常不设置固定值, ⽽是以参数的形式来进⾏传递
扩展性: 软件系统具备⾯对未来需求变化⽽进⾏扩展的能⼒ ,⽐如当前需求⼀⻚显⽰10条, 后期需求改为⼀⻚显⽰20条, 后端代码不需要任何修改
后端响应时, 需要响应给前端的数据
  • records 所查询到的数据列表(存储到List 集合中)
  • total 总记录数 (⽤于告诉前端显⽰多少⻚, 显⽰⻚数为: (total + pageSize -1)/pageSize
翻⻚请求和响应部分, 我们通常封装在两个对象中:
🎓翻⻚请求对象
package com.example.cl.model;

import lombok.Data;


@Data
public class PageRequest {
    private Integer pageNum =1;//当前页
    private Integer pageSize = 10;//每页中的记录数
    private Integer offset;//起始索引

    public Integer getOffset() {
        return (pageNum-1) * pageSize;
    }
}

通过当前页和每页的记录数,记录当前的起始索引。

🎓翻⻚列表结果类:
package com.example.cl.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@AllArgsConstructor  //生成构造方法
@NoArgsConstructor   //生成无构造方法
@Data
public class PageResult<T>{
    private List<T> records;//当前页的数据
    private Integer count;//所有的记录数
    private PageRequest pageRequest;//从哪个下标开始,显示多少本书
}

📝前后端交互

前端发出请求

url: 

/book/getListByPage?currentPage=1&pageSize=10
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

type:  get

参数:

响应:
{"errMsg":"","data":"","code":""}
我们约定, 浏览器给服务器发送⼀个 /book/getListByPage 这样的 HTTP 请求, 通过
currentPage 参数告诉服务器, 当前请求为第⼏⻚的数据, 后端根据请求参数, 返回对应⻚的数据
第⼀⻚可以不传参数, currentPage默认值为 1

📝实现服务器代码

🎓数据层

  /**
     * 获取当前页数据
     */@Select("select * from book_info where status !=0 order by id asc limit #{offset}, #{pageSize}")//升序
     List<BookInfo> queryBookListByPage(PageRequest pageRequest);//查询当前页所有未借阅的书
//offset索引起始 pageSize一页展示多少数据

    @Select("select count(1) from book_info where status<>0")
    Integer count();//统计未借阅的书的本数

🎓业务层

1. 翻⻚信息需要返回数据的总数和列表信息, 需要查两次SQL
2. 图书状态: 图书状态和数据库存储的status有⼀定的对应关系 ,如果后续状态码有变动, 我们需要修改项⽬中所有涉及的代码, 这种情况, 通常采⽤枚举类来处理映射关系
package com.example.cl.enums;

public enum BookStatus {
    DELETE(0,"删除"),
    NORMAL(1,"可借阅"),
    FORBIDDEN(2,"不可借阅")
    ;


    BookStatus(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    private Integer code;//数字
    private String desc;//内容


        //根据code,返回描述信息
    public static BookStatus getDescByCode(Integer code){
        switch (code){
            case 0:return DELETE;
            case 1:return NORMAL;
            case 2:
            default:return FORBIDDEN;
        }
    }
    public Integer getCode() {
        return code;
    }

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

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

业务层

 public PageResult<BookInfo> queryBookListByPage(PageRequest pageRequest){
        //获取所有未借阅的书的总数
        Integer count=bookInfoMapper.count();
        //获取当前页的记录
        List<BookInfo>bookInfos=bookInfoMapper.queryBookListByPage(pageRequest.getOffset(), pageRequest.getPageSize());
        //3.处理状态(0表示删除,1表示可借阅,2表示未借阅,之前用数字代替,现在需要转成string形式
        for (BookInfo bookInfo:bookInfos) {
            bookInfo.setStatus(BookStatus.getDescByCode(bookInfo.getStatus()).getCode());
        }
        return new PageResult(bookInfos,count,pageRequest);
    }

🎓控制层

    /**
     * 获取当前页数据
     */
    @RequestMapping("/getBookListByPage")
    public Result<PageResult<BookInfo>> getBookListByPage(PageRequest pageRequest,HttpSession session){
        Result<PageResult<BookInfo>> ans=new Result<>();
        //1.判断用户是否登录
        //如果用户信息为空, 说明用户未登录
        UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);
        if (loginUserInfo==null || loginUserInfo.getId()<=0){
            return Result.nologin("用户未登录");
        }
        //
        PageResult<BookInfo> bookList=bookInfoService.queryBookListByPage(pageRequest);
        return Result.success(bookList);
    }
    public static <T> Result success(T data){
        Result result=new Result();
        result.setCode(StatusResult.SUCCESS);
        result.setData(data);
        return result;
    }

此时结果返回bookList类型的。


📝测试前后端代码是否正确

先测后端
一共有32个书没有被借阅。
前端部分需要给博客列表做出来之后即可看到。


🎈修改图书

📝前后端交互

进⼊修改⻚⾯, 需要显⽰当前图书的信息
[请求]
/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"
}
根据图书ID, 获取当前图书的信息
点击修改按钮, 修改图书信息
[ 请求 ]
/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表单的形式来
提交数据 ,服务器返回处理结果, 返回""表⽰添加图书成功, 否则, 返回失败信息.

📝实现服务器代码 

🎓数据层
数据层我们需要先寻找该书通过id,因为当我们再点击修改的时候,我们就可以获取到该书的id号,然后再对该书进行更新。

    /**
     * 修改图书
     */
    //通过id查询图书信息
    @Select("select * from book_info where id=#{bookid} and status!=0")
    BookInfo queryBookById(Integer bookid);

    Integer updateBookById(BookInfo bookInfo);

通过点击修改按钮,之后跳转到更新页面的时候,就相当于通过id获取到图书信息显示再表单中,然后我们就可以再文本框中输入值,进行更改数据。

<?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.example.cl.mapper.BookMapper">
    <update id="updateBookById">
        update book_info
        <set>
            <if test="bookName != null">
                book_name = #{bookName},
            </if>
            <if test="author != null">
                author =#{author},
            </if>
            <if test="count != null">
                count = #{count},
            </if>
            <if test="price !=null">
                price = #{price},
            </if>
            <if test="publish != null">
                publish =#{publish},
            </if>
            <if test="status != null">
                status =#{status}
            </if>
        </set>
        where id=#{id}
    </update>
<mapper>

因为有些数据我们可能是不想改的,所以我们再更新图书的时候进行了动态sql,用了xml文件进行实现动态sql。


🎓业务层

    /*
    更改图书
     */
    public Integer updateBookById(BookInfo bookInfo){
        return bookInfoMapper.updateBookById(bookInfo);
    }
    public BookInfo queryBookById(Integer bookid){
        return bookInfoMapper.queryBookById(bookid);
    }

🎓控制层

 /**
     * 查询图书信息
     */
    @RequestMapping("/queryBookById")
    public Result<BookInfo> queryBookById(Integer bookId,HttpSession session) {
        Result<BookInfo>ans=new Result<>();
        //校验用户信息是否为空,说明是未登录状态
        UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);
        if (loginUserInfo==null || loginUserInfo.getId()<=0){
            ans.setData(null);
            ans.setCode(StatusResult.NOLOGIN);
            return ans;
        }
        log.info("根据ID查询图书信息, id:"+bookId);
        long start=System.currentTimeMillis();
        BookInfo bookInfo=bookInfoService.queryBookById(bookId);
        ans.setCode(StatusResult.SUCCESS);
        ans.setData(bookInfo);
        log.info("queryBookById 耗时: "+ (System.currentTimeMillis()-start) + "ms");
        return ans;
    }
    /**
     * 更新图书
     */
    @RequestMapping(value = "/updateBook", produces = "application/json")
    public Result<String> updateBook(BookInfo bookInfo,HttpSession session) {
        Result<String>ans=new Result<>();
        //效验参数是否登录
        UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);
        if (loginUserInfo==null || loginUserInfo.getId()<=0){
            ans.setCode(StatusResult.NOLOGIN);
            ans.setData(null);
            return ans;
        }
        log.info("更新图书, bookInfo: {}", bookInfo);
        try {
            Integer result=bookInfoService.updateBookById(bookInfo);
            if(result>0){
                ans.setData("");
                ans.setCode(StatusResult.SUCCESS);
                return ans;
            }
        }catch (Exception e){
            log.error("更新图书失败");
        }
        ans.setData(null);
        ans.setCode(StatusResult.FAIL);
        return ans;
    }

首先通过id查询图书信息,返回的数据是BookInfo类型的,因为我们需要把书返回给客户端。

然后通过id进行修改该书。如果更新的结果行数大于0,说明更新成功,如果没有,那么就更新失败。返回null数据。


📝测试前后端代码是否正确

先测后端代码,首先是未登录状态

登录状态:

通过id获取图书信息

通过id更改图书信息


检查前端代码:

刚刚后端测试的时候进行修改的,此时我们通过前端修改作者,然后会跳转到博客列表页

🎈删除图书

📝约定前后端交互接⼝

删除分为 逻辑删除 和物理删除
逻辑删除
逻辑删除也称为软删除、假删除、Soft Delete,即不真正删除数据,⽽在某⾏数据上增加类型 is_deleted的删除标识,⼀般使⽤UPDATE语句
物理删除
物理删除也称为硬删除,从数据库表中删除某⼀⾏或某⼀集合数据,⼀般使⽤DELETE语句
删除图书的两种实现⽅式
  • 逻辑删除 update book_info set status=0 where id = 1
  • 物理删除 delete from book_info where id=25
物理删除+归档的⽅式实现有些复杂, 咱们采⽤逻辑删除的⽅式
逻辑删除的话, 依然是更新逻辑, 我们可以直接使⽤修改图书的接⼝
/book/deleteBook
[ 请求 ]
/book/deleteBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[ 参数 ]
id=1&status=0
[ 响应 ]
"" // 失败信息 , 成功时返回空字符串
实现客⼾端代码

当后端接收到bookid的时候,数据层通过id删除该图书,由于删除的sql语句,返回的是int类型,如果删除成功,执行的长度>0,并且返回""数据,并且设置字节码为successs,然后跳转到列表页,如果执行失败,长度为0,返回的是null数据,并且设置字节码fail。失败的情况是未登录状态,如果返回的数据是空并且字节码是nologin,就返回的到登录页面


📝实现服务器代码 

🎓数据层

删除我们是按照id进行删除的,并且我们是按照逻辑删除的,所以就相当于通过id进行更新。


🎓服务层
   /**
     * 删除图书
     */
    public Integer deleteBook(Integer bookId){
       BookInfo bookInfo=new BookInfo();
       bookInfo.setId(bookId);
       bookInfo.setStatus(0);
       return bookInfoMapper.updateBookById(bookInfo);
    }
我们需要给定id,并且给状态设置为0,然后进行通过id进行修改即可。

🎓控制层

 /**
     * 删除图书
     */
    @RequestMapping("/deleteBook")
    public Result<Boolean> deleteBook(Integer bookId,HttpSession session){
        Result<Boolean>ans=new Result<>();
        //效验参数是否登录
        UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);
        if (loginUserInfo==null || loginUserInfo.getId()<=0){
            ans.setCode(StatusResult.NOLOGIN);
            ans.setData(null);
            return ans;
        }
        log.info("删除图书,bookId:{}",bookId);
        Integer result=bookInfoService.deleteBook(bookId);
        if(result>0){
            ans.setData(true);
            ans.setCode(StatusResult.SUCCESS);
            return ans;
        }
        ans.setData(false);
        ans.setCode(StatusResult.FAIL);
        return ans;
    }

控制层返回的数据是返回给客户端代码中。


📝测试前后端代码是否正确

先测后端代码,首先是未登录状态

登录状态:
通过id删除图书:

检查前端代码:

此时删除成功,返回的列表页。


🎈批量删除图书

📝约定前后端交互接⼝

/book/batchDeleteBook
[ 请求 ]
/book/batchDeleteBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[ 参数 ]
id=1&status=0
[ 响应 ]
"" // 失败信息 , 成功时返回空字符串
 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({
                    url: "/book/batchDeleteBook?ids=" + ids,
                    type: "post",
                    success: function (result) {
                        if (result.code == "SUCCESS" && result.data == "") {
                            //删除成功
                            location.href = "book_list.html";
                        } else {
                            alert(result.data);
                        }
                    },
                    error: function (error) {
                        //用户未登录
                        if (error.data == null && error.data=="NOLOGIN") {
                            location.href = "login.html";
                        }
                    }
                });
                // alert("批量删除成功");
            }
        }

前端将url和type发送给服务器。


📝实现服务器代码 

🎓数据层
Integer batchDeleteBookByIds(List<Integer> ids);

批量删除,我们需要将选中的id对应的图书都删除掉。用到动态sql语句

   <delete id="batchDeleteBookByIds">
        update book_info set status=0
        where id in
        <foreach collection="ids" open="(" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </delete>

🎓业务层

    /**
     * 批量删除
     */
    public Integer batchDeleteBookByIds(List<Integer> ids){
        return bookInfoMapper.batchDeleteBookByIds(ids);
    }

🎓控制层

    /**
     * 批量删除  ids
     */
    @RequestMapping(value = "/batchDeleteBook", produces = "application/json")
    public Result<String> batchDelete(@RequestParam List<Integer> ids, HttpSession session) {
        Result<String>ans=new Result<>();
        //效验参数是否登录
        UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);
        if (loginUserInfo==null || loginUserInfo.getId()<=0){
            ans.setCode(StatusResult.NOLOGIN);
            ans.setData(null);
            return ans;
        }
        Integer result= bookInfoService.batchDeleteBookByIds(ids);
        if(result>0){
            ans.setData("");
            ans.setCode(StatusResult.SUCCESS);
            return ans;
        }
        ans.setData("批量删除失败");
        ans.setCode(StatusResult.FAIL);
        return ans;
    }

📝测试前后端代码是否正确

先测后端代码,首先是未登录状态

登录状态:
通过ids批量删除图书:


检查前端代码:

此时批量删除成功!


永远有优秀的代码,永远有提升的空间,比如,我们次实现都要调用一个接口,是不是很复杂,如果有更多的功能接口,那么就会更复杂。我们想想会有什么方法来优化呢?后续会有统一功能等等,让这个项目做进一步的优化。


披星戴月走过的路必将繁华似锦!

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

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

相关文章

01 - matlab m_map地学绘图工具基础函数理解(一)

01 - matlab m_map地学绘图工具基础函数理解&#xff08;一&#xff09; 0. 引言1. m_demo2. 小结 0. 引言 上篇介绍了m_map的配置过程&#xff0c;本篇开始介绍下m_map中涉及到的所有可调用函数。如果配置的没有问题&#xff0c;执行">>help m_map"可以看到类…

【C++】C++入门的杂碎知识点

思维导图大纲&#xff1a; namespac命名空间 什么是namespace命名空间namespace命名空间有什么用 什么是命名空间 namespace命名空间是一种域&#xff0c;它可以将内部的成员隔绝起来。举个例子&#xff0c;我们都知道有全局变量和局部变量&#xff0c;全局变量存在于全局域…

趣味C语言——【猜数字】小游戏

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ &#x1f389;创作不易&#xff0c;请多多支持&#x1f389; &#x1f308;感谢大家的阅读、点赞、收藏和关注&#x1f495; &#x1f339;如有问题&#xff0c;欢迎指正 感谢 目录 代码…

抖音混剪素材哪里找?可以混剪搬运视频素材网站分享

在抖音上制作精彩的视频离不开高质量的素材资源。今天&#xff0c;我将为大家推荐几个优质的网站&#xff0c;帮助你解决素材短缺的问题。这些网站不仅提供丰富的素材&#xff0c;还符合百度SEO优化的规则&#xff0c;让你的视频更容易被发现。 蛙学府素材网 首先要推荐的是蛙…

模拟自动滚动并展开所有评论列表以及回复内容(如:抖音、b站等平台)

由于各大视频平台的回复内容排序不都是按照时间顺序&#xff0c;而且想看最新的评论回复讨论内容还需逐个点击展开&#xff0c;真的很蛋疼&#xff0c;尤其是热评很多的情况&#xff0c;还需要多次点击展开&#xff0c;太麻烦&#xff01; 于是写了一个自动化展开所有评论回复…

诊断解决方案——CANdesc和MICROSAR

文章目录 一、CANdesc二、MICROSAR一、CANdesc canbeded是Vector汽车电子开发软件Nun Autosar标准的工具链之一。 canbeded是以源代码的形式提供的可重用的组件,包括CAN Driver,交互层(IL),网络管理(NM),传输层(TP),诊断层(CANdesc) , 通信测量和标定协议(CCP,XCP) 和 通信控…

Es 索引查询排序分析

文章目录 概要一、Es数据存储1.1、_source1.2、stored fields 二、Doc values2.1、FieldCache2.2、DocValues 三、Fielddata四、Index sorting五、小结六、参考 概要 倒排索引 优势在于快速的查找到包含特定关键词的所有文档&#xff0c;但是排序&#xff0c;过滤、聚合等操作…

并发容器(二):Concurrent类下的ConcurrentHashMap源码级解析

并发容器-ConcurrentHashMap 前言数据结构JDK1.7版本HashEntrySegment 初始化 重要方法Put方法扩容rehash方法 前言 之前我们在集合篇里聊完了HashMap和HashTable&#xff0c;我们又学习了并发编程的基本内容&#xff0c;现在我们来聊一聊Concurrent类下的重要容器&#xff0c…

tsp可视化python

随机生成点的坐标并依据点集生成距离矩阵&#xff0c;通过点的坐标实现可视化 c代码看我的这篇文章tsp动态规划递归解法c from typing import List, Tuple import matplotlib.pyplot as plt from random import randintN: int 4 MAX: int 0x7f7f7f7fdistances: List[List[in…

最长不下降子序列LIS详解

最长不下降子序列指的是在一个数字序列中&#xff0c;找到一个最长的子序列&#xff08;可以不连续&#xff09;&#xff0c;使得这个子序列是不下降&#xff08;非递减&#xff09;的。 假如&#xff0c;现有序列A[1&#xff0c;2&#xff0c;3&#xff0c;-1&#xff0c;-2&…

60.WEB渗透测试-信息收集- 端口、目录扫描、源码泄露(8)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;59.WEB渗透测试-信息收集- 端口、目录扫描、源码泄露&#xff08;7&#xff09; 御剑是用…

植物大战僵尸杂交版全新版v2.1解决全屏问题

文章目录 &#x1f68b;一、植物大战僵尸杂交版❤️1. 游戏介绍&#x1f4a5;2. 如何下载《植物大战僵尸杂交版》 &#x1f680;二、解决最新2.1版的全屏问题&#x1f308;三、画质增强以及减少闪退 &#x1f68b;一、植物大战僵尸杂交版 《植物大战僵尸杂交版》是一款在原版《…

【Android】三种常见的布局LinearLayout、GridLayout、RelativeLayout

【Android】三种常见的布局LinearLayout、GridLayout、RelativeLayout 在 Android 开发中&#xff0c;布局&#xff08;Layout&#xff09;是构建用户界面的基础。通过合理的布局管理&#xff0c;可以确保应用在不同设备和屏幕尺寸上都能有良好的用户体验。本文将简单介绍 And…

困惑度作为nlp指标的理解示例

为了更清晰地说明困惑度的计算过程以及如何通过困惑度判断模型的优劣&#xff0c;我们可以通过一个简单的例子来演示。假设我们有一个非常简单的文本语料库和两个基础的语言模型进行比较。 示例文本 假设我们的文本数据包括以下两个句子&#xff1a; “cat sits on the mat”…

蔡崇信“预言”:微软与OpenAI未来极有可能会分道扬镳

近日&#xff0c;在美国投行摩根大通于上海举行的第二十届全球中国峰会上&#xff0c;阿里巴巴集团联合创始人、董事局主席蔡崇信与摩根大通北亚区董事长兼大中华区投资银行业务副主席关金星&#xff08;Kam Shing Kwang&#xff09;进行了一场精彩对话。蔡崇信深入分享了他对公…

线上教育培训办公系统系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;教师管理&#xff0c;学生管理&#xff0c;运营事件管理 教师账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;作业管理&#xff0c;电…

OpenCore 引导完美升级

备份原有 OC (做好回滚的准备下载新版 OpenCore https://github.com/acidanthera/OpenCorePkg/releases将 1, 3, 4 里面的文件使用新版进行替换 4 里面的文件严格来说并不需要, 只是留着方便使用不追求完美到这就可以收工了将 OC 复制到 U 盘 EFI U 盘格式化可以使用: diskutil…

js 用正则表达式 匹配自定义字符之间的字符串数据,如:( )、[ ]、{ }、< >、【】等括号之间的字符串数据

要使用正则表达式匹配尖括号()之间的数据&#xff0c;可以使用以下代码示例&#xff1a; 在JavaScript中&#xff0c;你可以使用正则表达式来匹配括号()之间的数据。以下是一个简单的例子&#xff0c;它展示了如何使用正则表达式来获取两对括号之间的文本。 // 示例字符串 con…

Spring-kafka消费者消费的一些问题

前言 Spring Kafka 无缝集成了 Spring Boot、Spring Framework 及其生态系统中的其他项目&#xff0c;如 Spring Cloud。通过与 Spring Boot 的自动配置结合&#xff0c;开发者可以快速启动和配置 Kafka 相关的功能。无需编写大量样板代码即可实现 Kafka 的生产和消费功能&…

现在用U盘的人还多吗?多用于哪些场景?

在公司中使用U盘的人仍然相当多&#xff0c;主要在以下场景下使用&#xff1a; 数据存储与备份&#xff1a;U盘作为一种便携式存储设备&#xff0c;被广泛应用于数据的存储和备份。对于需要经常在不同设备或地点之间传输数据的员工来说&#xff0c;U盘提供了一个方便、快捷的解…