java 自动补偿 方法级别的补偿

自动补偿实现

  1. 要求: 方法调用的过程中。失败的时候,系统有办法进行自动重试,重试达到一定次数后,钉钉通知开发
  2. 实现设计: 注解,反射,定时任务
    在这里插入图片描述

表的设计

– 补偿表

CREATE TABLE `a_compensation` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '数据库主键',
  `busKey` VARCHAR(255) NOT NULL COMMENT '业务主键',
  `type` VARCHAR(20) NOT NULL DEFAULT 'auto' COMMENT '处理方式:自动(auto),manual(人工介入)',
  `dataStatus` TINYTEXT NOT NULL COMMENT '数据状态:0待处理,1:成功,2:失败',
  `className` VARCHAR(200) NOT NULL COMMENT '完整类名',
  `beanName` VARCHAR(100) NOT NULL COMMENT '类名',
  `methodName` VARCHAR(100) NOT NULL COMMENT '方法名',
  `reqArgsType` VARCHAR(500) NOT NULL COMMENT '方法入参类型',
  `reqArgs` TEXT NOT NULL COMMENT '方法入参参数',
  `reqArgsTypeReal` VARCHAR(500) DEFAULT NULL COMMENT '实际参数类型',
  `retryCount` BIGINT(10) NOT NULL COMMENT '重试次数',
  `resultMsg` VARCHAR(2000) DEFAULT NULL COMMENT '返回信息',
  `gmtCreate` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
  `gmtModified` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '更新时间',
  `isValid` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '是否有效',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

代码

  • 注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Compensate {
	/**
	 * 异常类型,用于补偿指定异常的数据,默认值Exception
	 */
	Class<?>[] exceptionType() default Exception.class;

}
  • 切面类
@Aspect
@Component
public class AnnotationCompensateEntry {
    private static final Logger logger = LoggerFactory.getLogger(AnnotationCompensateEntry.class);
    @Autowired
    private CompensationService compensationService;

    /**
     * 切点,通过注解的方式
     *
     * @param joinPoint 连接点
     * @param e         异常
     */
    @AfterThrowing(pointcut = "@annotation(com.base.annotation.Compensate)", throwing = "e")
    public void aopCompensate(JoinPoint joinPoint, Exception e) {
        try {
            // 1:获取需要的数据,并且进行验证
            Method targetMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
            Compensate compensate = targetMethod.getAnnotation(Compensate.class);
            Object[] args = joinPoint.getArgs();
            if (args.length == 0) {
                logger.info("无参方法无法补偿!");
                return;
            }
            //异常类型
            Class<?>[] exceptionTypes = compensate.exceptionType();
            for (Class<?> exceptionType : exceptionTypes) {
                // exceptionType 是 e.getClass() 的父类或者一样的。
                if (exceptionType.isAssignableFrom(e.getClass())) {
                    saveCompensationDO(e, joinPoint);
                }
            }
        } catch (Throwable throwable) {
            // 异常需要抛出。要不然没办法,业务代码依赖这个报错的信息会被干扰。
            logger.error(throwable.getMessage(), throwable);
            Throwables.propagateIfPossible(throwable);
        }
    }

    /**
     * 2: 保存异常数据
     * 保存的数据,应该先查询下,看数据库中是否存在这样的数据,如果不存在就进行插入。存在啥也不做。
     *
     * @param e         异常类
     * @param joinPoint 切面类
     */
    public void saveCompensationDO(Exception e, JoinPoint joinPoint) throws JsonProcessingException {
        // 1:获取切面对象的参数
        MethodInfo methodInfo = new MethodInfo(joinPoint);
        methodInfo.init();
        // 2:构建需要补偿的对象
        CompensationEntity compensationDO = buildCompensationDO(methodInfo, e);
        // 3: 上面根据的数据,得保证是满足数据的完整性。然后进行数据的储存入库。然后结束。
        List<CompensationEntity> compensationEntities = compensationService.findByProperty(CompensationEntity.class, "busKey", compensationDO.getBusKey());
        if (CollectionUtils.isEmpty(compensationEntities)) {
            compensationService.save(compensationDO);
        }
    }

    /**
     * 2-1: 数据转换
     *
     * @param methodInfo 包装过后的数据对象
     * @return 转换的补偿类
     */
    private CompensationEntity buildCompensationDO(MethodInfo methodInfo, Exception e) {
        // 1:存入通用的参数
        CompensationEntity compensationDO = new CompensationEntity();
        Annotation[] annotations= methodInfo.getTargetClass().getAnnotations();
        String springBeanName="";
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(Service.class)){
                springBeanName=methodInfo.getTargetClass().getAnnotation(Service.class).value();
            }
            if (annotation.annotationType().equals(Component.class)){
                springBeanName=methodInfo.getTargetClass().getAnnotation(Component.class).value();
            }
        }
        if (StringUtil.isEmpty(springBeanName)){
            springBeanName=methodInfo.getTargetClass().getSimpleName();
        }
        compensationDO.setBeanName(springBeanName);
        compensationDO.setClassName(methodInfo.getClassName());
        compensationDO.setMethodName(methodInfo.getMethodName());
        compensationDO.setDataStatus(StatusEnum.StatusEnum_INIT.getCode());
        compensationDO.setRetryCount(0);
        compensationDO.setResultMsg(e.getMessage());
        compensationDO.setType(HandleMethodEnum.AUTO.getDes());
        // 2:处理请求参数类型
        // 一:消息无转换,说明入参是一样的。直接存。 1: 单个->类型 2:多个->数组 3. 无参不处理
        Object[] args = methodInfo.getArgs();
        ValidateUtils.isFalse(args.length == 0, "无参方法不允许使用补偿注解!");
        if (args.length == 1) {
            //获取方法参数类型数组
            compensationDO.setReqArgs(methodInfo.getJsonArgs());
            compensationDO.setReqArgsType(args[0].getClass().getName());
        }
        if (args.length > 1) {
            Class<?>[] classesReq = methodInfo.getMethod().getParameterTypes();
            StringBuilder stringBuffer = new StringBuilder();
            for (Class<?> aClass : classesReq) {
                stringBuffer.append(aClass.getName()).append(CommonConstants.Public.COMMA);
            }
            compensationDO.setReqArgs(methodInfo.getJsonArgs());
            compensationDO.setReqArgsType(Object[].class.getName());
            compensationDO.setReqArgsTypeReal(stringBuffer.substring(0, stringBuffer.length() - 1));
        }
        compensationDO.setBusKey(compensationDO.createBusKey());
        return compensationDO;
    }
}
  • 辅助类

@Data
public class MethodInfo {
    /*** 切入点信息*/
    private JoinPoint joinPoint;
    /*** 方法签名*/
    private MethodSignature signature;
    /*** 方法信息*/
    private Method method;
    /*** 类信息*/
    private Class<?> targetClass;
    /*** 参数信息*/
    private Object[] args;
    /*** 参数信息String*/
    private String jsonArgs;
    /*** 类名*/
    private String className;
    /*** 方法名*/
    private String methodName;

    public MethodInfo(JoinPoint joinPoint) {
        this.joinPoint = joinPoint;
    }

    public void init() {
        this.signature = (MethodSignature) joinPoint.getSignature();
        this.method = signature.getMethod();
        this.methodName = method.getName();
        this.targetClass = method.getDeclaringClass();
        this.className = targetClass.getName();
        this.args = joinPoint.getArgs();
        if (args.length == 1) {
            this.jsonArgs = JsonUtil.toJsonString(args[0]);
        } else {
            this.jsonArgs = JsonUtil.toJsonString(args);
        }

    }

}

public class CompensationEntity {
    /***数据库自增id*/
    private Integer id;
    /***业务主键 根据类名,方法名,入参生成一个摘要值*/
    private String busKey;
    /***人工处理,还是自动补偿*/
    private String type;
    /***数据状态 刚入库的时候,状态为:待重试:1,重试限定次数还失败给0,重试成功后给2*/
    private String dataStatus;
    /*** 完整类名*/
    private String className;
    /*** bean名*/
    private String beanName;
    /*** 方法名*/
    private String methodName;
    /*** 方法入参类型*/
    private String reqArgsType;
    /*** 方法入参参数*/
    private String reqArgs;
    /***实际参数类型*/
    private String reqArgsTypeReal;
    /*** 重试次数*/
    private Integer retryCount;
    /*** 错误描述*/
    private String resultMsg;
    /*** 创建时间*/
    private Date gmtCreate = new Date();
    /*** 修改时间*/
    private Date gmtModified = new Date();
    /*** 是否有效*/
    private Boolean isValid = Boolean.TRUE;

    @Id
    @GeneratedValue(generator = "generator")
    @GenericGenerator(name = "generator", strategy = "native")
    @Column(name = "Id")
    public Integer getId() {
        return id;
    }

    /**
     * 业务主键 根据类名,方法名,入参生成一个摘要值
     *
     * @return 业务主键
     */
    public String createBusKey() {
        return MD5Util.string2MD5(this.getClassName() + this.getMethodName() + this.getReqArgs());
    }
}
/**
 * Json工具
 */
public class JsonUtil {

    /** 默认使用jackson */
    public static ObjectMapper mapper;

    static {
        mapper = new ObjectMapper();
        //设置Jackson序列化时只包含不为空的字段
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        //设置在反序列化时忽略在JSON字符串中存在,而在Java中不存在的属性
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    }

    public static String toPrettyJsonString(Object value) {
        //打印原始格式不做格式化
        return toJsonString(value);
        /*String jsonString;
        try {
            jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
        } catch (JsonProcessingException e) {
            throw new BizException("Json序列化失败");
        }
        return jsonString;*/
    }

    public static String toJsonString(Object value) {
        String jsonString;
        try {
            jsonString = mapper.writeValueAsString(value);
        }catch (JsonProcessingException e){
            throw new BusinessException("Json序列化失败",false);
        }
        return jsonString;
    }

    public static <T> T fromJsonString(String content,Class<T> valueType) {
        T t;
        try {
            t = mapper.readValue(content,valueType);
        } catch (IOException e) {
            throw new BusinessException("Json反序列化失败\n" + GatewayUtil.getStackTraceAsString(e),false);
        }
        return t;
    }

}
  • 补偿那块的逻辑实现
@Service
@Transactional
public class CompensationServiceImpl extends CommonServiceImpl implements CompensationService {
    private static final Logger logger = LoggerFactory.getLogger(CompensationServiceImpl.class);
    public final static Integer RETRY_LIMIT_COUNT = 3;
    @Resource
    CompensationMiniDao compensationMiniDao;

    /**
     * 1:定时任务调度的方式
     *
     * @return 处理说明
     */
    @Override
    public String autoRetry() {
        // 1: 查询所有需要重试的记录,数据入库的时候重试次数为0
        StringBuilder stringBuffer = new StringBuilder();
        List<CompensationEntity> list = compensationMiniDao.getRetryList();
        stringBuffer.append("待补偿数据:").append(list.size()).append("条!");
        int successCount = 0;
        int failCount = 0;
        for (CompensationEntity compensationEntity : list) {
            int retryCount = compensationEntity.getRetryCount() + 1;
            compensationEntity.setRetryCount(retryCount);
            try {
                // 调用需要重试的方法
                boolean callStatus = callTargetMethod(compensationEntity);
                // 补偿成功
                if (callStatus) {
                    compensationEntity.setResultMsg(CommonConstants.Public.SUCCESS);
                    compensationEntity.setDataStatus(StatusEnum.StatusEnum_SUCCESS.getCode());
                    successCount++;
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                compensationEntity.setResultMsg(e.getMessage());
                if (RETRY_LIMIT_COUNT == retryCount) {
                    compensationEntity.setDataStatus(StatusEnum.StatusEnum_FAIL.getCode());
                }
                failCount++;
            }
            this.updateBySqlString(buildUpdateSql(compensationEntity));
        }
        stringBuffer.append("处理成功:").append(successCount).append("条!");
        stringBuffer.append("处理失败:").append(failCount).append("条!");
        return stringBuffer.toString();
    }

    /**
     * 2:人工重试的方式
     *
     * @param id id
     * @return 处理信息
     */
    @Override
    public String manualRetry(Integer id) {
        // 1: 定损需要处理的对象和返回的参数
        CompensationEntity dealObj = null;
        String result = CommonConstants.Public.SUCCESS;
        try {
            // 2: 根据id查询数据库
            List<CompensationEntity> dealObjList = this.findByProperty(CompensationEntity.class, "id", id);
            ValidateUtils.notEmpty(dealObjList, String.format("根据id:%s,查询返回的对象为空", id));
            dealObj = dealObjList.get(0);
            int retryCount = dealObj.getRetryCount() + 1;
            dealObj.setRetryCount(retryCount);
            dealObj.setType(HandleMethodEnum.MANUAL.getMsg());
            boolean handleResult = callTargetMethod(dealObj);
            if (handleResult) {
                dealObj.setDataStatus(StatusEnum.StatusEnum_SUCCESS.getCode());
            }
        } catch (Exception e) {
            logger.error(e.getCause().getMessage());
            result = e.getCause().getMessage();
        }
        // 3: 操作保存数据,如果依然是失败只改重试的次数,不修改数据的状态
        if (Objects.nonNull(dealObj)) {
            dealObj.setResultMsg(result);
            this.updateBySqlString(buildUpdateSql(dealObj));
        }
        return result;
    }

    /**
     * 构建修改的sql预计
     *
     * @param compensationEntity 实体类对象
     * @return 构建的update sql
     */
    private String buildUpdateSql(CompensationEntity compensationEntity) {
        StringBuilder stringBufferUpdate = new StringBuilder("update a_compensation set dataStatus='");
        stringBufferUpdate.append(compensationEntity.getDataStatus()).append("'").append(CommonConstants.Public.COMMA);
        stringBufferUpdate.append(" retryCount=").append(compensationEntity.getRetryCount()).append(CommonConstants.Public.COMMA);
        stringBufferUpdate.append(" type=").append("\"").append(compensationEntity.getType()).append("\"");
        if (StringUtil.isNotEmpty(compensationEntity.getResultMsg())) {
            stringBufferUpdate.append(CommonConstants.Public.COMMA);
            stringBufferUpdate.append("resultMsg=").append("\"").append(compensationEntity.getResultMsg()).append("\"");
        }
        stringBufferUpdate.append(" where id=").append(compensationEntity.getId());
        return stringBufferUpdate.toString();
    }

    /**
     * 分析数据
     *
     * @return 分析数据的结果
     */
    @Override
    public String analysisData() {
        List<CompensationEntity> allCompensationEntityData = this.getList(CompensationEntity.class);
        StringBuilder dataStr = new StringBuilder(String.format("补偿表数据总量为:%s条!", allCompensationEntityData.size()));
        if (CollectionUtils.isNotEmpty(allCompensationEntityData)) {
            Map<String, List<CompensationEntity>> resultList = allCompensationEntityData.stream().collect(Collectors.groupingBy(CompensationEntity::getDataStatus));
            resultList.forEach((k, v) -> {
                StatusEnum statusEnum = StatusEnum.getStatusEnumByCode(k);
                ValidateUtils.notNull(statusEnum, String.format("补偿表钉钉提醒的时候,查询的数据状态有问题,状态为:%s", k));
                dataStr.append(statusEnum.getMsg()).append(":").append(v.size()).append("条!");
            });
        }
        return dataStr.toString();
    }

    /**
     * 3: 调用的核心代码
     *
     * @param compensationEntity 补偿对象。
     */
    private boolean callTargetMethod(CompensationEntity compensationEntity) throws Exception {
        // 1: 通过类找到对应的方法
        Method targetMethod = null;
        try {
            Class<?> targetClass = Class.forName(compensationEntity.getClassName());
            ValidateUtils.notNull(targetClass, "补偿时未获取到对应的class bean");
            Method[] methods = targetClass.getMethods();
            for (Method method : methods) {
                // 匹配方法
                if (matchMethod(method, compensationEntity)) {
                    targetMethod = method;
                    break;
                }
            }
        } catch (ClassNotFoundException e) {
            logger.error(e.getMessage(), e);
            throw e;
        }
        try {
            //补偿的目标方法中的参数类型
            ValidateUtils.notNull(targetMethod, "补偿时未获取到对应的bean需要调用的目标方法");
            // 2: 回去方法的参数类型
            Class<?>[] paramTypes = targetMethod.getParameterTypes();
            // 3: 从spring 取的对象
            Object targetObject = SpringUtils.getBean(StringUtil.firstLowerCase(compensationEntity.getBeanName()));
            if (paramTypes.length == 1) {
                // 转成实际类型调用目标方法
                Object obj = JsonUtil.mapper.readValue(compensationEntity.getReqArgs(), paramTypes[0]);
                targetMethod.invoke(targetObject, obj);
            } else if (paramTypes.length > 1) {
                List<Object> objectList = JsonUtil.mapper.readValue(compensationEntity.getReqArgs(), new TypeReference<List<Object>>() {
                });
                Type[] paramTypesReq = targetMethod.getGenericParameterTypes();
                Object[] realParam = new Object[objectList.size()];
                for (int i = 0; i < objectList.size(); i++) {
                    Type paramType = paramTypesReq[i];
                    String param = JsonUtil.mapper.writeValueAsString(objectList.get(i));
                    realParam[i] = JsonUtil.mapper.readValue(param, JsonUtil.mapper.getTypeFactory().constructType(paramType));
                }
                targetMethod.invoke(targetObject, realParam);
            } else {
                logger.error("不支持的参数类型调用");
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw e;
        }
        return true;
    }

    /**
     * 3-1: 寻找类中对应的方法。考虑重载
     *
     * @param method             方法method
     * @param compensationEntity 补偿的类
     * @return 是否匹配上
     */
    private Boolean matchMethod(Method method, CompensationEntity compensationEntity) {
        //  1:先判断方法名,如果类名不匹配,直接返回false
        if (!method.getName().equals(compensationEntity.getMethodName())) {
            return false;
        }
        // 2: 方法名匹配成功后,匹配参数
        Class<?>[] args = method.getParameterTypes();
        if (args.length == 1) {
            return args[0].getName().equals(compensationEntity.getReqArgsType()) && StringUtils.isBlank(compensationEntity.getReqArgsTypeReal());
        } else {
            Class<?>[] classesReq = method.getParameterTypes();
            StringBuilder stringBuffer = new StringBuilder();
            for (Class<?> aClass : classesReq) {
                stringBuffer.append(aClass.getName()).append(CommonConstants.Public.COMMA);
            }
            String multiParamStr = stringBuffer.substring(0, stringBuffer.length() - 1);
            return compensationEntity.getReqArgsTypeReal().equals(multiParamStr);
        }
    }
}

  • 定时任务类
@Component
public class CompensateDataSchedulerTask {
    private static final Logger logger = LoggerFactory.getLogger(CompensateDataSchedulerTask.class);
    @Resource
    CompensationService compensationService;
    @Resource
    MessageReminderHandler messageReminderHandler;
    @Autowired
    private UtilDictKVService utilDictKVService;

    public final static String INTERFACE_NAME = "通用补偿接口";

    /**
     * 1: 任务只重试status=0的
     * 每隔5分钟一次,每次每条记录重试1次
     * tip:
     * 因为考虑到 定时任务是5分钟跑一次,所以数据量不会很大,因此,
     * 我们不用异步和线程池处理了。就同步处理。数据也同步更新到数据库。
     * 后期有改造,再进行优化
     * <p>
     * 考虑到线上有多台机器。我们默认指定一台机器进行运行。
     */
    @Scheduled(initialDelay = 10000, fixedDelay = 300000)
    public void retry() {
        if (exeScheduledIP()) {
            logger.info("开始进行补偿!");
            String dealResult = compensationService.autoRetry();
            logger.info(dealResult);
        }
    }

    /**
     * 钉钉提醒:失败待补偿的钉钉提醒。
     * 每天上午10:15触发
     */
    @Scheduled(cron = "0 15 10 ? * *")
    public void dingDingCompensationRemind() {
        if (exeScheduledIP()) {
            // 获取需要发送的 内容
            String env = messageReminderHandler.getEnv();
            String remindStr = compensationService.analysisData();
            DingDingMsgVO dingDingMsgVO = messageReminderHandler.buildDingDingMsgVO(INTERFACE_NAME, "环境:" + env + "!" + remindStr, DictCodeKeyEnum.DING_DING_COMPENSATE_MOBILE);
            String sendResult = messageReminderHandler.dingDingSend(DictCodeKeyEnum.DING_DING_COMPENSATE_URL, dingDingMsgVO);
            logger.info("CompensateDataSchedulerTask->dingDingRemind->sendResult:" + sendResult);
        }
    }


    /**
     * 是否可以执行定时任务
     *
     * @return true/false
     */
    private Boolean exeScheduledIP() {
        UtilDictKVEntity utilDictKVEntity = utilDictKVService.getKvsByDictCodeKey(DictCodeKeyEnum.CURRENT_COMPENSATE_IP.getCode(), DictCodeKeyEnum.CURRENT_COMPENSATE_IP.getKey(), 3600L);
        ValidateUtils.notNull(utilDictKVEntity, "未配置执行定时任务的");
        String localIp = null;
        try {
            InetAddress ia = InetAddress.getLocalHost();
            localIp = ia.getHostAddress();
        } catch (UnknownHostException e) {
            logger.error(e.getMessage(), e);
            return false;
        }
        return localIp.equals(utilDictKVEntity.getDictValue());
    }

}
  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值