• 接口补偿机制需求分析&方案设计


    接口补偿机制需求分析&方案设计
    文章目录
    接口补偿机制需求分析&方案设计
    需求分析
    背景
    解决方案
    业务示例
    注意事项
    示例
    业务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

  • 相关阅读:
    python之面向对象封装,多态
    python之面向对象继承
    python之面向对象类空间问题以及类之间的关系
    python之面向对象初识
    python之包及logging日志
    python之规范化开发
    python之模块Ⅱ
    python之模块Ⅰ
    python函数之闭包及装饰器
    python函数之内置函数
  • 原文地址:https://www.cnblogs.com/softidea/p/12327791.html
Copyright © 2020-2023  润新知