《程序猿入职必会(6) · 返回结果统一封装》

CSDN 2024-08-21 08:35:01 阅读 85

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

写在前面的话返回结果统一封装定义一个返回值VO类处理返回值的几种方案HandlerMethodReturnValueHandler

总结陈词

CSDN.gif

写在前面的话

本系列博文已连载到第六篇,通过前五篇博文,我们已完成了教师信息的基础增删改查功能,在介绍其他知识专栏之前,先来谈一谈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;

}

@Override

public 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 {

@Override

public boolean supportsReturnType(MethodParameter returnType) {

return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResultModelAnnotation.class) || returnType.hasMethodAnnotation(ResultModelAnnotation.class));

}

@Override

public 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");

// 设置ContentType

response.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 {

@Override

public 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 插件封装思路,以及对于这一返回封装结果的接受处理。

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

CSDN_END.gif



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。