当前位置: 首页 > news >正文

SpringBoot 图书管理系统

文章目录

  • 一、删除图书
  • 二、批量删除
  • 三、强制登录
    • 3.1 不使用拦截器
    • 3.2 使用拦截器
  • 四、更新图书

一、删除图书

  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_infoset status = 0where 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 {@Overridepublic 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;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("目标方法执行后");}}
@Configuration
public class webConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic 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>

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 鸿蒙开发笔记_电商严选02_登录页面跳转到我的页面、并传值
  • Matlab:科学计算与工程应用的强大利器
  • 【Linux】精通GDB:打造你的Linux调试超能力
  • cJSON-轻量级解析模块、字符串的神——编织STM32C8T6与阿里云信息传递的纽带
  • 引用和指针的区别(面试概念性题型)
  • cross-plateform 跨平台应用程序-09-phonegap/Apache Cordova 介绍
  • 使用nvm工具实现多个nodejs版本的维护和切换
  • 炫酷HTML蜘蛛侠登录页面
  • 裸金属服务器与云服务器的区别有哪些?
  • vue3路由基本使用
  • leetcode练习 子集II
  • uni-app实现web-view和App之间的相互通信
  • 思维训练900
  • windows系统安装docker
  • PostgreSQL - tutorial
  • python3.6+scrapy+mysql 爬虫实战
  • 「面试题」如何实现一个圣杯布局?
  • Android 控件背景颜色处理
  • Android单元测试 - 几个重要问题
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • eclipse的离线汉化
  • gulp 教程
  • JavaScript服务器推送技术之 WebSocket
  • php面试题 汇集2
  • Theano - 导数
  • webpack+react项目初体验——记录我的webpack环境配置
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 大数据与云计算学习:数据分析(二)
  • 浮现式设计
  • 开发基于以太坊智能合约的DApp
  • 力扣(LeetCode)357
  • 设计模式 开闭原则
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 小程序测试方案初探
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • 原生Ajax
  • 终端用户监控:真实用户监控还是模拟监控?
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • Spring第一个helloWorld
  • 阿里云ACE认证之理解CDN技术
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​【数据结构与算法】冒泡排序:简单易懂的排序算法解析
  • ​Linux·i2c驱动架构​
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • #includecmath
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (1)Nginx简介和安装教程
  • (13)DroneCAN 适配器节点(一)
  • (a /b)*c的值
  • (附源码)c#+winform实现远程开机(广域网可用)
  • (论文阅读40-45)图像描述1
  • (十二)Flink Table API
  • (四)c52学习之旅-流水LED灯
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题