springboot实现自定义注解加日志切面记录

使用自定义注解,日志切面(前置通知 后置通知,获取controller的error执行结果),多线程

首先写个自定义注解

/**
 * 系统日志自定义注解
 *
 * @author weijianxing
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})//作用于参数或方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {

    /**
     * 日志名称
     *
     * @return
     */
    String description() default "";

}

然后写AOP切面

@Aspect
@Component
public class SysLogAspect {
	 /**
     * Controller层切点,注解方式
     */
    // @Pointcut("execution(* *..controller..*Controller*.*(..))")
    @Pointcut("@annotation(com.face.ele.api.common.annotation.SystemLog)")
    public void controllerAspect() {

    }

    /**
     * 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间
     *
     * @param joinPoint 切点
     * @throws InterruptedException
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) throws InterruptedException {

        //线程绑定变量(该数据只有当前请求的线程可见)
        Date beginTime = new Date();
        beginTimeThreadLocal.set(beginTime);
    }

    /**
     * 后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作
     * @author weijianxing
     * @param joinPoint 切点
     * @param Object 方法执行的结果
     */
    @AfterReturning(returning="obj",pointcut ="@annotation(com.face.ele.api.common.annotation.SystemLog)")
    public void after(JoinPoint joinPoint,Object obj) {
        System.out.println("获取目标返回值" + obj);
        try {
        	//这里获取切点的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            //判断这个日志是否记录
            boolean flag = true;
            SystemLog perms = method.getAnnotation(SystemLog.class);

            //这里要通过日志配置的权限码和每个访问的注解权限码匹配
            QueryWrapper<OperationLogConfigEntity> codeWrapper = new QueryWrapper<>();
            codeWrapper.eq("LOG_CONFIG_CODE", perms.type());
            OperationLogConfigEntity logConfig = configService.getOne(codeWrapper);
            if (perms != null) {
                if (logConfig.getLogRecord() == 0) {
                    flag = false;
                }
            }
            //下面是获取保存日志的各个参数
            Map<String, String[]> logParams = request.getParameterMap();
            String principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
            //用户
            String userId = null;
            if (!"anonymousUser".equals(principal)) {
                UserDetails user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
                SysUser sysUser = userService.findByUsername(user.getUsername());
                userId = sysUser.getId();
            }
            //获取注解中对方法的描述信息
            Map params = getControllerMethodInfo(joinPoint);
            //日志类型
            int type = MapUtil.getInt(params, "type");
            OperationLogEntity logEntity = new OperationLogEntity();
            //请求开始时间
            long beginTime = beginTimeThreadLocal.get().getTime();
            long endTime = System.currentTimeMillis();
            //请求耗时
            Long logElapsedTime = endTime - beginTime;
            //这里是第一种情况保存,默认方法前加了需要添加注释的方法,并且日志记录中配置了需要记录日志
            this.saveAllOperateByAnnotation(flag,perms,logEntity,obj,logConfig,method,joinPoint,type,
                    params,userId,logParams, logElapsedTime);
        } catch (Exception e) {
            log.error("AOP后置通知异常", e);
        }
    }


	/**
     * 获取所有的参数
     * @author weijianxing
     */
    public void saveAllOperateByAnnotation(boolean flag,SystemLog sysLog,OperationLogEntity logEntity,Object obj,
                                           OperationLogConfigEntity logConfig,Method method,JoinPoint joinPoint,int type,
                                           Map params,String userId,Map<String, String[]> logParams,Long logElapsedTime ){
        //请求用户
        if (sysLog != null) {
            logEntity.setLogContent(sysLog.description());
        } else {
            //没有加SystemLog的,表示其他默认系统操作的
            //默认没有加该注解,就用API的内容描述(其他每个类就不需要都去添加该注解)
            ApiOperation otherLog = method.getAnnotation(ApiOperation.class);
            logEntity.setLogContent(otherLog.value());
        }
        //判断日志上面有标注了类型的,没有就表示默认的都归为 普通的系统操作
        logEntity.setLogType(type);
        //这里获取每个方法的参数
        Object[] args = joinPoint.getArgs();
        String param = new Gson().toJson(args);
        if (param.length() < 5000)
            logEntity.setLogParams(param);
        else {
            param = new Gson().toJson(args[1]);
            logEntity.setLogParams(param);
        }

        logEntity.setOperationTime(new Date());
        logEntity.setOperationUser(userId);
        logEntity.setLogUrl(request.getRequestURI());
        logEntity.setCostTime(logElapsedTime.intValue());
        log.info("本次请求耗时:{}毫秒;请求参数{},路径{}", logElapsedTime, JSONUtil.toJsonStr(logParams), request.getRequestURI());
        //先调用含有错误日志的,如果没有错误的就直接调用 默认添加日志的
        saveErrorOperate(flag,logEntity,obj,logConfig,type,userId,logElapsedTime);
    }

    /**
     * 这里保存第二次含有错误日志信息
     * 获取  controller中执行 的 error的信息,可以获取错误的信息 进行保存
     * @author weijianxing
     */
    public void saveErrorOperate(boolean flag,OperationLogEntity logEntity,Object obj,OperationLogConfigEntity logConfig,int type,String userId, Long logElapsedTime){
        //这里是第二种情况保存,controller中error的信息,可以获取错误的信息 进行保存
        String jsonStr = JSON.toJSONString(obj);
        JSONObject resultJson = JSON.parseObject(jsonStr);
        //只记录错误信息,保存到数据库日志中
        if (resultJson.get("success") != null) {
            if (resultJson.get("success").equals(false)) {
                //获取当前操作的日志等级
                int level=0;
                if(logConfig!=null){
                    level=logConfig.getLogLevel();
                }
                //获取信息保存登录错误信息
                OperationLogEntity operateLog=new OperationLogEntity(
                        type,userId,new Date(),new Gson().toJson(request.getParameterMap()),
                        resultJson.get("message").toString(),logElapsedTime.intValue(),
                        request.getRequestURI(),level,1);
                operationLogService.addOperateLog(operateLog);
            }else{
                if (flag) {
                    //调用线程保存
                    ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(logEntity, operationLogService));
                }
            }
        }else{
            if (flag) {
                //调用线程保存
                ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(logEntity, operationLogService));
            }
        }
    }

    /**
     * 保存日志至数据库
     */
    private static class SaveSystemLogThread implements Runnable {

        private OperationLogEntity log;
        private OperationLogService logService;

        public SaveSystemLogThread(OperationLogEntity esLog, OperationLogService logService) {
            this.log = esLog;
            this.logService = logService;
        }

        @Override
        public void run() {

            logService.save(log);
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    public static Map<String, Object> getControllerMethodInfo(JoinPoint joinPoint) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>(16);
        //获取目标类名
        String targetName = joinPoint.getTarget().getClass().getName();
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //获取相关参数
        Object[] arguments = joinPoint.getArgs();
        //生成类对象
        Class targetClass = Class.forName(targetName);
        //获取该类中的方法
        Method[] methods = targetClass.getMethods();

        String description = "";
        Integer type = null;
        Integer subtype = null;
        for (Method method : methods) {
            if (!method.getName().equals(methodName)) {
                continue;
            }
            Class[] clazzs = method.getParameterTypes();
            if (clazzs.length != arguments.length) {
                //比较方法中参数个数与从切点中获取的参数个数是否相同,原因是方法可以重载
                continue;
            }
            description = method.getAnnotation(SystemLog.class).description();
            type = method.getAnnotation(SystemLog.class).type().getValue();
            subtype = method.getAnnotation(SystemLog.class).logSubType().getValue();
            map.put("description", description);
            map.put("type", type);
            map.put("subtype", subtype);
            
        }
        return map;
    }



数据库

CREATE TABLE `ele_operation_log_config` (
  `log_config_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `log_config_name` varchar(50) DEFAULT NULL COMMENT '日志配置名称',
  `log_config_code` varchar(50) DEFAULT NULL COMMENT '日志配置权限码',
  `sort` int(11) DEFAULT '0' COMMENT '排序',
  `log_record` int(2) DEFAULT NULL COMMENT '是否记录日志(0:不记录,1:记录)',
  `remark` varchar(50) DEFAULT NULL COMMENT '描述',
  `log_level` int(2) DEFAULT '0' COMMENT '日志等级(0:普通,1:一般,2:重要)',
  PRIMARY KEY (`log_config_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

CREATE TABLE `ele_operation_log` (
  `operation_log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '操作日志表id',
  `log_type` int(1) DEFAULT NULL COMMENT ' 1、系统操作, 2,用户绑定角色操作, 3 用户管理,4 登录等操作',
  `log_sub_type` int(1) DEFAULT NULL COMMENT '日志类型,1增加、2修改、3删除',
  `operation_user` varchar(33) DEFAULT NULL COMMENT '操作人',
  `operation_time` datetime DEFAULT NULL COMMENT '操作时间',
  `log_params` varchar(5000) DEFAULT NULL COMMENT '操作关键参数',
  `log_content` varchar(100) DEFAULT NULL COMMENT '操作内容',
  `log_url` varchar(255) DEFAULT NULL,
  `log_path` varchar(255) DEFAULT NULL COMMENT '操作路径',
  `cost_time` int(10) DEFAULT NULL COMMENT '耗时',
  `log_level` int(2) DEFAULT '0' COMMENT '日志等级(0:正常1:异常错误 2:越权访问 3:)',
  `log_result` int(2) DEFAULT '0' COMMENT '操作结果 0:成功,1:失败',
  PRIMARY KEY (`operation_log_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1328185403790069763 DEFAULT CHARSET=utf8 COMMENT='操作日志表';
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值