说明:

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

、、、、、

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

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

【Redis】redis异步消息队列+Spring自定义注解+AOP方式实现系统日志持久化的更多相关文章

  1. SpringCloud微服务实战——搭建企业级开发框架(三十九):使用Redis分布式锁(Redisson)+自定义注解+AOP实现微服务重复请求控制

      通常我们可以在前端通过防抖和节流来解决短时间内请求重复提交的问题,如果因网络问题.Nginx重试机制.微服务Feign重试机制或者用户故意绕过前端防抖和节流设置,直接频繁发起请求,都会导致系统防重 ...

  2. ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存

    基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...

  3. Spring Cloud(7):事件驱动(Stream)分布式缓存(Redis)及消息队列(Kafka)

    分布式缓存(Redis)及消息队列(Kafka) 设想一种情况,服务A频繁的调用服务B的数据,但是服务B的数据更新的并不频繁. 实际上,这种情况并不少见,大多数情况,用户的操作更多的是查询.如果我们缓 ...

  4. php和redis怎么实现消息队列

    把瞬间服务器的请求处理换成异步处理,缓解服务器的压力,实现数据顺序排列获取.本文主要和大家分享php和redis如何实现消息队列,希望能帮助到大家. redis实现消息队列步骤如下: 1).redis ...

  5. Redis 学习笔记(六)Redis 如何实现消息队列

    一.消息队列 消息队列(Messeage Queue,MQ)是在分布式系统架构中常用的一种中间件技术,从字面表述看,是一个存储消息的队列,所以它一般用于给 MQ 中间的两个组件提供通信服务. 1.1 ...

  6. Redis+php-resque实现消息队列

      服务器硬件配置 Dell PowerEdge R310英特尔单路机架式服务器 Intel Xeon Processor X3430 2.4GHz, 8MB Cache 8GB内存(2 x 4GB) ...

  7. 如何使用NODEJS+REDIS开发一个消息队列

    作者: RobanLee 原创文章,转载请注明: 萝卜李 http://www.robanlee.com MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应 ...

  8. Delayer 基于 Redis 的延迟消息队列中间件

    Delayer 基于 Redis 的延迟消息队列中间件,采用 Golang 开发,支持 PHP.Golang 等多种语言客户端. 参考 有赞延迟队列设计 中的部分设计,优化后实现. 项目链接:http ...

  9. 八.利用springAMQP实现异步消息队列的日志管理

    经过前段时间的学习和铺垫,已经对spring amqp有了大概的了解.俗话说学以致用,今天就利用springAMQP来完成一个日志管理模块.大概的需求是这样的:系统中有很多地方需要记录操作日志,比如登 ...

随机推荐

  1. div+伪元素实现太极图

    需求:使用div和伪元素实现阴阳太极图 图例: 代码: <html> <head> <title>太极图</title> <style type= ...

  2. 关于github的使用学习心得

    先写先介绍一下如何用github上创建一个项目吧. 用户登录后的界面如上所示.右下角是我们已经建好的库.点击其中任何一个就可以查看相应的库了.如果要新建一个项目的话,就点击Start a projec ...

  3. js toFixed

    为什么(2.55).toFixed(1)等于2.5? 上次遇到了一个奇怪的问题:JS的(2.55).toFixed(1)输出是2.5,而不是四舍五入的2.6,这是为什么呢? 进一步观察: 发现,并不是 ...

  4. 2、MyBatis教程之第一个MyBatis程序

    3.MyBatis第一个程序 1.搭建实验数据库 CREATE DATABASE `mybatis`; USE `mybatis`; DROP TABLE IF EXISTS `user`; CREA ...

  5. Spring笔记(五)

    Spring 事务操作 一.事务(概念) 1. 什么是事务 事务是数据库的最基本单元,逻辑上的一组操作,要么都成功,如果有一个失败,那么所有的操作都失败 典型场景: lucy转账100元给mary l ...

  6. 使用python的虚拟环境virtualenv

    技术背景 在前面几篇博客中我们介绍了容器的使用(博客1.博客2.博客3.博客4.博客5),容器是一种系统级的隔离方案,更多的强调资源上的隔离.而这里我们要介绍的python的虚拟环境,更加强调的是依赖 ...

  7. 基于Hive进行数仓建设的资源元数据信息统计:Hive篇

    在数据仓库建设中,元数据管理是非常重要的环节之一.根据Kimball的数据仓库理论,可以将元数据分为这三类: 技术元数据,如表的存储结构结构.文件的路径 业务元数据,如血缘关系.业务的归属 过程元数据 ...

  8. kubernetes中有状态应用的优雅缩容

    将有状态的应用程序部署到Kubernetes是棘手的. StatefulSet使它变得容易得多,但是它们仍然不能解决所有问题.最大的挑战之一是如何缩小StatefulSet而不将数据留在断开连接的Pe ...

  9. HTTPS证书通过cert-manager自动获取,部署,续期

    HTTP-01验证和DNS-01验证 使用cert-manager给阿里云的DNS域名授权SSL证书 第一步:安装cert-manager 配置 CRD kubectl apply -f https: ...

  10. 快速了解 JavaScript ES2019 的五个新增特性

    ES2019 规范是对 JavaScript 的一个较小的补充,但它仍然带来了一些有用的功能.本文将向你展示五个 ES2019 新增的特性,这些特性或许可以让你的编程轻松一点.这些特性包括 trimS ...