注解

@Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ShowEvent {
    String value() default "";
    //环境,默认全部环境打日志
    String[] profile() default "";
}

实现

import cn.hutool.core.lang.UUID;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

@Order(1)
@Slf4j
@Component
@Aspect
public class LogsAspect {
    private static String ING_HEADERS[] = new String[]{
            "accept", "accept-encoding", "accept-language", "connection", "content-length", "cookie",
            "host", "token"
    };

    @Value("${spring.profiles.active}")
    private String env;

    @Resource
    private Validator validator;

    @Pointcut("@annotation(com.x2era.xcloud.order.base.ShowEvent) ")
    public void cutReq() {

    }

    @Around("cutReq()")
    public Object globalLog(ProceedingJoinPoint joinPoint) throws Throwable {
        Class<?> clazz = joinPoint.getTarget().getClass();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Method realMethod = clazz.getMethod(signature.getName(), method.getParameterTypes());
        ShowEvent showEvent = method.getAnnotation(ShowEvent.class);
        boolean canLog = Arrays.stream(showEvent.profile()).anyMatch(pf->"".equals(pf) || StringUtils.equalsAnyIgnoreCase(pf, env));
//        Class<?> returnType = realMethod.getReturnType();
        Object[] args = joinPoint.getArgs();
        String uuid = UUID.fastUUID().toString(true);

        //校验参数
        if (canLog) {
            Pair<Boolean, String> validateArgs = argsValidate(joinPoint.getTarget(), realMethod, args);
            if (!validateArgs.getLeft()) {
                log.error(" {} 全局日志 {}", uuid, JsonUtil.toJson(LogData.builder()
                        .title(String.format("%s validateError, %s.%s", showEvent.value(), clazz.getSimpleName(), realMethod.getName()))
                        .request(getArgs(args))
                        .response(validateArgs.getRight())
                        .build()));
//                log.error("{} {}参数校验不通过", showEvent.value(), method.getName());
            }
        }

        Object result = null;
        long start = DateUtils.timestamp();
        try {
            result = joinPoint.proceed();
        } catch (IllegalArgumentException e) {
            log.error("{} 全局日志 参数错误:{}", uuid, e.getMessage(), e);
            return Result.error(ResultCodeEnum.ILLEGAL_PARAM_ERROR.getKey(), ResultCodeEnum.ILLEGAL_PARAM_ERROR.getValue());
        } catch (Exception e) {
            log.error("{} Exception occurred at .", uuid, e);
            throw e;
        } finally {
            if (canLog) {
                log.info("{} 全局日志 {}", uuid, JsonUtil.toJson(LogData.builder()
                        .title(String.format("%s %s.%s",showEvent.value() ,clazz.getSimpleName(), realMethod.getName()))
                        .header(getHeaders())
                        .request(getArgs(args))
                        .response(result)
                        .costs(DateUtils.timestamp() - start).build()));
            }
        }

        return result;
    }

    private Map<String, String> getHeaders() {
        Map<String, String> result = new HashMap<>();
        RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
        if (requestAttr instanceof ServletRequestAttributes) {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttr).getRequest();
            if (request != null) {
                Enumeration<String> hk = request.getHeaderNames();
                while (hk.hasMoreElements()) {
                    String key = hk.nextElement();
                    if (!ArrayUtils.contains(ING_HEADERS, key.toLowerCase())) {
                        result.put(key, request.getHeader(key));
                    }
                }
            }
        }
        return result;
    }

    private Object[] getArgs(Object[] args) {
        Object[] arguments = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ServletRequest ||
                    args[i] instanceof ServletResponse ||
                    args[i] instanceof MultipartFile ||
                    args[i] instanceof BindingResult ) {
                //ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
                //ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
                continue;
            }
            arguments[i] = args[i];
        }
        return arguments;
    }

    private Pair<Boolean, String> argsValidate(Object validateParamService, Method method, Object[] args) {
        Pair<Boolean, String> validate1 = methodArgsValidate(validateParamService, method, args);
        if (!validate1.getLeft()) {
            return validate1;
        }
        Pair<Boolean, String> validate2 = methodArgsFieldValidate(args);
        if (!validate2.getLeft()) {
            return validate2;
        }
        return Pair.of(true, null);
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class LogData implements Serializable {
        private String title;
        private Map<String, String> header;
        private Object request;
        private Object response;
        private Long costs;
    }



    /**
     * 方法参数校验
     *
     * @param validateParamService 拦截的service
     * @param method               具体拦截的方法
     * @param args                 方法参数
     * @return true|false、errorMessage
     */
    private Pair<Boolean, String> methodArgsValidate(Object validateParamService, Method method, Object[] args) {
        if (ArrayUtils.isEmpty(args)) {
            return Pair.of(true, null);
        }
        ExecutableValidator executableValidator = validator.forExecutables();
        Set<ConstraintViolation<Object>> constraintViolationSet = executableValidator.validateParameters(validateParamService, method, args);
        if (CollectionUtils.isEmpty(constraintViolationSet)) {
            return Pair.of(true, null);
        }
        List<String> errorMessage = constraintViolationSet.stream()
                .map(e -> e.getPropertyPath().toString() + e.getMessage())
                .collect(Collectors.toList());
        return Pair.of(false, JsonUtil.toJson(errorMessage));
    }

    /**
     * 方法参数的属性校验
     *
     * @param args args
     * @return true | false
     */
    private Pair<Boolean, String> methodArgsFieldValidate(Object[] args) {
        if (ArrayUtils.isEmpty(args)) {
            return Pair.of(true, null);
        }
        for (Object arg : args) {
            if (arg != null) {
                Set<ConstraintViolation<Object>> error = validator.validate(arg);
                if (!CollectionUtils.isEmpty(error)) {
                    List<String> errorMessage = error.stream()
                            .map(e -> e.getPropertyPath().toString() + e.getMessage())
                            .collect(Collectors.toList());
                    return Pair.of(false, JsonUtil.toJson(errorMessage));
                }
            }
        }
        return Pair.of(true, null);
    }

}