后台在接收前端请求数据的时候,后端处理完需要对前端请求的ajax或者H5页面的跳转做统一的返回处理,同时也需要对可能造成的异常进行全局统一处理,使当程序运行异常的时候ajax返回统一的异常码,H5请求跳转到统一的错误提示页面。
1、先对全局正常返回类进行包装
@RestControllerAdvice(basePackages = {"com.xxx.xxx.v2"})
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
private static final Logger logger = LoggerFactory.getLogger(ControllerResponseAdvice.class);
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
// response是ResultVo类型,或者注释了NotControllerResponseAdvice都不进行包装
return !(methodParameter.getParameterType().isAssignableFrom(ResultVo.class)
|| methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class));
}
@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// String类型不能直接包装
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将数据包装在ResultVo里后转换为json串进行返回
return objectMapper.writeValueAsString(new ResultVo(data));
} catch (Exception e) {
e.printStackTrace();
}
}
// 否则直接包装成ResultVo返回
return new ResultVo(data);
}
}
ResultVo 为统一返回的包装类方便前台统一解析。
public class ResultVo {
// 状态码
private int code;
// 状态信息
private String msg;
// 返回对象
private Object data;
// 手动设置返回vo
public ResultVo(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
// 默认返回成功状态码,数据对象
public ResultVo(Object data) {
this.code = ResultCode.SUCCESS.getCode();
this.msg = ResultCode.SUCCESS.getMsg();
this.data = data;
}
// 返回指定状态码,数据对象
public ResultVo(ResultCode resultCode, Object data) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.data = data;
}
// 只返回状态码
public ResultVo(ResultCode resultCode) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.data = null;
}
//省略get set方法
}
NotControllerResponseAdvice.class 这个类是一个注解,意思是忽略返回包装类,大概类似通信接口直接返回 success 就行了,需要要状态吗和返回数据体。注解这边写的可以先忽略注释掉。
这样的话当请求一个controller的时候在没有成功异常返回的都是正常的统一包装体了
@RestController
@RequestMapping("/textApi")
public class TextApi {
@Autowired
LoanService loanService;
@RequestMapping("/queryLoan")
public Loan queryLoan(){
//int i = 1/0;
return loanService.queryById(40L);
}
@RequestMapping("/queryPage")
public ModelAndView queryPage(){
//int i = 1/0;
return new ModelAndView("/error");
}
}
可以看到代码里面直接返回的实体类,但前端接口返回的是在面自动加了ResultVo。
后面还需要对可能的异常做一下统一返回的处理,模拟逻辑错误可以把
int i = 1/0;这行代码的注释放开进行模拟。
首先创建一个全局异常类的拦截处理,对异常进项包装返回前端
@Slf4j
@RestControllerAdvice(basePackages = {"com.xxx.xxx.v2"})
public class GlobalExceptionHandlerV2 {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandlerV2.class);
/**
* 请求格式为json
*/
private static final String APPLICATION_JSON = "application/json";
/**
* 请求格式为xml
*/
private static final String XML_HTTP_REQUEST = "XMLHttpRequest";
/**
* json后缀
*/
private static final String JSON_SUFFIX = ".json";
/**
* xml后缀
*/
private static final String XML_SUFFIX = ".xml";
/**
* 索引越界
*/
private static final int INDEX_NOT_FOUND = -1;
@ExceptionHandler(value = Exception.class)
ModelAndView MethodArgumentNotValidExceptionHandler(Exception e, HttpServletRequest request){
//ajax 请求异常返回统一的错误码
if(isAjaxRequest(request)){
ModelAndView modelAndView=new ModelAndView(new MappingJackson2JsonView());
modelAndView.addObject(new ResultVo(ResultCode.VALIDATE_ERROR, e.getMessage()));
e.printStackTrace();
return modelAndView;
}else{
//H5页面报错返回统一的样式
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg",e.getMessage());
modelAndView.setViewName("/error");
e.printStackTrace();
return modelAndView;
}
}
/**
* 是否是Ajax异步请求
*
* @param request 客户端请求
* @return boolean
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
String accept = request.getHeader("accept");
if (accept != null && accept.contains(APPLICATION_JSON)) {
return true;
}
String xRequestedWith = request.getHeader("X-Requested-With");
if (xRequestedWith != null && xRequestedWith.contains(XML_HTTP_REQUEST)) {
return true;
}
String uri = request.getRequestURI();
if (inStringIgnoreCase(uri, JSON_SUFFIX, XML_SUFFIX)) {
return true;
}
String ajax = request.getParameter("__ajax");
return inStringIgnoreCase(ajax, "json", "xml");
}
/**
* 是否包含字符串(不区分大小写)
*
* @param str 验证字符串
* @param searchStrings 字符串组
* @return boolean
*/
public static boolean inStringIgnoreCase(String str, String... searchStrings) {
if (str != null && searchStrings != null) {
for (String s : searchStrings) {
if (str.equalsIgnoreCase(StringUtils.trim(s))) {
return true;
}
}
}
return false;
}
}
这里面进行了请求是否为ajax的判断,若是前端为ajax则返回统一的JSON格式,若是H5的页面跳转则跳到统一的error页面,其中 MappingJackson2JsonView这个类需要了解一下。
这个就进行的错误的统一返回包装了,前端获取到数据判断下code 不为约定的成功就是代表数据有问题了。
这个总体可以理解为两个流程:
1、先实现数据的统一返回。
2、全局controller的异常处理。