zoukankan      html  css  js  c++  java
  • 接口补偿机制需求分析&方案设计

    接口补偿机制需求分析&方案设计
    文章目录
    接口补偿机制需求分析&方案设计
    需求分析
    背景
    解决方案
    业务示例
    注意事项
    示例
    业务Controller
    实现
    重试信息类&数据处理入库
    接口重试的主要方法
    需求分析
    背景
    业务系统逐渐开始与多个第三方系统进行对接,在对接时,需要调用外部系统接口进行数据的交换,如果在接口请求的过程中发生了网络抖动或其他问题,会导致接口调用失败;
    对于此类问题,需要一个长效的接口重新调用机制,在发生网络抖动时可以进行自动地补偿调用,或者记录下来通知人工处理。
    解决方案
    建立 “补偿接口信息表” ,主要字段:
    全类名(即包名+类名):class_name
    方法名:method_name
    参数类型数组:method_param_types 按照方法签名的顺序插入数组
    参数值数组:method_param_valuesTips:对象-->JsonString、null-->'null',按照方法签名的顺序插入数组 ,组成字符串数组
    错误信息: error_msg
    重试次数:retry_count
    最大次数:max_retry_count
    重试有效期: retry_expiry_date
    数据防重code:unique_hash_code Unique_key Hash(class_name+method_name+method_param_values)
    状态:status 10:未解决;20:已解决
    编写InterfaceRetryInfo类用以记录类名、方法名、参数值数组、最大次数、重试有效期等信息,开发人员在需要重试的业务方法中调用第三方系统接口失败时,给这些信息赋值并调用InterfaceRetryInfoService.asyncRetry(retryInfo)方法异步存入数据库,之后抛出RetryFlagException异常,以方便补偿方法可以判断重试调用成功与否;
    编写接口补偿方法public boolean processRetryInfo(InterfaceRetryInfo retryInfo),通过反射获取方法和参数并调用;
    编写遍历方法doRetry(),遍历数据库中的所有未解决的接口补偿数据,并提供Restful接口;
    接入公司定时任务系统DING,定时调用doRetry()进行接口补偿,如果补偿成功,则修改数据状态为**20:已解决**。
    业务示例
    注意事项
    需要补偿的第三方接口需满足幂等性
    调用第三方接口的逻辑需为单独的处理方法,和业务逻辑分离;
    第三方接口调用失败或异常的情况下,需保证处理方法一定要抛出RetryFlagException异常。
    处理方法的参数,类型可以为T、List<String>、List<T>、Map<String,String>,T代表Java基础类型或者POJO,且POJO的属性中如果有Map<K,V>,则K、V必须是String或其他Java基础类型;否则处理会出错。
    如果处理方法使用了@Async注解实现异步处理,则返回值必须为Future且异常处理最后一定要return false。
    示例
    业务Controller
    @RestController
    @Slf4j
    @RequestMapping(value = "/retry/demo")
    public class RetryDemoController {
    @Autowired
    private InterfaceRetryInfoService interfaceRetryInfoService;
    @RequestMapping(method = RequestMethod.GET)
    public BaseResult<String> retryDemo(){
    List<SampleBoxDO> sampleBoxDOList = new ArrayList<>();
    List<InventoryOwnCardDO> demoList = new LinkedList<>();
    Map<String, String> demoMap = new HashMap<>();
    demoMap.put("test11", "test11");
    demoMap.put("test22", "test22");
    //省略初始化&赋值代码
    sampleBoxDO.setDemoList(demoList);
    sampleBoxDO.setDemoMap(demoMap);
    sampleBoxDOList.add(sampleBoxDO);
    sampleBoxDOList.add(sampleBoxDO2);
    //调用第三方接口的逻辑需为单独的处理方法,和业务逻辑分离
    String msg = notifySomeone("Hello World", null, sampleBoxDOList, demoMap);
    return BaseResultUtils.ok(msg);
    }
    //调用第三方接口的处理方法
    @Async
    private Boolean notifySomeone(String param1, List<String> param2, List<SampleBoxDO> param3, Map<String, String> param4) {
    try {
    Random random = new Random();
    int a = random.nextInt(10);
    if (a < 5){ //模拟调用第三方失败
    //如果调用第三方接口异常,异步保持处理方法的信息到数据库,等待定时任务进行补偿重试
    log.error("notifySomeone error--->");
    String className = RetryDemoController.class.getName();
    String methodName = "notifySomeone";
    //方法参数值是不定长参数,param1、param2...paramn
    InterfaceRetryInfo retryInfo = new InterfaceRetryInfo(className, methodName, e.getMessage(), param1, param2, param3, param4);
    interfaceRetryInfoService.asyncRetry(retryInfo);
    throw new RetryFlagException("调用第三方失败,需要重试");
    }else {//模拟调用第三方成功
    System.out.println("param1->"+param1);
    if (CollectionUtils.isNotEmpty(param2)){
    param2.forEach(p -> System.out.println(p));
    }
    if (CollectionUtils.isNotEmpty(param3)){
    param3.forEach(p -> System.out.println(p.toString()));
    }
    }
    } catch (Exception e) {
    //如果调用第三方接口异常,异步保持处理方法的信息到数据库,等待定时任务进行补偿重试
    log.error("notifySomeone error--->",e);
    String className = RetryDemoController.class.getName();
    String methodName = "notifySomeone";
    //方法参数值是不定长参数,param1、param2...paramn
    InterfaceRetryInfo retryInfo = new InterfaceRetryInfo(className, methodName, e.getMessage(), param1, param2, param3, param4);
    interfaceRetryInfoService.asyncRetry(retryInfo);
    //处理方法最后一定要return false
    return new AsyncResult<>(false);
    }
    return true;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    实现
    重试信息类&数据处理入库
    @Data
    public class InterfaceRetryInfo implements Serializable {
    //。。。省略属性定义

    /*构造方法
    * 通过反射获取方法的参数类型数组
    * 使用JsonObject将参数值序列化为字符串
    * 存入数据库
    */
    public InterfaceRetryInfo(String className, String methodName, String errorMsg, Object... methodParamValues) {
    Assert.notNull(className);
    Assert.notNull(methodName);
    Assert.notNull(errorMsg);

    this.className = className;
    this.methodName = methodName;

    if (methodParamValues != null && methodParamValues.length > 0) {
    try {
    //反射获取类的Class,并获取所有的声明方法,遍历之,获取需要进行重试的方法(该方法不可重载,否则无法获取准确的方法)
    Class<?> clazz = Class.forName(className);
    Method[] methods = clazz.getDeclaredMethods();
    Method calledMethod=null;
    for(Method method:methods){
    if(method.getName().equals(methodName)){
    calledMethod=method;
    break;
    }
    }
    //获取方法的参数类型,遍历参数值数组
    Class<?>[] paramTypes = calledMethod.getParameterTypes();
    List<String> paramValueStrList = new LinkedList<>();
    for (int i = 0; i < methodParamValues.length; i++) {
    Object paramObj = methodParamValues[i];
    //如果值为空,则存入 "null"
    if (null == paramObj){
    paramValueStrList.add("null");
    }else {
    //如果参数是String类型,则直接存入
    if (paramTypes[i] == String.class){
    paramValueStrList.add((String) paramObj);
    }else {
    //如果参数是POJO或集合类型,则序列化为字符串
    paramValueStrList.add(JSONObject.toJSONString(paramObj));
    }
    }
    }
    this.methodParamValues = JSONObject.toJSONString(paramValueStrList);
    this.methodParamTypes = JSONObject.toJSONString(paramTypes);

    } catch (ClassNotFoundException e ) {}
    }

    this.errorMsg = errorMsg;

    this.uniqueHashCode = MD5.getInstance().getMD5String((className + methodName + this.methodParamValues).getBytes());
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    接口重试的主要方法
    @Override
    public boolean processRetryInfo(InterfaceRetryInfo retryInfo) {
    String className = retryInfo.getClassName();
    String methodName = retryInfo.getMethodName();
    String paramValuesStr = retryInfo.getMethodParamValues();
    //反射获取类的Class,并定位到要重试的方法
    try {
    Class<?> clazz = Class.forName(className);
    Object object = context.getBean(clazz);
    Method[] methods = clazz.getDeclaredMethods();
    Method calledMethod=null;
    for(Method method:methods){
    if(method.getName().equals(methodName)){
    calledMethod=method;
    break;
    }
    }

    Object[] paramValueList = null;
    //如果方法有参数,则进行参数解析
    if (StringUtils.isNotEmpty(paramValuesStr)){
    List<String> paramValueStrList = JSONObject.parseArray(paramValuesStr, String.class);
    //获取方法所有参数的Type
    Type[] paramTypes = calledMethod.getGenericParameterTypes();
    paramValueList = new Object[paramTypes.length];
    for (int i = 0; i < paramValueStrList.size(); i++) {
    String paramStr = paramValueStrList.get(i);
    //如果参数值为空,则置为null
    if ("null".equalsIgnoreCase(paramStr)){
    paramValueList[i] = null;
    }else {
    //如果参数是String类型,则直接赋值
    if (paramTypes[i] == String.class){
    paramValueList[i] = paramStr;
    // 如果参数是带泛型的集合类或者不带泛型的List,则需要特殊处理
    }else if(paramTypes[i] instanceof ParameterizedType || paramTypes[i] == List.class){
    Type genericType = paramTypes[i];

    //如果是不带泛型的List 直接解析数组
    if (genericType == List.class){
    paramValueList[i] = JSON.parseObject(paramStr, List.class);
    }else if (((ParameterizedTypeImpl) genericType).getRawType() == List.class){
    // 如果是带泛型的List,则获取其泛型参数类型
    ParameterizedType pt = (ParameterizedType) genericType;
    //得到泛型类型对象
    Class<?> genericClazz = (Class<?>)pt.getActualTypeArguments()[0];
    //反序列化
    paramValueList[i] = JSON.parseArray(paramStr, genericClazz);
    }else {
    //如果是带泛型的其他集合类型,直接反序列化
    paramValueList[i] = JSON.parseObject(paramStr, paramTypes[i], Feature.OrderedField);
    }
    }else {
    //如果是POJO类型,则直接解析对象
    paramValueList[i] = JSON.parseObject(paramStr, paramTypes[i], Feature.OrderedField);
    }
    }
    }
    }
    //设置访问权限,否则会调用失败,throw IllegalAccessException
    calledMethod.setAccessible(true);
    //反射调用方法
    boolean asyncFlag = false;
    Annotation[] annotations = calledMethod.getDeclaredAnnotations();
    if (annotations != null && annotations.length > 0){
    for (Annotation annotation : annotations) {
    if (annotation.annotationType().getTypeName().equalsIgnoreCase("org.springframework.scheduling.annotation.Async")){
    asyncFlag = true;
    }
    }
    }

    if (asyncFlag){
    Future<Boolean> future = (Future) calledMethod.invoke(object, paramValueList);
    Boolean flag = future.get();
    if(!flag){
    throw new RetryFlagException();
    }
    }else {
    calledMethod.invoke(object, paramValueList);
    }

    retryInfo.setStatus(InterfaceRetryInfoStatusEnum.HAS_DONE.getCode());
    } catch (ClassNotFoundException | IllegalAccessException | InterruptedException | ExecutionException e ) {
    log.error("反射异常-->",e);
    return false;
    }catch (InvocationTargetException | RetryFlagException e){
    log.error("重试调用失败,更新次数-->",e);
    }
    retryInfo.setRetryTimes((retryInfo.getRetryTimes()) + 1);
    interfaceRetryInfoDAO.updateByPrimaryKeySelective(retryInfo);
    return true;
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94

    ————————————————
    版权声明:本文为CSDN博主「忙里偷闲得几回」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/panyongcsd/article/details/81485298

  • 相关阅读:
    POJ 3253 Fence Repair
    POJ 2431 Expedition
    NYOJ 269 VF
    NYOJ 456 邮票分你一半
    划分数问题 DP
    HDU 1253 胜利大逃亡
    NYOJ 294 Bot Trust
    NYOJ 36 最长公共子序列
    HDU 1555 How many days?
    01背包 (大数据)
  • 原文地址:https://www.cnblogs.com/softidea/p/12327791.html
Copyright © 2011-2022 走看看