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

 

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

 

posted @ 2020-02-18 20:24  沧海一滴  阅读(1205)  评论(0编辑  收藏  举报