在开发过程中,可能会遇到需要记录用户操作记录的情况,这时候可以使用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);
}