自定义注解
标记环绕通知方法体
@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){
// ...业务调用
}