【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);
    }
}

、、、、、

其他的任務實現接口即可。

 特殊說明:以上代碼需要重構的地方很多,僅給大家參考思路。也歡迎指正


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com