Yiner

Nov 08, 2021

后端封装接口返回

约定JSON格式

一般我们和前端约定json格式是这样的
{ "code": 200, "message": "成功", "data": { } }
  • code: 返回状态码
  • message: 返回信息的描述
  • data: 返回值
 

封装java bean

定义返回状态码枚举

💡
定义返回状态码,和信息一一对应,可以按数字分组并约定xxx~xxx 是什么类型的错误码,防止后期错误码重复,更加统一便于维护。
package xxx.xxx.utils.algoUtils; import lombok.Getter; @Getter public enum ResultCode { SUCCESS(200, "成功"), BAD_REQUEST(400, "请求错误"), UNAUTHORIZED(401, "认证失败"), NOT_FOUND(404, "接口不存在"), METHOD_NOT_ALLOWED(405, "方法不被允许"), INTERNAL_SERVER_ERROR(500, "服务器内部错误"), /*参数错误:1001-1999*/ PARAMS_IS_INVALID(1001, "参数无效"), PARAMS_IS_BLANK(1002, "参数为空"), /*用户错误2001-2999*/ PUBLISH_FAILURE(2001, "发布失败"), ...; private final Integer code; private final String message; ResultCode(int code, String message) { this.code = code; this.message = message; } }
 

定义返回结果体

package xxx.xxx.utils.algoUtils; import lombok.Data; import java.io.Serializable; @Data public class Result<T> implements Serializable { private static final long serialVersionUID = 6308315887056661996L; private Integer code; private String message; private T data; public Result setResult(ResultCode resultCode) { this.code = resultCode.getCode(); this.message = resultCode.getMessage(); return this; } public Result setResult(ResultCode resultCode, T data) { this.code = resultCode.getCode(); this.message = resultCode.getMessage(); this.setData(data); return this; } }
  • 返回结果中的code,和message都从定义的状态枚举中获取。
  • 最后结果返回泛型的原因:因为泛型效率要高于object,object需要强制类型转换,
  • 实现了Serializable接口的原因:web传输中,通过流bytes传输方式,速率更快。
 

定义返回结果方法

💡
一般业务返回要么是 success成功,要么就是failure失败,所以需要单独定义两个返回实体对象方法。
 
package xxx.xxx.utils.algoUtils; public class ResultResponse { private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS"; // 成功只返回状态 public static Result success() { return new Result().setResult(ResultCode.SUCCESS); } // 成功返回数据 public static Result success(Object data) { return new Result().setResult(ResultCode.SUCCESS, data); } // 失败只返回状态 public static Result failure(ResultCode resultCode) { return new Result().setResult(resultCode); } // 失败返回数据 public static Result failure(ResultCode resultCode, Object data) { return new Result().setResult(resultCode, data); } }
💡
注意这里定义的是静态方法,因为使用构造方法创建对象再调用太麻烦了, 我们使用静态方法就直接能类调用很方便。
 
 

调用返回结果

手动调用

💡
controller中调用返回结果方法,返回统一json格式。
package xxx.xx.controller; // result相关 import xxx.xxx.utils.algoUtils.ResponseResult; import xxx.xxx.utils.algoUtils.Result; import xxx.xxx.bean.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController //默认全部返回json @RequestMapping("/user") public class UserController { @GetMapping("/list") public Result getUserInfo(){ User u=new User(); u.setUserId("21"); u.setUsername("kenx"); u.setPassword("224r2"); return ResultResponse.success(u); } }
 

统一包装

以上手动调用的代码不够完善,比如:
  1. 每次controller中所有的方法的返回必须都是要Result类型,我们想返回其他类型格式比较难处理;
  1. 不够语义化,其他开发人员看你方法根本就不知道具体返回了什么信息。
 
如果改成以下这个样子,其他开发人员一看就知道具体是返回什么数据,但是实际上发送给前端的数据应该是result格式封装的。
@GetMapping("/list") public User getUserInfo() { User u = new User(); u.setUserId("21"); u.setUsername("kenx"); u.setPassword("224r2"); return u; }
💡
可以通过SpringBoot提供的ResponseBodyAdvice进行统一响应处理。
 

1. 定义注解来识别

💡
该注解用于标识需要包装。
💡
自定义注解@ResponseResult来拦截在controller中有此注解的,代表需要统一返回json格式,没有此注解则按照原样返回。
package xxx.xxx.utils.algoUtils; import java.lang.annotation.*; /** * 统一包装接口返回的值 Result */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResponseResult { }
 

2. 定义请求拦截器

💡
该拦截器用于过滤和设置拦截标志。
💡
定义请求拦截器,通过反射获取到有此注解的HandlerMethod,设置包装拦截标志。
package xxx.xxx.utils.algoUtils; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /** * @author yiner * @since 2021-10-20 */ @Component public class ResponseResultInterceptor implements HandlerInterceptor { //标记名称 public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 请求方法 if (handler instanceof HandlerMethod) { final HandlerMethod handlerMethod = (HandlerMethod) handler; final Class<?> clazz = handlerMethod.getBeanType(); final Method method = handlerMethod.getMethod(); // 判断是否在类上加了注解 if (clazz.isAnnotationPresent(ResponseResult.class)) { // 设置此请求返回体需要包装,往下传递,在ResponseBodyAdvice接口进行判断 request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); // 方法体上是否有注解 } else if (method.isAnnotationPresent(ResponseResult.class)) { // 设置此请求返回体需要包装,往下传递,在ResponseBodyAdvice接口进行判断 request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); } } return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } }
 

3. 定义返回结果包装器

💡
该类是实际进行返回结果包装处理的类。
💡
实现ResponseBodyAdvice<Object> 接口,自定义json返回解析器,根据包装拦截标志判断是否需要自定义返回类型。
package xxx.xxx.utils.algoUtils; import com.alibaba.fastjson.JSON; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import javax.servlet.http.HttpServletRequest; /** * 全局统一响应返回体处理 */ @RestControllerAdvice public class ResponseResultHandler implements ResponseBodyAdvice<Object> { public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attrs.getRequest(); // 判断请求是否有包装标志 ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN); return responseResultAnn != null; } @Override public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) { // write failure result if (body instanceof ResultCode) { return ResultResponse.failure((ResultCode) body); } // write success if (body instanceof String) { return JSON.toJSONString(ResultResponse.success(body)); } else { return ResultResponse.success(body); } } }
  • supports方法用于判断是否有包装标志,supports返回true才会在结果返回前调用下面的beforeBodyWrite方法。
  • 这里偷懒,失败的返回结果直接在Controller里面返回了ResultCode类型,所以用body instanceof ResultCode来判断是否是失败结果。
💡
注意这里string类型返回要单独json序列化返回一下,不然会报转换异常。
 

4. 使用

💡
在Controller中返回任意类型,不用每次都必须返回 Result 。(成功结果的情况)
package xxx.xxx.controller; import xxx.xxx.services.algo.AlgoService; import xxx.xxx.utils.algoUtils.ResponseResult; import xxx.xxx.utils.algoUtils.ResultCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; @CrossOrigin @RestController @RequestMapping("/api/algo") @ResponseResult // !!添加需要「包装返回结果」的注解,表示所有该类方法都会包装返回结果 public class AlgoManageController { @Autowired AlgoService algoService; // 成功结果,直接返回业务结果,会自动包装返回结果 @RequestMapping(value = "/models", method = RequestMethod.POST) public void insertAlgo(HttpServletRequest request, @RequestBody Algo algo) throws IOException { algoService.insertAlgo(algo); } // 失败结果,返回ResultCode类型的错误,会再包装返回结果 @RequestMapping(value = "/publish", method = RequestMethod.POST) public Object publish(HttpServletResponse response, @RequestParam("name") String name, @RequestParam("project") String project, @RequestParam("code") String code) { try { Algo algo = algoService.publish(name, project, code); return algo; } catch (AlgoAlreadyExistsException e) { return ResultCode.PUBLISH_XXX; } catch (AlgoPublishException e) { return ResultCode.PUBLISH_FAILURE; } } }
 

Copyright © 2024 Yiner

logo