前言
现在我们做项目基本上中大型项目都是选择前后端分离,前后端分离已经成了一个趋势了,所以总这样·我们就要和前端约定统一的api 接口返回json 格式,
这样我们需要封装一个统一通用全局 模版api返回格式,下次再写项目时候直接拿来用就可以了
约定JSON格式
一般我们和前端约定json格式是这样的
1{ 2 "code": 200, 3 "message": "成功", 4 "data": { 5 6 } 7}
- code: 返回状态码
- message: 返回信息的描述
- data: 返回值
封装java bean
定义状态枚举
1package cn.soboys.core.ret; 2 3 4import lombok.Data; 5import lombok.Getter; 6 7/** 8 * @author kenx 9 * @version 1.0 10 * @date 2021/6/17 15:35 11 * 响应码枚举,对应HTTP状态码 12 */ 13@Getter 14public enum ResultCode { 15 16 SUCCESS(200, "成功"),//成功 17 //FAIL(400, "失败"),//失败 18 BAD_REQUEST(400, "Bad Request"), 19 UNAUTHORIZED(401, "认证失败"),//未认证 20 NOT_FOUND(404, "接口不存在"),//接口不存在 21 INTERNAL_SERVER_ERROR(500, "系统繁忙"),//服务器内部错误 22 METHOD_NOT_ALLOWED(405,"方法不被允许"), 23 24 /*参数错误:1001-1999*/ 25 PARAMS_IS_INVALID(1001, "参数无效"), 26 PARAMS_IS_BLANK(1002, "参数为空"); 27 /*用户错误2001-2999*/ 28 29 30 private Integer code; 31 private String message; 32 33 ResultCode(int code, String message) { 34 this.code = code; 35 this.message = message; 36 } 37}
定义返回状态码,和信息一一对应,我们可以约定xxx~xxx 为什么错误码,防止后期错误码重复,使用混乱不清楚,
定义返回体结果体
1package cn.soboys.core.ret; 2 3import lombok.Data; 4 5import java.io.Serializable; 6 7/** 8 * @author kenx 9 * @version 1.0 10 * @date 2021/6/17 15:47 11 * 统一API响应结果格式封装 12 */ 13@Data 14public class Result<T> implements Serializable { 15 16 private static final long serialVersionUID = 6308315887056661996L; 17 private Integer code; 18 private String message; 19 private T data; 20 21 22 public Result setResult(ResultCode resultCode) { 23 this.code = resultCode.getCode(); 24 this.message = resultCode.getMessage(); 25 return this; 26 } 27 28 public Result setResult(ResultCode resultCode,T data) { 29 this.code = resultCode.getCode(); 30 this.message = resultCode.getMessage(); 31 this.setData(data); 32 return this; 33 } 34 35 36}
code,和message都从定义的状态枚举中获取
这里有两个需要注意地方我的数据类型
T data返回的是泛型类型而不是object类型而且我的结果累实现了Serializable接口
我看到网上有很多返回object,最后返回泛型因为泛型效率要高于object,object需要强制类型转换,还有最后实现了Serializable接口因为通过流bytes传输方式web传输,速率更块
定义返回结果方法
一般业务返回要么是 success成功,要么就是failure失败,所以我们需要单独定义两个返回实体对象方法,
1package cn.soboys.core.ret; 2 3/** 4 * @author kenx 5 * @version 1.0 6 * @date 2021/6/17 16:30 7 * 响应结果返回封装 8 */ 9public class ResultResponse { 10 private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS"; 11 12 // 只返回状态 13 public static Result success() { 14 return new Result() 15 .setResult(ResultCode.SUCCESS); 16 } 17 18 // 成功返回数据 19 public static Result success(Object data) { 20 return new Result() 21 .setResult(ResultCode.SUCCESS, data); 22 23 24 } 25 26 // 失败 27 public static Result failure(ResultCode resultCode) { 28 return new Result() 29 .setResult(resultCode); 30 } 31 32 // 失败 33 public static Result failure(ResultCode resultCode, Object data) { 34 return new Result() 35 .setResult(resultCode, data); 36 } 37 38 39 40}
注意这里我定义的是静态工具方法,因为使用构造方法进行创建对象调用太麻烦了, 我们使用静态方法来就直接类调用很方便
这样我们就可以在controller中很方便返回统一api格式了
1 package cn.soboys.mallapi.controller; 2 3import cn.soboys.core.ret.Result; 4import cn.soboys.core.ret.ResultResponse; 5import cn.soboys.mallapi.bean.User; 6import org.springframework.web.bind.annotation.GetMapping; 7import org.springframework.web.bind.annotation.RequestMapping; 8import org.springframework.web.bind.annotation.RestController; 9 10/** 11 * @author kenx 12 * @version 1.0 13 * @date 2021/7/2 20:28 14 */ 15@RestController //默认全部返回json 16@RequestMapping("/user") 17public class UserController { 18 @GetMapping("/list") 19 public Result getUserInfo(){ 20 User u=new User(); 21 u.setUserId("21"); 22 u.setUsername("kenx"); 23 u.setPassword("224r2"); 24 return ResultResponse.success(u); 25 } 26} 27

返回结果符合我们预期json格式 但是这个代码还可以优化,不够完善,比如,每次controller中所有的方法的返回必须都是要Result类型,我们想返回其他类型格式怎么半,还有就是不够语义化,其他开发人员看你方法根本就不知道具体返回什么信息
如果改成这个样子就完美了如
1 @GetMapping("/list") 2 public User getUserInfo() { 3 User u = new User(); 4 u.setUserId("21"); 5 u.setUsername("kenx"); 6 u.setPassword("224r2"); 7 return u; 8 }
其他开发人员一看就知道具体是返回什么数据。但这个格式要怎么去统一出来?
其实我们可以这么去优化,通过SpringBoot提供的ResponseBodyAdvice进行统一响应处理
- 自定义注解@ResponseResult来拦截有此controller注解类的代表需要统一返回json格式,没有就安照原来返回
1package cn.soboys.core.ret; 2 3import java.lang.annotation.*; 4 5/** 6 * @author kenx 7 * @version 1.0 8 * @date 2021/6/17 16:43 9 * 统一包装接口返回的值 Result 10 */ 11@Target({ElementType.TYPE, ElementType.METHOD}) 12@Retention(RetentionPolicy.RUNTIME) 13@Documented 14public @interface ResponseResult { 15} 16
- 定义请求拦截器通过反射获取到有此注解的HandlerMethod设置包装拦截标志
1package cn.soboys.core.ret; 2 3import org.springframework.web.method.HandlerMethod; 4import org.springframework.web.servlet.HandlerInterceptor; 5 6import javax.servlet.http.HttpServletRequest; 7import javax.servlet.http.HttpServletResponse; 8import java.lang.reflect.Method; 9 10/** 11 * @author kenx 12 * @version 1.0 13 * @date 2021/6/17 17:10 14 * 请求拦截 15 */ 16public class ResponseResultInterceptor implements HandlerInterceptor { 17 18 //标记名称 19 public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; 20 21 @Override 22 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 23 //请求方法 24 if (handler instanceof HandlerMethod) { 25 final HandlerMethod handlerMethod = (HandlerMethod) handler; 26 final Class<?> clazz = handlerMethod.getBeanType(); 27 final Method method = handlerMethod.getMethod(); 28 //判断是否在对象上加了注解 29 if (clazz.isAnnotationPresent(ResponseResult.class)) { 30 //设置此请求返回体需要包装,往下传递,在ResponseBodyAdvice接口进行判断 31 request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); 32 //方法体上是否有注解 33 } else if (method.isAnnotationPresent(ResponseResult.class)) { 34 //设置此请求返回体需要包装,往下传递,在ResponseBodyAdvice接口进行判断 35 request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); 36 } 37 } 38 return true; 39 } 40} 41
- 实现
ResponseBodyAdvice<Object>接口自定义json返回解析器根据包装拦截标志判断是否需要自定义返回类型返回类型
1package cn.soboys.core.ret; 2 3import cn.soboys.core.utils.HttpContextUtil; 4import com.alibaba.fastjson.JSON; 5import lombok.extern.slf4j.Slf4j; 6import org.springframework.core.MethodParameter; 7import org.springframework.http.MediaType; 8import org.springframework.http.converter.HttpMessageConverter; 9import org.springframework.http.server.ServerHttpRequest; 10import org.springframework.http.server.ServerHttpResponse; 11import org.springframework.web.bind.annotation.ControllerAdvice; 12import org.springframework.web.context.request.RequestContextHolder; 13import org.springframework.web.context.request.ServletRequestAttributes; 14import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; 15 16 17import javax.servlet.http.HttpServletRequest; 18 19/** 20 * @author kenx 21 * @version 1.0 22 * @date 2021/6/17 16:47 23 * 全局统一响应返回体处理 24 */ 25@Slf4j 26@ControllerAdvice 27public class ResponseResultHandler implements ResponseBodyAdvice<Object> { 28 29 public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; 30 31 /** 32 * @param methodParameter 33 * @param aClass 34 * @return 此处如果返回false , 则不执行当前Advice的业务 35 * 是否请求包含了包装注解 标记,没有直接返回不需要重写返回体, 36 */ 37 @Override 38 public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { 39 HttpServletRequest request = HttpContextUtil.getRequest(); 40 //判断请求是否有包装标志 41 ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN); 42 return responseResultAnn == null ? false : true; 43 } 44 45 /** 46 * @param body 47 * @param methodParameter 48 * @param mediaType 49 * @param aClass 50 * @param serverHttpRequest 51 * @param serverHttpResponse 52 * @return 处理响应的具体业务方法 53 */ 54 @Override 55 public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { 56 if (body instanceof Result) { 57 return body; 58 } else if (body instanceof String) { 59 return JSON.toJSONString(ResultResponse.success(body)); 60 } else { 61 return ResultResponse.success(body); 62 } 63 } 64}
注意这里string类型返回要单独json序列化返回一下,不然会报转换异常
这样我们就可以在controler中返回任意类型,了不用每次都必须返回 Result 如
1package cn.soboys.mallapi.controller; 2 3import cn.soboys.core.ret.ResponseResult; 4import cn.soboys.core.ret.Result; 5import cn.soboys.core.ret.ResultResponse; 6import cn.soboys.mallapi.bean.User; 7import org.springframework.web.bind.annotation.GetMapping; 8import org.springframework.web.bind.annotation.RequestMapping; 9import org.springframework.web.bind.annotation.RestController; 10 11/** 12 * @author kenx 13 * @version 1.0 14 * @date 2021/7/2 20:28 15 */ 16@RestController //默认全部返回json 17@RequestMapping("/user") 18@ResponseResult 19public class UserController { 20 @GetMapping("/list") 21 public User getUserInfo() { 22 User u = new User(); 23 u.setUserId("21"); 24 u.setUsername("kenx"); 25 u.setPassword("224r2"); 26 return u; 27 } 28 29 @GetMapping("/test") 30 public String test() { 31 return "ok"; 32 } 33 @GetMapping("/test2") 34 public Result test1(){ 35 return ResultResponse.success(); 36 } 37 38}
这里还有一个问题?正常情况返回成功的话是统一json 格式,但是返回失败,或者异常了,怎么统一返回错误json 格式,sprinboot有自己的错误格式?

请参考我上一篇,SpringBoot优雅的全局异常处理
扫码关注公众号猿人生了解更多好文

