# 全局定义

# 异常处理

开发项目的时,我们程序会抛出各种异常信息,这时候我们一般需要对这些异常进行统一的处理,尤其是提供RESTFul接口服务的后端系统,对错误的异常返回,进行统一的封装处理。

异常的定义源码位于:rapid-api-common 模块中的 tech.lancelot.exceptions 包中。

异常的统一处理源码位于:rapid-api-web 模块中的 tech.lancelot.config.GlobalExceptionHandler 包中,根据之前定义的不同的异常类型,返回不同的处理结果响应。

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

   /**
    * 处理所有不可知的异常
    */
   @ExceptionHandler(Throwable.class)
   public Result handleException(Throwable e){
       // 打印堆栈信息
       log.error(ThrowableUtil.getStackTrace(e));
       return Result.error(BAD_REQUEST.value(),e.getMessage());
   }

   /**
    * BadCredentialsException
    */
   @ExceptionHandler(BadCredentialsException.class)
   public Result badCredentialsException(BadCredentialsException e){
       // 打印堆栈信息
       String message = "坏的凭证".equals(e.getMessage()) 
           ? "用户名或密码不正确" : e.getMessage();
       log.error(message);
       return Result.error(BAD_REQUEST.value(),e.getMessage());
   }

   /**
    * 处理自定义异常
    */
   @ExceptionHandler(value = BadRequestException.class)
   public Result badRequestException(BadRequestException e) {
       // 打印堆栈信息
       log.error(ThrowableUtil.getStackTrace(e));
       return Result.error(e.getStatus(),e.getMessage());
   }

   /**
    * 处理 EntityExist
    */
   @ExceptionHandler(value = EntityExistException.class)
   public Result entityExistException(EntityExistException e) {
       // 打印堆栈信息
       log.error(ThrowableUtil.getStackTrace(e));
       return Result.error(BAD_REQUEST.value(),e.getMessage());
   }

   /**
    * 处理 EntityNotFound
    */
   @ExceptionHandler(value = EntityNotFoundException.class)
   public Result entityNotFoundException(EntityNotFoundException e) {
       // 打印堆栈信息
       log.error(ThrowableUtil.getStackTrace(e));
       return Result.error(NOT_FOUND.value(),e.getMessage());
   }

   /**
    * 处理所有接口数据验证异常
    */
   @ExceptionHandler(MethodArgumentNotValidException.class)
   public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
       // 打印堆栈信息
       log.error(ThrowableUtil.getStackTrace(e));
       String[] str = Objects.requireNonNull(e.getBindingResult()
                       .getAllErrors().get(0).getCodes())[1].split("\\.");
       String message = e.getBindingResult()
           .getAllErrors().get(0).getDefaultMessage();
       String msg = "不能为空";
       if(msg.equals(message)){
           message = str[1] + ":" + message;
       }
       return Result.error(BAD_REQUEST.value(),e.getMessage());
   }
}

# 系统日志

本项目使用 AOP 方式记录用户操作日志,需要在 对应controller 的方法上使用 @Log 注解,就可以将用户操作记录到数据库,源码可查看rapid-api-common 项目下tech.lancelot.annotations.Log类:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
	String value() default "";
}

@Log("博客|查询归档")
@ApiOperation("博客|查询归档")
@GetMapping
@AnonymousGetMapping
public Result getAll() {
    return Result.success(archiveService.getAll());
}

关于日志切面的定义和异常通知,代码如下:

@Component
@Aspect
@Slf4j
public class LogAspect {

    ThreadLocal<Long> currentTime = new ThreadLocal<>();
    private final LogService logService;

    public LogAspect(LogService logService) {
        this.logService = logService;
    }

    /**
     * 配置切入点
     */
    @Pointcut("@annotation(tech.lancelot.annotations.Log)")
    public void logPointcut() {
        // 该方法无方法体,主要为了让同类中其他方法使用此切入点
    }

    /**
     * 配置环绕通知,使用在方法logPointcut()上注册的切入点
     *
     * @param joinPoint join point for advice
     */
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result;
        currentTime.set(System.currentTimeMillis());
        result = joinPoint.proceed();
        Log log = new Log("INFO", System.currentTimeMillis() - currentTime.get());
        currentTime.remove();
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        logService.save(getUsername(), StringUtils.getBrowser(request),
              StringUtils.getIp(request), joinPoint, log);
        return result;
    }

    /**
     * 配置异常通知
     *
     * @param joinPoint join point for advice
     * @param e         exception
     */
    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        Log log = new Log("ERROR", System.currentTimeMillis() - currentTime.get());
        currentTime.remove();
        log.setExceptionDetail(ThrowableUtil.getStackTrace(e).getBytes());
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        logService.save(getUsername(), StringUtils.getBrowser(request),
               StringUtils.getIp(request), (ProceedingJoinPoint) joinPoint, log);
    }

    public String getUsername() {
        try {
            return SecurityUtils.getCurrentUsername();
        } catch (Exception e) {
            return "";
        }
    }
}

当用户登录并操作系统时,相关的日志将被记录在数据库中,日志区分为 操作日志异常日志,并可以通过前端界面进行查看。

image-20210219141206263