📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍
文章目录
- 写在前面的话
- 返回结果统一封装
- 定义一个返回值VO类
- 处理返回值的几种方案
- HandlerMethodReturnValueHandler
- 总结陈词
写在前面的话
本系列博文已连载到第六篇,通过前五篇博文,我们已完成了教师信息的基础增删改查功能,在介绍其他知识专栏之前,先来谈一谈CURD页面的规范问题。
前后端分离的开发模式中,后端程序猿有必要与前端程序猿约定一个相对于规范的返回格式,如果仅仅返回数据,有点像裸奔。因此,后端项目需要对返回结果进行统一封装返回,前端也需要封装请求后置拦截器对返回结果处理。
按业内约定俗成的规范,返回结果至少包含:code 状态码、data 数据、msg 消息内容、error 错误内容。
上述只是基础部分,实际开发中,可能还包含:timestamp 时间戳、requestId 日志ID等等。
加油,程序猿,保持住Tempo,开干,玩的就是真实!
关联文章:
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》
《程序猿入职必会(2) · 搭建具备前端展示效果的 Vue》
《程序猿入职必会(3) · SpringBoot 各层功能完善 》
《程序猿入职必会(4) · Vue 完成 CURD 案例 》
《程序猿入职必会(5) · CURD 页面细节规范 》
返回结果统一封装
定义一个返回值VO类
这个是考虑统一封装的第一步,很简单,仅提供参考。
@Data
public class ResultModel<T> {/*** 成功编码*/public static final String SUCCESS_CODE = ResponseCodeEnum.SUCCESS.getCode();/*** 异常编码*/public static final String ERROR_CODE = ResponseCodeEnum.EX_ERROR.getCode();/*** 响应编码*/private String code = SUCCESS_CODE;/*** 响应数据*/private T data;/*** 响应信息*/private String message = "";/*** 异常详细信息*/private String error = "";/*** 返回成功* @param data* @param <T>* @return*/public static <T> ResultModel<T> success(T data) {return success(data, "");}/*** 返回成功* @param data* @param message* @param <T>* @return*/public static <T> ResultModel<T> success(T data, String message) {return new ResultModel(SUCCESS_CODE, data, message);}/*** 返回失败* @param code* @param message* @param error* @return*/public static ResultModel fail(String code, String message, String error) {return new ResultModel(code, null, message, error);}public static ResultModel fail(ResponseCodeEnum code) {return new ResultModel(code.getCode(), null, code.getMessage(), code.getMessage());}public static ResultModel fail(ResponseCodeEnum code, String error) {return new ResultModel(code.getCode(), null, code.getMessage(), error);}public static ResultModel fail(String error) {return new ResultModel(ResponseCodeEnum.EX_ERROR.getCode(), null, error, error);}public boolean isSuccess() {return Objects.equals(this.code, ResponseCodeEnum.SUCCESS.getCode());}public ResultModel() {}public ResultModel(String code, T data, String message) {this.code = code;this.data = data;this.message = message;}public ResultModel(String code, T data, String message, String error) {this.code = code;this.data = data;this.message = message;this.error = error;}
}
也可以定义一个状态枚举类,非必须:
public enum ResponseCodeEnum {/*** 调用成功*/SUCCESS("00000", "调用成功"),/*** 系统异常*/EX_ERROR("EX00000", "系统异常"),/*** 参数不合法*/EX_PARAM("EX00001", "参数不合法"),/*** 接口调用异常*/EX_REQUEST("EX00002", "接口调用异常"),/*** 接口返回错误*/EX_RESULT("EX00003", "接口返回错误"),/*** 微信接口异常*/EX_WECHAT("EX00004", "微信接口异常"),/*** 令牌为空*/EX_TOKEN_EMPTY("EX00005", "令牌为空"),/*** 令牌无效*/EX_TOKEN_INVALID("EX00006", "令牌无效"),/*** 网站来源无效*/EX_REFERER_INVALID("EX00007", "网站来源无效"),/*** 404*/EX_PAGE_404("EX404", "页面地址无效");private String code;private String message;ResponseCodeEnum(String code, String message) {this.code = code;this.message = message;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overridepublic String toString() {return "[" + this.code + "]" + this.message;}
}
处理返回值的几种方案
SpringBoot 针对 返回值处理有多种方案,相关关键词诸如 ResponseBodyAdvice、MessageConverters、 HandlerMethodReturnValueHandler。
【三者比较】
1、ResponseBodyAdvice(响应拦截器):
作用:ResponseBodyAdvice 允许你在将响应体写入 HTTP 响应之前拦截和修改它。它提供了一种全局定制响应处理逻辑的方式,适用于 Spring MVC 或 Spring WebFlux 应用程序。
工作原理:ResponseBodyAdvice 接口定义了在响应体写入之前将被调用的方法,你可以在这些方法中检查或修改响应体、方法返回类型、请求和其他上下文信息。这使得你可以根据应用程序的需求对响应进行定制化处理。
示例:你可以使用 ResponseBodyAdvice 添加全局的响应头信息、对返回数据进行统一的格式化等。
2、MessageConverters(消息转换器):
作用:MessageConverters 负责将 Controller 方法的返回值转换为 HTTP 响应的内容,以及将请求的内容转换为 Controller 方法的参数。
工作原理:消息转换器负责将 Java 对象与特定的媒体类型之间进行转换,例如 JSON、XML、HTML 等。它可以根据请求的 Content-Type 头信息和方法的返回值类型,选择适当的转换器来进行转换。
示例:你可以使用 MappingJackson2HttpMessageConverter 将 Java 对象转换为 JSON 格式的响应体,或将请求体中的 JSON 数据转换为 Java 对象。
3、HandlerMethodReturnValueHandler(返回值处理器):
作用:HandlerMethodReturnValueHandler 用于处理方法的返回值,将其转换为合适的响应内容。它负责将方法的返回值转换为 HTTP 响应体的内容。
工作原理:HandlerMethodReturnValueHandler 负责将方法的返回值转换为特定的响应内容,例如对象、字符串、视图等。它可以根据返回值的类型和请求的信息来选择适当的处理方式。
示例:你可以使用 ViewMethodReturnValueHandler 将返回值转换为视图,HttpEntityMethodProcessor 将返回的 HttpEntity 对象转换为 HTTP 响应。
总的来说,ResponseBodyAdvice 允许你在响应体写入之前对其进行全局性的处理,MessageConverters 负责将 Java 对象与特定的媒体类型之间进行转换,而 HandlerMethodReturnValueHandler 用于根据方法的返回值类型和请求信息将其转换为合适的响应内容。
关于顺序,HandlerMethodReturnValueHandler 负责处理方法的返回值,ResponseBodyAdvice 在写入响应体之前提供额外的处理机会,而 MessageConverters 则负责将处理过的结果转换为特定的媒体类型。因此,它们的执行顺序是:先执行 HandlerMethodReturnValueHandler,然后是 ResponseBodyAdvice,最后是 MessageConverters。
【方案点评】
三种处理方案各有千秋,本文选用 HandlerMethodReturnValueHandler 展开介绍,顺便可以介绍一下自定义注解的结合使用。
当然,博主所在公司进行框架封装时,采用 ResponseBodyAdvice,并未采用 HandlerMethodReturnValueHandler,原因是,自定义 HandlerMethodReturnValueHandler 意味着要替换 RequestResponseBodyMethodProcessor, SpringMVC 的若干默认定制功能就消失了,可能导致非意料的情况,具体后续再专栏介绍。
HandlerMethodReturnValueHandler
废话不多说,直接上代码。
Step1、定义两个自定义注解,放着备用
后续需要进行返回值封装处理的控制器,就使用@ResultController 注解即可。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResultModelAnnotation {
}@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RestController
@ResultModelAnnotation
public @interface ResultController {
}
Step2、自定义 HandlerMethodReturnValueHandler
实现 HandlerMethodReturnValueHandler 接口,实现 supportsReturnType 和 handleReturnValue 方法。
supportsReturnType 代表生效时机,下方意思是当类或者方法包含 ResultModelAnnotation 注解的时生效。
handleReturnValue 代表返回值处理逻辑,其实就是封装成 ResultModel 格式,再 response 出去。
public class ResultModelHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResultModelAnnotation.class) || returnType.hasMethodAnnotation(ResultModelAnnotation.class));}@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {ResultModel<Object> resultModel;ApiOperation methodAnnotation = returnType.getMethodAnnotation(ApiOperation.class);String message = "";if (methodAnnotation != null) {message = methodAnnotation.value() + "成功";}if (returnValue instanceof ResultModel) {resultModel = (ResultModel<Object>) returnValue;if (!resultModel.isSuccess()) {resultModel.setMessage(message + "error");}} else {resultModel = ResultModel.success(returnValue, message);}mavContainer.setRequestHandled(true);HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);// 设置状态码response.setStatus(HttpStatus.OK.value());response.setHeader("result-model", "true");// 设置ContentTyperesponse.setContentType(MediaType.APPLICATION_JSON_VALUE);// 避免乱码response.setCharacterEncoding("UTF-8");PrintWriter writer = null;try {writer = response.getWriter();writer.write(JSON.toJSONString(resultModel, SerializerFeature.WriteMapNullValue));writer.flush();} catch (IOException ex) {ex.printStackTrace();} finally {if (writer != null) {writer.close();}}}
}
Step3、自定义RequestMappingHandlerAdapter
继承 RequestMappingHandlerAdapter,重写 afterPropertiesSet 方法。
逻辑就是将前面自定义的 ResultModelHandlerMethodReturnValueHandler,放到第一位,首发选手。
public class ResultRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {@Overridepublic void afterPropertiesSet() {super.afterPropertiesSet();List<HandlerMethodReturnValueHandler> returnValueHandlers = super.getReturnValueHandlers();ResultModelHandlerMethodReturnValueHandler handler = new ResultModelHandlerMethodReturnValueHandler();List<HandlerMethodReturnValueHandler> list = new ArrayList<>();list.add(handler);list.addAll(returnValueHandlers);super.setReturnValueHandlers(list);}
}
Step4、控制类添加自定义注解
直接用前面博文提到的教师信息控制器,将 @RestController 注解修改为 @ResultController
Tips:若部分接口不需要按这个格式返回,则不需要修改注解。
@ResultController
@Api(value = "ZyTeacherInfoController", tags = {"教师信息表服务"})
@RequestMapping(value = "/zyTeacherInfo")
public class ZyTeacherInfoController extends BaseController {}
Step5、万事俱备,测试一下
启动服务,访问单个教师的接口:http://localhost:8083/zyTeacherInfo/2
输出信息如下,可以看到其格式了,搞定收工!
{"code": "00000","data": {"createdTime": "2024-05-16 20:07:21","modifiedTime": null,"sortNo": null,"stuItem": null,"teaCode": "2","teaConfig": null,"teaImg": null,"teaName": "李老师","teaPhone": null,"teaType": null,"validFlag": "1"},"error": "","message": "获取教师信息表详细信息成功","success": true
}
总结陈词
此篇文章介绍了前后端分离项目中,关于统一返回结果的封装,仅供学习参考。
下一篇文章介绍前端 Axios 插件封装思路,以及对于这一返回封装结果的接受处理。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。