AOP记录业务在分布式服务中的执行时间及异常信息

自定义注解

标记环绕通知方法体

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface XxxLogAOP {
  // 流程名称
  String value() default "";
}

data层

  • 将流程的信息记录在相应日志表的一个字段(process)中,采用json对象数组的方式进行保存,一个流程一个对象
  • 日志表(OrderLogPO)设计:id - 主键, orderId - 订单号, process - 执行记录, exceptionMessage - 异常信息, createTime - 创建时间, updateTime - 更新时间, version - 乐观锁
  • process - json: [{执行名1,方法名,开始时间,结束时间},{执行名3,方法名,开始时间,结束时间},{执行名2,方法名,开始时间,结束时间}]

process信息类

  /** 流程名称 */
  @JSONField(ordinal = 0)
  private String name;

  /** 执行方法 */
  @JSONField(ordinal = 1)
  private String method;

  /** 开始时间 */
  @JSONField(ordinal = 2)
  private String startTime;

  /** 结束时间 */
  @JSONField(ordinal = 3)
  private String endTime;

编写切面类

在第一个调用的服务切面类中先去new一个新记录,后续服务均去做更新操作

@Component
@Aspect
@Slf4j
public class OrderLogAspect {

  @Autowired private OrderLogMapper orderLogMapper;

  @Around("@annotation(logAop)")
  public Object execute(ProceedingJoinPoint joinPoint, OrderLogAOP logAop) {
    Object proceed = null;
    // 获取执行方法的参数
    Object[] args = joinPoint.getArgs();
    // 获取执行方法的方法名,记录用
    String method = joinPoint.getSignature().getName();
    String orderId = ((String) args[0]).getOrderId();
    // 生成订单相应日志表记录 - 后续调用服务不需要生成,只需要将获取orderId改为获取在第一个服务中生成的id即可
    OrderLogPO po = new OrderLogPO();
    if (StringUtil.isNotEmpty(orderId)) {
      Date now = new Date();
      po.setOrderId(orderId);
      po.setProcess(JsonUtil.toJsonFromObject(new ArrayList<>(0)));
      po.setCreateTime(now);
      po.setUpdateTime(now);
      po.setVersion(1);
      orderLogMapper.insert(po);
      // 将生成的记录id传递到后续调用服务中
      args[1] = po.getId();
    }
    // 保存流程处理时间点
    OrderProcessDTO process = new OrderProcessDTO();
    process.setName(annotation.value());
    process.setMethod(method);
    process.setStartTime(DateUtil.toStrFromDate(new Date(), DateUtil.YYYY_MM_DD_HMS));

    String eMessage = null;
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    try {
      // 将我们增加的参数重新传入方法并执行
      proceed = joinPoint.proceed(args);
      process.setEndTime(DateUtil.toStrFromDate(new Date(), DateUtil.YYYY_MM_DD_HMS));
    } catch (Throwable e) {
      log.error("订单执行步骤[{}]异常:", process.getName(), e);
      // 记录异常信息
      e.printStackTrace(pw);
      eMessage = sw.toString();
    } finally {
      pw.close();
    }

    // 更新订单相应日志表记录,(乐观锁+自旋锁)防止执行速度导致前序流程记录覆盖后序流程记录,json丢失数据
    if (StringUtil.isNotEmpty(orderId)) {
      // 自旋锁
      int update = 0;
      do {
        po = orderLogMapper.selectById(po.getId());
        po.setVersion(po.getVersion() + 1);
        if (null != eMessage) {
          po.setExceptionMessage(eMessage);
        }
        List<OrderProcessDTO> processList =
            JsonUtil.toList(po.getProcess(), OrderProcessDTO.class);
        processList.add(process);
        po.setProcess(JsonUtil.toJsonFromObject(processList));
        po.setUpdateTime(new Date());
        update =
            orderLogMapper.update(
                po,
                new LambdaUpdateWrapper<OrderLogPO>()
                    .eq(OrderLogPO::getId, po.getId())
                    .eq(OrderLogPO::getVersion, po.getVersion() - 1));
      } while (update == 0);
    }
    return proceed;
  }
}

使用

在相应方法上打上注解即可,不影响原有程序执行效率

// 这里调用者只传一个参数,id这个参数是我们的日志记录id,在AOP中获取并重新传入
@CreditLogAOP("1-WEB 点击确认订单")
@PostMapping("/xxx")
public WebResponse xxx(String orderId, Long id){
	// ...参数校验
	// ...业务调用
    return xxxService.xxx(orderId, id);
}

后续被调用的服务使用一致

// 日志id在map中传递
@CreditLogAOP("2-WMS 仓库下单")
@GetMapping("/yyy")
public void yyy(Map<String, Object> map){
	// ...业务调用
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZHAIKEsir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值