MSHA控制台提供在切流前或者切流结束后添加自定义动作的功能,支持切流前或者切流结束后进行业务自定义接口的回调,即支持添加自定义回调接口的前置或者后置动作。本文介绍如何添加前置或者后置的自定义动作。
使用限制
一个多活命名空间下,最多允许配置5个自定义动作。
操作步骤
规范
使用自定义动作功能时,回调接口需要满足以下规范。
- 回调接口需是HTTP/HTTPS协议。
- 回调接口需要免登,即不做登录态校验。
- 回调接口不能有CSRF Token校验。
- 回调接口参数名需要遵循以下名称规范。MSHA控制台发起回调时,用户需要勾选以下名称的参数,包含了切流工单基本信息和安全校验摘要。
参数 参数描述 mshaTenantId 多活命名空间ID。 id 切流工单ID。 name 切流工单名称。 sourceUnitFlag 切流源单元标识,表示从sourceUnitFlag->向targetUnitFlag切流。 targetUnitFlag 切流目的单元,表示从sourceUnitFlag->向targetUnitFlag切流。 status 切流结束状态。
- 正常结束状态枚举值为:
- complete:正常结束。
- canceled:人为取消终止。
- 异常结束状态枚举值为:
- autoCanceled:异常后自动取消终止(回滚完成)。
- cancelFailed:取消失败。
- closed:强制关闭(不继续执行也不回滚)。
- fail:其他情况异常终止。
completeTime 切流结束时间。 changeTokenRange 当范围类型切流时,切流变更的范围区间。当非范围类型切流时,值为空字符串("")。
格式:[$区间开始,$区间结束],其中 [] 括号表示数学上的闭区间(固定为闭区间,不会有开区间或半开区间的情况)。
Java语言赋值样例:
String changeTokenRange="[1,9999]";
。changeTokenList 当精准类型切流时,切流变更的精准名单。当非精准类型切流时,值为空字符串("")。
格式:以英文半角逗号隔开的精准名单。
Java语言赋值样例:
String changeTokenList="11,22,33";
。digest 安全摘要,用于验证调用是否可信以及参数防篡改。 - 正常结束状态枚举值为:
回调接口样例
以下是Java语言版本,使用Spring MVC框架的编写的回调接口样例:
@RestController
@RequestMapping("/demo")
@Slf4j
public class DemoController {
@RequestMapping(path = "/switchEndCallback", method = {RequestMethod.POST, RequestMethod.GET})
public void switchEndCallback( @RequestParam String mshaTenantId,
@RequestParam String id,
@RequestParam String name,
@RequestParam String sourceUnitFlag,
@RequestParam String targetUnitFlag,
@RequestParam String status,
@RequestParam String completeTime,
@RequestParam String changeTokenRange,
@RequestParam String changeTokenList,
@RequestParam String digest) throws Exception {
//do something
}
}
校验调用来源是否可信
MSHA控制台接口回调时,会带上digest安全摘要参数,用于验证调用来源是否可信以及参数防篡改。校验流程如下:
- 自定义一个digestSalt值,并将digestSalt配置到MSHA控制台切流结束回调接口信息中。
- 切流结束后,接口调用者(MSHA控制台)将发起接口回调,使用digestSalt和回调参数生成摘要digest,并在回调时带上该digest参数。
- 接口提供者(业务应用/业务系统)接收到回调接口时,使用跟步骤2一样的方式生成期望的摘要expectedDigest,跟回调参数中的摘要digest做比较。若完全相同表示验证通过,调用来源是否可信且参数没有被篡改。
说明 仅当接口调用者和接口提供者都使用相同盐值和参数生成的摘要才会验证通过。
使用示例如下:
@RestController
@RequestMapping("/demo")
@Slf4j
public class DemoController {
@RequestMapping(path = "/switchEndCallback", method = {RequestMethod.POST, RequestMethod.GET})
public Object switchEndCallback(@RequestParam String mshaTenantId,
@RequestParam String id,
@RequestParam String name,
@RequestParam String sourceUnitFlag,
@RequestParam String targetUnitFlag,
@RequestParam String status,
@RequestParam String completeTime,
@RequestParam String changeTokenRange,
@RequestParam String changeTokenList,
@RequestParam String digest) throws Exception {
//使用TreeMap保证后续拼接的StringBuilder是固定顺序的。
TreeMap<String, String> sortedParamMap = new TreeMap<>();
sortedParamMap.put("mshaTenantId", mshaTenantId);
sortedParamMap.put("id", id);
sortedParamMap.put("name", name);
sortedParamMap.put("sourceUnitFlag", sourceUnitFlag);
sortedParamMap.put("targetUnitFlag", targetUnitFlag);
sortedParamMap.put("status", status);
sortedParamMap.put("completeTime", completeTime);
sortedParamMap.put("changeTokenRange", changeTokenRange);
sortedParamMap.put("changeTokenList", changeTokenList);
log.info("switchEndCallback params:" + sortedParamMap);
//盐值,在请求参数生成安全摘要时使用,用于验证调用来源是否可信。
String digestSalt = "kbBO1nD1BM_Ymr76XOoZkbJ72k4";
StringBuilder paramsStringBuilder = new StringBuilder();
for (Map.Entry<String, String> entry : sortedParamMap.entrySet()) {
paramsStringBuilder.append(entry.getValue());
}
paramsStringBuilder.append(digestSalt);
//推荐使用commons-codec:commons-codec:jar:1.10(或以上版本)DigestUtils来生成md5哈希摘要。
String expectedDigest = org.apache.commons.codec.digest.DigestUtils.md5Hex(paramsStringBuilder.toString().getBytes("utf-8"));
//只有接口调用者和接口提供者,都使用相同盐值、相同的参数生成的摘要才会验证通过。
boolean digestCheckPassed = digest.equalsIgnoreCase(expectedDigest);
log.info("switchEndCallback digestCheckPassed:{}", digestCheckPassed);
return "switchEndCallback returned success";
}
}