解决主单下多个子单,并会分开多次请求的场景下,主单号的幂等问题。
场景:
上游系统请求下游系统。
上游情况:
上游单据分为主单和子单(明细单),一个主单对应多个子单。
发送请求给下游系统的时候,参数结构是一个主单包含多个子单,并且可能会多次请求下游系统。
下游情况:
下游单据同样分为主单和子单(明细单),一个主单对应多个子单。主单需要幂等。
上下游单据对应关系:
上游主单和下游主单是1:n关系。
上游子单和下游子单是1:1关系。
上游请求参数结构示例:
{
"bizCode":"INVENTORY_ADJUST",
"subOrderDTOs":[
{
"detailOrderId":"2000001394868005",
"itemCode":"..."
},
{
"detailOrderId":"2000001394868006",
"itemCode":"..."
}
],
"locationCode":"TESTCS1",
"mainOrderId":"CSCSTESTCS1kt210914000005",
"merchantCode":"CSCS"
}
请求示例:
一个上游主单会拆分为多次请求:
第一次请求上游主单号CSCSTESTCS1kt210914000005,对应上游子单号2000001394868005和2000001394868006;
第二次请求上游主单号同样是CSCSTESTCS1kt210914000005,但是对应上游子单号是2000001394868007,2000001394868008和2000001394868009;
第三次有请求上游主单号同样是CSCSTESTCS1kt210914000005,但是对应上游子单号是,2000001394868008和2000001394868009;(子单号在第二次请求的时候已经包含在内了)
第四次有请求上游主单号同样是CSCSTESTCS1kt210914000005,但是对应上游子单号是,2000001394868009和2000001394868008;(与第三次子单号顺序不同)
第五次请求上游主单号CSCSTESTCS1kt210914000005,对应上游子单号2000001394868005和2000001394868006;(与第一次完全相同)
请求分析
对于前三次的请求,我们认为都是不需要幂等的。
因为子单号的信息不同,我们认为是不同请求。对于第三次的子单号在第二次请求的时候已经包含在内了,但是我们认为这是一次有效的请求,不需要进行幂等。
第四次和第五次请求是需要幂等的。因为子单号相同,顺序不同,认为是同一请求。
处理方案
主单号是一个字符串信息。
方案1
是用商家编码、仓编码、上游主单号、业务编码组成
"CSCS|TESTCS1|CSCSTESTCS1kt210914000005|INVENTORY_ADJUST"
存在问题:
同一主单不同子单,会被直接幂等掉,无法进行后续操作。
方案2
用商家编码、仓编码、上游主单号、子单号、业务编码组成
"CSCS|TESTCS1|CSCSTESTCS1kt210914000005|2000001394868005|2000001394868006|INVENTORY_ADJUST"
存在问题:
如果统一主单下有多个子单,如果子单过多的话,会导致组成的字符串过长,存入数据库时会出错。如果子单号排序不同,会被认为是不同单据。
方案3
用商家编码、仓编码、上游主单号、子单号排序后组成字符串再MD5加密、业务编码组成
"CSCS|TESTCS1|CSCSTESTCS1kt210914000005|2f7625f846b788abfd84b2e991dd03f8|INVENTORY_ADJUST"
优点:
同时解决了方案2和方案1的问题,保证了子单号的顺序,一次请求下子单过多,在经过MD5加密后可以得到固定长度的字符串,这样就可以满足主单据的幂等要求了。
伪代码
//获取子单号列表
List<String> outDetailOrderIdList = getOutDetailOrderIdList(adjustOrderDTO);
//子单号列表排序
outDetailOrderIdList.sort(String::compareTo);
//子单号列表拼接字符串
String outDetailOrderIdListJoinStr = String.join("|", outDetailOrderIdList);
//子单号列表字符串进行MD5编码
String outDetailOrderIdListMd5 = MD5Util.toMD5(outDetailOrderIdListJoinStr);
//产生幂等号
String idempotentNoStr = adjustOrderDTO.getMerchantCode() + VERTICAL_LINE +
adjustOrderDTO.getWarehouseCode() + VERTICAL_LINE +
adjustOrderDTO.getOutMainOrderId() +VERTICAL_LINE +
outDetailOrderIdListMd5 + VERTICAL_LINE +
adjustOrderDTO.getOutBizCode() + VERTICAL_LINE;
结论
所以我们采用了方案3来做幂等验证,刚好满足业务需求。