简介
一次偶然的机会,在做一个系统的时候需要记录用户操作日志,以下分享下。
步骤
1、依赖
需要引入相应依赖,这里肯定时能和数据库关联了(简单的来说就是能够在浏览器上进行增删改查)。
<!-- aop的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、自定义日志注解
定义一个方法级别的@Log
注解,用于标注需要监控的方法:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义一个Log的日志注解,用在方法上
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
3、创建库表和实体
在数据库中创建一张sys_operation_log
表,用于保存用户的操作日志
a. 数据库
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for sys_operation_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_operation_log`;
CREATE TABLE `sys_operation_log` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户操作日志主键',
`LOG_NAME` varchar(255) DEFAULT NULL COMMENT '日志名称',
`USER_ID` varchar(30) DEFAULT NULL COMMENT '用户id',
`API` varchar(255) DEFAULT NULL COMMENT 'api名称',
`METHOD` varchar(255) DEFAULT NULL COMMENT '方法名称',
`CREATE_TIME` datetime DEFAULT NULL COMMENT '创建时间',
`SUCCEED` tinyint(4) DEFAULT NULL COMMENT '是否执行成功(0失败1成功)',
`MESSAGE` varchar(255) DEFAULT NULL COMMENT '具体消息备注',
PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='操作日志';
-- ----------------------------
-- Records of sys_operation_log
-- ----------------------------
INSERT INTO `sys_operation_log` VALUES ('17', '业务操作日志', 'tom', '/resource/menus', 'GET', '2018-04-22 16:05:05', '1', null);
INSERT INTO `sys_operation_log` VALUES ('18', '业务操作日志', 'tom', '/resource/menus', 'GET', '2018-04-22 16:05:09', '1', null);
INSERT INTO `sys_operation_log` VALUES ('19', '业务操作日志', 'tom', '/resource/api/-1/1/10', 'GET', '2018-04-22 16:08:15', '1', null);
b. 实体类
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 用户操作日志主键
* Create by ck on 2019/6/17
*/
@Data
public class sysOperationLog implements Serializable {
private static final long serialVersionUID = 8147899229715719433L;
/**
* 主键
*/
private Integer id;
/**
* 日志名称
*/
private String logName;
/**
* 用户id
*/
private String userId;
/**
* api名称
*/
private String api;
/**
* 方法名称
*/
private String method;
/**
* 是否执行成功 0失败 -- 1成功
*/
private Byte succeed;
/**
* 详细备注
*/
private String message;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}
4、切面和切点
定义一个LogAspect类,使用@Aspect
标注让其成为一个切面,切点为使用@Log
注解标注的方法,使用@Around
环绕通知:
import com.ck.syscheck.annotate.Log;
import com.ck.syscheck.model.SysOperationLog;
import com.ck.syscheck.service.SysOperationService;
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 java.lang.reflect.Method;
import java.util.Date;
@Aspect
@Component
public class LogAspect {
@Autowired
private SysOperationService sysOperationService;
@Pointcut("@annotation(com.ck.syscheck.annotate.Log)")
public void pointcut() { }
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) {
Object result = null;
long beginTime = System.currentTimeMillis();
try {
// 执行方法
result = point.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
// 保存日志
saveLog(point, time);
return result;
}
private void saveLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysOperationLog sysOperationLog = new SysOperationLog();
Log logAnnotation = method.getAnnotation(Log.class);
if (logAnnotation != null) {
// 注解上的描述
sysOperationLog.setApi(logAnnotation.value());
}
// 请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysOperationLog.setMethod(className + "." + methodName + "()");
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
if (args != null && paramNames != null) {
String params = "";
for (int i = 0; i < args.length; i++) {
params += " " + paramNames[i] + ": " + args[i];
}
sysOperationLog.setMessage(params);
}
// 模拟一个用户名
sysOperationLog.setUserId("admin");
sysOperationLog.setCreateTime(new Date());
// 保存系统日志
sysOperationService.insert(sysOperationLog);
}
}
5、测试
import com.ck.syscheck.annotate.Log;
import org.springframework.web.servlet.ModelAndView;
@RestController
@RequestMapping("/api")
public class SysUserController {
/**
* @return 测试
*/
@Log("测试test")
@RequestMapping("/test")
public ModelAndView test(ModelAndView modelAndView) {
modelAndView.setViewName("/front/test");
return modelAndView;
}
@Log("添加add")
@RequestMapping("/add")
public ModelAndView add(ModelAndView modelAndView) {
modelAndView.setViewName("/front/add");
return modelAndView;
}
@Log("更新Update")
@RequestMapping("/update")
public ModelAndView update(ModelAndView modelAndView) {
modelAndView.setViewName("/front/update");
return modelAndView;
}
}
可以存到数据库
这里面是以部分字段作为参考栗子,具体的还需要看项目需求。demo源码所在位置:
GitHub:https://github.com/Tidetrace/sys-check