SpringBoot 图书管理系统

一、删除图书

  1. 并不使用delete语句
    • 原因:企业开发中,因为数据就意味着金钱,所以我们不会使用delete去删除(delete删除是物理删除,找不回来那种)
    • delete使用场景:delete 语句通常在进行数据修复时才会使用,比如测试人员如果要进行测试,是需要手工造一些数据的。当测试完毕后,这些数据就是脏数据(假数据)了,是没有任何价值的,此时就可以使用delete把数据删掉
  2. 删除的分类:逻辑删除 + 物理删除
    • 物理删除:直接把数据删掉
    • 逻辑删除:软删除/假删除,通过字段的标识来表示这个数据被删除了
      • 很多地方都在使用,比如软件开发、硬盘删除上
      • 删除时不是说把这块内容给清空了,而是把标识改了,这也是我们可以找回的原因(再把标识改回来)
    • 物理删除+存档 或 逻辑删除+存档
      • 什么是存档:
        • 建一个和原表字段一致的表 ,如果原表要删除一个数据,就把这个数据移到存档表里
        • 存档表我们大多数情况不使用,查找时是从原表里查,当需要数据恢复时,才会从存档表里查
      • 为什么都使用逻辑删除了,还要使用存档表:存档表可以认为是一个流水表
  3. 删除图书功能的实现方法:因为我们此处我们使用逻辑删除,所以没必要搞一个deleteBook,沿用修改图书的接口updateBook即可
  4. 代码:由于后端代码updateBook已经实现了,所以此处只要写前端代码即可(把id传过去,status固定为0)
function deleteBook(bookId){
    var isDelete = confirm("确认删除?");
    if (isDelete){
        $.ajax({
            type: "post",
            url: "/book/updateBook",
            data: {
                id: bookId,
                status: 0  //接口设计中,0表示被删除的
            },
            success: function (result){
                if (result == ""){
                    location = "book_list.html";
                }else{
                    alert(result);
                }
            }
        });
    }
}

二、批量删除

  1. 思路解析:因为我们使用了逻辑删除,所以批量删除就等于批量更新,但我们不能像删除单个图书一样,使用updateBook,因为此处我们需要更新多个,updateBook只能更新一个
  2. 后端代码
    Controller 层
    • @RequestParam:因为我们使用下面的方式发请求,参数是在查询字符串上。且后端设置的接口是List,默认接收是用数组(如果使用的是数组,可以不加该注解),所以要使用@RequestParam

      • @RequestParam:请求参数是查询字符串上的参数
      • @RequestBody:请求参数是body正文,需要把请求正文的内容转换为对象
      • @ResponseBody:返回的内容是响应正文
    • @RequestParam:关于参数的设计

      • 我们可以使用各种方法去接收前端发来的参数,可以设置参数在正文、url……里,接收的是数组、List、其他类……只要合乎逻辑,能完成需求即可

在这里插入图片描述

@RequestMapping("/batchDelete")
public String batchDeleteBook(@RequestParam List<Integer> ids){
    log.info("接收到的ids:{}", ids);
    Integer res = bookService.batchDeleteBook(ids);
    if (res <= 0){
        log.error("批量删除失败,ids:{}", ids);
        return "失败";
    }
    return "";
}

Service 层

  • try-catch:bookInfo.batchDelete(ids)为【主逻辑方法】,但是代码有可能会执行失败,所以要【try-catch】
  • 关于日志的打印
    • 可以方便我们后续找错,因为会有【需求没有实现 + 一条日志都没有的情况】,如SQL正确运行了,但影响的行数为0
    • 此时如果要排除错误,就需要一点一点debug,十分麻烦。有了日志,可以快速定位是哪里有问题
public Integer batchDeleteBook(@RequestParam List<Integer> ids){
    Integer res = null;
    try{
        res = bookInfoMapper.batchDelete(ids);
    }catch (Exception e){
        log.error("批量删除失败, ids:{}", ids);
    }

    return res;
}

Mapper 层

  • 因为会涉及到动态SQL,为了方便,此处使用xml来编写
Integer batchDelete(List<Integer> ids);
<update id="batchDelete">
    update book_info
    set status = 0
    where id in
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</update>
  1. 前端代码
    • input:checkbox:获取所有的复选框
    • name=‘selectBook’:复选框有很多,此时我们要获取名字为selectBook的
    • checked:表示已经被选中的
    • each(function):对每一个选中的复选框进行某个操作
    • ids.push($(this).val()):把复选框的值放到ids这个数组里
function batchDelete(){
    var isDelete = confirm("确认批量删除?");
    if (isDelete){
        var ids = [];
        $("input:checkbox[name='selectBook']:checked").each(function (){
            ids.push($(this).val());
        })
        $.ajax({
            type: "post",
            url: "/book/batchDelete?ids=" + ids,
            success:function (result){
                if (result == ""){
                    location.href = "book_list.html";
                }else{
                    alert(result);
                }
            }
        });
    }
}

三、强制登录

3.1 不使用拦截器

  1. 需求介绍:我们希望后端能检查有无登录,如果没有登录跳转到登录页面的操作
  2. 新增业务状态码和错误信息
    • 业务状态码:用来表示后端是否正确响应了,和http状态码是两个概念(业务状态码表示的是业务的情况,http状态码则是连接的情况,http请求成功才可能有业务状态码)
      • 案例:如果业务状态码表示成功,但是返回的数据为0,表示当前没有数据。如果业务状态码表示失败,数据也为0,此时就表示后端请求失败了
      • 数字的定义:业务状态码由程序员自定义(什么数字是什么情况),不过,我们一般会把失败定义为的数,把成功定义为>0的数
      • 前端的情况:哪怕业务状态码返回的那个数字表示的是失败,依旧是http成功连接的情况,前端走的是【success:funcation】,如果http状态码表示的是失败,走的是【error:function】
    • 错误信息:根据code知道具体错误是什么,然后前端把这个具体错误反馈给用户
@Data
public class PageResult<T> {
    private List<BookInfo> records;
    private Integer total;

    //此处设置0表示成功,-1为失败
    private Integer code;
    //错误信息
    private String errMsg;
    
    private PageRequest request;

    public PageResult(List<BookInfo> records, Integer total, PageRequest request) {
        this.records = records;
        this.total = total;
        this.request = request;
    }
}
  1. 新增Result类
    • 原因
      • 其他接口都需要强制登录等统一操作,如果都写在代码里,要写很多份,而且一旦要改需求,改动量大,耦合性太高
      • 故我们可以把之前的返回结果进行一个封装,封装为Result类,即每一个Controller接口返回的数据都是Result类,前端根据Result里的信息进行不同的反馈
        在这里插入图片描述
    • 优化tip ---->使用枚举
      • 原因:与其状态码这边使用Integer,还不如使用枚举,因为使用Integer还需要我们专门去查文档来看各个数字的是什么意思
      • Getter 和 Setter:因为枚举是不能用@Data的,所以此处我们要自己写Getter和Setter方法
public enum ResultCode {
    SUCCESS(0),
    FAIL(-1),
    UNLOGIN(-2);
    private int code;

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

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}
  1. 提取Session
    • 原因
      此时的Session是通过【session.setAttribute(“session_user_key”,userInfo)】存在【session_user_key】,但这依然有耦合性太高的可能性,而且是个【硬编码问题】,所以我们可以把他提取为一个常量
    • 使用方法
      在这里插入图片描述
    • 其他:关于硬编码问题 ----> 我们要避免字符串直接出现在代码中
      • 如果这个常量在很多地方要用,那就提到一个专门用来放常量的类里
      • 如果这个常量,只在当前这个类中使用,也可以通过【private static final xxx = xxx】的提上去
  2. 代码
    • new Result()
      在这里插入图片描述
    • 提取成构造函数
      在这里插入图片描述
      在这里插入图片描述
    • 优化tip:使用泛型Result<>
      • Obejct的时机:方法里我们用的是static,表示【静态】,静态的执行时机是要比类早的,但是泛型是要类执行了才能拿到类型是什么。所以当前最多给成员属性设置为泛型,方法中还是要使用Object
      • 代码:需要在方法里面再声明一下
@Data
public class Result<T> {
    /**
     * 业务状态码
     */
    private ResultCode code;
    /**
     * 错误信息
     */
    private String errMsg;
    /**
     * 把所有的返回数据都塞到这里
     */
    private T data;

    /**
     * 成功时执行的方法
     * @return
     */
    public static <T> Result<T> seccess(Object data){
        Result result = new Result();
        result.setCode(ResultCode.SUCCESS);
        result.setErrMsg("");
        result.setData(data);
        return result;
    }

    /**
     * 失败时执行的方法
     * 有错误,无数据
     * @param errMsg
     * @return
     */
    public static <T> Result<T> fail(String errMsg){
        Result result = new Result();
        result.setCode(ResultCode.FAIL);
        result.setErrMsg(errMsg);
        result.setData(null);
        return result;
    }
    /**
     * 失败时执行的方法
     * 有错误,有
     * @param data
     * @param errMsg
     * @return
     */
    public static <T> Result<T> fail(Object data, String errMsg){
        Result result = new Result();
        result.setCode(ResultCode.FAIL);
        result.setErrMsg(errMsg);
        result.setData(data);
        return result;
    }

    /**
     * 未登录时执行的方法
     * @return
     */
    public static <T> Result<T> unlogin(){
        Result result = new Result();
        result.setCode(ResultCode.UNLOGIN);
        result.setErrMsg("用户未登录");
        result.setData(null);
        return result;
    }
}
  1. 测试
    • 需要先登录才能访问到列表页面
    • 如果已经登陆了,但是修改sessionId的值,依旧需要重新登录(找不到服务器里对应的session了)
      在这里插入图片描述

3.2 使用拦截器

  1. 关于写法:写法有很多,重点是实现需求
  2. response.setStatus(401):设置返回的http状态码为401
    • 401:未认证登录,或者提供的认证没被认可
    • 业务状态码由开发人员自定义,http状态码其实也是由http开发人员自定义的,但现在这已经成为了易总规范,大家都需要遵守
  3. 后端代码
    • response.setStatus(401):设置返回的http状态码为401
      • 401:未认证登录,或者提供的认证没被认可
      • 业务状态码由开发人员自定义,http状态码其实也是由http开发人员自定义的,但现在这已经成为了易总规范,大家都需要遵守
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("用户登录校验开始");
        HttpSession session = request.getSession(false);

        if (session != null && session.getAttribute(Constants.SESSION_USER_KEY) != null){
            UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
            if (userInfo != null && userInfo.getId() > 0){
                return true;
            }
        }
        
        response.setStatus(401);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("目标方法执行后");
    }

}
@Configuration
public class webConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/user/login");  //在执行登录操作时,不要拦截
    }
}

在这里插入图片描述
4. 前端代码

  • 如果前端出现了错误,可以一行行注掉代码后,通过console.log打印日志来判断错误在哪
//http连接失败时执行的方法
error: function (error) {
    console.log(error);
    if (error.status == 401) {
        console.log("401");
        location.href = "login.html";
    }
}

四、更新图书

  1. 后端代码
    Controller层
@RequestMapping("/updateBook")
public String updateBook(BookInfo bookInfo){
    log.info("接收到的bookInfo:{}", bookInfo);

    Integer result = bookService.updateBook(bookInfo);
    if (result == 0){
        log.error("更新图书失败,请联系管理员");
        return "失败";
    }

    return "";
}

Service层

public Integer updateBook(BookInfo bookInfo){
    Integer res = 0;
    try{
        res = bookInfoMapper.updateBook(bookInfo);
    }catch (Exception e){
        log.error("更新图书失败,e:{}", e);
    }

    return res;
}

Mapper层

<?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.book_test.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="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>
  1. 前端代码
<script>
    $.ajax({
        type: "get",
        url: "/book/queryBookInfoById" + 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)
            } else {
                alert("图书不存在")
            }
        }
    });

    function update() {
        $.ajax({
            type: "post",
            url: "/book/updateBook",
            data: $("#updateBook").serialize(),
            success: function (result) {
                if (result != null) {
                    location.href = "book_list.html";
                } else {
                    alert(result);
                }
            }
        });
    }
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值