spring项目中,我们通常规定了返回的格式(成功-失败-异常),特别是异常怎么处理方便呢?
1.自定义状态码实体
package com.ruoyi.common.constant; /** * 返回状态码 * * @author ruoyi */ public class HttpStatus { /** * 操作成功 */ public static final int SUCCESS = 200; /** * 对象创建成功 */ public static final int CREATED = 201; /** * 请求已经被接受 */ public static final int ACCEPTED = 202; /** * 操作已经执行成功,但是没有返回数据 */ public static final int NO_CONTENT = 204; /** * 资源已被移除 */ public static final int MOVED_PERM = 301; /** * 重定向 */ public static final int SEE_OTHER = 303; /** * 资源没有被修改 */ public static final int NOT_MODIFIED = 304; /** * 参数列表错误(缺少,格式不匹配) */ public static final int BAD_REQUEST = 400; /** * 未授权 */ public static final int UNAUTHORIZED = 401; /** * 访问受限,授权过期 */ public static final int FORBIDDEN = 403; /** * 资源,服务未找到 */ public static final int NOT_FOUND = 404; /** * 不允许的http方法 */ public static final int BAD_METHOD = 405; /** * 资源冲突,或者资源被锁 */ public static final int CONFLICT = 409; /** * 不支持的数据,媒体类型 */ public static final int UNSUPPORTED_TYPE = 415; /** * 系统内部错误 */ public static final int ERROR = 500; /** * 接口未实现 */ public static final int NOT_IMPLEMENTED = 501; }
2.创建返回实体
按照规定的格式创建返回实体,这样子就可以规范返回的格式-下面是一个自定义的返回实体
package com.ruoyi.common.core.domain; import java.util.HashMap; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.utils.StringUtils; /** * 操作消息提醒 * * @author ruoyi */ public class AjaxResult extends HashMap<String, Object> { private static final long serialVersionUID = 1L; /** 状态码 */ public static final String CODE_TAG = "code"; /** 返回内容 */ public static final String MSG_TAG = "msg"; /** 数据对象 */ public static final String DATA_TAG = "data"; /** * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 */ public AjaxResult() { } /** * 初始化一个新创建的 AjaxResult 对象 * * @param code 状态码 * @param msg 返回内容 */ public AjaxResult(int code, String msg) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); } /** * 初始化一个新创建的 AjaxResult 对象 * * @param code 状态码 * @param msg 返回内容 * @param data 数据对象 */ public AjaxResult(int code, String msg, Object data) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); if (StringUtils.isNotNull(data)) { super.put(DATA_TAG, data); } } /** * 返回成功消息 * * @return 成功消息 */ public static AjaxResult success() { return AjaxResult.success("操作成功"); } /** * 返回成功数据 * * @return 成功消息 */ public static AjaxResult success(Object data) { return AjaxResult.success("操作成功", data); } /** * 返回成功消息 * * @param msg 返回内容 * @return 成功消息 */ public static AjaxResult success(String msg) { return AjaxResult.success(msg, null); } /** * 返回成功消息 * * @param msg 返回内容 * @param data 数据对象 * @return 成功消息 */ public static AjaxResult success(String msg, Object data) { return new AjaxResult(HttpStatus.SUCCESS, msg, data); } /** * 返回错误消息 * * @return */ public static AjaxResult error() { return AjaxResult.error("操作失败"); } /** * 返回错误消息 * * @param msg 返回内容 * @return 警告消息 */ public static AjaxResult error(String msg) { return AjaxResult.error(msg, null); } /** * 返回错误消息 * * @param msg 返回内容 * @param data 数据对象 * @return 警告消息 */ public static AjaxResult error(String msg, Object data) { return new AjaxResult(HttpStatus.ERROR, msg, data); } /** * 返回错误消息 * * @param code 状态码 * @param msg 返回内容 * @return 警告消息 */ public static AjaxResult error(int code, String msg) { return new AjaxResult(code, msg, null); } }
3.使用@RestControllerAdvice创建全局处理器
@RestControllerAdvice是@ControllerAdvice和@ResponseBody的合并。此注解标记的类就是全局处理类,在这个类中可以自定义一个个的方法,用 @ExceptionHandler(异常类型)注解,那么它就回去拦截对应的异常,在该方法中进行处理,且把处理结果返回给页面。
3.1创建全局异常处理器
package com.ruoyi.framework.web.exception; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AccountExpiredException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.validation.BindException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.NoHandlerFoundException; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.exception.BaseException; import com.ruoyi.common.exception.CustomException; import com.ruoyi.common.exception.DemoModeException; import com.ruoyi.common.utils.StringUtils; /** * 全局异常处理器 * * @author ruoyi */ @RestControllerAdvice //标识这是全局异常处理器 public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 基础异常 */ @ExceptionHandler(BaseException.class) //表示对指定异常进行拦截并处理 public AjaxResult baseException(BaseException e) { return AjaxResult.error(e.getMessage()); }
/**
* 业务异常
*/
@ExceptionHandler(CustomException.class)
public AjaxResult businessException(CustomException e)
{
if (StringUtils.isNull(e.getCode()))
{
return AjaxResult.error(e.getMessage());
}
return AjaxResult.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class) public AjaxResult handleException(Exception e) { log.error(e.getMessage(), e); return AjaxResult.error(e.getMessage()); } }
注解1:@RestControllerAdvice //标识这是全局异常处理器
注解2:@ExceptionHandler(BaseException.class) //表示对指定异常进行拦截并处理。当我们抛出指定的异常(可以自定义异常)时,会被ExceptionHandler拦截,并进行处理
3.2上面使用了自定义的异常
package com.ruoyi.common.exception; /** * 自定义异常 * * @author ruoyi */ public class CustomException extends RuntimeException { private static final long serialVersionUID = 1L; private Integer code; private String message; public CustomException(String message) { this.message = message; } public CustomException(String message, Integer code) { this.message = message; this.code = code; } public CustomException(String message, Throwable e) { super(message, e); this.message = message; } @Override public String getMessage() { return message; } public Integer getCode() { return code; } }
3.3业务中抛出异常
public String login(String username, String password, String code, String uuid) { //1.校验验证码 String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; //缓存中存入验证码格式 {Constants.CAPTCHA_CODE_KEY + uuid:验证码} String captcha = redisCache.getCacheObject(verifyKey); redisCache.deleteObject(verifyKey); if (captcha == null) { //缓存中没有验证码 发起一个异步任务-记录此次错误登录信息 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); throw new CaptchaExpireException(); } if (!code.equalsIgnoreCase(captcha)) //验证码错误 发起一个异步任务-记录此次错误登录信息 { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); throw new CaptchaException(); } // 2.用户验证 Authentication authentication = null; try { // 该方法会去调用package com.ruoyi.framework.web.service.UserDetailsServiceImpl.loadUserByUsername 若是校验失败,会抛出异常 authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(username, password)); //loadUserByUsername方法会获取用户,匹配密码是自动完成的 } catch (Exception e) { if (e instanceof BadCredentialsException) //用户密码不匹配 发起一个异步任务-记录此次错误登录信息 { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } else { //其它不匹配 比如没有该用户 该用户被停用等等 发起一个异步任务-记录此次错误登录信息 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new CustomException(e.getMessage()); } } //发起一个异步任务-记录此次成功登录信息 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); // 生成token return tokenService.createToken(loginUser); }
throw new CustomException(e.getMessage());
这里我们在service层就直接抛出异常CustomException,会被上面的全局异常处理器拦截,和@ExceptionHandler(CustomException.class)匹配,匹配到了下面的方法
/** * 业务异常 */ @ExceptionHandler(CustomException.class) public AjaxResult businessException(CustomException e) { if (StringUtils.isNull(e.getCode())) { return AjaxResult.error(e.getMessage()); } return AjaxResult.error(e.getCode(), e.getMessage()); }
抛出CustomException,该方法执行,最后返回一个AjaxResult对象给页面 ----return AjaxResult.error(e.getCode(), e.getMessage());
{
- code: 状态码
- msg: 返回内容
- data :数据对象
}
3.4说明
全局异常管理器实际上就是创建了拦截器对抛出的异常进行处理,并把处理结果返回给页面
上面我们使用的是@RestControllerAdvice
而不是@ControllerAdvice
,它是@ControllerAdvice
和@ResponseBody
的结合,返回的都是json数据。如果使用@ControllerAdvice
,方法上需要添加@ResponseBody才能返回json格式数据。