
从硬编码到复合Key路由一个策略工厂的救赎之路摘要在多平台电子面单架构中策略工厂StrategyFactory承担着按平台分发请求构建、响应解析、异常判断三大策略的重任。然而最初的设计埋下了一个致命的“覆盖”陷阱——抖音普通订单和代发订单共用一个平台编码导致策略互相覆盖。本文记录了该工厂从单维度硬编码路由到引入复合Key机制、统一Key构建方法、增加默认策略兜底的全过程是一份可复用的设计演进案例。系列导航系列开篇从“能跑就行”到“整洁架构”上一篇多平台统一架构设计本文策略工厂复合Key路由改造后续京东、拼多多等平台专项篇一、事故抖音普通订单“串门”到了代发策略故事要从一次测试说起。那天我正验证抖音普通订单的取号流程日志却打印出“抖音代发API调用”。我心头一紧普通订单怎么走代发逻辑了翻开StrategyFactory的构造方法问题一目了然publicStrategyFactory(){// 奇门系列requestMap.put(TM,newQiMenRequestStrategy());requestMap.put(TB,newQiMenRequestStrategy());// 抖音危险同一个 Key 被用了两次requestMap.put(DY,newDouYinRequestStrategy());// 抖音普通requestMap.put(DY,newDouYinDaiFaRequestStrategy());// 抖音代发覆盖了上面}同一个platformCode被 put 了两次第二次直接覆盖了第一次。抖音普通订单永远拿不到正确的请求策略因为它被代发策略“偷梁换柱”了。这可不是小问题——如果上线所有抖音普通订单都会按代发的逻辑去签名、调接口轻则报错重则发错面单。本文重点讲工厂路由的演进工厂模式的完整讲解见《Java 23 种设计模式从踩坑到精通》系列文章。二、问题根源单维度 Key 的致命缺陷当初设计StrategyFactory时只考虑了“一个平台对应一套策略”于是直接用platformCode作为 Map 的 Key。这在只有淘宝、京东等“一个平台一个编码”的场景下没问题。但抖音的特殊之处在于同一个平台下存在两种完全不同的业务模式——普通订单和代发订单。它们的请求格式、签名算法、API 地址都不同必须用不同的策略处理。而我们的系统里这两种模式的platformCode都是DY。单维度 Key 无法区分必然发生覆盖。更糟糕的是这个问题不仅存在于策略工厂。ApiInvokerAPI 调用调度层同样用platformCode路由处理器抖音普通和代发也会互相覆盖。两处隐患一触即发。设计模式视角这种“单维度路由导致覆盖”的问题本质上是工厂模式在路由设计上的不足——简单工厂通常只用单一标识符定位对象当同一个标识符对应多种实现时就会失效。在《Java 23种设计模式从踩坑到精通》系列的**第3篇工厂模式和第4篇抽象工厂**中我详细拆解了工厂模式如何应对多维度路由场景欢迎延伸阅读。三、改造思路引入复合Key3.1 从“平台编码”到“平台编码原始渠道”仔细观察订单数据每个订单除了sourcePlatformCode平台编码如DY还有一个字段叫tocPlatFormOriginal原始渠道如DF表示代发普通订单则为null。于是方案自然浮现用platformCode _ platFormOriginal作为复合 Key。订单类型platformCodeplatFormOriginal复合Key抖音普通DYnullDY_DEFAULT抖音代发DYDFDY_DF奇门/天猫TMnullTM_DEFAULT这样两个抖音模式各走各的 Key再也不冲突了。3.2 统一 Key 构建方法复合 Key 的拼接规则如果散落在各处未来调整比如加租户维度就会成为灾难。必须收口到一个地方。我在平台常量类TocWmsSourcePlatFormType中新增了一个静态方法publicabstractclassTocWmsSourcePlatFormType{// 平台常量...publicstaticfinalStringPLAT_DY_CODEDY;publicstaticfinalStringPLAT_DY_DF_CODEDF;/** * 构建策略/处理器查找的复合 Key * param platformCode 订单来源平台编码 * param platFormOriginal 订单原始渠道可为 null * return 复合 Key如 DY_DEFAULT / DY_DF */publicstaticStringbuildCompositeKey(StringplatformCode,StringplatFormOriginal){if(platformCodenull||platformCode.isEmpty()){thrownewIllegalArgumentException(platformCode 不能为空);}if(platFormOriginal!null!platFormOriginal.isEmpty()){returnplatformCode_platFormOriginal;}returnplatformCode_DEFAULT;}}这个方法随后被StrategyFactory和ApiInvoker同时调用确保全局 Key 规则一致。将来如果要加维度改这一个方法就行。四、改造实施4.1 策略工厂从单维度 put 到复合 Key 注册改造后的构造方法publicStrategyFactory(){// 奇门系列StringtmKeyTM_DEFAULT;requestMap.put(tmKey,newQiMenRequestStrategy());parseMap.put(tmKey,newQiMenParseStrategy());exceptionMap.put(tmKey,newQiMenExceptionStrategy());// 抖音普通DY_DEFAULTStringdyDefaultKeyDY_DEFAULT;requestMap.put(dyDefaultKey,newDouYinRequestStrategy());parseMap.put(dyDefaultKey,newDouYinParseStrategy());exceptionMap.put(dyDefaultKey,newDouYinExceptionStrategy());// 抖音代发DY_DFStringdyDfKeyDY_DF;requestMap.put(dyDfKey,newDouYinDaiFaRequestStrategy());parseMap.put(dyDfKey,newDouYinDaiFaParseStrategy());exceptionMap.put(dyDfKey,newDouYinDaiFaExceptionStrategy());}获取策略的方法也同步调整增加platFormOriginal参数并加入默认策略兜底publicRequestStrategygetRequestStrategy(StringplatformCode,StringplatFormOriginal){StringkeyTocWmsSourcePlatFormType.buildCompositeKey(platformCode,platFormOriginal);RequestStrategystrategyrequestMap.get(key);returnstrategy!null?strategy:defaultRequest;// 默认策略兜底}4.2 API 调度器同步升级路由ApiInvoker中同样改造// 注册 HandlerregisterPlatRequestHandler(DY,null,newDouYinSampleHandler());// 抖音普通 → DY_DEFAULTregisterPlatRequestHandler(DY,DF,newSimpleHttpHandler(douyin_daifa));// 抖音代发 → DY_DF// invoke 方法中StringkeyTocWmsSourcePlatFormType.buildCompositeKey(platformCode,platformOriginal);RequestHandlerhandlerplatHandlerMap.get(key);4.3 调用方适配门面层WaybillFetchService获取策略时传入两个参数StringplatformCodeticket.getSourcePlatformCode();StringplatFormOriginalticket.getTocPlatFormOriginal();RequestStrategyreqstrategyFactory.getRequestStrategy(platformCode,platFormOriginal);ParseStrategyparsestrategyFactory.getParseStrategy(platformCode,platFormOriginal);ExceptionStrategyexstrategyFactory.getExceptionStrategy(platformCode,platFormOriginal);订单原有的两个字段直接复用无需额外改动数据模型。关于策略模式的更深入讲解可参考我的《Java面试·实战笔记》系列文章《从多平台电子面单架构看接口与抽象类的真实选型》。五、防御性设计的额外收益5.1 默认策略兜底在改造过程中我还发现一个隐患如果某个平台忘记注册策略获取方法直接返回null后续调用必然抛NullPointerException。于是增加了默认策略兜底privatefinalRequestStrategydefaultRequestnewDefaultRequestStrategy();privatefinalParseStrategydefaultParsenewDefaultParseStrategy();privatefinalExceptionStrategydefaultExceptionnewDefaultExceptionStrategy();即使配置遗漏系统也不至于崩溃只会走默认的“保守”逻辑并打印WARN日志方便排查。5.2 平台编码非空校验buildCompositeKey中对platformCode做了非空校验。如果上游数据出问题导致平台编码为空会在路由层直接抛异常而不是拼出一个奇怪的_DEFAULTKey避免排查困难。六、改造前后对比维度改造前改造后路由方式单维度platformCode复合 KeyplatformCode_original抖音普通/代发互相覆盖路由错乱各自独立精确匹配Key 构建各组件内联拼接统一静态方法buildCompositeKey未注册渠道返回nullNPE 风险返回默认策略安全兜底扩展性新增子渠道需改造路由逻辑新增子渠道只需增加复合 Key 映射为了更直观地展示复合 Key 的构建与路由分发逻辑这里补充一张流程图路由分发与执行统一 Key 构建platFormOriginal 为空platFormOriginal 不为空命中未命中命中未命中订单数据提取字段platformCode _DEFAULTplatformCode _ platFormOriginalbuildCompositeKeyStrategyFactory 查找策略ApiInvoker 查找 Handler返回对应策略返回默认策略兜底调用对应平台 API抛出异常 Fail-Fast图释统一收口左侧子图无论platFormOriginal是否为空所有订单数据均通过buildCompositeKey方法生成标准化的复合 Key彻底杜绝了各组件内联拼接导致的规则不一致。差异化防御右侧子图StrategyFactory与ApiInvoker共享同一套 Key 构建规则但在未命中时采取不同策略。前者返回默认策略进行保守兜底防止系统崩溃后者作为 API 调用的最后一道防线若找不到 Handler 则直接抛出异常Fail-Fast避免向第三方平台发送错误报文。架构收益该流程在视觉上直观证明了策略工厂和 API 调度器在路由规则上的完全统一兼顾了系统的灵活性与健壮性。七、工程权衡与后续演进当前取舍为什么先硬编码在架构设计中没有完美的方案只有适合当前阶段的方案。本次改造的核心目标是快速跑通十几个电商渠道的对接因此在以下几个点上做了务实的取舍1. 策略注册仍保留硬编码当前策略实例仍然在StrategyFactory构造方法中new出来直接put这确实违反了开闭原则新增渠道需修改工厂代码。但在目前阶段这样做有两个好处改动最小不引入 Spring 自动扫描、自定义注解等新机制团队上手成本为零。问题排查快所有策略注册一目了然出问题时直接看构造方法即可定位。2. 复合 Key 仍使用字符串拼接buildCompositeKey用_作为分隔符理论上存在隐患——如果未来某个平台编码本身包含下划线会产生歧义。但在当前所有平台编码TM、DY、JD、PDD等都不含下划线的前提下这个风险可控。3. 默认策略兜底偏保守当前找不到策略时返回默认策略而非抛异常是为了避免配置遗漏导致线上 NPE。默认策略内部会打印WARN日志确保问题可追踪。后续优化路线图阶段优化项触发条件短期抽取registerStrategy方法减少构造器重复代码渠道数量超过 8 个中期Spring 自动扫描策略实现类消除构造器硬编码渠道数量稳定不再频繁新增长期Key 升级为CompositeKey对象消除字符串拼接隐患出现含特殊字符的平台编码长期默认策略接入告警走兜底时自动通知生产环境出现过配置遗漏八、总结这次策略工厂的改造表面上是修了一个“覆盖”Bug实际上是完成了一次从单维度到多维度的路由升级。核心动作只有三个引入复合 Key用platformCode _ platFormOriginal区分同一平台下的不同业务模式。统一 Key 构建方法收口到常量类避免规则散落。增加默认策略兜底提升系统健壮性。改造完成后策略工厂和 API 调度器在路由规则上实现了完全统一。当前保留的硬编码注册方式是“快速交付”阶段的务实选择——不追求一步到位的完美架构而是在正确的方向上逐步演进。这为后续接入京东、支付宝等新平台打下了坚实的基础无论未来出现多么复杂的子渠道组合只需遵循复合 Key 规范便可轻松扩展。九、系列导航与参考本篇文章是「电商多平台电子面单对接实战」的第五篇策略工厂路由改造篇它解决了多平台对接中策略路由冲突的典型痛点提炼出一套基于复合Key的路由方案与系列架构设计篇相互呼应。系列文章目录开篇从“能跑就行”到“整洁架构”第一篇奇门对接顺丰电子面单第二篇抖音代发电子面单对接第三篇抖音普通订单电子面单对接第四篇多平台统一架构设计第五篇策略工厂复合Key路由改造本文第六篇快递公司前置校验改造第七篇解析器职责分离改造第八篇模板方法的组合与继承抉择第九篇API调用调度层Handler分组设计第十篇奇门 trade_order_list 排查实录第十一篇数据库查询优化让多包裹取号快一倍第十二篇两次架构升级完整复盘第十三篇常量与配置集中管控改造后续京东、拼多多、微信视频号等平台专项篇延伸阅读Java 23种设计模式实战系列本文中策略工厂的复合Key路由改造核心运用了工厂模式和策略模式的组合——工厂负责路由定位策略负责差异化执行。在《Java 23种设计模式从踩坑到精通》系列中这些模式有更体系化的拆解。如果你对以下问题感兴趣推荐延伸阅读工厂模式简单工厂、工厂方法、抽象工厂如何在路由场景中选型策略模式如何定义算法族并与工厂配合实现动态切换单一职责原则如何判断工厂是否承担了过多路由逻辑《Java 23 种设计模式从踩坑到精通》系列开篇从踩坑到精通 —— 总览与导航工厂模式 —— 简单工厂→工厂方法→抽象工厂全演进策略模式 —— 算法族的封装与切换学习建议电子面单系列侧重业务落地与路由设计设计模式系列侧重理论体系与设计思维。两者搭配阅读既能解决实际的路由冲突问题又能掌握背后的设计模式精髓形成“实战→理论→反哺实战”的闭环。十、一起交流共同进步技术之路一个人走得快一群人走得远。如果您的团队也在为多平台对接头疼希望本文的路由设计能给您带来启发。欢迎留言交流。关注我点击上方“关注”第一时间获取系列更新推送。留言讨论如果您在实际对接中遇到类似问题或对文章有任何建议欢迎在评论区留言我会定期回复。分享转发如果本文对您有帮助请点赞、收藏、分享让更多同行看到。