【JavaEE进阶】图书管理系统开发日记——捌

文章目录

  • 🍃前言
  • 🎍统一数据返回格式
    • 🚩快速入门
    • 🚩存在问题
      • 🎈问题原因
      • 🎈代码修改
    • 🚩统一格式返回的优点
  • 🍀统一异常处理
  • 🌲前端代码的修改
    • 🚩登录页面
    • 🚩图书列表
    • 🚩删除图书
    • 🚩批量删除图书
    • 🚩添加图书
    • 🚩获取图书详情
    • 🚩修改图书
  • ⭕总结

🍃前言

今天我们将对图书管理系统进行收尾工作,今天的开发任务有两个

  1. 实现统一数据返回格式
  2. 实现统一异常的处理

🎍统一数据返回格式

在【JavaEE进阶】图书管理系统开发日记——柒实现拦截器时,博主对数据的返回格式其实已经进行了封装
在这里插入图片描述
在这里插入图片描述
但是如果每一个接口都这样写,岂不太麻烦了一点儿。

其实spring boot为我们提供了统一数据格式返回的功能

🚩快速入门

统⼀的数据返回格式使⽤ @ControllerAdvice 和ResponseBodyAdvice 的⽅式实现@ControllerAdvice 表⽰控制器通知类

添加类 ResponseAdvice ,实现 ResponseBodyAdvice 接⼝,并在类上添加@ControllerAdvice 注解

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType,MediaType selectedContentType, Class selectedConverterType, ServerHttpRequestrequest, ServerHttpResponse response) {return Result.success(body);}
}
  • supports方法:判断是否要执行beforeBodyWrite方法.true为执行,false不执行.通过该⽅法可以选择哪些类或哪些方法的response要进行处理,其他的不进行处理
从returnType获取类名和⽅法名
//获取执⾏的类
Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
//获取执⾏的⽅法
Method method = returnType.getMethod();
  • beforeBodyWrite方法:对response方法进行具体操作处理

接下来我们进行测试一下,我们先来看一下没有添加统一功能的时候的登录返回
在这里插入图片描述
添加统一功能返回后

在这里插入图片描述
但是此时如果运用到我们的项目中,是会出现问题的

🚩存在问题

这里我就不演示错误的情况了

这里直接给出结论,返回结果为String或i为Result类型时会出现错误

🎈问题原因

那么是什么原因造成的呢?

SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter (从先后顺序排列分别为ByteArrayHttpMessageConverter ,
StringHttpMessageConverter , SourceHttpMessageConverter ,SourceHttpMessageConverter,AllEncompassingFormHttpMessageConverter )

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {//...public RequestMappingHandlerAdapter() {this.messageConverters = new ArrayList<>(4);this.messageConverters.add(new ByteArrayHttpMessageConverter());this.messageConverters.add(new StringHttpMessageConverter());if (!shouldIgnoreXml) {try {this.messageConverters.add(new SourceHttpMessageConverter<>());}catch (Error err) {
// Ignore when no TransformerFactory implementation is available}}this.messageConverters.add(newAllEncompassingFormHttpMessageConverter());}
//...
}

其中AllEncompassingFormHttpMessageConverter会根据项⽬依赖情况添加对应的HttpMessageConverter

public AllEncompassingFormHttpMessageConverter() {if (!shouldIgnoreXml) {try {addPartConverter(new SourceHttpMessageConverter<>());}catch (Error err) {
// Ignore when no TransformerFactory implementation is available}if (jaxb2Present && !jackson2XmlPresent) {addPartConverter(new Jaxb2RootElementHttpMessageConverter());}}if (kotlinSerializationJsonPresent) {addPartConverter(new KotlinSerializationJsonHttpMessageConverter());}if (jackson2Present) {addPartConverter(new MappingJackson2HttpMessageConverter());}else if (gsonPresent) {addPartConverter(new GsonHttpMessageConverter());}else if (jsonbPresent) {addPartConverter(new JsonbHttpMessageConverter());}if (jackson2XmlPresent && !shouldIgnoreXml) {addPartConverter(new MappingJackson2XmlHttpMessageConverter());}if (jackson2SmilePresent) {addPartConverter(new MappingJackson2SmileHttpMessageConverter());}
}

在依赖中引⼊jackson包后,容器会把MappingJackson2HttpMessageConverter 自动注册到
messageConverters 链的末尾.

Spring会根据返回的数据类型,从 messageConverters 链选择合适的HttpMessageConverter .

当返回的数据是非字符串时,使用的MappingJackson2HttpMessageConverter 写⼊返回对象.

当返回的数据是字符串时,StringHttpMessageConverter 会先被遍历到,这时会认为StringHttpMessageConverter 可以使用

public abstract class AbstractMessageConverterMethodProcessor extendsAbstractMessageConverterMethodArgumentResolverimplements HandlerMethodReturnValueHandler {//...代码省略protected <T> void writeWithMessageConverters(@Nullable T value,MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponseoutputMessage)throws IOException, HttpMediaTypeNotAcceptableException,HttpMessageNotWritableException {
//...代码省略if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();for (HttpMessageConverter<?> converter : this.messageConverters) {GenericHttpMessageConverter genericConverter = (converterinstanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ?((GenericHttpMessageConverter)converter).canWrite(targetType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {
//getAdvice().beforeBodyWrite 执⾏之后, body转换成了Result类型的结果body = getAdvice().beforeBodyWrite(body, returnType,selectedMediaType,(Class<? extends HttpMessageConverter<?>>)converter.getClass(),inputMessage, outputMessage);if (body != null) {Object theBody = body;LogFormatUtils.traceDebug(logger, traceOn ->"Writing [" + LogFormatUtils.formatValue(theBody,!traceOn) + "]");addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {genericConverter.write(body, targetType,selectedMediaType, outputMessage);}else {
//此时cover为StringHttpMessageConverter((HttpMessageConverter) converter).write(body,selectedMediaType, outputMessage);}}else {if (logger.isDebugEnabled()) {logger.debug("Nothing to write: null body");}}return;}}}
//...代码省略}
//...代码省略
}

在 ((HttpMessageConverter) converter).write(body, selectedMediaType,outputMessage) 的处理中,调用父类的write方法

由于 StringHttpMessageConverter 重写了addDefaultHeaders方法,所以会执行⼦类的⽅法

在这里插入图片描述

然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String,此时t为Result类型,所以出现类型不匹配"Result cannot be cast to java.lang.String"的异常

🎈代码修改

如果⼀些⽅法返回的结果已经是Result类型了,那就直接返回Result类型的结果即可

如果是String类型,进行处理一下即可

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {//在返回之前, 需要做的事情//body 是返回的结果if (body instanceof Result){return body;}if (body instanceof String){return objectMapper.writeValueAsString(Result.success(body));}return Result.success(body);}
}

🚩统一格式返回的优点

  1. ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据
  2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接⼝都是这样返回的.
  3. 有利于项⽬统⼀数据的维护和修改.
  4. 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容

🍀统一异常处理

统⼀异常处理使⽤的是 @ControllerAdvice+@ExceptionHandler 来实现的,

  • @ControllerAdvice 表⽰控制器通知类
  • @ExceptionHandler 是异常处理器,

两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件

简单使用如下:

@ControllerAdvice
@ResponseBody
public class ErrorAdvice {@ExceptionHandlerpublic Object handler(Exception e) {return Result.fail(e.getMessage());}
}

以上代码表⽰,如果代码出现Exception异常(包括Exception的⼦类),就返回⼀个Result的对象,

Result对象的设置参考博主对返回消息的包装Result.fail(e.getMessage())

public static Result fail(String msg) {Result result = new Result();result.setStatus(ResultStatus.FAIL);result.setErrorMessage(msg);result.setData("");return result;
}

现在我们将它应用于项目中,我们需要针对不同的异常进行返回不同的结果
在这里插入图片描述

@ResponseBody
@ControllerAdvice
public class ErrorAdvice {@ExceptionHandlerpublic Object handler(Exception e) {return Result.fail(e.getMessage());}@ExceptionHandlerpublic Object handler(NullPointerException e) {return Result.fail("发⽣NullPointerException:"+e.getMessage());}@ExceptionHandlerpublic Object handler(ArithmeticException e) {return Result.fail("发⽣ArithmeticException:"+e.getMessage());}
}

至此我们的图书管理系统后端开发已经完毕了,接下来由于我们统一了数据返回格式,所以我们需要对前端代码进行修改

🌲前端代码的修改

🚩登录页面

登录界⾯没有拦截,只是返回结果发⽣了变化,所以只需要根据返回结果修改对应代码即可

登录结果代码修改

function login() {$.ajax({type: "post",url: "/user/login",data: {name: $("#userName").val(),password: $("#password").val()},success: function (result) {if (result.status=="SUCCESS" && result.data==true) {location.href = "book_list.html";} else {alert("账号或密码不正确!");}}});
}

🚩图书列表

针对图书列表⻚有两处变化

  1. 拦截器进行了强制登录校验,如果校验失败,则http状态码返回401,此时会⾛ajax的error逻辑处理
  2. 接⼝返回结果发生了变化

图书列表代码修改:

function getBookList() {$.ajax({type: "get",url: "/book/getListByPage"+location.search,success: function (result) {console.log(result);if (result == null || result.data == null) {location.href = "login.html";return;}if (result != null) {var finalHtml = "";var data = result.data;for (var book of data.records) {finalHtml += '<tr>';finalHtml += '<td><input type="checkbox" name="selectBook" value="'+book.id+'" id="selectBook" class="book-select"></td>'finalHtml += '<td>' + book.id + '</td>';finalHtml += '<td>' + book.bookName + '</td>';finalHtml += '<td>' + book.author + '</td>';finalHtml += '<td>' + book.count + '</td>';finalHtml += '<td>' + book.price + '</td>';finalHtml += '<td>' + book.publish + '</td>';finalHtml += '<td>' + book.statusCN + '</td>';finalHtml += '<td><div class="op">';finalHtml += '<a href="book_update.html?bookId=' + book.id + '">修改</a>'finalHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + book.id + ')">删除</a>'finalHtml += '</div></td>';finalHtml += "</tr>";}$("tbody").html(finalHtml);//翻页信息$("#pageContainer").jqPaginator({totalCounts: data.total, //总记录数pageSize: 10,    //每页的个数visiblePages: 5, //可视页数currentPage: data.pageRequest.currentPage,  //当前页码first: '<li class="page-item"><a class="page-link">首页</a></li>',prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页<\/a><\/li>',next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页<\/a><\/li>',last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页<\/a><\/li>',page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',//页面初始化和页码点击时都会执行onPageChange: function (page, type) {console.log("第" + page + "⻚, 类型:" + type);if (type != 'init') {location.href = "book_list.html?currentPage=" + page;}}});}},error(error) {if(error.status == 401) {location.href = "login.html";}}});
}

🚩删除图书

function deleteBook(id) {//...代码省略success: function (result) {if(result.status=="SUCCESS" || result.data==""){//重新刷新⻚⾯location.href = "book_list.html"}else{alert(result.data);}},error: function (error) {if (error != null && error.status == 401) {//⽤⼾未登录location.href = "login.html";}}//...代码省略
}

🚩批量删除图书

function batchDelete() {var isDelete = confirm("确认批量删除?");if (isDelete) {//获取复选框的idvar ids = [];$("input:checkbox[name='selectBook']:checked").each(function () {ids.push($(this).val());});console.log(ids);//批量删除$.ajax({type: "post",url: "/book/batchDeleteBook?ids="+ids,success: function (result) {if (result.status=="SUCCESS" || result.data==true) {alert("删除成功");//重新刷新⻚⾯location.href = "book_list.html"}},error: function (error) {if (error != null && error.status == 401) {//⽤⼾未登录location.href = "login.html";}}});}
}

🚩添加图书

function add() {$.ajax({type: "post",url: "/book/addBook",data: $("#addBook").serialize(),success: function (result) {console.log(result);console.log(result.data);if (result.status == "SUCCESS" && result.data == "") {location.href = "book_list.html"} else {console.log(result);alert("添加失败:" + result.data);}},error: function (error) {if (error != null && error.status == 401) {//⽤⼾未登录alert("⽤⼾未登录");location.href = "login.html";}}});
}

🚩获取图书详情

$.ajax({type:"get",url: "/book/queryBookById"+location.search,success:function(book){if (result.status == "SUCCESS" && result.data != null) {var book = result.data;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);}}},error: function (error) {if (error != null && error.status == 401) {//⽤⼾未登录alert("⽤⼾未登录");location.href = "login.html";}}
});

🚩修改图书

function update() {$.ajax({type: "post",url: "/book/updateBook",data: $("#updateBook").serialize(),success: function (result) {if (result.status == "SUCCESS" || result.data == "") {location.href = "book_list.html"} else {console.log(result);alert("修改失败:" + result.data);}},error: function (error) {if (error != null && error.status == 401) {//⽤⼾未登录location.href = "login.html";}}});
}

⭕总结

关于《【JavaEE进阶】 图书管理系统开发日记——捌》就讲解到这儿,图书管理系统到此也就开发完毕了,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

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

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

相关文章

docker desktop windows 下载

下载地址 xDocker: Accelerated Container Application Development 安装步骤&#xff1a;下一步&#xff0c;下一步即可

vue -- watermark水印添加方法

前言 项目生成公司水印是很普遍的需求&#xff0c;下面是vue项目生产水印的方法。话不多说&#xff0c;复制粘贴就可以马上解决你的需求。 步骤1 创建watermark.js文件。目录结构 /** 水印添加方法 */let setWatermark (str1, str2) > {let id 1.23452384164.1234124…

算法BFS经典例题(迷宫,多源BFS,BFS解决拓扑排序,FloodFill算法)

目录 力扣733.图像渲染 力扣200.岛屿数量 力扣695岛屿最大面积 力扣130.被围绕的区域&#xff08;微难&#xff09; int[2][3] 力扣733.图像渲染 FloodFill算法 洪水灌溉 广度优先遍历的本质就是暴力遍历 把所有的结果都走一遍&#xff08;具体说怎么走的方向没有谈及&…

Linux下性能分析的可视化图表工具

1 sar 和sadf 1.1 简介 sar命令可以记录系统下的常见活动信息&#xff0c;例如CPU使用率、网络统计数据、Block I/O数据、内存使用情况 等。 sar命令的“-o [file_name]”参数可以将系统活动数据记录到file_name文件&#xff0c;然后通过sadf来解析&#xff0c;sadf命令的“-g…

SpringMVC 学习(八)之文件上传与下载

目录 1 文件上传 2 文件下载 1 文件上传 SpringMVC 对文件的上传做了很好的封装&#xff0c;提供了两种解析器。 CommonsMultipartResolver&#xff1a;兼容性较好&#xff0c;可以兼容 Servlet3.0 之前的版本&#xff0c;但是它依赖了 commons-fileupload …

1817735-38-8,羧基五 PEG5 磺酸,含有羧酸和磺酸的PEG连接剂

1817735-38-8&#xff0c;Carboxy-PEG5-sulfonic acid&#xff0c;羧基五聚乙二醇磺酸&#xff0c;羧基五 PEG5 磺酸&#xff0c;含有羧酸和磺酸的PEG连接剂&#xff0c;可以进行酯化反应 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;1817735-38-8&#xff0c;…

鸿蒙媒体开发【简述】

媒体系统架构 媒体系统提供用户视觉、听觉信息的处理能力&#xff0c;如音视频信息的采集、压缩存储、解压播放等。在操作系统实现中&#xff0c;通常基于不同的媒体信息处理内容&#xff0c;将媒体划分为不同的模块&#xff0c;包括&#xff1a;音频、视频&#xff08;也称播…

pycharm配置环境出现unsupported

情况概述&#xff1a; 本人电脑中的pycharm版本是2019的&#xff0c;在使用python3.10环境的时候&#xff0c;pycharm无法识别&#xff0c;出现如下错误&#xff1a; 网上说是因为python版本过高&#xff0c;无法兼容低版本的pycharm&#xff0c;解决方案分两种&#xff1a;要…

redis中的分布式锁(setIfAbsent)(expire)

目录 应用场景 代码实例1&#xff1a; 代码实例2&#xff1a; setIfAbsent&#xff1a; expire&#xff1a; 举例说明&#xff1a; 代码实例3&#xff1a; 代码实例4&#xff1a; 还是一个同事问的一个问题&#xff0c;然后闲着没事就记录下来了。多人操作同一个保单&a…

全网最详细的接口自动化测试框架实战(Pytest+Allure+Excel)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1. Allure 简介 Allure 框架是一个灵活的、轻量级的、支持多语…

k8s service的概念以及创建方法

Service 的功能&#xff1a; Service主要用于提供网络服务&#xff0c;通过Service的定义&#xff0c;能够为客户端应用提供稳定的访问地址&#xff08;域名或IP地址&#xff09;和负载均衡功能&#xff0c;以及屏蔽后端Endpoint的变化&#xff0c;是K8s实现微服务的核心资源。…

react-JSX基本使用

1.目标 能够知道什么是JSX 能够使用JSX创建React元素 能够在JSX中使用JS表达式 能够使用JSX的条件渲染和列表渲染 能够给JSX添加样式 2.目录 JSX的基本使用 JSX中使用JS表达式 JSX的条件渲染 JSX的列表渲染 JSX的样式处理 3.JSX的基本使用 3.1 createElement()的问题 A. …

在Node.js中如何实现用户身份验证和授权

当涉及到构建安全的应用程序时&#xff0c;用户身份验证和授权是至关重要的一环。在Node.js中&#xff0c;我们可以利用一些流行的库和技术来实现这些功能&#xff0c;确保我们的应用程序具有所需的安全性。本篇博客将介绍如何在Node.js中实现用户身份验证和授权。 用户身份验…

“智农”-大棚可视化

基于自主可控的数字孪生技术、物联网技术、大数据技术&#xff0c;构建全流程的新型农业一体化管理平台&#xff0c;围绕产运销管理全流程&#xff0c;实现生产->存储->包装->运输->销售的全链条管理。融合农业数据管理、农业数据预警显示、多维数据综合显示、农产…

Jvm之内存泄漏

1 内存溢出 1.1 概念 java.lang.OutOfMemoryError&#xff0c;是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;出现OutOfMemoryError。产生该错误的原因主要包括&#xff1a;JVM内存过小。程序不严密&#xff0c;产生了过多的垃圾。 程序体现: 内…

微服务架构 SpringCloud

单体应用架构 将项目所有模块(功能)打成jar或者war&#xff0c;然后部署一个进程--医院挂号系统&#xff1b; > 优点: > 1:部署简单:由于是完整的结构体&#xff0c;可以直接部署在一个服务器上即可。 > 2:技术单一:项目不需要复杂的技术栈&#xff0c;往往一套熟悉的…

LabVIEW水下温盐深数据一体化采集与分析

LabVIEW水下温盐深数据一体化采集与分析 开发一个基于LabVIEW的水下温盐深数据一体化采集与分析系统&#xff0c;实现海洋环境监测的自动化和精确化。通过集成温度、盐度和深度传感器&#xff0c;结合USB数据采集卡&#xff0c;利用LabVIEW软件开发的图形化界面&#xff0c;实…

腾讯云服务器4核8G性能,和阿里云比怎么样?

腾讯云4核8G服务器支持多少人在线访问&#xff1f;支持25人同时访问。实际上程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&#xff0c;假设公网带宽太小&#xff0c;流量直接卡在入口&#xff0c;4核8G配置的CPU内存也会造成计算…

这波知识点分享可得接稳了!非线性模型线性化方法技巧!

现在电力系统优化方向的文章几乎都要提及将非线性模型线性化&#xff0c;使用的方法大致可包括分段线性化&#xff08;最基础&#xff09;&#xff0c;混合整数线性化方法&#xff0c;绝对值法&#xff0c;大M方法&#xff0c;关于非线性模型线性化方法的文章和推文介绍也数不胜…

JAVA AQS源码深度讲解和分析

为方便理解&#xff0c;本文章以非公平锁ReentrantLock()为例作为突破讲解方法lock。 前置知识&#xff1a;JAVA AQS源码分析前置知识-CSDN博客 ReentrantLock的原理 Lock接口的实现类&#xff0c;基本都是通过聚合了一个队列同步器的子类完成线程访问控制的 从最简单的lock方…