zoukankan      html  css  js  c++  java
  • 【Redis】redis异步消息队列+Spring自定义注解+AOP方式实现系统日志持久化

    说明:

      SSM项目中的每一个请求都需要进行日志记录操作。一般操作做的思路是:使用springAOP思想,对指定的方法进行拦截。拼装日志信息实体,然后持久化到数据库中。可是仔细想一下会发现:每次的客户端的每一次请求,服务器都会处理两件事情。一个是正常的业务操作;另一个就是我们额外要做的日志数据记录。这样的话,每次请求的“效率”就变得收到影响了,换句话说就是“耦合”了。明明一个请求是干一件特定的事情,你却又给我加上一部分东西。而且这一次请求是必须在额外做的事情做完才能返回。面向切面 编程就是为了“解耦”的。所以想到了日志持久化这个动作使用异步处理方式,不当误真正的请求效率。(这一段写的可能有点luan,大家先将就着看)。

    分析:

      ① 异步消息队列中有【消费者】和【生产者两个角色】。生产者负责产生消息,并放入队列中。消费者负责监听队列,一旦队列中有新的消息了,取出后根据消息的类型选择对应的业务处理操作。

      ② 消费者在这里是在系统启动的时候,启动一个线程,对redis指定的key进行监听。使用redis的指令brpop阻塞指令进行监听对应的list。

    环境:

      jdk1.8、maven、idea、jedis3.2、mysql数据库

    代码:

      自定义注解:

    /**
     * 自定义系统日志注解
     * @author 魏正迪
     */
    @Documented
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SysLog {
        /**
         * 操作描述
         * @return
         */
        String value() default "";
    
        /**
         * 日志类型
         * @return
         */
        short type();
    }

      AOP切面

    /**
     * @author wzd
     * @data 2018/03/06
     * 系统日志切面
     */
    @Component
    @Aspect
    public class LogAspect {
    
        @Autowired
        private ILogService logService;
        @Autowired
        private JedisClientPool jedisClientPool;
        /**
         * 自动注册当前线程的request对象
         */
        @Autowired
        private HttpServletRequest request;
    
        /**
         * 日志的切点
         */
        @Pointcut("@annotation(top.oldwei.common.annotation.SysLog)")
        public void logPoint(){ }
    
        /**
         * 日志采用环绕通知来进行处理
         * @param point
         * @return
         * @throws Throwable
         */
        @Around("logPoint()")
        public Object around(ProceedingJoinPoint point)throws Throwable{
            // 执行方法之前
            UserEntity currentUser = ShiroUtils.getUserEntity();
            long start = SystemClock.now();
            Object result = point.proceed();
            long end = SystemClock.now();
            saveSysLog(point,end-start,currentUser);
            return result;
        }
    
        /**
         * 保存日志操作
         * @param point
         * @param time
         * @param userEntity
         */
        private void saveSysLog(ProceedingJoinPoint point,long time ,UserEntity userEntity){
            try{
                MethodSignature methodSignature = (MethodSignature) point.getSignature();
                Method method = methodSignature.getMethod();
                LogEntity logEntity = new LogEntity();
                logEntity.setId(IdWorker.getId());
                SysLog syslog = method.getAnnotation(SysLog.class);
                if(StringUtils.checkValNotNull(syslog)){
                    // 注解的value
                    logEntity.setOperation(syslog.value());
                    // 注解的type
                    logEntity.setType(syslog.type());
                }
                // 调用的方法
                logEntity.setMethod(point.getTarget().getClass().getName()+"."+method.getName()+"()");
                logEntity.setIp(IpUtils.getIpAddr(request));
                logEntity.setTime(time);
                // 请求参数
                Object [] args = point.getArgs();
                try{
                    logEntity.setParams(JSON.toJSON(args[0]).toString());
                }catch (Exception e){}
                if(StringUtils.checkValNotNull(userEntity)){
                    // 创建人
                    logEntity.setCreateByCode(userEntity.getUserCode());
                    logEntity.setCreateByName(userEntity.getUserName());
                }else{
                    // 登录操作时,方法执行后才能获取用户信息
                    userEntity = ShiroUtils.getUserEntity();
                    if(StringUtils.checkValNotNull(userEntity)){
                        logEntity.setCreateByCode(userEntity.getUserCode());
                        logEntity.setCreateByName(userEntity.getUserName());
                    }else{
                        logEntity.setCreateByCode("");
                        logEntity.setCreateByName("");
                    }
                }
                logEntity.setCreateDate(new Date());
                // 使用redis异步队列方式进行保存日志
                //logService.save(logEntity);
                TaskEntity taskEntity = new TaskEntity();
                taskEntity.setTaskType(TaskType.LOG_TASK);
                taskEntity.setData(JSON.toJSONString(logEntity));
                jedisClientPool.lpush(JedisConstants.AYSC_TASK_KEY,JSON.toJSONString(taskEntity));
                taskEntity.setTaskType(TaskType.MAIL_TASK);
                jedisClientPool.lpush(JedisConstants.AYSC_TASK_KEY,JSON.toJSONString(taskEntity));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

      消息实体类

    /**
     * 任务实体类
     * @author wzd
     * @date 2018/04/01
     */
    public class TaskEntity implements Serializable {
        /**
         * 任务的唯一性编码
         */
        private Long id;
        /**
         * 任务类型,通过类型找到对应任务处理器进行处理
         */
        private TaskType taskType;
        /**
         * 需要传输的数据 json格式的
         */
        private String data;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public TaskType getTaskType() {
            return taskType;
        }
    
        public void setTaskType(TaskType taskType) {
            this.taskType = taskType;
        }
    
        public String getData() {
            return data;
        }
    
        public void setData(String data) {
            this.data = data;
        }
    }

      消费者:启动注册消费者任务处理器多个。监听队列,取出任务根据任务类型选择对应的 任务处理器进行相应处理。

    /**
     * redis 队列消费者
     * 容器启动时加载并启动相应的线程,进行阻塞读取redis
     * 对应的任务队列。根据任务的类型选择对应的任务处理器进行处理。
     * @author wzd
     * @data 2018/04/01
     */
    @Component
    public class TaskConstomer implements InitializingBean, ApplicationContextAware {
        /**
         * spring上下文
         */
        private ApplicationContext applicationContext;
        /**
         * 加载所有的任务处理器
         */
        private Map<TaskType, List<TaskHandler>> config = new HashMap<>();
        /**
         * redis操作
         */
        @Autowired
        private JedisClientPool jedisClientPool;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            // 获取系统所有实现TaskHandler的任务处理器
            Map<String,TaskHandler> handlers = applicationContext.getBeansOfType(TaskHandler.class);
            if(StringUtils.checkValNotNull(handlers)){
                for(Map.Entry<String,TaskHandler> entry:handlers.entrySet()){
                    List<TaskType> supportTaskTypes = entry.getValue().getTaskType();
                    for(TaskType taskType:supportTaskTypes){
                        if(!config.containsKey(taskType)){
                            config.put(taskType,new ArrayList<TaskHandler>());
                        }
                        config.get(taskType).add(entry.getValue());
                    }
                }
            }
            // 启动线程
            // 构建线程池 ExecutorService executorService = Executors.newCachedThreadPool();
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        List<String> task = jedisClientPool.brpop(10, JedisConstants.AYSC_TASK_KEY);
                        if(StringUtils.checkValNotNull(task) && task.size()>1 ){
                            TaskEntity entity = JSON.parseObject(task.get(1),TaskEntity.class);
                            if(config.containsKey(entity.getTaskType())){
                                for(TaskHandler handler:config.get(entity.getTaskType())){
                                    handler.doTask(entity);
                                }
                            }
                        }
                    }
                }
            });
            thread.start();
    
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }

      任务处理器接口:

    /**
     * @author wzd
     * 异步任务通用接口
     */
    public interface TaskHandler {
        /**
         * 执行任务
         * @param taskEntity
         */
        void doTask(TaskEntity taskEntity);
    
        /**
         * 任务类型
         *
         * @return
         */
        default List<TaskType> getTaskType(){
            return new ArrayList<>();
        }
    }

      日志任务

    /**
     * 日志处理任务
     * @author wzd
     */
    @Component
    public class LogTaskHandler implements TaskHandler {
    
        @Autowired
        private ILogService logService;
    
        @Override
        public void doTask(TaskEntity taskEntity) {
            try{
                LogEntity logEntity = JSON.parseObject(taskEntity.getData(),LogEntity.class);
                logService.save(logEntity);
            }catch (Exception e){}
        }
    
        @Override
        public List<TaskType> getTaskType() {
            return Arrays.asList(TaskType.LOG_TASK);
        }
    }

      发送邮件任务

    /**
     * @author wzd
     * 发送短信的异步队列任务
     */
    @Component
    public class MailTaskHandler implements TaskHandler{
    
        @Autowired
        private MailMessageHandler mailMessageHandler;
    
        @Override
        public void doTask(TaskEntity taskEntity) {
            // 进行发送短信的业务逻辑
            try{
                mailMessageHandler.doSend(null);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
        @Override
        public List<TaskType> getTaskType() {
            return Arrays.asList(TaskType.MAIL_TASK);
        }
    }

    、、、、、

    其他的任务实现接口即可。

     特殊说明:以上代码需要重构的地方很多,仅给大家参考思路。也欢迎指正

  • 相关阅读:
    mysql InnoDB 索引小记
    Linux shell 脚本小记
    Java中Integer的源码学习
    nginx小记
    位运算小结
    Redis小结
    CSS中nth-child和nth-of-type的简单使用
    Linux awk小记
    mysql小记--基础知识
    让44.1版本的sketch打开更高版本的sketch文件
  • 原文地址:https://www.cnblogs.com/oldwei/p/10207778.html
Copyright © 2011-2022 走看看