34、使用Aop的方式实现自动日志记录
自动日志记录的实现的两种方式:
①通过监听器去监听,当访问到具体的类方法,通过aop切面去获取访问的方法,然后将日志记录下来
②通过拦截器,编写一个类去继承HandlerInterceptorAdapter,重写preHandle,postHandle,然后在里面进行日志记录,编写的类加到spring容器里
采用第一种方式:
1、第一步、定义一个注解:
Annotation 注解的作用:
@interface 表示这是一个注解类, 不是interface,是注解类 定义注解用的,是jdk1.5之后加入的,java没有给它新的关键字,所以就用@interface 这么个东西表示了
@Inherited //这个Annotation 可以被继承
@Documented //这个Annotation可以被写入javadoc
@Target:注解的作用目标
@Target(ElementType.TYPE) //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
@Retention(RetentionPolicy.RUNTIME) //可以用来修饰注解,是注解的注解,称为元注解。
public enum RetentionPolicy { SOURCE, // 编译器处理完Annotation后不存储在class中 CLASS, // 编译器把Annotation存储在class中,这是默认值 RUNTIME // 编译器把Annotation存储在class中,可以由虚拟机读取,反射需要 }
创建一个注解:
default 0 相当于set和get方法,添加一个默认值
/** * 系统日志注解 * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AutoLog { /** * 日志内容 * * @return */ String value() default ""; * 日志类型 * @return 1:登录日志;2:操作日志;3:访问日志;4:异常日志;5:定时任务; int logType() default CommonConstant.LOG_TYPE_2; * 操作日志类型 * @return (1查询,2添加,3修改,4删除) int operateType() default 0; }
CommonConstant 相关的配置
public interface CommonConstant { /** * 正常状态 */ public static final Integer STATUS_NORMAL = 0; * 禁用状态 public static final Integer STATUS_DISABLE = -1; * 删除标志 public static final Integer DEL_FLAG_DELETED = 1; * 未删除 public static final Integer DEL_FLAG_UNDELETED = 0; * 系统日志类型: 登录 public static final int LOG_TYPE_1 = 1; * 系统日志类型: 操作 public static final int LOG_TYPE_2 = 2; /** * 系统日志类型: 访问 */ public static final int LOG_TYPE_3 = 3; * 系统日志类型: 异常 public static final int LOG_TYPE_4 = 4; * 系统日志类型: 定时任务 public static final int LOG_TYPE_5 = 5; * 系统日志类型: 用户管理 public static final int LOG_TYPE_6 = 6; * 系统登陆日志:正常账户密码登录 public static final int OPERATE_TYPE_LT1_1 = 1; * 系统登陆日志:二维码登陆 public static final int OPERATE_TYPE_LT1_2 = 2; * 系统登陆日志:单点登陆 public static final int OPERATE_TYPE_LT1_3 = 3; * 系统登陆日志:登出 public static final int OPERATE_TYPE_LT1_4 = 4; * 系统登陆日志:模拟登陆 public static final int OPERATE_TYPE_LT1_5 = 5; * 操作日志类型: 查询 public static final int OPERATE_TYPE_LT2_1 = 1; * 操作日志类型: 添加 public static final int OPERATE_TYPE_LT2_2 = 2; * 操作日志类型: 更新 public static final int OPERATE_TYPE_LT2_3 = 3; * 操作日志类型: 删除 public static final int OPERATE_TYPE_LT2_4 = 4; * 操作日志类型: 导入 public static final int OPERATE_TYPE_LT2_5 = 5; * 操作日志类型: 导出 public static final int OPERATE_TYPE_LT2_6 = 6; * 访问日志类型: 进入 public static final int OPERATE_TYPE_LT3_1 = 1; * 异常日志类型: 普通操作即代码错误 public static final int OPERATE_TYPE_LT4_1 = 1; * 异常日志类型: 非法操作即越权操作 public static final int OPERATE_TYPE_LT4_2 = 2; public static final String CLIENT_TYPE_PC="0"; public static final String CLIENT_TYPE_MOBILE="1"; /** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */ public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500; /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */ public static final Integer SC_OK_200 = 200; /**访问权限认证未通过 510*/ public static final Integer SC_JEECG_NO_AUTHZ=510; /** 登录用户Shiro权限缓存KEY前缀 */ public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.modules.shiro.authc.ShiroRealm.authorizationCache:"; /** 登录用户Token令牌缓存KEY前缀 */ public static final String PREFIX_USER_TOKEN = "prefix_user_token_"; /** Token缓存时间:3600秒即一小时 */ public static final int TOKEN_EXPIRE_TIME = 3600; * 0:一级菜单 public static final Integer MENU_TYPE_0 = 0; /** * 1:子菜单 */ public static final Integer MENU_TYPE_1 = 1; * 2:按钮权限 public static final Integer MENU_TYPE_2 = 2; /**通告对象类型(USER:指定用户,ALL:全体用户)*/ public static final String MSG_TYPE_UESR = "USER"; public static final String MSG_TYPE_ALL = "ALL"; /**发布状态(0未发布,1已发布,2已撤销)*/ public static final String NO_SEND = "0"; public static final String HAS_SEND = "1"; public static final String HAS_CANCLE = "2"; /**阅读状态(0未读,1已读)*/ public static final String HAS_READ_FLAG = "1"; public static final String NO_READ_FLAG = "0"; /**优先级(L低,M中,H高)*/ public static final String PRIORITY_L = "L"; public static final String PRIORITY_M = "M"; public static final String PRIORITY_H = "H"; * 短信模板方式 0 .登录模板、1.注册模板、2.忘记密码模板 public static final String SMS_TPL_TYPE_0 = "0"; public static final String SMS_TPL_TYPE_1 = "1"; public static final String SMS_TPL_TYPE_2 = "2"; * 状态(0无效1有效) public static final String STATUS_0 = "0"; public static final String STATUS_1 = "1"; * 同步工作流引擎1同步0不同步 public static final String ACT_SYNC_0 = "0"; public static final String ACT_SYNC_1 = "1"; * 消息类型1:通知公告2:系统消息 public static final String MSG_CATEGORY_1 = "1"; public static final String MSG_CATEGORY_2 = "2"; * 是否配置菜单的数据权限 1是0否 public static final Integer RULE_FLAG_0 = 0; public static final Integer RULE_FLAG_1 = 1; * 用户状态 0冻结 1正常 2待定 public static final Integer USER_FREEZE = 0; public static final Integer USER_NORMAL = 1; * 用户删除标志位 0未删 1已删 public static final Integer USER_DELETE_NO=0; public static final Integer USER_DELETE_YES=1; /**字典翻译文本后缀*/ public static final String DICT_TEXT_SUFFIX = "_dictText"; public static final String ITEM_DISPLAY = "_display"; * 表单设计器主表类型 public static final Integer DESIGN_FORM_TYPE_MAIN = 1; * 表单设计器子表表类型 public static final Integer DESIGN_FORM_TYPE_SUB = 2; * 表单设计器URL授权通过 public static final Integer DESIGN_FORM_URL_STATUS_PASSED = 1; * 表单设计器URL授权未通过 public static final Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2; * 表单设计器新增 Flag public static final String DESIGN_FORM_URL_TYPE_ADD = "add"; * 表单设计器修改 Flag public static final String DESIGN_FORM_URL_TYPE_EDIT = "edit"; * 表单设计器详情 Flag public static final String DESIGN_FORM_URL_TYPE_DETAIL = "detail"; * 表单设计器复用数据 Flag public static final String DESIGN_FORM_URL_TYPE_REUSE = "reuse"; * 表单设计器编辑 Flag (已弃用) public static final String DESIGN_FORM_URL_TYPE_VIEW = "view"; * online参数值设置(是:Y, 否:N) public static final String ONLINE_PARAM_VAL_IS_TURE = "Y"; public static final String ONLINE_PARAM_VAL_IS_FALSE = "N"; * 文件上传类型(本地:local,Minio:minio,阿里云:alioss) public static final String UPLOAD_TYPE_LOCAL = "local"; public static final String UPLOAD_TYPE_MINIO = "minio"; public static final String UPLOAD_TYPE_OSS = "alioss"; * 员工身份 (1:普通员工 2:上级) public static final Integer USER_IDENTITY_1 = 1; public static final Integer USER_IDENTITY_2 = 2; * 日期格式 public static final String TIME_FORMAT_YMD = "yyyy-MM-dd"; public static final String TIME_FORMAT_YMDHMS = "yyyy-MM-dd HH:mm:ss"; public static final String TIME_FORMAT_YMDHMSSZ = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; }
2、第二步、编写一个切面
@Aspect 表示这是一个切面
@Component 告诉spring 这是一个bean ,注入
@annotation 获取定义的注解
@Pointcut 切点,
@Pointcut("@annotation(xx.AutoLog)") 表示,使用了这个注解的,就是切入点
@Around的作用
既可以在目标方法之前织入增强动作,也可以在执行目标方法之后织入增强动作;
可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标目标方法的执行;
可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值; 当需要改变目标方法的返回值时,只能使用Around方法;
虽然Around功能强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before、AfterReturing增强方法就可以解决的事情,就没有必要使用Around增强处理了。
ProceedingJoinPoint 环绕通知,主要作用找到程序执行中的可识别的点,当aop的切入点
- 环绕通知 ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行,这也是环绕通知和前置、后置通知方法的一个最大区别。
- 简单理解,环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的.
/** * 系统日志,切面处理类 * */ @Aspect @Component public class AutoLogAspect { @Autowired private ISysLogService sysLogService; @Pointcut("@annotation(xx.AutoLog)") public void logPointCut() { } @Around("logPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { long beginTime = System.currentTimeMillis(); //执行方法 Object result = point.proceed(); //执行时长(毫秒) long time = System.currentTimeMillis() - beginTime; //保存日志 saveSysLog(point, time); return result; } private void saveSysLog(ProceedingJoinPoint joinPoint, long time) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); SysLog sysLog = new SysLog(); AutoLog syslog = method.getAnnotation(AutoLog.class); if (syslog != null) { //注解上的描述,操作日志内容 sysLog.setLogContent(syslog.value()); sysLog.setLogType(syslog.logType()); } //请求的方法名 String className = joinPoint.getTarget().getClass().getName(); String methodName = signature.getName(); sysLog.setMethod(className + "." + methodName + "()"); //设置操作类型 if (sysLog.getLogType() == CommonConstant.LOG_TYPE_2) { sysLog.setOperateType(getOperateType(methodName, syslog.operateType())); } //请求的参数 Object[] args = joinPoint.getArgs(); try { String params = JSONObject.toJSONString(args); sysLog.setRequestParam(params); } catch (Exception e) { } try { //获取request HttpServletRequest request = SpringContextUtils.getHttpServletRequest(); //设置IP地址 sysLog.setIp(IPUtils.getIpAddr(request)); } catch (Exception e) { } //获取登录用户信息 LoginUser sysUser = null; try { sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); } catch (Exception e) { } if (sysUser != null) { sysLog.setUserId(sysUser.getId()); sysLog.setUserName(sysUser.getUserName()); sysLog.setRealName(sysUser.getRealName()); sysLog.setOrgId(sysUser.getNowOrgId()); sysLog.setOrgName(sysUser.getNowOrgName()); } //耗时 sysLog.setCostTime(time); sysLog.setCreateTime(new Date()); //保存系统日志 sysLogService.save(sysLog); } /** * 获取操作类型 */ private int getOperateType(String methodName, int operateType) { if (operateType > 0) { return operateType; } if (methodName.startsWith("list")) { return CommonConstant.OPERATE_TYPE_LT2_1; } if (methodName.startsWith("add")) { return CommonConstant.OPERATE_TYPE_LT2_2; } if (methodName.startsWith("edit")) { return CommonConstant.OPERATE_TYPE_LT2_3; } if (methodName.startsWith("delete")) { return CommonConstant.OPERATE_TYPE_LT2_4; } if (methodName.startsWith("import")) { return CommonConstant.OPERATE_TYPE_LT2_5; } if (methodName.startsWith("export")) { return CommonConstant.OPERATE_TYPE_LT2_6; } return CommonConstant.OPERATE_TYPE_LT2_1; } @AfterThrowing(pointcut = "logPointCut()", throwing = "ex") public void afterThrowing(JoinPoint joinPoint, Throwable ex) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); SysLog sysLog = new SysLog(); StackTraceElement[] stackTraceElements = ex.getStackTrace(); String rootExceptionName = ex.getClass().getName(); StringBuilder resultContent = new StringBuilder("异常类:" + rootExceptionName); int count = 0; int maxTrace = 3; for (StackTraceElement stackTraceElement : stackTraceElements) { if (stackTraceElement.getClassName().contains("com.lingxu") && count < maxTrace) { resultContent.append("\n出现于").append(stackTraceElement.getClassName()) .append("类中的").append(stackTraceElement.getMethodName()) .append("方法中 位于该类文件的第").append(stackTraceElement.getLineNumber()) .append("行)"); count++; if (count == maxTrace) { break; } } } sysLog.setExceptionContent(resultContent.toString()); AutoLog syslog = method.getAnnotation(AutoLog.class); if (syslog != null) { //注解上的描述,操作日志内容 sysLog.setLogContent(syslog.value() + "出现异常"); sysLog.setLogType(CommonConstant.LOG_TYPE_4); } //请求的方法名 String className = joinPoint.getTarget().getClass().getName(); String methodName = signature.getName(); sysLog.setMethod(className + "." + methodName + "()"); //设置操作类型 sysLog.setOperateType(CommonConstant.OPERATE_TYPE_LT4_1); //请求的参数 Object[] args = joinPoint.getArgs(); try { String params = JSONObject.toJSONString(args); sysLog.setRequestParam(params); } catch (Exception e) { } try { //获取request HttpServletRequest request = SpringContextUtils.getHttpServletRequest(); //设置IP地址 sysLog.setIp(IPUtils.getIpAddr(request)); } catch (Exception e) { } try { //获取登录用户信息 LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); if (sysUser != null) { sysLog.setUserId(sysUser.getId()); sysLog.setUserName(sysUser.getUserName()); sysLog.setRealName(sysUser.getRealName()); sysLog.setOrgId(sysUser.getNowOrgId()); sysLog.setOrgName(sysUser.getNowOrgName()); } } catch (Exception e) { } //保存系统日志 sysLogService.save(sysLog); } }
3、使用自定义注解
可以在controller或者实现类上进行注解的加入
@AutoLog(value = "sss",logType = CommonConstant.LOG_TYPE_3, operateType = 2) @ApiOperation(value="记录查询日志-分页列表查询", notes="记录查询日志-分页列表查询") @PostMapping(value = "/queryPage") public Result> queryPage(@RequestBody SysSelectLog sysSelectLog){ Pagepage = new Page<>(sysSelectLog.getPageNo(),sysSelectLog.getPageSize()); IPage sysSelectLogIPage = sysSelectLogService.queryPage(page,sysSelectLog); return Result.ok(sysSelectLogIPage); }
到此这篇关于使用Aop的方式实现自动日志记录的文章就介绍到这了,更多相关Aop自动日志记录内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!