SpringBoot 图书管理系统(删除、强制登录、更新图书)详细代码
目录
- 一、删除图书
- 二、批量删除
- 三、强制登录
- 3.1 不使用拦截器
- 3.2 使用拦截器
- 四、更新图书
一、删除图书
1.并不使用delete语句:
- 原因:企业开发中,因为数据就意味着金钱,所以我们不会使用delete去删除(delete删除是物理删除,找不回来那种)
- delete使用场景:delete 语句通常在进行数据修复时才会使用,比如测试人员如果要进行测试,是需要手工造一些数据的。当测试完毕后,这些数据就是脏数据(假数据)了,是没有任何价值的,此时就可
2.以使用delete把数据删掉
- 删除的分类:逻辑删除 + 物理删除
- 物理删除:直接把数据删掉
- 逻辑删除:软删除/假删除,通过字段的标识来表示这个数据被删除了
- 很多地方都在使用,比如软件开发、硬盘删除上
- 删除时不是说把这块内容给清空了,而是把标识改了,这也是我们可以找回的原因(再把标识改回来)
- 物理删除+存档 或 逻辑删除+存档:
- 什么是存档:
- 建一个和原表字段一致的表 ,如果原表要删除一个数据,就把这个数据移到存档表里
- 存档表我们大多数情况不使用,查找时是从原表里查,当需要数据恢复时,才会从存档表里查
- 为什么都使用逻辑删除了,还要使用存档表:存档表可以认为是一个流水表
- 什么是存档:
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】
- 关于日志的打印:
- 可以方便我们后续找错,因为会有【需求没python有实现 + 一条日志都没有的情况】,如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>
3.前端代码:
- 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 不使用拦截器
- 需求介绍:我们希望后端能检查有无登录,如果没有登录跳转到登录页面的操作
- 新增业务状态码和错误信息:
- 业务状态码:用来表示后端是否正确响应了,和http状态码是两个概念(业务状态码表示的是业务的情况,http状态码则是连接的情况,http请求成功才可能有业务状态码)
- 案例:如果业务状态码表示成功,但是返回的数据为0,表示当前没有数据。如果业务状态码表示失败,数据也为0,此时就表示后端请求失败了
- 数字的定义:业务状态码由程序员自定义(什么数字是什么情况),不过,我们一般会把失败定义为的数,把成功定义为>0的数
- 前端的情况:哪怕业务状态码返回的那个数字表示的是失败,依旧是http成功连接的情况,前端走的是【success:funcation】,如果http状态码表示的是失败,走的是【error:function】
- 业务状态码:用来表示后端是否正确响应了,和http状态码是两个概念(业务状态码表示的是业务的情况,http状态码则是连接的情况,http请求成功才可能有业务状态码)
- 错误信息:根据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; } }
3.新增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; } }
4.提取Session:
- 原因:
- 此时的Session是通过【session.setAttribute(“session_user_key”,userInfo)】存在【session_user_key】,但这依然有耦合性太高的可能性,而且是个【硬编码问题】,所以我们可以把他提取为一个常量
- 使用方法:
- 其他:关于硬编码问题 ----> 我们要避免字符串直接出现在代码中
- 如果这个常量在很多地方要用,那就提到一个专门用来放常量的类里
- 如果这个常量,只在当前这个类中使用,也可以通过【private static final xxx = xxx】的提上去
提取成构造函数:
优化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; } }
测试
- 需要先登录才能访问到列表页面
- 如果已经登陆了,但是修改sessionId的值,依旧需要重新登录(找不到服务器里对应的session了)
3.2 使用拦截器
- 关于写法:写法有很多,重点是实现需求
- response.setStatus(401):设置返回的http状态码为401
- 401:未认证登录,或者提供的认证没被认可
- 业务状态码由开发人员自定义,http状态码其实也是由http开发人员自定义的,但现在这已经成为了易总规范,大家都需要遵守
- 后端代码:
- response.setStatus(401):设置返回的http状态码为4php01
- 401:未认证登录,或者提供的认证没被认可
- 业务状态码由开发人员自定义,http状态码其实也是由http开发人员自定义的,但现在这已经成为了易总规范,大家都需要遵守
- response.setStatus(401):设置返回的http状态码为4php01
@Component @Slf4j public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest req编程客栈uest, 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 { @Autowirjavascripted 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"; } }
四、更新图书
后端代码:
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>
前端代码:
<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.jsprice); $("#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>
到此这篇关于SpringBoot 图书管理系统(删除、强制登录、更新图书)详细代码的文章就介绍到这了,更多相关SpringBoot 图书管理系统内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论