用AOP做Controller层操作日志记录

功能概述

公司系统需要增加操作记录功能,包括编辑页面内容调整和列表页的任务分配、任务启禁用记录,记录最近一周(前6天0点-当天当前时间点)的操作记录。

思路

直接在代码中拦截工作量大,且代码侵入性高,想到用对代码侵入性较小的AOP来实现此功能。

配置文件

spring-mvc.xml中配置包扫描和aop支持

1
2
<context:component-scan base-package="xxx.xxx.xxx" />
<aop:aspectj-autoproxy proxy-target-class="true" />

数据库表设计

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE `task_operate_log` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`operateUser` varchar(16) DEFAULT NULL COMMENT '操作人',
`operateTime` datetime DEFAULT NULL COMMENT '操作时间',
`operateDesc` varchar(128) DEFAULT NULL COMMENT '操作描述',
`operateType` varchar(16) DEFAULT NULL COMMENT '操作类型',
`ip` varchar(16) DEFAULT NULL COMMENT '访问ip',
`url` varchar(128) DEFAULT NULL COMMENT '访问url',
`method` varchar(256) DEFAULT NULL COMMENT '访问方法',
PRIMARY KEY (`id`),
KEY `AK_OPERATE_TIME` (`operateTime`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

日志实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public class TaskOperateLogEntity implements Serializable {

private static final long serialVersionUID = 7095698665191965855L;

private String id;

private String operateUser;

private String operateTime;

private String operateDesc;

private String operateType;

private String ip;

private String url;

private String method;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getOperateUser() {
return operateUser;
}

public void setOperateUser(String operateUser) {
this.operateUser = operateUser;
}

public String getOperateTime() {
return operateTime;
}

public void setOperateTime(String operateTime) {
this.operateTime = operateTime;
}

public String getOperateDesc() {
return operateDesc;
}

public void setOperateDesc(String operateDesc) {
this.operateDesc = operateDesc;
}

public String getOperateType() {
return operateType;
}

public void setOperateType(String operateType) {
this.operateType = operateType;
}

public String getIp() {
return ip;
}

public void setIp(String ip) {
this.ip = ip;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getMethod() {
return method;
}

public void setMethod(String method) {
this.method = method;
}

@Override
public String toString() {
final StringBuffer sb = new StringBuffer("TaskOperateLogVo{");
sb.append("id='").append(id).append('\'');
sb.append(", operateUser='").append(operateUser).append('\'');
sb.append(", operateTime='").append(operateTime).append('\'');
sb.append(", operateDesc='").append(operateDesc).append('\'');
sb.append(", operateType='").append(operateType).append('\'');
sb.append(", ip='").append(ip).append('\'');
sb.append(", url='").append(url).append('\'');
sb.append(", method='").append(method).append('\'');
sb.append('}');
return sb.toString();
}
}

创建处理日志的切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@Component
@Aspect
public class TaskOperateLogAop {

private static final Logger LOGGER = LoggerFactory.getLogger(TaskOperateLogAop.class);

@Autowired
private HttpServletRequest request;

@Resource(name = "taskOperateLogBusiness")
private TaskOperateLogBusiness taskOperateLogBusiness;

private String visitTime;
// 访问的类
private Class clazz;
// 访问的方法
private Method method;

// 切入点表达式,表示TaskController类下的所有包含自定义注解的方法
@Before("execution(* com.xxx.web.controller.TaskController.*(..)) && @annotation(log)")
public void doBefore(JoinPoint jp, OperateLog log) {
try {
// 访问时间
visitTime = DateUtils.formatDate(new Date());
// 访问的类
clazz = jp.getTarget().getClass();
// 访问的方法名
String methodName = jp.getSignature().getName();
// 获取访问的方法的参数
Object[] args = jp.getArgs();
if (args == null || args.length == 0) {// 无参数
// 获取无参方法
method = clazz.getMethod(methodName);
} else {
// 有参数,获取参数类型
Class[] parameterTypes = ((MethodSignature) jp.getSignature()).getMethod().getParameterTypes();
// 获取有参方法
method = clazz.getMethod(methodName, parameterTypes);
}
} catch (SecurityException | NoSuchMethodException e) {
LOGGER.error("TaskOperateLogAop保存操作记录前置通知异常", e);
}
}

// 切入点表达式,表示TaskController类下的所有包含自定义注解的方法
@After("execution(* com.xxx.web.controller.TaskController.*(..)) && @annotation(log)")
public void doAfter(JoinPoint jp, OperateLog log) {
System.out.println("后置通知开始");
if (clazz != null && method != null && clazz != TaskOperateLogAop.class) {
// 获取类上的@RequestMapping对象
RequestMapping classAnnotation = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
if (classAnnotation != null) {
// 获取方法上的@RequestMapping对象
RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
if (methodAnnotation != null) {
try {
String url = classAnnotation.value()[0] + methodAnnotation.value()[0];
TaskOperateLogEntity entity = new TaskOperateLogEntity();
// 访问时间
entity.setOperateTime(visitTime);
// 访问的url
entity.setUrl(url);
// 访问ip
entity.setIp(request.getRemoteAddr());
// 获取操作用户
String remoteUser = request.getRemoteUser();
entity.setOperateUser(remoteUser);
entity.setMethod("类名: " + clazz.getName() + " 方法名: " + method.getName());
// 操作类型和操作描述
entity.setOperateType(log.operateType());
entity.setOperateDesc(log.operateDesc());
// entity入库
taskOperateLogBusiness.save(entity);
} catch (Exception e) {
LOGGER.error("TaskOperateLogAop保存操作记录后置通知异常", e);
}
}
}
}
}

}

自定义注解类

1
2
3
4
5
6
7
8
@Target(value = { ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {
// 操作类型和操作描述 01新增任务 02修改任务 03删除任务 04更改任务状态 05分配任务
String operateType() default "";
String operateDesc() default "";
}

要记录日志的Controller方法

这里只列举一个方法,其他需要记录操作日志的方法加上@OperateLog自定义注解即可

1
2
3
4
5
@RequestMapping("/createTask.do")
@OperateLog(operateType = "01", operateDesc = "新建任务")
public void createTask(HttpServletResponse response, HttpServletRequest request) {
// ...方法略,功能是新增一个任务
}

业务层

数据库insert操作,dalClient封装的是持久层CRUD方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service("taskOperateLogBusiness")
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class TaskOperateLogBusiness extends CommonBusiness {

private static final String SQL_NAMESPACE = "taskOperateLog.";

/**
* 保存操作记录
*
* @param entity
*/
public void save(TaskOperateLogEntity entity) throws Exception{
if (null == entity) {
LOGGER.info("获取日志信息为空");
return;
}
Map<String, Object> param = new HashMap<>();
param.put("operateUser", entity.getOperateUser());
param.put("operateTime", entity.getOperateTime());
param.put("operateDesc", entity.getOperateDesc());
param.put("operateType", entity.getOperateType());
param.put("ip", entity.getIp());
param.put("url", entity.getUrl());
param.put("method", entity.getMethod());

dalClient.execute(SQL_NAMESPACE + "insertTaskOperateLog", param);
}
}

效果

当发生新增、修改、删除等操作时,记录入表

0%