使用SpringBoot/Aspect实现日志埋点

在开发过程中,可能会遇到需要记录用户操作记录的情况,这时候可以使用Spring的AOP实现日志埋点,记录用户的操作行为

实现步骤:

1.日志记录注解(使用时直接添加到需要记录的方法上)

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 OperateLogAnnotation {
    String module() default "";    //模块

    String remark() default "";    //备注

    OperationType operationType() default OperationType.UNKNOWN;//操作类型
}

2.操作类型

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;

/**
 * 操作类型(enum):主要是select,insert,update,delete
 */
@AllArgsConstructor
@Getter
public enum OperationType {

    /**
     * 操作类型
     */
    UNKNOWN("unknown"),
    DELETE("delete"),
    SELECT("select"),
    UPDATE("update"),
    INSERT("insert");

    private String value;
}

3.定义切面类

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.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 用户操作日志
 *
 * @author Jonathan.WQ
 */
@Aspect
@Component
public class OperationLogAspect {
    
    @Autowired
    private IOperateLogService operateLogService;

    @Pointcut("@annotation(com.itcode.annotation.OperateLogAnnotation)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object result = null;
        //模块开始时间
        long start = System.currentTimeMillis();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        OperateLog operateLog = new OperateLog();
        operateLog.setId(IdUtils.getInstanse().getUID());
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        // 设置请求的方法名
        operateLog.setMethod(className + "." + methodName + "()");
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames != null) {
            StringBuilder paramsBuilder = new StringBuilder();
            for (int i = 0; i < args.length; i++) {
                String paramName = paramNames[i];
                Object arg = args[i];
                //考虑到参数过长的情况,故而截取
                paramsBuilder.append(paramName).append(": ");
                String paramValue = String.valueOf(arg);
                if (paramValue.length() > 50) {
                    paramsBuilder.append(paramValue.substring(0, 50));
                } else {
                    paramsBuilder.append(paramValue);
                }
                paramsBuilder.append("...");
                if (i != args.length - 1) {
                    paramsBuilder.append("  ,");
                }
            }
            // 设置请求的方法参数名称
            operateLog.setParameter(paramsBuilder.toString());
        }
        // 获取request
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 设置IP地址
        operateLog.setIp(IpAddressUtil.getIpAddr(request));
        // 获取用户
        String authorization = request.getParameter("Authorization");
        if (StringUtils.isNotNull(authorization)) {
            BaseUser user = UserInfoThreadLocal.getUser();
            operateLog.setUserName(user.getName());
            operateLog.setUserId(user.getId());
        }
        //获取系统时间
        operateLog.setCreateTime(DateUtils.getCurrentDate());
        OperateLogAnnotation OperateLogAnnotation = method.getAnnotation(OperateLogAnnotation.class);
        if (OperateLogAnnotation != null) {
            // 注解上的描述
            try {
                operateLog.setModule(OperateLogAnnotation.module());
                operateLog.setRemark(OperateLogAnnotation.remark());
                operateLog.setOperationType(OperateLogAnnotation.operationType().name());
                result = joinPoint.proceed();
                long end = System.currentTimeMillis();
                //将计算好的时间保存在实体中
                operateLog.setResponseTime("" + (end - start));
                operateLog.setCommit("success");
                //保存进数据库
                operateLogService.save(operateLog);
            } catch (Throwable e) {
                operateLog.setModule(OperateLogAnnotation.module());
                long end = System.currentTimeMillis();
                operateLog.setResponseTime("" + (end - start));
                operateLog.setCommit("failed");
                operateLog.setExceptionType(e.getClass().getName());
                //保存进数据库
                operateLogService.save(operateLog);
            }
        }
        return result;
    }
}

4.用户操作日志


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * 用户操作记录表
 *
 * @author Jonathan.WQ
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName("operate_log")
public class OperateLog implements Serializable {

    private static final long serialVersionUID = 1L;


    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.INPUT)
    private String id;//

    /**
     * 用户ID
     */
    @TableField("user_id")
    private String userId;


    /**
     * 用户昵称
     */
    @TableField("user_name")
    private String userName;


    /**
     * 用户类型
     */
    @TableField("user_type")
    private String userType;


    /**
     * 访问模块
     */
    @TableField("module")
    private String module;

    /**
     * 操作类型
     */
    @TableField("operation_type")
    private String operationType;


    /**
     * 备注
     */
    @TableField("remark")
    private String remark;


    /**
     * 访问方法
     */
    @TableField("method")
    private String method;


    /**
     * 访问参数
     */
    @TableField("parameter")
    private String parameter;


    /**
     * 响应时间ms
     */
    @TableField("response_time")
    private String responseTime;


    /**
     * 创建时间
     */
    @TableField("create_time")
    private Date createTime;


    /**
     * 执行结果
     */
    @TableField("commit")
    private String commit;


    /**
     * 异常类型
     */
    @TableField("exception_type")
    private String exceptionType;


    /**
     * IP地址
     */
    @TableField("ip")
    private String ip;
}

5.IOperateService接口及实现类
本文章是基于mybatis-plus 3.0.6开发的,涉及到的Controller、IService、ServiceImpl、Mapper都是用mybatis代码生成器实现的。这里附上代码生成器的源码:
可以新建一个普通的java项目,添加dom4j.jar、freemarker-gae-2.3.22.jar、mysql-connector-java-5.1.16.jar三个jar包即可,也可以创建maven工程。
5.1 Controller的FreeMarker模板

package ${companyPackageName}<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;

import ${companyPackageName}.common.entity<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.${entityClassName};
import ${companyPackageName}<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.service.I${entityClassName}Service;

import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@Api(tags = "${moduleName}api")
public class ${entityClassName}Controller extends BaseController {
		
	@Autowired
	private I${entityClassName}Service ${beanName}Service;
	
}

5.2 IService的FreeMarker模板

package ${companyPackageName}<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.service;

import com.baomidou.mybatisplus.extension.service.IService;
import ${companyPackageName}.entity<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.${entityClassName};


public interface I${entityClassName}Service extends IService<${entityClassName}> {

}

5.3 ServiceImpl实现类FreeMarker模板

package ${companyPackageName}<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.service.impl;

import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import ${companyPackageName}.entity<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.${entityClassName};
import ${companyPackageName}<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.mapper.${entityClassName}Mapper;
import ${companyPackageName}<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.service.I${entityClassName}Service;

@Service
public class ${entityClassName}ServiceImpl extends ServiceImpl<${entityClassName}Mapper, ${entityClassName}>  implements I${entityClassName}Service {
	
}

5.4 实体类FreeMarker模板

package ${companyPackageName}.entity<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>;

import java.io.Serializable;
import java.util.Date;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * ${tableComment}
 *
 * @author ${author} ${createDate}
 *
 */
@Data
@Accessors(chain = true)
@TableName("${tableName}")
public class ${entityClassName} implements Serializable {

	private static final long serialVersionUID = 1L;

    <#list upperColumns as column>
    <#list properties as prop>
    <#list propertyTypes as propType>
    <#list columnComments as columnComment>
    <#list lowerColumns as lowerColumn>
    <#if column_index == prop_index >
    <#if prop_index == propType_index >
    <#if propType_index == columnComment_index >
    <#if columnComment_index == lowerColumn_index >
    <#if column == "ID" >
         
    /**
     * ${columnComment}
     */ 
    @TableId(value="id", type=IdType.INPUT)
	private ${propType} ${prop};//
	<#else> 
	
	/**
     * ${columnComment}
     */
	@TableField("${lowerColumn}")
	private ${propType} ${prop};
	   
	</#if>
	</#if>
	</#if>
	</#if>
	</#if>
	</#list>
	</#list>
	</#list>
	</#list>
	</#list>
	public ${entityClassName}(){
		
	}
	
	/**
	 * 实体属性转换数据库字段
	 * @param propName 实体属性
	 * @return 数据库字段
	 */
	public static String getColumnName(String propName){
		<#list upperColumns as column>
	    <#list properties as prop>
	    <#list propertyTypes as propType>
	    <#list columnComments as columnComment>
	    <#list lowerColumns as lowerColumn>
	    <#if column_index == prop_index >
	    <#if prop_index == propType_index >
	    <#if propType_index == columnComment_index >
	    <#if columnComment_index == lowerColumn_index >
	    <#if columnComment_index == 0 >
	   
	    if("${prop}".equals(propName)) {
			return "${lowerColumn}";
		}
	    <#else>
	    else if("${prop}".equals(propName)) {
			return "${lowerColumn}";
		}
	    </#if>
	    <#if columnComment_index == columnMaxIndex >
	    else {
			return propName;
		}
	    </#if>
		</#if>
		</#if>
		</#if>
		</#if>
		</#list>
		</#list>
		</#list>
		</#list>
		</#list>
	}			
}

5.5 Mapper.java的FreeMarker模板

package ${companyPackageName}<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import ${companyPackageName}.entity<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.${entityClassName};

public interface ${entityClassName}Mapper extends BaseMapper<${entityClassName}> {
	
}

5.6 Mapper.xml的FreeMarker模板

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${companyPackageName}<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.mapper.${entityClassName}Mapper">
	
	<!-- 通用查询映射结果 -->
	<resultMap id="${entityClassName}" type="${companyPackageName}.entity<#if modulePackageName?exists><#if modulePackageName != "">.${modulePackageName}</#if></#if>.${entityClassName}" > 
		<#list upperColumns as column>
		<#list properties as prop>
		<#if column_index == prop_index >
		<#if column == "ID" >
		<id column="ID" property="id"/> 	
		<#else>
		<result column="${column}" property="${prop}" />
		</#if>
		</#if>
		</#list>
		</#list>
	</resultMap>

	<!-- 通用查询条件 -->
	<sql id="base_condition_Sql">
		<#list upperColumns as column>
		<#list properties as prop>
		<#list propertyTypes as type>
		<#if column_index == prop_index> 
		<#if prop_index == type_index>
		<#if type != "Date">
		<if test="${prop} != null and ${prop} !=''">
			AND ${column}= ${r'#'}{${prop}}
		</if>
		<#if column_has_next></#if>
		</#if>
		</#if>
		</#if>
		</#list>
		</#list>
		</#list>
	</sql>
	
	<!-- 通用查询结果列 -->
	<sql id="Base_Column_List">
		<#list upperColumns as column>
		<#list properties as prop>
		<#if column_index == prop_index >
		${column}<#if column_has_next>,</#if>
		</#if>
		</#list>
		</#list>
	</sql>
</mapper>

5.7 调用方法,生成各模块

 public void generateCode(){
        // JDBC 数据库连接参数
        CodeGenerator.JDBC_DB_NAME = "连接的数据库名";
        CodeGenerator.JDBC_CLASSNAME = "com.mysql.jdbc.Driver";
        CodeGenerator.JDBC_URL = "jdbc:mysql://IP:端口/" + CodeGenerator.JDBC_DB_NAME;
        CodeGenerator.JDBC_USER_NAME = "用户名";
        CodeGenerator.JDBC_PASSWORD = "密码";
        CodeGenerator.author = "创建人";//创建人
        CodeGenerator.moduleName = "模块名称";//模块名称

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        CodeGenerator.createDate = dateFormat.format(new Date());//创建时间

        List<String> list = new ArrayList<String>();
        //list.add("数据库表名称,多个就add多个");
        //例如:
        list.add("data_order");
        list.add("data_order_detail");

        for (String tableName : list) {

            // 业务表名称
            CodeGenerator.tableName = tableName.toUpperCase();

            String cn = "";
            String[] tableNames = tableName.split("_");
            for (int i = 0; i < tableNames.length; i++) {
                if (i == 0) {
                    cn += tableNames[i].substring(0, 1).toUpperCase() + tableNames[i].substring(1);
                } else {
                    cn += "_" + tableNames[i].substring(0, 1).toUpperCase() + tableNames[i].substring(1);
                }
            }
            // 实体类名
            CodeGenerator.className = cn;
            //CodeGenerator.srcPath = "项目源码根目录";
            //例如:
            CodeGenerator.srcPath = "E:\\project\\tdytcloud\\src";
            //CodeGenerator.resourcePath = "项目资源文件根目录,生成出来的 mybatis mapper文件将会存放于此";
            //例如:
            CodeGenerator.resourcePath = "E:\\project\\tdytcloud\\config\\mapper\\mysql";
            // 代码包名称
            CodeGenerator.companyPackageName = "com.immo.ymhis";
            // 模块名称;可空,不为空时以此属性值建立子文件夹存放代码文件
            CodeGenerator.modulePackageName = "";
            try {
                // 执行代码生成
                CodeGenerator.generated();
                Logger.getGlobal().log(Level.INFO, cn + "生成完毕...");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

6.使用注解

@OperateLogAnnotation(remark = "新增意见反馈",operationType= OperationType.INSERT)
@PostMapping("/add")
public CommonResult addHandler(@RequestParam("feedBack") FeedBackDTO feedbackDTO, HttpServletRequest request) {
        return feedbackService.add(feedbackDTO, request);
}
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值