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

news2024/12/28 5:10:07

目录

🚩项目所需要的技术栈

🚩项目准备工作

🎈环境准备

🎈数据库准备

🚩前后端交互分析

🎈登录

📝前后端交互

📝实现服务器代码

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

🎈添加图书

📝前后端交互

📝实现服务器代码

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

🎈图书列表

📝前后端交互

📝实现服务器代码

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

🎈修改图书

📝前后端交互

📝实现服务器代码 

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

🎈删除图书

📝约定前后端交互接⼝

📝实现服务器代码 

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

🎈批量删除图书

📝约定前后端交互接⼝

📝实现服务器代码 

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


🚩项目所需要的技术栈

 该项目是一个针对于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批量删除图书:


检查前端代码:

此时批量删除成功!


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


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

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

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

相关文章

Caffe、PyTorch、Scikit-learn、Spark MLlib 和 TensorFlowOnSpark 概述

在 AI 框架方面,有几种工具可用于图像分类、视觉和语音等任务。有些很受欢迎,如 PyTorch 和 Caffe,而另一些则更受限制。以下是四种流行的 AI 工具的亮点。 Caffee Caffee是贾扬青在加州大学伯克利分校(UC Berkeley)时开发的深度学习框架。该工具可用于图像分类、语音和…

Nativefier—使用—快速将网站打包成桌面程序

--天蝎座 Nativefier简介 Nativefier是一个命令行工具&#xff0c;仅仅通过一行代码就可以轻松地为任何的网站创建桌面应用程序&#xff0c;应用程序通过Electron打包成系统可执行文件&#xff08;如.app, .exe等&#xff09;&#xff0c;可以运行在Windows&#xff0c;Mac和L…

需求:如何给文件添加水印

今天给大家介绍一个简单易用的水印添加框架&#xff0c;框架抽象了各个文件类型的对于水印添加的方法。仅使用几行代码即可为不同类型的文件添加相同样式的水印。 如果你有给PDF、图片添加水印的需求&#xff0c;EasyWatermark是一个很好的选择&#xff0c;主要功能就是传入一…

Mybatis工作流程和插件开发

在了解插件开发之前&#xff0c;我们先总体的来梳理一下Mybatis的大致执行流程&#xff1a; 1.new SqlSessionFactoryBuilder().build(inputStream):先根据配置文件&#xff08;包含了全局配置文件和映射配置文件&#xff09;初始化一个对象Configuration&#xff08;这里对象里…

LaTex入门教程

目录 1.说明 2.页面的分区 3.入门介绍 &#xff08;1&#xff09;命令 &#xff08;2&#xff09;环境 &#xff08;3&#xff09;声明 &#xff08;4&#xff09;注释 4.代码结构 &#xff08;1&#xff09;导言区 &#xff08;2&#xff09;支持中文 &#xff08;3…

2024都市解压爆笑喜剧《脑洞大开》6月28日上映

随着暑期档的临近&#xff0c;电影市场迎来了一剂强心针——由何欢、王迅、克拉拉、卜钰、孙越、九孔等众多实力派笑星联袂主演的都市解压爆笑喜剧《脑洞大开》正式宣布定档&#xff0c;将于6月28日在全国各大影院欢乐上映&#xff0c;誓为观众带来今夏最畅快淋漓的笑声风暴。 …

逆天改命 17岁中专女生横扫全球数学竞赛

“逆天改命!17岁中专女生横扫全球数学竞赛,清华北大高手纷纷落马!” 最近全网被这则消息震惊了。 来!随便挑几个题目,让大家体验一下阿里巴巴全球数学竞赛的难度? 数学是人工智能算法的基石。它为算法提供了逻辑框架和分析工具,使得人工智能能够处理复杂的数据和问…

驾考模拟 | 电脑上使用浏览器模拟科目一考试

驾考模拟 背景 有个亲戚要考科目一&#xff0c;大叔之前没怎么用过电脑&#xff0c;想要在电脑上练习科目一&#xff0c;找找使用电脑考试的感觉。 有一些本地安装的软件可以满足这个需求&#xff0c;但通常要付费&#xff0c;没这个必要&#xff0c;毕竟只是用来模拟考的。 …

【最新鸿蒙应用开发】——鸿蒙中的“Slot插槽”?@BuilderParam

构建函数-BuilderParam 传递 UI 1. 引言 BuilderParam 该装饰器用于声明任意UI描述的一个元素&#xff0c;类似slot占位符。 简而言之&#xff1a;就是自定义组件允许外部传递 UI Entry Component struct Index {build() {Column({ space: 15 }) {SonCom() {// 直接传递进来…

《大数据分析》期末考试整理

一、单项选择题&#xff08;1*9&#xff09; 1.大数据发展历程&#xff1a;出现阶段、热门阶段和应用阶段 P2 2.大数据影响 P3 1&#xff09;大数据对科学活动的影响 2&#xff09;大数据对思维方式的影响 3&#xff09;大数据对社会发展的影响 4&#xff09;大数…

昂科烧录器支持Prolific旺玖科技的电力监控芯片PL7413C1FIG

芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表&#xff0c;其中Prolific旺玖科技的高度集成的电力监控芯片PL7413C1FIG已经被昂科的通用烧录平台AP8000所支持。 PL7413C1FIG是一款高度集成的电力监控芯片&#xff0c;用于测量电力使用情况的…

vue-饼形图-详细

显示效果 代码 <template> <div style"height: 350px;"> <div :class"className" :style"{height:height,width:width}"></div> </div> </template> <script> import * as echarts from echarts; req…

Typora实现设置代码块默认语言_亲测有效(AutoHotKey方式和修改配置文件)

Typora实现设置代码块默认语言&#xff08;AutoHotKey方式和修改配置文件&#xff09; 前言&#xff0c;需求使用AutoHotKey热键脚本【最简单方便】实现步骤建议 最终效果其他方法自定义Typora代码块快捷键设置。应对ctrlshiftk快捷键被其他占用的情况。 前言&#xff0c;需求 …

07--Zabbix监控告警

前言&#xff1a;和普米一样运维必会的技能&#xff0c;这里总结一下&#xff0c;适用范围非常广泛&#xff0c;有图形化界面&#xff0c;能帮助运维极快确定问题所在&#xff0c;这里记录下概念和基础操作。 1、zabbix简介 Zabbix是一个基于 Web 界面的企业级开源解决方案&a…

厂里资讯之自媒体文章自动审核

自媒体文章-自动审核 1)自媒体文章自动审核流程 1 自媒体端发布文章后&#xff0c;开始审核文章 2 审核的主要是审核文章的内容&#xff08;文本内容和图片&#xff09; 3 借助第三方提供的接口审核文本 4 借助第三方提供的接口审核图片&#xff0c;由于图片存储到minIO中&…

高速信号——NRZ,PAM4调制技术

1&#xff1a;码元 了解调制技术需要引出“码元”的概念。 一个码元就是一个脉冲信号&#xff0c;即一个最小信号周期内的信号&#xff0c;我们都能够理解&#xff0c;最简单的电路&#xff0c;以高电平代表1&#xff0c;低电平代表0&#xff0c;一个代表1或者0的信号&#x…

Linux基础I/O之文件描述符fd 重定向(上)

目录 一、预备知识 二、C语言中的文件接口 三、系统调用中的文件接口 一、预备知识 首先我们要明确的一个观点是 --- 文件 内容 属性。而且我们之前也还将过一个概念&#xff0c;那就是Linux下一切皆文件。 内容是数据&#xff0c;属性也是数据 --- 那么也就是说我…

t265 jetpack 6 px4 ros2

Ubuntu22.04 realsenseSDK2和ROS2Wrapper安装方法,包含T265版本踩坑问题_ros2 realsense-CSDN博客 210 git clone https://github.com/IntelRealSense/librealsense.git 212 git branch 215 git tag 218 git checkout v2.51.1 219 git branch 265 git clone https://…

C语言---------深入理解指针

目录 一、字符指针 二、指针数组&#xff1a; 三、数组指针&#xff1a; 1、定义&#xff1a; 2、&数组名和数组名区别&#xff1a; 3、数组指针的使用&#xff1a; 四、数组参数&#xff0c;指针参数&#xff1a; 1、一维数组传参&#xff1a; 2、二维数组传参&am…

基于springboot的大学计算机基础网络教学系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于springboot的大学计算机基础网络教学…