• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

SpringAop实现操作日志记录

java 搞代码 4年前 (2022-01-05) 16次浏览 已收录 0个评论
文章目录[隐藏]

这篇文章主要介绍了SpringAop实现操作日志记录的方法,帮助大家更好的理解和使用SpringAop,感兴趣的朋友可以了解下

前言

大家好,这里是经典鸡翅,今天给大家带来一篇基于SpringAop实现的操作日志记录的解决的方案。大家可能会说,切,操作日志记录这么简单的东西,老生常谈了。不!

网上的操作日志一般就是记录操作人,操作的描述,ip等。好一点的增加了修改的数据和执行时间。那么!我这篇有什么不同呢!今天这种不仅可以记录上方所说的一切,还增加记录了操作前的数据,错误的信息,堆栈信息等。正文开始~~~~~

思路介绍

记录操作日志的操作前数据是需要思考的重点。我们以修改场景来作为探讨。当我们要完全记录数据的流向的时候,我们必然要记录修改前的数据,而前台进行提交的时候,只有修改的数据,那么如何找到修改前的数据呢。有三个大的要素,我们需要知道修改前数据的表名,表的字段主键,表主键的值。这样通过这三个属性,我们可以很容易的拼出 select * from 表名 where 主键字段 = 主键值。我们就获得了修改前的数据,转换为json之后就可以存入到数据库中了。如何获取三个属性就是重中之重了。我们采取的方案是通过提交的映射实体,在实体上打上注解,根据 Java 的反射取到值。再进一步拼装获得对象数据。那么AOP是在哪里用的呢,我们需要在记录操作日志的方法上,打上注解,再通过切面获取到切点,一切的数据都通过反射来进行获得。

定义操作日志注解

既然是基于spinrg的aop实现切面。那么必然是需要一个自定义注解的。用来作为切点。我们定义的注解,可以带一些必要的属性,例如操作的描述,操作的类型。操作的类型需要说一下,我们分为新增、修改、删除、查询。那么只有修改和删除的时候,我们需要查询一下修改前的数据。其他两种是不需要的,这个也可以用来作为判断。

 @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface OperateLog { String operation() default ""; String operateType() default ""; }

定义用于找到表和表主键的注解

表和表主键的注解打在实体上,内部有两个属性 tableName 和 idName。这两个属性的值获得后,可以进行拼接 select * from 表名 where 主键字段。

 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SelectTable { String tableName() default ""; String idName() default ""; }

定义获取主键值的注解

根据上面所说的三个元素,我们还缺最后一个元素主键值的获取,用于告诉我们,我们应该从提交的请求的那个字段,拿到其中的值。

 @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SelectPrimaryKey { }

注解的总结

有了上面的三个注解,注解的准备工作已经进行完毕。我们通过反射取到数据,可以获得一切。接下来开始实现切面,对于注解的值进行拼接处理,最终存入到我们的数据库操作日志表中。

切面的实现

对于切面来说,我们需要实现切点、数据库的插入、反射的数据获取。我们先分开进行解释,最后给出全面的实现代码。方便大家的理解和学习。

切面的定义

基于spring的aspect进行声明这是一个切面。

 @Aspect @Component public class OperateLogAspect { }

切点的定义

切点就是对所有的打上OperateLog的注解的请求进行拦截和加强。我们使用annotation进行拦截。

 @Pointcut("@annotation(com.jichi.aop.operateLog.OperateLog)") private void operateLogPointCut(){ }

获取请求ip的共用方法

 private String getIp(HttpServletRequest request){ String ip = request.getHeader("X-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); <i style="color:transparent">来源gaodai$ma#com搞$代*码网</i>} if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; }

数据库的日志插入操作

我们将插入数据库的日志操作进行单独的抽取。

 private void insertIntoLogTable(OperateLogInfo operateLogInfo){ operateLogInfo.setId(UUID.randomUUID().toString().replace("-","")); String sql="insert into log values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; jdbcTemplate.update(sql,operateLogInfo.getId(),operateLogInfo.getUserId(), operateLogInfo.getUserName(),operateLogInfo.getOperation(),operateLogInfo.getMethod(), operateLogInfo.getModifiedData(),operateLogInfo.getPreModifiedData(), operateLogInfo.getResult(),operateLogInfo.getErrorMessage(),operateLogInfo.getErrorStackTrace(), operateLogInfo.getExecuteTime(),operateLogInfo.getDuration(),operateLogInfo.getIp(), operateLogInfo.getModule(),operateLogInfo.getOperateType()); }

环绕通知的实现

日志的实体类实现

 @TableName("operate_log") @Data public class OperateLogInfo { //主键id @TableId private String id; //操作人id private String userId; //操作人名称 private String userName; //操作内容 private String operation; //操作方法名称 private String method; //操作后的数据 private String modifiedData; //操作前数据 private String preModifiedData; //操作是否成功 private String result; //报错信息 private String errorMessage; //报错堆栈信息 private String errorStackTrace; //开始执行时间 private Date executeTime; //执行持续时间 private Long duration; //ip private String ip; //操作类型 private String operateType; }

准备工作全部完成。接下来的重点是对环绕通知的实现。思路分为数据处理、异常捕获、finally执行数据库插入操作。环绕通知的重点类就是ProceedingJoinPoint ,我们通过它的getSignature方法可以获取到打在方法上注解的值。例如下方。

 MethodSignature signature = (MethodSignature) pjp.getSignature(); OperateLog declaredAnnotation = signature.getMethod().getDeclaredAnnotation(OperateLog.class); operateLogInfo.setOperation(declaredAnnotation.operation()); operateLogInfo.setModule(declaredAnnotation.module()); operateLogInfo.setOperateType(declaredAnnotation.operateType()); //获取执行的方法 String method = signature.getDeclaringType().getName() + "." + signature.getName(); operateLogInfo.setMethod(method); String operateType = declaredAnnotation.operateType();

获取请求的数据,也是通过这个类来实现,这里有一点是需要注意的,就是我们要约定参数的传递必须是第一个参数。这样才能保证我们取到的数据是提交的数据。

 if(pjp.getArgs().length>0){ Object args = pjp.getArgs()[0]; operateLogInfo.setModifiedData(new Gson().toJson(args)); }

接下来的一步就是对修改前的数据进行拼接。之前我们提到过如果是修改和删除,我们才会进行数据的拼接获取,主要是通过类来判断书否存在注解,如果存在注解,那么就要判断注解上的值是否是控制或者,非空才能正确的进行拼接。取field的值的时候,要注意私有的变量需要通过setAccessible(true)才可以进行访问。

 if(GlobalStaticParas.OPERATE_MOD.equals(operateType) || GlobalStaticParas.OPERATE_DELETE.equals(operateType)){ String tableName = ""; String idName = ""; String selectPrimaryKey = ""; if(pjp.getArgs().length>0){ Object args = pjp.getArgs()[0]; //获取操作前的数据 boolean selectTableFlag = args.getClass().isAnnotationPresent(SelectTable.class); if(selectTableFlag){ tableName = args.getClass().getAnnotation(SelectTable.class).tableName(); idName = args.getClass().getAnnotation(SelectTable.class).idName(); }else { throw new RuntimeException("操作日志类型为修改或删除,实体类必须指定表面和主键注解!"); } Field[] fields = args.getClass().getDeclaredFields(); Field[] fieldsCopy = fields; boolean isFindField = false; int fieldLength = fields.length; for(int i = 0; i <fieldLength; ++i) { Field field = fieldsCopy[i]; boolean hasPrimaryField = field.isAnnotationPresent(SelectPrimaryKey.class); if (hasPrimaryField) { isFindField = true; field.setAccessible(true); selectPrimaryKey = (String)field.get(args); } } if(!isFindField){ throw new RuntimeException("实体类必须指定主键属性!"); } } if(StringUtils.isNotEmpty(tableName) && StringUtils.isNotEmpty(idName)&& StringUtils.isNotEmpty(selectPrimaryKey)){ StringBuffer sb = new StringBuffer(); sb.append(" select * from "); sb.append(tableName); sb.append(" where "); sb.append(idName); sb.append(" = ? "); String sql = sb.toString(); try{ List<Map> maps = jdbcTemplate.queryForList(sql, selectPrimaryKey); if(maps!=null){ operateLogInfo.setPreModifiedData(new Gson().toJson(maps)); } }catch (Exception e){ e.printStackTrace(); throw new RuntimeException("查询操作前数据出错!"); } }else { throw new RuntimeException("表名、主键名或主键值 存在空值情况,请核实!"); } }else{ operateLogInfo.setPreModifiedData(""); }

切面的完整实现代码

 @Aspect @Component public class OperateLogAspect { @Autowired private JdbcTemplate jdbcTemplate; @Pointcut("@annotation(com.jichi.aop.operateLog.OperateLog)") private void operateLogPointCut(){ } @Around("operateLogPointCut()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object responseObj = null; OperateLogInfo operateLogInfo = new OperateLogInfo(); String flag = "success"; try{ HttpServletRequest request = SpringContextUtil.getHttpServletRequest(); DomainUserDetails currentUser = SecurityUtils.getCurrentUser(); if(currentUser!=null){ operateLogInfo.setUserId(currentUser.getId()); operateLogInfo.setUserName(currentUser.getUsername()); } MethodSignature signature = (MethodSignature) pjp.getSignature(); OperateLog declaredAnnotation = signature.getMethod().getDeclaredAnnotation(OperateLog.class); operateLogInfo.setOperation(declaredAnnotation.operation()); operateLogInfo.setModule(declaredAnnotation.module()); operateLogInfo.setOperateType(declaredAnnotation.operateType()); //获取执行的方法 String method = signature.getDeclaringType().getName() + "." + signature.getName(); operateLogInfo.setMethod(method); String operateType = declaredAnnotation.operateType(); if(pjp.getArgs().length>0){ Object args = pjp.getArgs()[0]; operateLogInfo.setModifiedData(new Gson().toJson(args)); } if(GlobalStaticParas.OPERATE_MOD.equals(operateType) || GlobalStaticParas.OPERATE_DELETE.equals(operateType)){ String tableName = ""; String idName = ""; String selectPrimaryKey = ""; if(pjp.getArgs().length>0){ Object args = pjp.getArgs()[0]; //获取操作前的数据 boolean selectTableFlag = args.getClass().isAnnotationPresent(SelectTable.class); if(selectTableFlag){ tableName = args.getClass().getAnnotation(SelectTable.class).tableName(); idName = args.getClass().getAnnotation(SelectTable.class).idName(); }else { throw new RuntimeException("操作日志类型为修改或删除,实体类必须指定表面和主键注解!"); } Field[] fields = args.getClass().getDeclaredFields(); Field[] fieldsCopy = fields; boolean isFindField = false; int fieldLength = fields.length; for(int i = 0; i <fieldLength; ++i) { Field field = fieldsCopy[i]; boolean hasPrimaryField = field.isAnnotationPresent(SelectPrimaryKey.class); if (hasPrimaryField) { isFindField = true; field.setAccessible(true); selectPrimaryKey = (String)field.get(args); } } if(!isFindField){ throw new RuntimeException("实体类必须指定主键属性!"); } } if(StringUtils.isNotEmpty(tableName) && StringUtils.isNotEmpty(idName)&& StringUtils.isNotEmpty(selectPrimaryKey)){ StringBuffer sb = new StringBuffer(); sb.append(" select * from "); sb.append(tableName); sb.append(" where "); sb.append(idName); sb.append(" = ? "); String sql = sb.toString(); try{ List<Map> maps = jdbcTemplate.queryForList(sql, selectPrimaryKey); if(maps!=null){ operateLogInfo.setPreModifiedData(new Gson().toJson(maps)); } }catch (Exception e){ e.printStackTrace(); throw new RuntimeException("查询操作前数据出错!"); } }else { throw new RuntimeException("表名、主键名或主键值 存在空值情况,请核实!"); } }else{ operateLogInfo.setPreModifiedData(""); } //操作时间 Date beforeDate = new Date(); Long startTime = beforeDate.getTime(); operateLogInfo.setExecuteTime(beforeDate); responseObj = pjp.proceed(); Date afterDate = new Date(); Long endTime = afterDate.getTime(); Long duration = endTime - startTime; operateLogInfo.setDuration(duration); operateLogInfo.setIp(getIp(request)); operateLogInfo.setResult(flag); }catch (RuntimeException e){ throw new RuntimeException(e); }catch (Exception e){ flag = "fail"; operateLogInfo.setResult(flag); operateLogInfo.setErrorMessage(e.getMessage()); operateLogInfo.setErrorStackTrace(e.getStackTrace().toString()); e.printStackTrace(); }finally { insertIntoLogTable(operateLogInfo); } return responseObj; } private void insertIntoLogTable(OperateLogInfo operateLogInfo){ operateLogInfo.setId(UUID.randomUUID().toString().replace("-","")); String sql="insert into energy_log values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; jdbcTemplate.update(sql,operateLogInfo.getId(),operateLogInfo.getUserId(), operateLogInfo.getUserName(),operateLogInfo.getOperation(),operateLogInfo.getMethod(), operateLogInfo.getModifiedData(),operateLogInfo.getPreModifiedData(), operateLogInfo.getResult(),operateLogInfo.getErrorMessage(),operateLogInfo.getErrorStackTrace(), operateLogInfo.getExecuteTime(),operateLogInfo.getDuration(),operateLogInfo.getIp(), operateLogInfo.getModule(),operateLogInfo.getOperateType()); } private String getIp(HttpServletRequest request){ String ip = request.getHeader("X-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } }

示例的使用方式

针对于示例来说我们要在controller上面打上操作日志的注解。

 @PostMapping("/updateInfo") @OperateLog(operation = "修改信息",operateType = GlobalStaticParas.OPERATE_MOD) public void updateInfo(@RequestBody Info info) { service.updateInfo(info); }

针对于Info的实体类,我们则要对其中的字段和表名进行标识。

 @Data @SelectTable(tableName = "info",idName = "id") public class Info { @SelectPrimaryKey private String id; private String name; }

总结

文章写到这,也就结束了,文中难免有不足,欢迎大家批评指正

以上就是SpringAop实现操作日志记录的详细内容,更多关于SpringAop 操作日志记录的资料请关注gaodaima搞代码网其它相关文章!

以上就是SpringAop实现操作日志记录的详细内容,更多请关注gaodaima搞代码网其它相关文章!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:SpringAop实现操作日志记录

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址