Spring Boot 使用切面记录日志

Spring Boot 使用切面记录日志

  • 记录一下日常开发
    • 方案选择
    • 方案实现
      • 创建日志注解类
      • 创建方法所属模块枚举
      • 创建接口操作类型枚举
      • 创建相关常量枚举
      • 创建日志切面
      • 创建日志写入文件工具类
    • 问题来了
      • 获取当前操作执行的sql语句
      • 附件

记录一下日常开发

根据甲方规定,基于用户操作行为的日志采集的相关技术要求,对用户登录应用系统和资源库后查询、新增、修改、删除信息资源等操作的事件记录。

方案选择

针对记录用户操作日志,面向切面编程无疑是最好的解决方法:

  1. 创建日志注解类
  2. 创建方法所属模块枚举、接口操作类型枚举、相关常量枚举
  3. 创建日志切面
  4. 创建日志写入文件工具类

方案实现

创建日志注解类

import com.shxp.project.sys.constants.FunctionModuleEnum;
import com.shxp.project.sys.constants.OperateTypeEnum;

import java.lang.annotation.*;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogTail {

    /**
     * 方法名称
     */
    String name();
    
    /**
     * 所属模块
     */
    FunctionModuleEnum module();
    
    /**
     * 方法的操作类型
     */
    OperateTypeEnum operateType();
}

创建方法所属模块枚举

根据项目中的模块创建所属模块

/**
 * 方法所属模块
 */
public enum FunctionModuleEnum {

    STATISTICS("统计分析"),

    DATA_MANAGEMENT("数据管理"),

    SYSTEM("系统配置")
    ;

    private String name;

    FunctionModuleEnum(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

创建接口操作类型枚举

根据甲方文档要求创建接口操作类型枚举


/**
 * 接口操作类型
 */
public enum OperateTypeEnum {

    LOG_IN(0, "登录"),

    QUERY(1, "查询"),

    ADD(2, "新增"),

    UPDATE(3, "更新"),

    DELETE(4, "删除"),

    LOG_OUT(5, "退出"),
    ;

    private Integer code;

    private String title;

    OperateTypeEnum(Integer code, String title) {
        this.code = code;
        this.title = title;
    }

    public Integer getCode() {
        return code;
    }

    public String getTitle() {
        return title;
    }

    public static OperateTypeEnum getByCode(Integer code) {
        for (OperateTypeEnum value : values()) {
            if (value.code.equals(code)) {
                return value;
            }
        }
        return null;
    }
}

创建相关常量枚举

/**
 * 相关常量
 */
public class Constants {

    /**
     * 分隔符
     */
    public static final String SEPARATOR = "|";

    /**
     * 换行符
     */
    public static final String LINE = "\n";
}

创建日志切面

import com.alibaba.fastjson.JSON;
import com.google.common.base.Joiner;
import com.shxp.project.common.KoalClient;
import com.shxp.project.common.LogUtils;
import com.shxp.project.common.entity.vo.UserMessage;
import com.shxp.project.common.util.DateUtils;
import com.shxp.project.common.util.StringTools;
import com.shxp.project.common.util.UserUtil;
import com.shxp.project.common.util.Util;
import com.shxp.project.shiro.entity.ShiroUserEntity;
import com.shxp.project.sys.annotation.LogParam;
import com.shxp.project.sys.annotation.LogTail;
import com.shxp.project.sys.constants.Constants;
import com.shxp.project.sys.constants.DeptEnum;
import com.shxp.project.sys.entity.LogBean;
import com.shxp.project.sys.mapper.LogMapper;
import com.shxp.project.sys.service.LogRecordService;
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.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
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 java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 日志切面
 */
@Aspect
@Component
public class LogTailAspect {

    private static final Logger LOG = LoggerFactory.getLogger("operate");

    private static Field[] fields;

    @Resource
    private LogRecordService logRecordService;

    @Value("${product.id:310000000000}")
    private String productId;

    @Value("${product.name:机场打击整治非法客运管理}")
    private String productName;

    @Pointcut("execution(* com.shxp.project.*.controller..*.*(..))")
    public void excudeService() {

    }

    static {
        Class<LogBean> clazz = LogBean.class;
        fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
        }
    }

    @Around("@annotation(com.shxp.project.sys.annotation.LogTail)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Date start = new Date();
        Object object = joinPoint.proceed();
        Date end = new Date();
        execute(joinPoint, object, start, end);
        return object;
    }

    private void execute(ProceedingJoinPoint joinPoint, Object result, Date start, Date end) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();

        MethodSignature methodSignature = Optional.of(joinPoint)
                .map(ProceedingJoinPoint::getSignature)
                .map(e -> (MethodSignature) e)
                .orElse(null);

        LogTail logTail = Optional.of(methodSignature)
                .map(MethodSignature::getMethod)
                .map(e -> e.getAnnotation(LogTail.class))
                .orElse(null);

        Method method = methodSignature.getMethod();
        Annotation[][] annotations = method.getParameterAnnotations();
        Class[] classes = methodSignature.getParameterTypes();
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();

        LogBean logBean = new LogBean();

        /**
         * 获取用户信息
         */
        UserMessage user = getUser(names, args);
        if (Objects.nonNull(user)) {
            logBean.setOperatorID(user.getUser_id()+"");
            logBean.setOperatorAccount(user.getUser_id()+"");
            logBean.setOperatorName(user.getUser_name());
            logBean.setOrganizationID(user.getOrg_code());
            logBean.setOrganizationName(user.getFull_name());
        }else{
            ShiroUserEntity currentUser = UserUtil.getCurrentUser();
            assert currentUser != null;
            logBean.setOperatorID(currentUser.getId()+"");
            logBean.setOperatorAccount(currentUser.getUserAccount()+"");
            logBean.setOperatorName(currentUser.getName());
            DeptEnum deptEnum = DeptEnum.getByCode(currentUser.getDept());
            if (deptEnum!=null){
                logBean.setOrganizationID(deptEnum.getOrg());
                logBean.setOrganizationName(deptEnum.getTitle());
            }
        }

        logBean.setProductID(productId);
        logBean.setProductName(productName);
        logBean.setLogID(UUID.randomUUID().toString());
        logBean.setOperatorIdentity(1);
        logBean.setOpTime(DateUtils.format(start, DateUtils.DEFAULT_DATE_PATTERN14));
        logBean.setResTime(DateUtils.format(end, DateUtils.DEFAULT_DATE_PATTERN14));
        logBean.setTerminalType(0);
        logBean.setOpType(logTail.operateType().getCode());
        logBean.setClientIp(request.getRequestURL().toString());
        logBean.setClientPort(Objects.isNull(request.getRemotePort())?null:String.valueOf(request.getRemotePort()));
        logBean.setURL(request.getRequestURI());
//        logBean.setObjectParams();
        logBean.setSessionID(request.getRequestedSessionId());
        logBean.setFuncModuleName(logTail.module().getName()+"-"+logTail.name());
        logBean.setObjectIP(request.getRequestURL().toString());
        logBean.setObjectPort(Objects.isNull(request.getRemotePort())?null:String.valueOf(request.getRemotePort()));
//        logBean.setOperateCondition();
        logBean.setOperateCondition(getRequestParam(joinPoint));
//        logBean.setTerminalID();
//        logBean.setTerminalMac();
//        logBean.setQuerySql();
        String log = Stream.of(fields).map(e -> {
            try {
                return e.get(logBean);
            } catch (IllegalAccessException ex) {
                return "";
            }
        }).map(Util::getString).collect(Collectors.joining(Constants.SEPARATOR));
        LogUtils.insertLog(log);
        logRecordService.insertLog(logBean);
    }

    /**
     * 从参数列表中获取userId参数,返回用户信息
     *
     * @param names
     * @param args
     * @return
     */
    private UserMessage getUser(String[] names, Object[] args) {
        UserMessage user = null;
        for (int i = 0; i < names.length; i++) {
            if ("userId".equals(names[i]) && args.length >= i+1) {
                String userId = StringTools.getStringValue(args[i]);
                List<UserMessage> userMessages = KoalClient.queryUserByIdcard(userId);
                if (!CollectionUtils.isEmpty(userMessages)){
                    user = userMessages.get(0);
                }
            }
        }
        return user;
    }
    private String getRequestParam(JoinPoint pjp){
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        if (HttpMethod.PUT.name().equals(request.getMethod()) || HttpMethod.POST.name().equals(request.getMethod())){
            return getBodyContent(pjp);
        } else {
            Map<String,Object> map = getParamContent(request.getParameterMap());
            return JSON.toJSONString(map);
        }
    }
    private Map<String,Object> getParamContent(Map<String, String[]> params) {
        Map<String,Object> resultMap = new HashMap<>();
        try {
            for (Map.Entry<String, String[]> entry : params.entrySet()) {
                Object value = new Object();
                if (entry.getValue() != null && entry.getValue().length == 1) {
                    value = entry.getValue()[0];
                } else {
                    value = entry.getValue();
                }
                resultMap.put(entry.getKey(),value);
            }
        } catch (Exception e) {
            LOG.error("日志记录解析param参数失败",e.getMessage());
            resultMap.put("paramParse","参数解析失败");
        }
        return resultMap;
    }

    private String getBodyContent(JoinPoint pjp) {
        String bodyContent = "";
        try {
            Object[] args = pjp.getArgs();
            List arguments = new ArrayList();
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
                    //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.add(args[i]);
            }
            if (org.apache.commons.collections.CollectionUtils.isNotEmpty(arguments) && arguments.size() == 1) {
                bodyContent = JSON.toJSONString(arguments.get(0));
            } else {
                bodyContent = JSON.toJSONString(arguments);
            }
        }catch (Exception e) {
            LOG.error("日志记录解析body参数失败",e);
            Map<String,Object> resultMap = new HashMap<>();
            resultMap.put("paramParse","参数解析失败");
        }
        return bodyContent;
    }
   

}

创建日志写入文件工具类


import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;

/**
 * 日志文件存储位置
 * 日志保存成文本(log)格式,各数据之间通过 “|” 进行分割;存储位置为:
 * Linux: /etc/log_audit/operator_logs
 * Windows: C:/log_audit/operator_logs
 */
public class LogUtils {

    public static void insertLog(String content){
        String nowDay = DateUtil.getNowDay();
        String dir="C:/log_audit/operator_logs";
        String os = System.getProperty("os.name");
        //Windows操作系统
        if (os != null && os.toLowerCase().startsWith("windows")) {
            dir="C:/log_audit/operator_logs";
        } else if (os != null && os.toLowerCase().startsWith("linux")) {//Linux操作系统
            dir="/etc/log_audit/operator_logs";
        }
        File fDir = new File(dir);
        if (!fDir.exists()) {
            fDir.mkdirs();
        }

        FileOutputStream outSTr = null;
        BufferedOutputStream Buff = null;
        StringBuffer write ;
        try {
            outSTr = new FileOutputStream(new File(dir+"\\jchn_operator_log_"+nowDay+".log"),true);
            Buff = new BufferedOutputStream(outSTr);
            write = new StringBuffer();
            write.append(content);
            write.append("\r\n");
            Buff.write(write.toString().getBytes("UTF-8"));
            Buff.flush();
            Buff.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                Buff.close();
                outSTr.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

问题来了

从上面方法可以看出,并没有记录当前操作执行的sql语句

获取当前操作执行的sql语句

如何获取当前操作的sql呢,我在网上查了一些资料
实现方法,主要借鉴 WangJi92/mybatis-sql-log
将上面借鉴的MybatisSqlCompletePrintInterceptor 中的方法整合到日志切面中

package com.shxp.project.sys.config;

import com.alibaba.fastjson.JSON;
import com.google.common.base.Joiner;
import com.shxp.project.common.KoalClient;
import com.shxp.project.common.LogUtils;
import com.shxp.project.common.entity.vo.UserMessage;
import com.shxp.project.common.util.*;
import com.shxp.project.shiro.entity.ShiroUserEntity;
import com.shxp.project.sys.annotation.LogParam;
import com.shxp.project.sys.annotation.LogTail;
import com.shxp.project.sys.constants.Constants;
import com.shxp.project.sys.constants.DeptEnum;
import com.shxp.project.sys.entity.LogBean;
import com.shxp.project.sys.service.LogRecordService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
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.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
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 java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 为阿里神经元日志系统推送日志
 * 使用{@link com.shxp.project.sys.annotation.LogTail}的方法将会打印操作日志,
 * 如需要操作用户信息,需要将userId作为方法的第一个参数
 */
@Aspect
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
@Slf4j
public class LogTailAspect implements Interceptor, Ordered {

    private static final Logger LOG = LoggerFactory.getLogger("operate");

    private static Field[] fields;

    @Resource
    private LogRecordService logRecordService;

    @Value("${product.id:310000000000}")
    private String productId;

    @Value("${product.name:机场打击整治非法客运管理}")
    private String productName;

    private Object targetis;

    @Pointcut("execution(* com.shxp.project.*.controller..*.*(..))")
    public void excudeService() {

    }

    static {
        Class<LogBean> clazz = LogBean.class;
        fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
        }
    }

    @Around("@annotation(com.shxp.project.sys.annotation.LogTail)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Date start = new Date();
        Object object = joinPoint.proceed();
        Date end = new Date();
        execute(joinPoint, object, start, end);
        return object;
    }

    private void execute(ProceedingJoinPoint joinPoint, Object result, Date start, Date end) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();

        MethodSignature methodSignature = Optional.of(joinPoint)
                .map(ProceedingJoinPoint::getSignature)
                .map(e -> (MethodSignature) e)
                .orElse(null);

        LogTail logTail = Optional.of(methodSignature)
                .map(MethodSignature::getMethod)
                .map(e -> e.getAnnotation(LogTail.class))
                .orElse(null);

        Method method = methodSignature.getMethod();
        Annotation[][] annotations = method.getParameterAnnotations();
        Class[] classes = methodSignature.getParameterTypes();
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();

        LogBean logBean = new LogBean();

        /**
         * 获取用户信息
         */
        UserMessage user = getUser(names, args);
        if (Objects.nonNull(user)) {
            logBean.setOperatorID(user.getUser_id()+"");
            logBean.setOperatorAccount(user.getUser_id()+"");
            logBean.setOperatorName(user.getUser_name());
            logBean.setOrganizationID(user.getOrg_code());
            logBean.setOrganizationName(user.getFull_name());
        }else{
            ShiroUserEntity currentUser = UserUtil.getCurrentUser();
            assert currentUser != null;
            logBean.setOperatorID(currentUser.getId()+"");
            logBean.setOperatorAccount(currentUser.getUserAccount()+"");
            logBean.setOperatorName(currentUser.getName());
            DeptEnum deptEnum = DeptEnum.getByCode(currentUser.getDept());
            if (deptEnum!=null){
                logBean.setOrganizationID(deptEnum.getOrg());
                logBean.setOrganizationName(deptEnum.getName());
            }
        }

        logBean.setProductID(productId);
        logBean.setProductName(productName);
        logBean.setLogID(UUID.randomUUID().toString());
        logBean.setOperatorIdentity(1);
        logBean.setOpTime(DateUtils.format(start, DateUtils.DEFAULT_DATE_PATTERN14));
        logBean.setResTime(DateUtils.format(end, DateUtils.DEFAULT_DATE_PATTERN14));
        logBean.setTerminalType(0);
        logBean.setOpType(logTail.operateType().getCode());
        logBean.setClientIp(IpUtils.getIpAddr(request));
        logBean.setClientPort(Objects.isNull(request.getRemotePort())?null:String.valueOf(request.getRemotePort()));
        logBean.setURL(request.getRequestURI());
//        logBean.setObjectParams();
        logBean.setSessionID(request.getRequestedSessionId());
        logBean.setFuncModuleName(logTail.module().getName()+"-"+logTail.name());
        logBean.setObjectIP(IpUtils.getIpAddr(request));
        logBean.setObjectPort(Objects.isNull(request.getRemotePort())?null:String.valueOf(request.getRemotePort()));
//        logBean.setOperateCondition();
        logBean.setOperateCondition(getRequestParam(joinPoint));
//        logBean.setTerminalID();
//        logBean.setTerminalMac();
        logBean.setQuerySql(getSql(targetis));
        String log = Stream.of(fields).map(e -> {
            try {
                return e.get(logBean);
            } catch (IllegalAccessException ex) {
                return "";
            }
        }).map(Util::getString).collect(Collectors.joining(Constants.SEPARATOR));
        // 日志写入文件中
        LogUtils.insertLog(log);
        // 日志入库
        logRecordService.insertLog(logBean);
    }

    /**
     * 从参数列表中获取userId参数,返回用户信息
     *
     * @param names
     * @param args
     * @return
     */
    private UserMessage getUser(String[] names, Object[] args) {
        UserMessage user = null;
        for (int i = 0; i < names.length; i++) {
            if ("userId".equals(names[i]) && args.length >= i+1) {
                String userId = StringTools.getStringValue(args[i]);
                List<UserMessage> userMessages = KoalClient.queryUserByIdcard(userId);
                if (!CollectionUtils.isEmpty(userMessages)){
                    user = userMessages.get(0);
                }
            }
        }
        return user;
    }
    private String getRequestParam(JoinPoint pjp){
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        if (HttpMethod.PUT.name().equals(request.getMethod()) || HttpMethod.POST.name().equals(request.getMethod())){
            return getBodyContent(pjp);
        } else {
            Map<String,Object> map = getParamContent(request.getParameterMap());
            return JSON.toJSONString(map);
        }
    }
    private Map<String,Object> getParamContent(Map<String, String[]> params) {
        Map<String,Object> resultMap = new HashMap<>();
        try {
            for (Map.Entry<String, String[]> entry : params.entrySet()) {
                Object value = new Object();
                if (entry.getValue() != null && entry.getValue().length == 1) {
                    value = entry.getValue()[0];
                } else {
                    value = entry.getValue();
                }
                resultMap.put(entry.getKey(),value);
            }
        } catch (Exception e) {
            LOG.error("日志记录解析param参数失败",e.getMessage());
            resultMap.put("paramParse","参数解析失败");
        }
        return resultMap;
    }

    private String getBodyContent(JoinPoint pjp) {
        String bodyContent = "";
        try {
            Object[] args = pjp.getArgs();
            List arguments = new ArrayList();
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
                    //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.add(args[i]);
            }
            if (org.apache.commons.collections.CollectionUtils.isNotEmpty(arguments) && arguments.size() == 1) {
                bodyContent = JSON.toJSONString(arguments.get(0));
            } else {
                bodyContent = JSON.toJSONString(arguments);
            }
        }catch (Exception e) {
            LOG.error("日志记录解析body参数失败",e);
            Map<String,Object> resultMap = new HashMap<>();
            resultMap.put("paramParse","参数解析失败");
        }
        return bodyContent;
    }
    
    private Configuration configuration = null;

    private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT_THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        }
    };

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        targetis = target;
        long startTime = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } finally {
            String sql = this.getSql(target);
            long endTime = System.currentTimeMillis();
            long sqlCost = endTime - startTime;
//            log.info("SQL:{}    执行耗时={}", sql, sqlCost);
        }
    }

    /**
     * 获取sql
     *
     * @param target
     * @return
     * @throws IllegalAccessException
     */
    private String getSql(Object target) {
        try {
            StatementHandler statementHandler = (StatementHandler) target;
            BoundSql boundSql = statementHandler.getBoundSql();
            if (configuration == null) {
                final DefaultParameterHandler parameterHandler = (DefaultParameterHandler) statementHandler.getParameterHandler();
                Field configurationField = ReflectionUtils.findField(parameterHandler.getClass(), "configuration");
                ReflectionUtils.makeAccessible(configurationField);
                this.configuration = (Configuration) configurationField.get(parameterHandler);
            }
            //替换参数格式化Sql语句,去除换行符
            return formatSql(boundSql, configuration);
        } catch (Exception e) {
            log.warn("get sql error {}", target, e);
        }
        return "";
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 获取完整的sql实体的信息
     *
     * @param boundSql
     * @return
     */
    private String formatSql(BoundSql boundSql, Configuration configuration) {
        String sql = boundSql.getSql();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        Object parameterObject = boundSql.getParameterObject();
        // 输入sql字符串空判断
        if (sql == null || sql.length() == 0) {
            return "";
        }

        if (configuration == null) {
            return "";
        }

        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

        // 美化sql
        sql = beautifySql(sql);
        /**
         * @see DefaultParameterHandler 参考Mybatis 参数处理
         */
        if (parameterMappings != null) {
            for (ParameterMapping parameterMapping : parameterMappings) {
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    String paramValueStr = "";
                    if (value instanceof String) {
                        paramValueStr = "'" + value + "'";
                    } else if (value instanceof Date) {
                        paramValueStr = "'" + DATE_FORMAT_THREAD_LOCAL.get().format(value) + "'";
                    } else {
                        paramValueStr = value + "";
                    }
                    // mybatis generator 中的参数不打印出来
                    if (!propertyName.contains("frch_criterion")) {
                        paramValueStr = "/*" + propertyName + "*/" + paramValueStr;
                    }
                    // java.lang.IllegalArgumentException: Illegal group reference
                    // https://github.com/WangJi92/mybatis-sql-log/issues/4
                    sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(paramValueStr));
                }
            }
        }
        return sql;
    }

    /**
     * 美化Sql
     */
    private String beautifySql(String sql) {
        sql = sql.replaceAll("[\\s\n ]+", " ");
        return sql;
    }


    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

功能可以实现了,效果如下:
日志文件
Spring Boot 使用切面记录日志_第1张图片
数据库记录
Spring Boot 使用切面记录日志_第2张图片

测试的时候有个小bug,他只能记录你这个方法中的最后执行的sql语句,在项目刚启动的时候,查询数据列表,有的字段需要字典翻译,查字典的方法如果在最后面,日志会记录查字典的sql,所以开发的时候要求他们字典查询放到列表查询前面,影响不大

附件

日志实体类


import lombok.Data;

@Data
public class LogBean {

    /**
     * 应用系统的ID号,政务微信轻应用按照政务微信标准填写,其他应用系统按照4A平台标准填写
     */
    private String ProductID;

    /**
     * 应用系统的中文名称
     */
    private String ProductName;

    /**
     * 日志的ID号,在日志记录产生时生成,其格式和产生方式由应用系统自行决定
     */
    private String LogID;

    /**
     * 操作人在应用中的ID号
     */
    private String OperatorID;

    /**
     * 操作人居民身份证号,按照4A平台标准填写
     */
    private String OperatorAccount;

    /**
     * 0.管理员;1.普通用户
     */
    private Integer OperatorIdentity;

    /**
     * 操作人的真实姓名
     */
    private String OperatorName;

    /**
     * 操作人的所在单位名称,按照4A平台标准填写
     */
    private String OrganizationName;

    /**
     * 操作人所属单位的公安机关机构代码,按照4A平台标准填写
     */
    private String OrganizationID;

    /**
     * 操作人操作后获得系统反馈响应的时间,
     * 采用格式yyyyMMddHHmmssSSSS,24小时格式,精确到毫秒
     */
    private String OpTime;

    /**
     * 操作人操作后获得系统反馈响应的时间,
     * 采用格式yyyyMMddHHmmssSSSS,24小时格式,精确到毫秒
     */
    private String ResTime;

    /**
     * 0.非移动终端;1.移动终端
     */
    private Integer TerminalType;

    /**
     * 用户操作时所使用的信息处理终端的标识。
     * 通过非移动终端如桌面终端进行操作的,其终端标识为其网络IP地址;
     * 通过移动终端APP进行操作的,其终端标识为IMEI序列号;
     * 通过政务微信轻应用进行操作的,置空
     */
    private String TerminalID;

    /**
     * 通过移动终端APP接入的,记录其终端手机号码;通过政务微信轻应用接入的,置空
     */
    private String TerminalNum;

    /**
     * 通过移动终端APP接入的,记录其终端MAC地址;通过政务微信轻应用接入的,置空
     */
    private String TerminalMac;

    /**
     * 0:登录;1:查询;2:新增;3:修改;4:删除;5:退出
     */
    private Integer OpType;

    /**
     * 操作类型为1-查询、3-修改、4-删除类型时,记录用户进行操作时的数据筛选条件,
     * 填写数据操作SQL语句的where子句内容,
     * 如:name=‘张三’;当操作类型为其他类型时,置空
     */
    private String OperateCondition;

    /**
     * 操作人操作的结果,包括成功/失败。0:失败;1:成功
     */
    private Integer OperateResult;

    /**
     * 当操作结果为失败时,可记录操作失败的原因代码
     */
    private Integer ErrCode;

    /**
     * 客户端的IP地址,如果是服务端操作或系统生产帐号操作则填写
     */
    private String ClientIp;

    /**
     * 客户端访问时的源端口。若操作由服务端或系统生产账号产生,置空
     */
    private String ClientPort;

    /**
     * 操作目标的URL
     */
    private String URL;

    /**
     * 当请求为HTTP GET时,填写URL后面的queryString内容;为POST时,填写body内容
     */
    private String ObjectParams;

    /**
     * 用于标识应用系统产生的一次会话
     */
    private String SessionID;

    /**
     * 操作人所操作的应用内的具体功能模块名称
     */
    private String FuncModuleName;

    /**
     * 操作目标的IP地址,如目标对象在本机或本系统时,则目标IP与公开IP一致,否则取实际访问值
     */
    private String ObjectIP;

    /**
     * 操作目标提供访问或服务的端口
     */
    private String ObjectPort;

    /**
     * 当操作执行为SQL语句时,填写SQL语句,有多个SQL语句时应全部填写;当操作执行为XML等方式时,填写相对应数据
     */
}

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