【Java】SpringBoot使用AOP进行日志解析打印+系统异常全局处理配置

文章目录

  • 前言
  • 一、导入Lombok
  • 二、创建日志打印Model
  • 三、创建日志切面工具类
  • 四、需要用到的一些常量类
  • 五、创建接口请求切面
  • 六、系统异常全局配置
  • 总结


前言

为了方便项目部署在服务器之后,当出现BUG以及某些特殊需求时,会因为无法看到日志或者出现异常无法快速及时处理的情况的时候,那么AOP日志拦截打印的作用就体现出来了。同时加上作者日志生成到服务器的工具,将会更加方便处理问题;


提示:以下是本篇文章正文内容,下面案例可供参考

一、导入Lombok

代码如下

            <!--Lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.24</version>
            </dependency>

二、创建日志打印Model

代码如下:

import lombok.Data;

/**
 * @author 孤巷.
 * @description 日志打印model
 * @date 2021/3/13 11:44
 */
@Data
public class SystemRequestLogModel {
  private String ip;
  private String url;
  private String httpMethod;
  private String classMethod;
  private Object requestParams;
  private Object result;
  private Long timeCost;
}

三、创建日志切面工具类

代码如下:

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author 孤巷.
 * @description 方法级日志切面
 * @date 2021/3/22 11:36
 */
@Slf4j
@Component
public class RequestUtil {

    @Autowired(required = false)
    protected HttpServletRequest request; // 自动注入request

    /**
     * reqContext对象中的key: 转换好的json对象
     */
    private static final String REQ_CONTEXT_KEY_PARAMJSON = "REQ_CONTEXT_KEY_PARAMJSON";

    /**
     * 获取客户端ip地址 *
     */
    public String getClientIp() {
        String ipAddress = null;
        ipAddress = request.getHeader("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
        }

        // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (ipAddress != null && ipAddress.length() > 15) {
            if (ipAddress.indexOf(",") > 0) {
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
            }
        }
        if ("0:0:0:0:0:0:0:1".equals(ipAddress)) {
            ipAddress = "127.0.0.1";
        }
        return ipAddress;
    }

    public String getRequestParams(JoinPoint proceedingJoinPoint) {
        // 参数名
        String[] paramNames =
                ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
        // 参数值
        Object[] paramValues = proceedingJoinPoint.getArgs();
        return buildRequestParam(paramNames, paramValues);
    }

    private String buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            if (value instanceof HttpServletRequest || value instanceof HttpServletResponse) {
                continue;
            }
            // 如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename(); // 获取文件名
            }
            requestParams.put(paramNames[i], value);
        }

        JSONObject json = new JSONObject(requestParams);
        return json.toJSONString();
    }
}


四、需要用到的一些常量类

代码如下:

/**
 * @author 孤巷.
 * @description 常量
 * @date 2021/3/13 11:44
 */
public class Constant {
    public static final String NULL_POINTER_EXCEPTION = "空指针异常";
    public static final String PARSE_EXCEPTION = "格式解析异常";
    public static final String ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION = "超出索引异常";
    public static final String SQL_EXCEPTION = "SQL异常";
    public static final String INTERNAL_SERVER_ERROR = "内部服务器异常";
    public static final String NETWORK_BUSY = "网络繁忙,请稍后重试";
    public static final String SQL_EXCEPTION_MESSAGE = "数据库执行异常:";
    public static final String DATE_FORMAT_ERROR = "时间格式异常";
    public static final String DATABASE_CONNECT_OUT_TIME = "数据库连接超时";
}

五、创建接口请求切面

重点来了哈!代码如下:

import cn.hutool.core.io.IORuntimeException;
import cn.hutool.crypto.CryptoException;
import cn.hutool.http.HttpException;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.whxx.common.constant.Constant;
import com.whxx.common.exception.IllegalTokenException;
import com.whxx.common.exception.NetworkBusyException;
import com.whxx.common.exception.PayException;
import com.whxx.common.exception.SysConfigException;
import com.whxx.common.model.SystemRequestLogModel;
import com.whxx.common.util.RequestUtil;
import com.whxx.common.util.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.SQLException;
import java.text.ParseException;
import java.time.format.DateTimeParseException;

/**
 * @author 孤巷.
 * @description 接口请求切面
 * @date 2020/11/17 9:44
 */
@Aspect
@Component
@Slf4j
public class ControllerRequestAspect {

    @Resource
    private RequestUtil requestUtil;

    /**
     * 扫描所有Controller
     */
    @Pointcut("execution(public * com.whxx.exchange.controller.*.*(..))")
    public void request() {
    }

    @Around("request()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            throw new Exception("ServletRequestAttributes is null");
        }
        HttpServletRequest request = attributes.getRequest();
        SystemRequestLogModel systemRequestLogModel = new SystemRequestLogModel();
        systemRequestLogModel.setIp(request.getRemoteAddr());
        systemRequestLogModel.setUrl(request.getRequestURL().toString());
        systemRequestLogModel.setHttpMethod(request.getMethod());
        if ("get".equalsIgnoreCase(request.getMethod())) {
            // 处理GET请求中文参数乱码
            String decodeParams = "";
            if (request.getQueryString() != null) {
                try {
                    decodeParams = URLDecoder.decode(request.getQueryString(), "utf-8"); // 将中文转码
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
            systemRequestLogModel.setRequestParams(decodeParams);
        } else {
            systemRequestLogModel.setRequestParams(
                    requestUtil.getRequestParams(proceedingJoinPoint));
        }
        systemRequestLogModel.setClassMethod(
                String.format(
                        "%s.%s",
                        proceedingJoinPoint.getSignature().getDeclaringTypeName(),
                        proceedingJoinPoint.getSignature().getName()));
        Object result;
        try {
            result = proceedingJoinPoint.proceed();
            systemRequestLogModel.setResult(result);
            systemRequestLogModel.setTimeCost(System.currentTimeMillis() - start);
            log.info("请求信息 == {}", systemRequestLogModel);
        } catch (Exception e) {
            result = exceptionGet(e, systemRequestLogModel);
        }
        return result;
    }

    private JSONObject exceptionGet(Exception e, SystemRequestLogModel systemRequestLogModel) {
        systemRequestLogModel.setResult(e);
        log.error(e.getMessage(), e);
        log.error("异常信息 == {}", systemRequestLogModel);
        if (e instanceof NetworkBusyException) { // 自定义异常:对外接口调用异常,失败/或对外接口自身报错
            return ResultUtil.error(Constant.NETWORK_BUSY);
        } else if (e instanceof PayException) { //自定义异常:支付异常
            return ResultUtil.fail(e.getMessage());
        } else if (e instanceof SysConfigException) { //自定义异常:系统配置异常
            return ResultUtil.fail(e.getMessage());
        } else if (e instanceof IllegalTokenException) {//自定义异常:非法token
            return ResultUtil.fail(e.getMessage());
        } else if (e instanceof NullPointerException) { // 空指针异常
            return ResultUtil.error(Constant.NULL_POINTER_EXCEPTION);
        } else if (e instanceof DuplicateKeyException) { // 唯一键冲突
            return ResultUtil.error(Constant.DUPLICATE_KEY);
        } else if (e instanceof ParseException) { // 时间格式解析异常
            return ResultUtil.error(Constant.PARSE_EXCEPTION);
        } else if (e instanceof ArrayIndexOutOfBoundsException) { // 超出索引异常
            return ResultUtil.error(Constant.ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION);
        } else if (e instanceof SQLException) { // sql异常
            return ResultUtil.error(Constant.SQL_EXCEPTION);
        } else if (e instanceof DateTimeParseException) { // 时间格式解析异常
            return ResultUtil.error(Constant.DATE_FORMAT_ERROR);
        } else if (e instanceof CannotGetJdbcConnectionException) { // 数据库连接超时
            return ResultUtil.error(Constant.DATABASE_CONNECT_OUT_TIME);
        }  else if (e instanceof CryptoException) { // 二维码无效
            return ResultUtil.fail(Constant.CRYPTO_ERROR);
        } else if (e instanceof IORuntimeException) {  //请求被拒绝,服务器未开启
            return ResultUtil.fail(e.getMessage());
        } else if (e instanceof HttpException) { //hutool http请求超时
            return ResultUtil.fail(e.getMessage());
        } else if (e instanceof BadSqlGrammarException) {//数据库执行异常
            return ResultUtil.error(Constant.SQL_EXCEPTION_MESSAGE + ((BadSqlGrammarException) e).getSQLException().getMessage());
        } else if (e instanceof JSONException) { //JSON相关异常
            return ResultUtil.fail(e.getMessage());
        } else if (e instanceof RuntimeException) {
            return ResultUtil.error(e.getMessage());
        } else { // 其他未知异常
            return ResultUtil.error(Constant.INTERNAL_SERVER_ERROR);
        }
    }
}

最后只需要把下方的controller路径修改为实际的项目路径即可

@Pointcut(“execution(public * com.whxx.exchange.controller..(…))”)

六、系统异常全局配置

代码如下:

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.whxx.common.util.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.UnexpectedTypeException;
import java.util.List;
import java.util.Objects;

/**
 * @author 孤巷.
 * @description 异常全局处理配置
 * @date 2020/11/17 9:44
 */
@RestControllerAdvice
@Slf4j
public class ExceptionControllerAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public JSONObject methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        BindingResult result = e.getBindingResult();
        List<ObjectError> errorList = result.getAllErrors();
        String defaultMessage = errorList.get(0).getDefaultMessage();
        log.error(String.format("参数校验异常1 == {}%s", result.getAllErrors()));
        return ResultUtil.failIllegalParameter(
                "参数【"
                        + Objects.requireNonNull(e.getFieldError()).getField()
                        + "】校验异常:"
                        + defaultMessage);
    }

    /**
     * 参数校验注解错误导致的异常
     */
    @ExceptionHandler(UnexpectedTypeException.class)
    public JSONObject unexpectedTypeException(UnexpectedTypeException e) {
        return ResultUtil.error(e.getMessage());
    }

    @ExceptionHandler(BindException.class)
    public JSONObject bindExceptionHandler(BindException e) {
        BindingResult result = e.getBindingResult();
        List<ObjectError> errorList = result.getAllErrors();
        String defaultMessage = errorList.get(0).getDefaultMessage();
        log.error(String.format("参数校验异常2 == {}%s", result.getAllErrors()));
        return ResultUtil.failIllegalParameter(
                "参数【"
                        + Objects.requireNonNull(e.getFieldError()).getField()
                        + "】异常:"
                        + defaultMessage);
    }

    @ExceptionHandler(InvalidFormatException.class)
    public JSONObject handHttpConverterException(InvalidFormatException e) {
        StringBuilder errors = new StringBuilder();
        List<JsonMappingException.Reference> path = e.getPath();
        for (JsonMappingException.Reference reference : path) {
            errors.append("参数【")
                    .append(reference.getFieldName())
                    .append("】输入不合法,需要的是 ")
                    .append(e.getTargetType().getName())
                    .append(" 类型,")
                    .append("提交的值是:")
                    .append(e.getValue().toString());
        }
        log.error("参数校验异常3 == {}", errors);
        return ResultUtil.failIllegalParameter(errors.toString());
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public JSONObject handleMissingServletRequestParameterException(
            MissingServletRequestParameterException e) {
        StringBuilder errors = new StringBuilder();
        errors.append("参数【").append(e.getParameterName()).append("】为必传字段:").append(e.getMessage());
        log.error("参数校验异常4 == {}", errors);
        return ResultUtil.failIllegalParameter(errors.toString());
    }

    @ExceptionHandler(HttpMessageConversionException.class)
    public JSONObject handleConversionException(HttpMessageConversionException e) {
        StringBuilder errors = new StringBuilder();
        errors.append("参数校验异常5:").append(e.getMessage());
        log.error("参数校验异常 == {}", errors);
        return ResultUtil.failIllegalParameter(errors.toString());
    }
}

总结

以上就是今天要讲的内容,本文仅仅简单介绍了AOP的使用,AOP不止是可以实现日志打印的效果,还能实现很多好用的方法,后续我们都会讲到,请持续关注。谢谢!

你可能感兴趣的:(java,spring,boot,spring)