SpringBoot统一Api接口返回格式
在最流行的前后端交互的项目中,后端一般都是返回指定格式的数据供前端解析,本文使用注解方式返回统一格式的数据,那么下面就看看是如何使用的吧
1)返回响应码实体
package com.zxh.example.entity.model; import lombok.Data; public enum ResultCode { SUCCESS(200, "处理成功"), FAILURE(201, "处理失败"), PARAM_ERROR(400, "参数错误"), NOT_PERMISSION(402, "未授权,无法访问"), WRONG_PASSWORD(403, "用户名或密码错误"), NOT_FOUND(404, "文件不存在或已被删除"), METHOD_NOT_SUPPORT(405, "方法不被允许"), SERVER_ERROR(500, "服务器异常,请联系管理员"); private Integer code; private String message; ResultCode(Integer code, String message) { this.code = code; this.message = message; } public Integer code(){ return this.code; } public String message(){ return this.message; } }
2)返回数据实体
package com.zxh.example.entity.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.io.Serializable; @Data @AllArgsConstructor @NoArgsConstructor @Accessors(chain = true) public class ResultData implements Serializable { //请求状态码 private Integer code; //返回信息描述 private String message; //返回内容 private Object data; public ResultData(Integer code, String message) { this.code = code; this.message = message; } public ResultData(ResultCode resultCode, Object data) { this.code = resultCode.code(); this.message = resultCode.message(); this.data = data; } public ResultData(ResultCode resultCode) { this.code = resultCode.code(); this.message = resultCode.message(); } public static ResultData success() { return new ResultData(ResultCode.SUCCESS); } public static ResultData success(Object data) { return new ResultData(ResultCode.SUCCESS, data); } public static ResultData failure(Integer code, String msg) { return new ResultData(code, msg); } public static ResultData failure(ResultCode resultCode) { return new ResultData(resultCode); } public static ResultData failure(ResultCode resultCode, Object data) { return new ResultData(resultCode, data); } }
若上述静态方法还不满足,可自定义添加。
3)定义注解
package com.zxh.example.anno; import java.lang.annotation.*; /** * 返回体标记注解 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD, ElementType.TYPE}) public @interface ResponseResult { }
此注解用在类或方法上,用来标记是否需要统一格式返回。
4)定义系统常量
package com.zxh.example.config; public class SysConst { public static final String RESPONSE_KEY = "response_result"; }
对于多个地方共有的方法或变量,应公共定义。
5)配置响应拦截器
package com.zxh.example.config; import com.zxh.example.anno.ResponseResult; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /** * 响应拦截器 */ @Component public class ResponseResultInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { //是否是请求的方法,若在方法或类上使用了注解,就设置到request对象中 if (handler instanceof HandlerMethod) { final HandlerMethod handlerMethod = (HandlerMethod) handler; final Class<?> clazz = handlerMethod.getBeanType(); final Method method = handlerMethod.getMethod(); Class<ResponseResult> aClass = ResponseResult.class; //判断是否需要转换,在类上或者方法上 if (clazz.isAnnotationPresent(aClass)) { request.setAttribute(SysConst.RESPONSE_KEY, clazz.getAnnotation(aClass)); } else if (method.isAnnotationPresent(aClass)) { request.setAttribute(SysConst.RESPONSE_KEY, method.getAnnotation(aClass)); } } return true; } }
此拦截器的主要作用是判断是否在类或方法上添加了上述定义的注解,若添加了则给request属性设置参数,以便在响应体返回时判断是否需要重写。
6)重写响应体
package com.zxh.example.config; import com.zxh.example.anno.ResponseResult; import com.zxh.example.entity.model.ResultData; 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.ControllerAdvice; 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; @ControllerAdvice public class ResponseResultHandler implements ResponseBodyAdvice<Object> { /** * 是否使用了包装注解,若使用了则会走 beforeBodyWrite方法 */ @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); ResponseResult responseResult = (ResponseResult) request.getAttribute(SysConst.RESPONSE_KEY); //若返回false则下面的配置不生效 return responseResult == null ? false : true; } @Override public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if (body instanceof ResultData) { return body; } return ResultData.success(body); } }
此类用于判断是否需要统一返回,若需要则按格式重写响应体并返回,否则不重写。
7)配置mvc拦截器
package com.zxh.example.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; /** * mvc配置类,配置拦截器 */ @Configuration public class WebAppConfigurer implements WebMvcConfigurer { @Autowired private ResponseResultInterceptor interceptor; /** * 配置拦截器 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor).addPathPatterns("/**"); } /** * 配置消息转换器 * @param converters */ @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { //去掉String类型转换器。否则当controller返回String类型时会出现类型转换异常ClassCastException converters.removeIf(httpMessageConverter -> httpMessageConverter.getClass() == StringHttpMessageConverter.class); } }
将响应拦截器加入mvc拦截器中,使其生效。若不配置则响应拦截器不会生效。
需要注意的是,必须要配置消息转换器,用来去掉String类型转换器,否则当controller中方法返回类型是String时会报错!
8)创建controller接口
package com.zxh.example.controller; import com.zxh.example.anno.ResponseResult; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api") @Slf4j //@ResponseResult public class TestController { @ResponseResult @GetMapping("/test") public String test(String name) { return "hello" + name; } @GetMapping("/test2") public Map test2() { Map<String, Object> map = new HashMap<>(); map.put("name", "张三"); map.put("age", 20); return map; } }
此接口中有两个方法,一个使用注解标记了,另一个直接返回相应的对象。
9)自定义异常类(需要时可创建)
package com.zxh.example.config; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; /** * 自定义异常类 */ @Setter @Getter @AllArgsConstructor @NoArgsConstructor public class ApiExceptionHandler extends RuntimeException { /** * 错误码 */ protected Integer errorCode; /** * 错误信息 */ protected String errorMsg; public ApiExceptionHandler(String errorMsg) { super(errorMsg); this.errorMsg = errorMsg; } public ApiExceptionHandler(Integer errorCode, String errorMsg, Throwable cause) { super(errorCode.toString(), cause); this.errorCode = errorCode; this.errorMsg = errorMsg; } @Override public synchronized Throwable fillInStackTrace() { return this; } }
10)全局异常处理(需要时可创建)
package com.zxh.example.config; import com.zxh.example.entity.model.ResultCode; import com.zxh.example.entity.model.ResultData; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.FileNotFoundException; /** * 全局异常处理 */ @ControllerAdvice @Slf4j @RestController public class GlobalExceptionHandler { /** * 处理自定义的业务异常 * * @param e * @return */ @ExceptionHandler(value = ApiExceptionHandler.class) public ResultData apiExceptionHandler(ApiExceptionHandler e) { log.error("发生业务异常!原因是:{}", e.getErrorMsg()); return ResultData.failure(e.errorCode, e.errorMsg); } /** * 处理空指针的异常 * * @param e * @return */ @ExceptionHandler(value = NullPointerException.class) public ResultData exceptionHandler(NullPointerException e) { log.error("发生空指针异常!原因是:", e); return ResultData.failure(ResultCode.PARAM_ERROR); } /** * 处理文件找不到的异常 * * @param e * @return */ @ExceptionHandler(value = FileNotFoundException.class) public ResultData exceptionHandler(FileNotFoundException e) { log.error("发生文件找不到异常!原因是:", e); return ResultData.failure(ResultCode.NOT_FOUND); } /** * 索引越界的异常 * * @param e * @return */ @ExceptionHandler(value = ArrayIndexOutOfBoundsException.class) public ResultData exceptionHandler(ArrayIndexOutOfBoundsException e) { log.error("发生数组索引越界异常!原因是:", e); return ResultData.failure(ResultCode.SERVER_ERROR.code(), "数组越界,请检查数据"); } /** * 处理其他异常 * * @param e * @return */ @ExceptionHandler(value = Exception.class) public ResultData exceptionHandler(Exception e, HttpServletResponse response) { response.setCharacterEncoding("UTF-8"); log.error("发生未知异常!原因是:", e); return ResultData.failure(ResultCode.SERVER_ERROR); } }
异常处理是必不可少的,在异常中也需要统一返回格式。
11)启动测试
启动项目,分别访问接口 localhost:8080/api/test?name=11112 和 localhost:8080/api/test2,返回结果如下
图1
图2
上述就是在方法上使用注解 @ResponseResult
进行统一返回格式,与此同时controller中的方法也都根据自己的特性返回了对应实体。
若此controller中所有方法都需要统一格式,则可直接在类加上注解 @ResponseResult
即可,无需在每个方法上都加此注解。
12)分页补充
对于需要分页返回的格式,则可再新建一个实体类,用于存储分页的数据,
package com.zxh.example.entity.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.io.Serializable; @Data @AllArgsConstructor @NoArgsConstructor @Accessors(chain = true) public class ResultPage implements Serializable { //数据内容 private Object data; //数据总条数 private Long total; }
在controller中新建一个方法模拟分页返回的数据,添加注解进行标记即可
@ResponseResult @GetMapping("/test3") public ResultPage test3() { List<String> list = Arrays.asList("123", "23444", "8544"); return new ResultPage(list, 3L); }
返回数据如下图