从API报错到本地拦截:电子面单快递公司前置校验改造
摘要:在多平台电子面单系统中,不同平台支持的快递公司各不相同。若运营选错快递公司,过去只能等到API调用后收到平台返回的模糊报错,排查费时费力。本文记录了如何通过引入
LogisticsSupportable接口,在请求构建前实现前置校验,将错误拦截在本地,并将报错信息从"非法的参数"优化为"平台抖音不支持快递公司京东"的清晰中文提示。
📖 系列导航
- 系列开篇:从"能跑就行"到"整洁架构"
- 上一篇:策略工厂复合Key路由改造
- 本文:快递公司前置校验改造
- 后续:京东、拼多多等平台专项篇
文章目录
- 从API报错到本地拦截:电子面单快递公司前置校验改造
-
- 一、事故:一次"非法参数"引发的排查
- 二、问题根源:缺少平台与快递的匹配校验
- 三、改造方案:在请求构建前增加前置校验
-
- [3.1 设计思路](#3.1 设计思路)
- [3.2 JDK 1.6 兼容方案:独立接口 + instanceof](#3.2 JDK 1.6 兼容方案:独立接口 + instanceof)
- 四、改造实施
-
- [4.1 新增 `LogisticsSupportable` 接口](#4.1 新增
LogisticsSupportable接口) - [4.2 抖音策略实现 `LogisticsSupportable`](#4.2 抖音策略实现
LogisticsSupportable) - [4.3 门面层增加前置校验](#4.3 门面层增加前置校验)
- [4.4 快递公司与平台名称中文化](#4.4 快递公司与平台名称中文化)
- [4.1 新增 `LogisticsSupportable` 接口](#4.1 新增
- 五、改造效果
- 六、工程权衡与后续演进
- 七、总结
- 八、系列导航与参考
- [延伸阅读:Java 23种设计模式实战系列](#延伸阅读:Java 23种设计模式实战系列)
- 九、一起交流,共同进步
一、事故:一次"非法参数"引发的排查
那天测试抖音普通订单的取号流程,系统却报出了一个令人困惑的错误:
获取运单号失败: 非法的参数
查看日志,抖音平台返回的是 "不支持该物流服务商"。顺着订单查下去,发现这个测试订单的快递公司选的是 京东物流(JD) ,而它走的却是 抖音取号 流程。
抖音平台压根不支持京东快递,这个请求从一开始就注定会失败。但我们的系统并没有在发出请求前检查这一点,傻傻地拼好 JSON、签好名,调用完 API 之后才拿到这个模糊的错误。
运营看到这样的提示,只能找开发:"帮我看看这个订单为什么失败?"------于是开发查日志、查平台文档、查订单数据......一次本可避免的排查浪费了半小时。
二、问题根源:缺少平台与快递的匹配校验
在多平台电子面单架构中,每个电商平台支持的快递公司列表是不同的。例如:
| 平台 | 支持的快递公司 |
|---|---|
| 抖音 | 顺丰、中通、圆通、申通、邮政 |
| 奇门(淘宝) | 几乎所有快递 |
| 京东 | 京东物流 |
但改造前的代码中,从订单数据到请求构建,没有任何地方校验"这个平台能不能发这个快递"。错误只有在快递公司的 API 返回报错时才会暴露,而此时已经浪费了一次网络调用,且错误信息往往是英文或技术化的,业务人员完全看不懂。
三、改造方案:在请求构建前增加前置校验
3.1 设计思路
校验的核心是回答一个问题:当前平台是否支持订单指定的快递公司?
我们希望:
- 校验逻辑与平台绑定:每个平台策略自己声明支持哪些快递。
- 校验时机尽可能早:在请求构建之前就拦截,不浪费 API 调用。
- 错误信息友好:用中文平台名称和快递名称,运营一眼能看懂。
但有一个现实约束:项目运行在 JDK 1.6 上,接口不能定义 default 方法。因此我们不能直接在 RequestStrategy 接口中加一个默认方法,需要另辟蹊径。
3.2 JDK 1.6 兼容方案:独立接口 + instanceof
我们设计一个独立的接口 LogisticsSupportable,只包含一个 supports 方法:
java
public interface LogisticsSupportable {
boolean supports(String logisticsCode);
}
- 需要校验的平台 (如抖音普通、抖音代发):实现
LogisticsSupportable,在supports方法中返回支持的快递公司列表。 - 无需校验的平台(如奇门,几乎所有快递都支持):不实现该接口,门面层默认放行。
门面层 WaybillFetchService 在获取策略后,通过 instanceof 判断是否需要校验:
java
if (req instanceof LogisticsSupportable) {
LogisticsSupportable checker = (LogisticsSupportable) req;
if (!checker.supports(logisticsCode)) {
// 抛出友好的业务异常
}
}
这个方案完全兼容 JDK 1.6,不修改现有 RequestStrategy 接口,未来升级 JDK 后可将 supports 直接合并进接口的 default 方法。
🏭 设计模式视角 :
LogisticsSupportable接口的设计体现了接口隔离原则(ISP) ------它只定义了一个"是否支持某快递"的独立能力,没有强迫所有策略类都实现它。这种"可选接口"的模式,是策略模式的一种灵活变体,在《Java 23种设计模式:从踩坑到精通》系列的第22篇(策略模式)中有更深入的拆解,欢迎延伸阅读。
四、改造实施
4.1 新增 LogisticsSupportable 接口
java
public interface LogisticsSupportable {
boolean supports(String logisticsCode);
}
4.2 抖音策略实现 LogisticsSupportable
java
public class DouYinRequestStrategy implements RequestStrategy, LogisticsSupportable {
@Override
public boolean supports(String logisticsCode) {
return TocWmsSourcePlatFormType.PLAT_DY_SUPPORTED_LOGISTICS.contains(logisticsCode);
}
@Override
public Object buildRequest(WaybillContext ctx) {
return DouYinWaybillBuilder.buildRequest(ctx);
}
}
其中支持的快递公司列表统一维护在常量类 TocWmsSourcePlatFormType 中:
java
public static final Set<String> PLAT_DY_SUPPORTED_LOGISTICS = new HashSet<String>(
Arrays.asList(
TocWmsExpressType.SF_CODE, // 顺丰
TocWmsExpressType.ZTO_CODE, // 中通
TocWmsExpressType.YTO_CODE, // 圆通
TocWmsExpressType.STO_CODE, // 申通
TocWmsExpressType.POSTB_CODE // 邮政
)
);
4.3 门面层增加前置校验
在 WaybillFetchService.fetchWaybill 中,获取策略后立即校验:
java
RequestStrategy req = strategyFactory.getRequestStrategy(platFormCode, platFormOriginal);
ParseStrategy parse = strategyFactory.getParseStrategy(platFormCode, platFormOriginal);
ExceptionStrategy ex = strategyFactory.getExceptionStrategy(platFormCode, platFormOriginal);
// 前置校验:检查平台是否支持该快递公司
if (req instanceof LogisticsSupportable) {
LogisticsSupportable checker = (LogisticsSupportable) req;
String logisticsCode = ticket.getLogisticsCode();
if (!checker.supports(logisticsCode)) {
String platFormName = TocWmsSourcePlatFormType.getPlatFormName(platFormCode);
String logisticsName = TocWmsExpressType.getLogisticsName(logisticsCode);
String errorMsg = "平台[" + platFormName + "]不支持快递公司[" + logisticsName + "]";
logger.warn(errorMsg);
throw new BusinessException(errorMsg);
}
}
4.4 快递公司与平台名称中文化
为了让错误信息更友好,我们在 TocWmsExpressType 和 TocWmsSourcePlatFormType 中分别增加了编码到中文名称的映射方法:
java
// TocWmsExpressType 中
public static String getLogisticsName(String logisticsCode) {
String name = LOGISTICS_NAME_MAP.get(logisticsCode);
return name != null ? name : logisticsCode;
}
// TocWmsSourcePlatFormType 中
public static String getPlatFormName(String platFormCode) {
String name = PLAT_FORM_NAME_MAP.get(platFormCode);
return name != null ? name : platFormCode;
}
五、改造效果
用一个抖音订单、快递选京东的错误数据测试,改造前后对比如下:
| 维度 | 改造前 | 改造后 |
|---|---|---|
| 错误信息 | "非法的参数" | "平台抖音不支持快递公司京东" |
| 拦截时机 | API 调用后 | 请求构建前,本地拦截 |
| API 调用浪费 | 每次错误都调用 | 零浪费 |
| 排查方式 | 开发介入查日志 | 运营看提示即可修正 |
日志输出:
平台[抖音]不支持快递公司[京东]
简洁、准确、无需翻译,业务人员一看就明白:订单选错快递了,改一下就好。
六、工程权衡与后续演进
当前取舍
1. 独立接口而非修改 RequestStrategy
受 JDK 1.6 限制,无法使用 default 方法,我们选择用 instanceof 进行能力检查。这比直接修改基础接口更安全,因为奇门等无需校验的平台完全不受影响。
2. 快递列表硬编码在常量类中
当前支持的快递列表写死在 TocWmsSourcePlatFormType 中,新增快递公司需修改常量类。在渠道快速接入阶段,这种集中管理方式反而更容易维护。后续可迁移至配置中心,实现动态调整。
后续优化路线图
| 阶段 | 优化项 | 触发条件 |
|---|---|---|
| 短期 | 快递公司列表迁移到 PlatformConfigCache,支持动态刷新 |
快递公司变更频繁 |
| 中期 | 升级 JDK 8+ 后,将 supports 合并到 RequestStrategy 的 default 方法 |
技术栈升级 |
| 长期 | 运营后台增加"平台-快递"绑定配置页面,自助管理 | 业务自助化需求 |
七、总结
这次改造的核心价值在于:将错误从"事后被动响应"变为"事前主动拦截"。
通过引入 LogisticsSupportable 接口,我们为需要校验的平台提供了统一的能力声明机制;通过门面层的 instanceof 检查,在不修改核心接口的前提下实现了前置拦截;通过快递公司和平台名称中文化,让错误信息从技术语言变成了业务语言。
代码改动量很小,但带来的运维收益和用户体验提升是显著的。这也是我们在整个电子面单系列中反复强调的理念:好的架构不一定是技术最先进的,但一定是问题发生时最能快速定位、最容易修复的。
八、系列导航与参考
本篇文章是「电商多平台电子面单对接实战」的第六篇(防御性设计篇),聚焦快递公司前置校验的落地实践。
系列文章目录:
- 开篇:从"能跑就行"到"整洁架构"
- 第一篇:奇门对接顺丰电子面单
- 第二篇:抖音代发电子面单对接
- 第三篇:抖音普通订单电子面单对接
- 第四篇:多平台统一架构设计
- 第五篇:策略工厂复合Key路由改造
- 第六篇:快递公司前置校验改造(本文)
- 后续:京东、拼多多等平台专项篇
延伸阅读:Java 23种设计模式实战系列
本文中前置校验改造的核心,巧妙地运用了接口隔离原则(ISP)和策略模式 的变体------通过一个独立的 LogisticsSupportable 接口,在不修改原有策略接口的前提下,为部分平台赋予了快递校验能力。在《Java 23种设计模式:从踩坑到精通》系列中,这些设计原则和模式有更体系化的拆解。如果你对以下问题感兴趣,推荐延伸阅读:
- 接口隔离原则:如何设计小而专一的接口,避免"胖接口"?
- 策略模式:如何定义算法族并动态切换,与工厂模式配合?
- 单一职责原则:如何判断一个类是否承担了过多职责?
📖 《Java 23 种设计模式:从踩坑到精通》
- 系列开篇:从踩坑到精通 ------ 总览与导航
- 策略模式 ------ 算法族的封装与切换
- 工厂模式 ------ 简单工厂→工厂方法→抽象工厂全演进
💡 学习建议 :电子面单系列侧重业务落地与防御性设计 ,设计模式系列侧重理论体系与设计思维。两者搭配阅读,既能解决眼前的校验问题,又能掌握背后的设计思想,形成"实战→理论→反哺实战"的闭环。
九、一起交流,共同进步
技术之路,一个人走得快,一群人走得远。
如果您的团队也在为多平台对接中的数据校验头疼,希望本文的设计能给您带来启发。
- 📌 关注我 :点击上方"关注",第一时间获取系列更新推送。
- 💬 留言讨论:如果您在实际对接中遇到类似问题,或对文章有任何建议,欢迎在评论区留言。
- 🔗 分享转发 :如果本文对您有帮助,请 点赞 、收藏 、分享,让更多同行看到。