controller层的处理
controller层的处理
controller请求 发送之后 为了更加规范 我们一般会对返回的数据进行封装 下面就来一步步进行封装
统一返回的数据
基本使用
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {
private String code;
private String message;
private Object object;
/**
* 成功返回结果
* @param message
* @return
*/
public static RespBean success(String message){
return new RespBean(CommonEnum.SUCCESS.getResultCode(), message,null);
}
// 默认返回成功状态码,数据对象
public static RespBean success(Object data) {
return new RespBean(CommonEnum.SUCCESS.getResultCode(),CommonEnum.SUCCESS.getResultMsg(),data);
}
/**
* 成功返回结果
* @param message
* @param obj
* @return
*/
public static RespBean success(String message, Object obj){
return new RespBean(CommonEnum.SUCCESS.getResultCode(), message,obj);
}
/**
* 失败返回结果
* @param commonEnum
* @return
*/
public static RespBean error(CommonEnum commonEnum){
return new RespBean(commonEnum.getResultCode(), commonEnum.getResultMsg(),null);
}
/**
* 失败返回结果
* @param commonEnum
* @return
*/
public static RespBean error(CommonEnum commonEnum,Object object){
return new RespBean(commonEnum.getResultCode(), commonEnum.getResultMsg(),object);
}
/**
* 失败返回结果
* @param message
* @return
*/
public static RespBean error(String message){
return new RespBean(CommonEnum.INTERNAL_SERVER_ERROR.getResultCode(), message,null);
}
/**
* 失败返回结果
* @param message
* @param obj
* @return
*/
public static RespBean error(String message, Object obj){
return new RespBean(CommonEnum.INTERNAL_SERVER_ERROR.getResultCode(), message,obj);
}
}
code为状态码 messages 是消息 object为前端要的数据
这些状态码当然不是直接手动写上去的呀 不然得多麻烦 而且万一要是搞混了 没有统一的标准 那 200是成功 300也是成功 前端根本搞不明白你在干什么
所以为了方便管理和协同开发 一般都是定好了相关的枚举类
在这之前 为了代码的规范 定义一个统一的标准 我们先定义一个状态码的接口
public interface StatusCode {
public int getCode();
public String getMsg();
}
然后写一个状态的枚举类实现StatusCode
/**
* 自定义的状态描述枚举类
* @author chenyaoyan
* @version
* @since 2022-08-17 13:50:57
*/
public enum CommonEnum implements StatusCode {
// 数据操作错误定义
SUCCESS("200", "成功!"),
VALIDATE_ERROR("400", "参数校验失败,请求的数据格式不符"),
SIGNATURE_NOT_MATCH("401","客户端请求未授权!"),
NOT_FOUND("404", "未找到该资源!"),
INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),
SERVER_BUSY("503","服务器正忙,请稍后再试!"),
METHOD_NOT_SUPPORTED("405", "不支持当前请求方法"),
MEDIA_TYPE_NOT_SUPPORTED("415", "不支持当前媒体类型"),
REQ_REJECT("403", "没有权限,请求被拒绝"),
RESPONSE_PACK_ERROR("1003","response返回包装失败");
/** 错误码 */
private String resultCode;
/** 错误描述 */
private String resultMsg;
CommonEnum(String resultCode, String resultMsg) {
this.resultCode = resultCode;
this.resultMsg = resultMsg;
}
@Override
public String getResultCode() {
return resultCode;
}
@Override
public String getResultMsg() {
return resultMsg;
}
}
然后我们就可以使用他了
在controller的方法中 new一个 RespBean 然后里面丢入 CommonEnum和数据 并返回
统一异常处理
有时候系统自己的异常 比如 sql 语句的错误 空指针异常什么的 这个时候如果我们手动的一个个捕捉异常并且封装好 你自己觉得不累吗 幸好 spring boot 有一个@RestControllerAdvice可以供我们使用
我们先定义好自己系统的业务的异常枚举类 然后实现状态码接口
@Getter
public enum BizCode implements StatusCode {
APP_ERROR("2000", "业务异常");
private String code;
private String msg;
BizCode(String code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public String getResultCode() {
return code;
}
@Override
public String getResultMsg() {
return msg;
}
}
自定义异常类
/**
* 自定义业务异常
* @author chenyaoyan
* @version
* @since 2022-08-17 13:50:57
*/
@Getter
public class BizException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
protected String errorCode;
/**
* 错误信息
*/
protected String errorMsg;
// 手动设置异常
public BizException(StatusCode statusCode,String message) {
super(message);
this.errorCode = statusCode.getResultCode();
this.errorMsg = statusCode.getResultMsg();
}
public BizException(StatusCode statusCode, Throwable cause) {
super(statusCode.getResultMsg(), cause);
this.errorCode = statusCode.getResultCode();
this.errorMsg = statusCode.getResultMsg();
}
public BizException(String errorMsg) {
super(errorMsg);
this.errorCode = BizCode.APP_ERROR.getCode();
this.errorMsg = BizCode.APP_ERROR.getMsg();
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}
然后统一的进行拦截
SpringBoot全局异常处理方式主要两种:
使用 @ControllerAdvice 和 @ExceptionHandler 注解。
使用 ErrorController类 来实现
区别:
1. @ControllerAdvice 方式只能处理控制器抛出的异常。此时请求已经进入控制器中。
@ControllerAdvice :表示这是一个控制器增强类,当控制器发生异常且符合类中定义的拦截异
常类,将会被拦截
@ExceptionHandler :定义拦截的异常类
2. ErrorController类 方式可以处理所有的异常,包括未进入控制器的错误,比如404,401等错误
3. 如果应用中两者共同存在,则 @ControllerAdvice 方式处理控制器抛出的异常,
ErrorController类 方式处理未进入控制器的异常。
4. @ControllerAdvice 方式可以定义多个拦截方法,拦截不同的异常类,并且可以获取抛出的异常
信息,自由度更大。
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 参数异常
* @param e
* @return
*/
@ExceptionHandler({BindException.class})
public RespBean MethodArgumentNotValidExceptionHandler(BindException e) {
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return RespBean.error(CommonEnum.VALIDATE_ERROR, objectError.getDefaultMessage());
}
/**
* 处理自定义的业务异常
* @param e
* @return
*/
@ExceptionHandler(BizException.class)
public RespBean APIExceptionHandler(BizException e) {
logger.error(e.getMessage());
return new RespBean(e.getErrorCode(), e.getErrorMsg(), e.getMessage());
}
/**
* 处理空指针的异常
* @param e
* @return
*/
@ExceptionHandler(value =NullPointerException.class)
@ResponseBody
public RespBean exceptionHandler(NullPointerException e){
logger.error("发生空指针异常!原因是:");
return RespBean.error(CommonEnum.INTERNAL_SERVER_ERROR,"空指针异常");
}
/**
* 处理数据库异常
* @param e
* @return
*/
@ExceptionHandler(value = SQLException.class)
public RespBean mySQLException(SQLException e) {
if (e instanceof SQLIntegrityConstraintViolationException) {
return RespBean.error("该数据有与之相关数据,操作失败!",e);
}
return RespBean.error("数据库异常,操作失败!",e);
}
/**
* 处理其他异常
* @param req
* @param e
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public RespBean exceptionHandler(HttpServletRequest req, Exception e){
logger.error("未知异常!原因是:",e);
return RespBean.error(CommonEnum.INTERNAL_SERVER_ERROR.getResultMsg());
}
}
统一响应
如果每次写一个controller中的方法 都要去封装一次返回的类 那多类啊 为了偷懒和实现统一效应
/**
* @author chenyaoyan
* @version 1.0
* @Description 统一响应,拦截处理RestController
* @date 2022-08-17 13:50:57
*/
@RestControllerAdvice(basePackages = {"com.cyy.test.controller"})
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
// response是RespBean类型,或者注释了NotControllerResponseAdvice都不进行包装
return !(methodParameter.getParameterType().isAssignableFrom(RespBean.class)
|| methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class));
}
@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
// String类型不能直接包装
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将数据包装在ResultVo里后转换为json串进行返回
return objectMapper.writeValueAsString(RespBean.success(data));
} catch (JsonProcessingException e) {
throw new BizException(CommonEnum.RESPONSE_PACK_ERROR, e.getMessage());
}
}
// 否则直接包装成RespBean返回
return RespBean.success(data);
}
}
①@RestControllerAdvice(basePackages = {"com.cyy.test.controller"}) 自动扫描了所有指定包下的 controller,在 Response 时进行统一处理。
②重写 supports 方法,也就是说,当返回类型已经是 ResultVo 了,那就不需要封装了,当不等与 ResultVo 时才进行调用 beforeBodyWrite 方法,跟过滤器的效果是一样的。
③最后重写我们的封装方法 beforeBodyWrite,注意除了 String 的返回值有点特殊,无法直接封装成 json,我们需要进行特殊处理,其他的直接 new ResultVo(data); 就 ok 了。
有些时候 如果我们不想要自动响应返回
那也好办
因为只有一部分不要统一响应返回 但是如果一个个过滤又很麻烦
所以定义一个注解 然后使用注解 在统一返回的时候筛选一下 如果又这个注解则不响应就可以了
注解定义
/**
* @author chenyaoyan
* @version 1.0
* @Description 自定义元注解 放在RestController的方法上 如果使用 则不自动包装返回类
* @date 2022-08-17 13:50:57
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotControllerResponseAdvice {
}
然后像上面那样过滤掉 就可以了