自定义注解类,记录请求参数并入表

使用

java自定义注解记录操作日志 自定义注解实现日志_json

在这里插入代码片

1.自定义注解类 Loggable

import com...enums.LogScopeEnum;
import com...enums.LogTypeEnum;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target({ElementType.METHOD}) //注解作用于方法级别
@Retention(RetentionPolicy.RUNTIME) //运行时起作用
public @interface Loggable {

    /**
     * 是否输出日志
     */
    boolean loggable() default true;

    /**
     * 日志信息描述,可以记录该方法的作用等信息。
     */
    String descp() default "";

    /**
     * 请求所属模块。
     */
    String module() default "";

    /**
     * 日志类型,可能存在多种接口类型都需要记录日志,比如dubbo接口,web接口
     */
    LogTypeEnum type() default LogTypeEnum.WEB;

    /**
     * 日志等级
     */
    String level() default "INFO";

    /**
     * 日志输出范围,用于标记需要记录的日志信息范围,包含入参、返回值等。
     * ALL-入参和出参, BEFORE-入参, AFTER-出参
     */
    LogScopeEnum scope() default LogScopeEnum.ALL;

    /**
     * 入参输出范围,值为入参变量名,多个则逗号分割。不为空时,入参日志仅打印include中的变量
     */
    String include() default "";

    /**
     * 是否存入数据库
     */
    boolean db() default true;

    /**
     * 是否输出到控制台
     *
     * @return
     */
    boolean console() default false;
}

用到的枚举类 LogScopeEnum和LogTypeEnum BaseConstants

/**
 * 日志作用范围枚举
 */
public enum LogScopeEnum {
    ALL, BEFORE, AFTER;

    public boolean contains(LogScopeEnum scope) {
        if (this == ALL) {
            return true;
        } else {
            return this == scope;
        }
    }

    @Override
    public String toString() {
        String str = "";
        switch (this) {
            case ALL:
                break;
            case BEFORE:
                str = "REQUEST";
                break;
            case AFTER:
                str = "RESPONSE";
                break;
            default:
                break;
        }
        return str;
    }
}
public enum BaseConstants {
    YES("YES"), NO("NO");

    private String label;

    private BaseConstants(String s) {
        this.label = s;
    }

    public static BaseConstants fromCode(String baseConstants) {
        switch (baseConstants) {
            case "YES":
                return YES;
            case "NO":
                return NO;
            default:
                return NO;
        }
    }
}

重点是切面类 WebLogAspect

import com.alibaba.fastjson.JSONObject;
import com...interfaces.Loggable;
import com...common.LogMessage;
import com...BaseConstants;
import com...enums.LogScopeEnum;
import com...service.LogMessageService;
import com...utils.HttpUtils;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtMethod;
import javassist.bytecode.LocalVariableAttribute;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * 日志记录AOP实现
 */
@Aspect
@Component
public class WebLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    @Autowired
    private LogMessageService logMessageService;

    @Autowired
    private HttpUtils httpUtils;

    // 开始时间
    private long startTime = 0L;

    // 结束时间
    private long endTime = 0L;

    /**
     * Controller层切点
     */
//    @Pointcut("execution(public * *(..))")
    @Pointcut("execution(* *..controller..*.*(..))")
    public void controllerAspect() {
    }

    /**
     * 前置通知 用于拦截Controller层记录用户的操作
     *
     * @param joinPoint 切点
     */
    @Before("controllerAspect()")
    public void doBeforeInServiceLayer(JoinPoint joinPoint) {
    }

    /**
     * 配置controller环绕通知,使用在方法aspect()上注册的切入点
     *
     * @param point 切点
     * @return
     * @throws Throwable
     */
    @Around("controllerAspect()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        // 获取request
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        // 获取ip地址
        String ipAddress = httpUtils.getIpAddress(request);
        //目标方法实体
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        boolean hasMethodLogAnno = method.isAnnotationPresent(Loggable.class);
        //没加注解 直接执行返回结果
        /*if (!hasMethodLogAnno) {
            return point.proceed();
        }*/

        //日志打印外部开关
        String logSwitch = BaseConstants.YES.toString();

        //记录日志信息
        LogMessage logMessage = new LogMessage();

        //方法注解实体
        Loggable methodLogAnno = method.getAnnotation(Loggable.class);

        //处理入参日志
        handleRequestLog(point, methodLogAnno, request, logMessage, logSwitch, ipAddress);

        //执行目标方法内容,获取执行结果
        Object result = point.proceed();

        //处理接口响应日志
        handleResponseLog(logSwitch, logMessage, methodLogAnno, result);
        return result;
    }

    /**
     * 处理入参日志
     *
     * @param point           切点
     * @param methodLogAnnon  日志注解
     * @param logMessage      日志信息记录实体
     */
    private void handleRequestLog(ProceedingJoinPoint point, Loggable methodLogAnnon, HttpServletRequest request,
                                 LogMessage logMessage, String logSwitch, String ipAddress) throws Exception {
        String paramsText = "";
        //参数列表
        String includeParam = methodLogAnnon.include();
        Map<String, Object> methodParamNames = getMethodParamNames(
                point.getTarget().getClass(), point.getSignature().getName(), includeParam);
        Map<String, Object> params = getArgsMap(
                point, methodParamNames);
        if (params != null) {
            //序列化参数列表
            paramsText = JSONObject.toJSONString(params);
        }

        //判断是否输出日志
        if (methodLogAnnon.loggable()
                && methodLogAnnon.scope().contains(LogScopeEnum.BEFORE)
                && methodLogAnnon.console()
                && StringUtils.equals(logSwitch, BaseConstants.YES.toString())) {
            //打印入参日志
            logger.info("【{}】 【{}】 【{}】 接口入参成功!, 方法名称:【{}】, 所属模块:【{}】, 请求参数:【{}】",
                    request.getRequestURI(), methodLogAnnon.descp(), ipAddress,
                    point.getSignature().getName(), methodLogAnnon.module(), paramsText);
        }
        // 1.构造开始时间
        startTime = System.currentTimeMillis();
        logMessage.setBeginTime(new Date(startTime));

        // 2.接口描述
        logMessage.setDescription(methodLogAnnon.descp());

        String systemId = "";
        if ((null != point.getArgs()) && (point.getArgs().length > 0)) {
            try {
                Object[] args = point.getArgs();
                String objStr = JSONObject.toJSONString(args[0]);
                // 如果转换json异常,则为get请求,直接从获取systemId
                JSONObject jsonObject = JSONObject.parseObject(objStr);
                systemId = null == jsonObject.get("systemId") ? "" : jsonObject.get("systemId").toString();
            } catch (Exception e) {
                if (null != params && null != params.get("systemId")) {
                    systemId = params.get("systemId").toString();
                }
            }
        }
        // 3.构造用户名
        logMessage.setUsername(systemId + ":" + ipAddress);
        // 5.构造调用接口
        logMessage.setQueryinterface(request.getRequestURI());
        // 5.构造调用方法
        logMessage.setMethod(point.getSignature().getName());
        // 6.参数
        logMessage.setParameter(paramsText);
        // 7.请求所属模块
        logMessage.setModule(methodLogAnnon.module().toString());
    }

    /**
     * 处理响应日志
     *
     * @param logSwitch      外部日志开关,用于外部动态开启日志打印
     * @param logMessage     日志记录信息实体
     * @param methodLogAnnon 日志注解实体
     * @param result           接口执行结果
     */
    private void handleResponseLog(String logSwitch, LogMessage logMessage, Loggable methodLogAnnon, Object result) {
        endTime = System.currentTimeMillis();
        //结束时间
        logMessage.setEndTime(new Date());
        //消耗时间
        logMessage.setSpendTime(endTime - startTime);
        //是否输出日志
        if (methodLogAnnon.loggable()
                && methodLogAnnon.scope().contains(LogScopeEnum.AFTER)) {
            //判断是否入库
            if (methodLogAnnon.db()) {
                // 入库
                try {
                    logMessageService.saveLogMessage(logMessage);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //判断是否输出到控制台
            if (methodLogAnnon.console()
                    && StringUtils.equals(logSwitch, BaseConstants.YES.toString())) {
                //输出到控制台
                System.out.println(logMessage);
            }
        }
    }

    /**
     * 获取方法入参变量名
     *
     * @param cls        触发的类
     * @param methodName 触发的方法名
     * @param include    需要打印的变量名
     * @return
     * @throws Exception
     */
    private Map<String, Object> getMethodParamNames(Class cls,
                                                    String methodName, String include) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(cls));
        CtMethod cm = pool.get(cls.getName()).getDeclaredMethod(methodName);
        LocalVariableAttribute attr = (LocalVariableAttribute) cm
                .getMethodInfo().getCodeAttribute()
                .getAttribute(LocalVariableAttribute.tag);

        if (attr == null) {
            throw new Exception("attr is null");
        } else {
            Map<String, Object> paramNames = new HashMap<>();
            int paramNamesLen = cm.getParameterTypes().length;
            int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
            if (StringUtils.isEmpty(include)) {
                for (int i = 0; i < paramNamesLen; i++) {
                    paramNames.put(attr.variableName(i + pos), i);
                }
            } else { // 若include不为空
                for (int i = 0; i < paramNamesLen; i++) {
                    String paramName = attr.variableName(i + pos);
                    if (include.indexOf(paramName) > -1) {
                        paramNames.put(paramName, i);
                    }
                }
            }
            return paramNames;
        }
    }

    /**
     * 组装入参Map
     *
     * @param point                   切点
     * @param methodParamNames  参数名称集合
     * @return
     */
    private Map getArgsMap(ProceedingJoinPoint point,
                           Map<String, Object> methodParamNames) {
        Object[] args = point.getArgs();
        if (null == methodParamNames) {
            return Collections.EMPTY_MAP;
        }
        for (Map.Entry<String, Object> entry : methodParamNames.entrySet()) {
            int index = Integer.valueOf(String.valueOf(entry.getValue()));
            if (args != null && args.length > 0) {
                if (args[index] instanceof ServletRequest || args[index] instanceof ServletResponse || args[index] instanceof MultipartFile) {
                    continue;
                } else {
                    Object arg = (null == args[index] ? "" : args[index]);
                    methodParamNames.put(entry.getKey(), arg);
                }
            }
        }
        return methodParamNames;
    }

}

日志实体类LogMessage

import lombok.Data;

import java.util.Date;

@Data
public class LogMessage {

    private static final long serialVersionUID = 1L;

    //    @Column(name = "id")
    private Integer id; // id ,后续考虑uuid

    //    @Column(name = "username")
    private String username; // 用户名

    //    @Column(name = "queryinterface")
    private String queryinterface; // 请求接口

    //    @Column(name = "method")
    private String method; // 请求方法

    //    @Column(name = "parameter")
    private String parameter; // 请求参数

    //    @Column(name = "description") // 描述信息
    private String description;

    //    @Column(name = "module") // 请求所属模块
    private String module;

    //    @Column(name = "begin_time")
    private Date beginTime;  // 开始时间

    //    @Column(name = "end_time")
    private Date endTime;  // 结束时间

    //    @Column(name = "spend_time")
    private Long spendTime;  // 耗费时间

    //    @Column(name = "d_date")
    private Date insertDate;  // 操作时间 插入数据的日期

    public LogMessage() {
        this.insertDate = new Date();
    }

    public LogMessage(Integer id, String username, String queryinterface, String method, String parameter, String description, String module, Date beginTime, Date endTime, Long spendTime) {
        this.id = id;
        this.username = username;
        this.queryinterface = queryinterface;
        this.method = method;
        this.parameter = parameter;
        this.description = description;
        this.module = module;
        this.beginTime = beginTime;
        this.endTime = endTime;
        this.spendTime = spendTime;
        this.insertDate = new Date();
    }

}

记录日志的Service类 LogMessageService

import com...common.LogMessage;
import com...mapper.WebLogAspectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class LogMessageService {

    @Autowired
    private WebLogAspectMapper webLogAspectMapper;


    /**
     * 保存日志记录
     *
     * @param logMessage
     * @return
     */
    public void saveLogMessage(LogMessage logMessage) {
        webLogAspectMapper.saveLogMessage(logMessage);
    }

    /**
     * 保存新日志记录
     *
     * @param username       用户名
     * @param queryinterface 接口
     * @param method         调用的方法名
     * @param parameter      参数
     * @param description    描述
     * @param module         接口所属模块
     * @param startTime      开始时间 System.currentTimeMillis();
     */
    public void saveNewLogMessage(String username,
                                  String queryinterface,
                                  String method,
                                  String parameter,
                                  String description,
                                  String module,
                                  Long startTime) {
        Long endTime = System.currentTimeMillis();
        LogMessage logMessage = new LogMessage(null, username,
                queryinterface,
                method,
                parameter,
                description,
                module,
                new Date(), new Date(endTime), endTime - startTime);
        webLogAspectMapper.saveLogMessage(logMessage);
    }


}

Mapper类

import com.htsc.thfx.performance.entity.common.LogMessage;

public interface WebLogAspectMapper {

    void saveLogMessage(LogMessage logMessage);
}

获取请求ip

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;

@Component
public class HttpUtils {

    private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);

    /**
     * 获取IP地址
     */
    public String getIpAddress(HttpServletRequest request) {
        String ipAddress = request.getHeader("X-Forwarded-For");
        if (StringUtils.isEmpty(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {
                // 根据网卡取本机配置的IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (Exception e) {
                    logger.error("get native ip by network card failed ==>, msg:{}", e.getMessage());
                }
                if (null != inet && inet.getHostAddress() != null) {
                    ipAddress = inet.getHostAddress();
                }
            }
        }
        // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (ipAddress != null && ipAddress.length() > 15) {
            if (ipAddress.indexOf(",") > 0) {
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
            }
        }
        return ipAddress;
    }
}