基于springboot1.x AOP的操作日志
[TOC]
前言
由于一些历史原因,为了方便运营部门登录后台系统,大家使用了一个权限非常大的账号。近期发现很多操作失误,找不到凶手的情况。
所以做两个事:
- 修改原有账号的密码,一人一号操作
- 编写操作日志记录的功能,让关键增删改操作,能找到人。好抓凶手,故引出本文。
正文
一、整体思路
- 基于AOP切面编程
- 切点第一类:拦截指定包及子包下的业务代码,对以特殊开头方法,进行拦截
- 切点第二类:通过自定义注解,对特殊开头方法进行拦截
- 拦截后将操作人,入库进行持久化
- 操作日志数据的处理
(1)存储媒介暂时是mysql
(2)定期数据移入历史库或是删除,控制数据库文件大小
(3)为了方便查询,后期考虑ES
二、数据库及基础服务
增删改查的基础服务,此处不贴代码凑字
数据库表如下:
CREATE TABLE `action_log` (
`id` bigint(32) NOT NULL AUTO_INCREMENT,
`user_id` varchar(50) DEFAULT NULL COMMENT 'open_id',
`user_name` varchar(100) DEFAULT NULL COMMENT '用户名',
`operation` varchar(100) DEFAULT NULL COMMENT '操作,注解value',
`method_name` text COMMENT '方法名',
`params` text COMMENT '参数json',
`ip` varchar(100) DEFAULT NULL COMMENT 'ip地址',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1148538203664441345 DEFAULT CHARSET=utf8
三、AOP切面实现
0.maven依赖
<!-- springboot1.x 的aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- web程序的启动项依赖,通过此依赖可引入内嵌的tomcat等web必须的jars -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1.文件路径过滤式
- 定义切点: com.fatsnake.projects包及其子包下的***的方法
/**
* 添加业务逻辑方法切入点
*/
@Pointcut("execution(* com.fatsnake.projects..*.add*(..)) "
+ "|| execution(* com.fatsnake.projects..*.create*(..)) "
+ "|| execution(* com.fatsnake.projects..*.insert*(..))")
public void insertCell() {
}
/**
* 删除业务逻辑方法切入点
*/
@Pointcut("execution(* com.fatsnake.projects..*.delete*(..)) "
+ "execution(* com.fatsnake.projects..*.remove*(..))")
public void deleteCell() {
}
/**
* 修改业务逻辑方法切入点
*/
@Pointcut("execution(* com.fatsnake.projects..*.update*(..))")
public void updateCell() {
}
2.自定义注解式
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解类
*/
@Target( {ElementType.TYPE, ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface FatsnakeActionLog {
/**
* 默认值
*
* @return String
*/
String value() default "";
}
- 定义注解式切点
/**
* 在注解FatsnakeActionLog的位置切入代码
* 定义切点 @Pointcut
*/
@Pointcut("@annotation( com.fatsnake.projects.springboot.common.annotation.FatsnakeActionLog)")
public void fatsnakeActionLogPoinCut() {
}
3.AOP方法
/**
* 在切入点(业务方法)方法执行之前执行
*
* @param joinPoint joinPoint
*/
@Before(" fatsnakeActionLogPoinCut() || insertCell() || deleteCell()|| updateCell() ")
public void logBeforeController(JoinPoint joinPoint) {
// 记录日志不能影响原有业务代码,所以所以异常均catch捕获,LOGGER.error打印出,ELK服务收集
try {
// 登录判断
// 全局缓存服务中获取当前登录的用户
User user = userService.getLogonUser();
// 操作人, 用户不能录,记录没有意义
if (user != null) {
FatsnakeActionLog fatsnakeActionLog = new FatsnakeActionLog();
fatsnakeActionLog.setOpenId(user.getOpenId());
fatsnakeActionLog.setUserName(user.getUserName());
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//获取请求的类名
String className = method.getDeclaringClass().getName();
//获取请求的方法名
String methodName = method.getName();
fatsnakeActionLog.setMethodName(className + "." + methodName);
//获取注解操作的value
com.fatsnake.projects.springboot.common.annotation.FatsnakeActionLog logAnnotation
= method.getAnnotation(com.fatsnake.projects.springboot.common.annotation.FatsnakeActionLog.class);
if (logAnnotation != null) {
String value = logAnnotation.value();
fatsnakeActionLog.setOperation(value); // 保存获取的操作
}
//请求的参数
Object[] args = joinPoint.getArgs();
//将参数所在的数组转换成json
String params = JSON.toJSONString(args);
fatsnakeActionLog.setParams(params);
// 操作时间
fatsnakeActionLog.setCreateTime(new Date());
// 获取用户ip地址
// 这个RequestContextHolder是Springmvc提供来获得请求的东西
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
fatsnakeActionLog.setIp(request.getRemoteAddr());
}
// 持久化日志
// 此处持久化业务方法名,不能满足切点,否则将被拦截,进入死循环,导致jvm堆内存溢出
fatsnakeActionLogService.produceLog(fatsnakeActionLog);
}
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
}
四、使用与测试
- 以上aop相关代码,以sdk的形式,通过maven引入项目中
- 在application.properties文件里加这样一条配置
spring.aop.auto=true // 此配置,在功能中加与不加,功能都能正常运行 - 注解式使用如下:
/**
* 套餐分页
*
* @param saleActivity saleActivity
* @param pageNo pageNo
* @param pageSize pageSize
* @return ResultData
*/
@PostMapping("/getSmartData")
@FatsnakeActionLog(value = "优惠活动查询")
public ResultData getList(@RequestBody SaleActivity saleActivity,
@NotBlank @RequestParam("pageNo") String pageNo,
@NotBlank @RequestParam("pageSize") String pageSize) {
Paging page = new Paging();
page.setPageNo(Integer.parseInt(pageNo));
page.setPageSize(Integer.parseInt(pageSize));
return renderSuccess(prepareModel(saleActivity, page));
}
五、扩展阅读
在SpringBoot中用SpringAOP实现日志记录功能
springboot—spring aop 实现系统操作日志记录存储到数据库
Spring boot学习(六)Spring boot实现AOP记录操作日志
spring aop expression 匹配多个条件 多个表达式
结语
功能以上正式站,目前仅是用在公司的业务管理系统中。还是有很多问题,有待以后优化。