logo头像

一路过来,不过游牧自己。。

系统操作记录如何设计


这几天一直沉浸在设计如何记录系统用户操作记录,现在在这边做一个记录。操作记录主要从两方面去思考:1、要记录什么样的字段。2、怎样去记录。

一、要记录什么样的字段?

实际生产过程中,我可能希望看到什么用户执行了什么操作,出现了什么样的结果。所以主要设计字段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** 主键 **/
private Long id;
/** 操作者 **/
private String operator;
/** 操作描述 **/
private String operateType;
/** 操作内容 **/
private String content;
/** ip **/
private String ip;
/** 操作时间 **/
private Date operatorTime;
/** 异常信息 **/
private String exception;
/** 创建时间 **/
private Date createTime;
/** 修改时间 **/
private Date updateTime;

在这里,操作异常在操作成功的时候是不存在的,所以可以设置为null。

二、怎样去记录?

经过思考,主要有一下方案:

(1)通过log4j日志去存储

对于这种方法:
日志记录操作何种方法比较少取。用户操作记录到日志中,然后读日志到数据库文件中,这种可能会涉及到日志格式处理。

(2)通过拦截器处理

拦截器可以拦截来自外面的请求,这种方式获取到的数据可能比较全,但是不够灵活。设置拦截路径,是一个全局的拦截。

(2)通过AOP注解处理

这里着重讲一下这种方法。因为笔者在实际的生产过程中用的就是这种方法。这种方法很灵活,耦合度也很低,想要那部分就直接通过添加注解的方式就可以很好的解决。Spring框架的优越性也展露无遗, 也是AOP切面编程一个非常好的应用。也有利于后期拓展。
首先,需要定义一下注解:

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface OperationLogAnnotation {
String descrption() default "" ;
String flag() default "";
}

如上所示,我们定义了一个注解,这里注解内可以定义描述等字段。
然后对注解进行操作记录逻辑编写:
代码如下:

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
@Aspect
@Component
@Order(-5)
public class OperationLogAspect {

@Autowired
OperationLogBiz operationLogBiz;

@Pointcut("@annotation(com.lianjia.home.ruleengine.common.annotation.OperationLogAnnotation)")
public void serviceAspect(){}

@AfterReturning("serviceAspect()")
public void recordLog(JoinPoint joinPoint) throws Throwable {
OperationLog operationLog =getOperationLogMessage(joinPoint);
operationLog.setException("null");
operationLogBiz.saveOrUpdate(operationLog);

}

@AfterThrowing(pointcut = "serviceAspect()",throwing="e")
public void doAfterThrowing(JoinPoint joinPoint,Throwable e) throws Throwable{
OperationLog operationLog =getOperationLogMessage(joinPoint);
if(e.getMessage()!=null){
operationLog.setException(e.getMessage().length()<255?e.getMessage():e.getMessage().substring(0,254));
}else{
operationLog.setException("出错了");
}
operationLogBiz.saveOrUpdate(operationLog);
}

private OperationLog getOperationLogMessage(JoinPoint joinPoint) throws ClassNotFoundException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
OperationLog operationLog = new OperationLog();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
operationLog.setOperator(SessionUserUtil.getCurrentSessionUser().getName());
Date date = new Date();
operationLog.setOperatorTime(date);
String decp = getServiceMethodDescription(joinPoint);
Object[] args = joinPoint.getArgs();
String params = JSON.toJSONString(args);
operationLog.setContent(method.getName()+":"+params);
if(decp.contains("删除")){
operationLog.setContent("删除id:"+params);
}else {
operationLog.setContent("参数修改为"+SessionUserUtil.getCurrentSessionUser().getSt());
}
operationLog.setOperateType(decp);
String ip = request.getRemoteAddr();
operationLog.setIp(ip);
return operationLog;
}

public String getServiceMethodDescription(JoinPoint joinpoint) throws ClassNotFoundException {
//获取连接点目标类名
String className =joinpoint.getTarget().getClass().getName() ;
//获取连接点签名的方法名
String methodName = joinpoint.getSignature().getName() ;
//获取连接点参数
Object[] args = joinpoint.getArgs() ;
//根据连接点类的名字获取指定类
Class targetClass = Class.forName(className);
//拿到类里面的方法
Method[] methods = targetClass.getMethods() ;

String description = "" ;
//遍历方法名,找到被调用的方法名
for (Method method : methods) {
if (method.getName().equals(methodName)){
Class[] clazzs = method.getParameterTypes() ;
if (clazzs.length==args.length){
//获取注解的说明
description = method.getAnnotation(OperationLogAnnotation. class).descrption();
break;
}
}
}
return description ;
}
}

注解中定义了可以获取到的字段和获取的方法。实际中,其如果函数有返回值,返回值内的数据也是可以获取到的。
最后应用注解:

1
2
3
4
5
6
7
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
@OperationLogAnnotation(descrption = "删除活动")
public void deleteActivityInfoByKey(Long id) {
throw new UserVisiableException(ExceptionConst.BUSINESS_EXCEPTION, "不可删除");
// activityInfoMapper.deleteByPrimaryKey(id);
}

最终可以得到最终的删除操作记录了。
目前 AOP设计的操作日志结束了,并不复杂,但是很好体现了AOP的思想,是一个很好的成长典例!

多走路,多思考,越努力,越幸运!
———————————————YoungerFary

微信打赏

赞赏是不耍流氓的鼓励