回收系统架构演进实战:与Cursor结对扫清系统混沌

"最好的代码不是一次写对的,而是不断重构出来的。" ------ Martin Fowler

前言:技术债务带来的效能瓶颈

我所在的团队负责一个多渠道回收业务系统,接入了十几个外部渠道。每个渠道都有自己独特的协议规范、业务流程和特殊要求。随着业务发展,系统逐渐的遇到了一系列问题:

  • 代码膨胀:单个策略类代码超过800行,包含大量if-else判断,可读性极差
  • 重复代码:不同渠道间有70%的代码逻辑相似,有很多都是逻辑类似,但参数名称、结构不一致,难以复用
  • 难以测试:业务逻辑与协议处理耦合,单元测试覆盖率不到30%,主要依赖人工测试
  • 维护困难:改一个渠道的逻辑,需要理解整个类的800行代码,调研成本极高

技术债务不仅影响代码质量,更直接冲击了团队的研发效能。我们在日常开发中遭遇了三个核心瓶颈:

① 新人上手困难,培养周期长

新同学加入团队后,需要2-3个月才能理解现有代码逻辑:

  1. 单个策略类800+行,找一个业务逻辑点要翻半天
  2. 协议处理、业务逻辑、数据操作混在一起,理不清头绪
  3. 没有清晰的架构文档,只能靠"师傅带徒弟"口口相传

② 需求承接效率低,开发成本高

每次接入新渠道,都是一次"复制粘贴+魔改"的过程:

  1. 复制一个相似渠道的策略类
  2. 对照需求文档,逐个方法修改参数、逻辑
  3. 开发写入个性化逻辑

③ 测试回归成本高,质量保障困难

代码耦合导致"牵一发而动全身":

  1. 修改一个渠道的逻辑,可能影响其他渠道,测试范围难以评估
  2. 单元测试覆盖率低(30%),主要靠人工回归测试
  3. 每次发版前,测试同学要回归受影响渠道的核心流程

此时我的脑子里出现了一个想法:为了早点下班~ 必须重构了!

一、 重构方案设计

针对上述的三大痛点,我们很快制定了初步的重构规划:

痛点 重构目标 预期效果
新人上手慢 架构清晰化、职责单一化 培养周期从3周降到1周,调研成本降低70%
需求效率低 通用逻辑可复用、新增渠道可配置化 新渠道接入成本降低70%
测试成本高 解耦分层、单元测试覆盖率≥80% 回归测试时间减半,Code Review时间降低80%

三大技术策略

  1. 分层解耦:将协议层、业务层、数据层清晰分离,让新人快速定位代码
  2. 责任链模式:每个Handler职责单一,可独立开发、测试、复用
  3. 适配器模式:新渠道只需实现Adapter,组装Handler即可

目标有了,问题也来了:这是一个对接10余个活跃渠道的业务系统,系统复杂,且每日有数百万请求打进系统,如何在不影响线上业务的前提下进行重构?

渐进式落地,而非一步到位

出于对生产环境的敬畏之心,我们不敢"一刀切"地推倒重来。经过评估,我们选择了渐进式重构策略: 整体改进是一个持续的过程,目标清晰但落地需要时间。我们会逐步验证架构可行性,再慢慢进行迁移,确保每一步都是稳定可控的。

阶段规划:

阶段 时间 目标
第一阶段 1-2周 架构验证:完成1个渠道的规范化改造
第二阶段 1-2个月 核心迁移:完成50%渠道的迁移
第三阶段 3-6个月 全量迁移:剩余渠道全部迁移
第四阶段 持续优化 补充协议Handler、完善监控

关键原则

  1. 新老并行:新架构与旧代码并存,通过配置开关灰度切换

  2. 小步验证:每完成一个渠道,立即上线验证,确保稳定后再推进下一个

  3. 数据对比:新老架构同时执行,对比结果一致性,发现问题及时回滚

二、 基于PDCA思想驱动的人机协作模式

PDCA(Plan-Do-Check-Act)是戴明环的核心思想,特别适合用于:

  • 复杂系统的渐进式改进:将大目标拆解为小任务
  • 风险可控的迭代验证:每个小步骤都可验证
  • 知识沉淀与持续优化:从单次需求到全局改进

在与AI协作时,PDCA的价值更加明显:

PDCA阶段 人的职责 AI的职责 协作模式
Plan 架构设计、任务拆解、确定路径 提供设计建议、分析技术方案 人主导,AI辅助
Do 编写核心逻辑、Review代码 生成模板代码、实现细节 结对编程
Check 验证业务逻辑、集成测试 生成单元测试、代码检查 人验证,AI执行
Act 总结经验、沉淀规则 整理文档、提取模式 共同迭代

三、 实战案例:渠道统一Handler架构重构

3.1 Plan阶段:架构设计与任务拆解

3.1.1 原有架构的痛点分析

重构前,我们的代码结构是这样的:

java 复制代码
@Service("kaChannelBusinessStrategy")
public class KaChannelBusinessStrategy extends BaseChannelBusinessStrategy {
    
    public EnquiryPriceResult getEnquiryPrice(...) {
        // 1. 参数校验(50行)
        // 2. 解密验签(30行)
        // 3. 获取渠道配置(40行)
        // 4. 调用报价服务(60行)
        // 5. 计算佣金(50行)
        // 6. 组装响应(30行)
        // 7. 加密签名(20行)
        // ... 总计500+行
    }
    
    public CreateOrderResult createRecycleOrder(...) {
        // 同样的模式,又是几百行...
    }
}

问题总结

  1. 职责不清:一个类既处理协议,又处理业务,还管数据
  2. 代码重复:AChannelBusinessStrategy、BChannelBusinessStrategy里有大量相似代码
  3. 难以扩展:新增一个渠道,需要复制粘贴800行代码
  4. 测试困难:依赖过多,mock成本高

3.1.2 新架构设计思路

本次我们的设计,是一个基于责任链模式的新架构:

核心设计点

  1. 分层解耦

    • Facade层:统一入口,参数校验、异常处理
    • Adapter层:渠道适配,协议转换
    • Handler层:责任链模式,每个Handler职责单一
  2. 可复用的Handler DuplicationCheckHandler → 幂等性检查(通用) ChannelConfigHandler → 渠道配置获取(通用) ReportMappingHandler → 报告映射(通用) OrderParamAssemblyHandler → 参数组装(可定制) OrderCreationHandler → 订单创建(通用)

  3. 灵活的编排机制

    java 复制代码
    // A渠道:标准流程
    chain.addHandler(duplicationCheck)
         .addHandler(channelConfig)
         .addHandler(reportMapping)
         .addHandler(orderParamAssembly)
         .addHandler(orderCreation);
    
    // KA渠道:额外增加权限校验
    chain.addHandler(duplicationCheck)
         .addHandler(kaAuthCheck)  // KA特有
         .addHandler(channelConfig)
         .addHandler(reportMapping)
         .addHandler(orderParamAssembly)
         .addHandler(orderCreation);

3.1.3 任务拆解

有了架构设计后,我将重构任务拆解为8个小任务:

任务ID 任务描述 依赖关系
T1 定义责任链上下文数据结构 -
T2 定义IChannelAdapter接口 T1
T3 定义BusinessHandler接口 T1
T4 实现5个通用Handler T3
T5 实现统一对外接口 T2
T6 实现ChannelAdapter T2, T4
T7 编写单元测试 T4-T6
T8 集成测试与灰度发布 T7

关键点 :每个任务都是独立可验证的,这也为后续与Cursor协作奠定了基础。

3.2 Do阶段:与Cursor协同编码

3.2.1 建立清晰的上下文

在开始编码前,我做了三件事:

1、 用自然语言写了一个详细的架构说明文档

AI更擅长理解结构化信息,为了提升Cursor的代码生成质量,我首先为Cursor量身定制了一份"产品需求文档"(PRD)。有了这份文档,Cursor代码生成的有效率大大提升,大部分时间,我只需要微调业务细节,就可以直接使用它生成的代码。

包含:

  • 架构全景图
  • 核心设计模式(责任链、适配器、模板方法、策略)
  • 数据流转过程(Context如何在Handler间传递)
  • 扩展点说明(如何新增渠道、Handler、Action)

2、 定义好接口和数据结构

java 复制代码
/**
 * 渠道上下文
 */
@Data
@Builder
public class ChannelContext {
    private String requestId;        // 请求ID
    private String channelCode;      // 渠道编码
    private String action;           // 业务动作
    private Object bizData;          // 业务数据
    private Map<String, Object> handlerResults;  // Handler结果
    // ...
}

3、 编写第一个Handler作为示例

我手写了一个DuplicationCheckHandler,作为Cursor的参考模板:

java 复制代码
@Component
public class DuplicationCheckBusinessHandler 
    extends AbstractTypedBusinessHandler<CreateOrderBizData, Boolean> {
    
    @Override
    protected Boolean handleTyped(CreateOrderBizData input, ChannelContext context) {
        // 1. 根据outOrderNo查询订单
        RecycleOrderVO existOrder = orderService.queryByOutOrderNo(input.getOutOrderNo());
        
        // 2. 如果订单已存在,返回true(跳过后续Handler)
        if (existOrder != null) {
            log.info("订单已存在,跳过创建流程, outOrderNo={}", input.getOutOrderNo());
            context.putHandlerResult("ORDER_EXISTS", existOrder);
            return true;
        }
        
        return false;
    }
    
    @Override
    public int getOrder() {
        return 100;  // 最先执行
    }
}

完成了上述步骤后,我开始与Cursor结对编程。

参考DuplicationCheckBusinessHandler,帮我实现ChannelConfigBusinessHandler,用于获取渠道配置。需要满足:

  1. 从ExternalChannelConfigService获取配置
  2. 从PricedSourceService获取报价源配置
  3. 将结果保存到context的handlerResults中
  4. order设置为200

Cursor生成的代码:

java 复制代码
@Component
public class ChannelConfigBusinessHandler 
    extends AbstractTypedBusinessHandler<CreateOrderBizData, ExternalChannelConfig> {
    
    @Resource
    private ExternalChannelConfigService configService;
    
    @Resource
    private PricedSourceService pricedSourceService;
    
    @Override
    protected ExternalChannelConfig handleTyped(CreateOrderBizData input, ChannelContext context) {
        String channelCode = context.getChannelCode();
        
        // 1. 获取渠道配置
        ExternalChannelConfig channelConfig = configService.getByChannelCode(channelCode);
        if (channelConfig == null) {
            throw new ChannelException("CHANNEL_NOT_FOUND", "渠道配置不存在: " + channelCode);
        }
        
        // 2. 获取报价源配置
        PricedSourceBO pricedSource = pricedSourceService.getPricedSource(
            channelConfig.getOrgId(), input.getCategoryId());
        
        // 3. 保存到上下文
        context.putHandlerResult("CHANNEL_CONFIG", channelConfig);
        context.putHandlerResult("PRICED_SOURCE", pricedSource);
        
        log.info("获取渠道配置成功, channelCode={}, orgId={}", 
            channelCode, channelConfig.getOrgId());
        
        return channelConfig;
    }
    
    @Override
    public int getOrder() {
        return 200;
    }
}

可以看到,代码结构完全符合预期,异常处理到位,日志也很规范。

3.2.3 快速迭代的秘诀

在Do阶段,我总结出了几个与Cursor高效协作的技巧:

技巧1:更加明确的提示

不好的提示词:

帮我实现所有的Handler

好的提示词:

先实现DuplicationCheckHandler,用于检查订单是否已存在

技巧2:提供参考代码

复制代码
参考DuplicationCheckHandler的实现方式,生成ChannelConfigHandler

技巧3:明确约束条件

markdown 复制代码
要求:
1. 继承AbstractTypedBusinessHandler
2. order设置为200
3. 必须包含日志
4. 异常使用ChannelException

技巧4:及时Check

每生成一个Handler,我都会:

  • 检查是否符合接口规范
  • 运行单元测试
  • 代码Review(看命名、异常处理、日志)

3.3 Check阶段:代码审查与验证

3.3.1 多层次的检查机制

在Check阶段,我建立了三层验证机制:

第一层:IDE即时检查

  • Cursor的智能提示会提醒语法错误
  • SonarLint插件实时检查代码质量

第二层:单元测试

我让Cursor帮我生成了单元测试:

为DuplicationCheckHandler生成单元测试,需要覆盖:

  1. 订单不存在的场景
  2. 订单已存在的场景
  3. 查询异常的场景

Cursor生成的测试:

java 复制代码
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:scf-spring-test.xml")
public class DuplicationCheckBusinessHandlerTest {
    
    @Resource
    private DuplicationCheckBusinessHandler handler;
    
    @Resource
    private IRecycleOrderService orderService;
    
    @Test
    public void testHandle_OrderNotExists() {
        // 构建测试数据
        CreateOrderBizData input = CreateOrderBizData.builder()
                .outOrderNo("OUT_ORDER_001")
                .channelCode("TEST_CHANNEL")
                .productId(123456L)
                .build();
        
        ChannelContext context = ChannelContext.builder()
                .requestId("REQ_001")
                .channelCode("TEST_CHANNEL")
                .action("CREATE_ORDER")
                .bizData(input)
                .build();
        
        // 执行
        Boolean result = handler.handleTyped(input, context);
        
        // 验证
        System.out.println("订单不存在场景测试结果: " + result);
        System.out.println("Context中的ORDER_EXISTS: " + context.getHandlerResult("ORDER_EXISTS"));
    }
    
    @Test
    public void testHandle_OrderExists() {
        // 构建测试数据 - 订单已存在的情况
        CreateOrderBizData input = CreateOrderBizData.builder()
                .outOrderNo("OUT_ORDER_002")  // 这个订单号在数据库中已存在
                .channelCode("TEST_CHANNEL")
                .productId(123456L)
                .build();
        
        ChannelContext context = ChannelContext.builder()
                .requestId("REQ_002")
                .channelCode("TEST_CHANNEL")
                .action("CREATE_ORDER")
                .bizData(input)
                .build();
        
        // 执行
        Boolean result = handler.handleTyped(input, context);
        
        // 验证
        RecycleOrderVO existOrder = (RecycleOrderVO) context.getHandlerResult("ORDER_EXISTS");
        System.out.println("订单已存在场景测试结果: " + result);
        System.out.println("已存在订单信息: " + JsonUtils.toJsonWithoutNull(existOrder));
    }
}

第三层:集成测试

我在测试环境跑了完整的下单流程,验证Handler链的协作:

java 复制代码
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:scf-spring-test.xml")
public class ChannelUnifiedFacadeTest {
    
    @Resource
    private IChannelUnifiedFacade channelUnifiedFacade;
    
    @Resource
    private IRecycleOrderService orderService;
    
    @Test
    public void testCreateOrder_FullProcess() {
        // 构建完整的渠道请求
        CreateOrderBizData bizData = CreateOrderBizData.builder()
                .outOrderNo("TEST_OUT_ORDER_" + System.currentTimeMillis())
                .channelCode("KA")
                .productId(123456L)
                .categoryId(1L)
                .skuId(1001L)
                .userId(888888L)
                .picList(Collections.singletonList(
                    Pic.builder().picUrl("https://test.com/pic1.jpg").build()
                ))
                .build();
        
        ChannelRequest<CreateOrderBizData> request = ChannelRequest.<CreateOrderBizData>builder()
                .requestId("REQ_" + System.currentTimeMillis())
                .channelCode("KA")
                .action("CREATE_ORDER")
                .bizData(bizData)
                .build();
        
        // 执行完整流程
        ChannelResponse<Long> response = channelUnifiedFacade.process(request);
        
        // 打印结果
        System.out.println("=== 集成测试结果 ===");
        System.out.println("请求成功: " + response.isSuccess());
        System.out.println("订单ID: " + response.getData());
        System.out.println("响应码: " + response.getCode());
        System.out.println("响应信息: " + response.getMessage());
        
        // 验证订单确实创建成功
        if (response.getData() != null) {
            RecycleOrderVO order = orderService.getById(response.getData());
            System.out.println("订单详情: " + JsonUtils.toJsonWithoutNull(order));
            System.out.println("订单状态: " + order.getOrderState());
            System.out.println("外部订单号: " + order.getOutOrderNo());
        }
    }
}

3.4 Act:规则沉淀与持续优化

3.4.1 沉淀提示词模板

经过多次迭代,我总结出了一套可复用的提示词模板:

模板1:生成Handler

scss 复制代码
角色:你是一个Java后端工程师,熟悉责任链模式

任务:实现一个BusinessHandler,用于[具体功能]

要求:
1. 继承AbstractTypedBusinessHandler<P, R>
2. 输入类型是[P],输出类型是[R]
3. order值设置为[ORDER_VALUE]
4. 从context中获取[DEPENDENCY]
5. 将结果保存到context.putHandlerResult("[RESULT_KEY]", result)
6. 异常使用ChannelException,格式:new ChannelException(errorCode, message)
7. 添加日志:处理开始、处理成功、处理失败

参考代码:[粘贴参考Handler代码]

模板2:生成Adapter

less 复制代码
角色:你是一个Java架构师,熟悉适配器模式和责任链模式

任务:实现[渠道名]ChannelAdapter

要求:
1. 继承AbstractIChannelAdapter
2. 注入以下Handler:[列出Handler列表]
3. 根据action构建不同的Handler链:
   - CREATE_ORDER: [Handler顺序]
   - UPDATE_ORDER: [Handler顺序]
4. 添加@Component注解,value为渠道code,用于自动注入
5. 添加完善的日志

参考代码:[粘贴AChannelAdapter代码]

3.4.2 持续优化

在重构过程中,我发现了一个可以提取的通用模式:参数校验

最初,每个Handler都自己做参数校验:

java 复制代码
// DuplicationCheckHandler
if (StringUtils.isBlank(input.getOutOrderNo())) {
    throw new ChannelException("PARAM_ERROR", "外部订单号不能为空");
}

// ChannelConfigHandler
if (StringUtils.isBlank(input.getChannelCode())) {
    throw new ChannelException("PARAM_ERROR", "渠道编码不能为空");
}

在Act阶段,我提取了一个通用的ParamValidationHandler

java 复制代码
@Component
public class ParamValidationBusinessHandler 
    extends AbstractTypedBusinessHandler<Object, Void> {
    
    @Override
    protected Void handleTyped(Object input, ChannelContext context) {
        //.....
        return null;
    }
    
    @Override
    public int getOrder() {
        return 50; 
    }
}

现在,只需要在Handler链中加入这个Handler,就能统一处理参数校验了。

最后的话

这篇文章分享的是我们正在进行的探索 ,而非一个 "完美的成功案例"

我们的重构之旅才走了一小步,还有很多坑要踩,很多问题要解决。但我们选择边做边分享,因为:

真实的经验比完美的故事更有价值。

愿你在AI时代,既能拥抱新工具提升效率,又能保持对代码质量的追求、对架构美感的坚持。

关于作者,于天奇,侠客汇Java开发工程师。 想了解更多转转公司的业务实践,欢迎点击关注下方公众号
转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。 关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

相关推荐
AI分享猿35 分钟前
Java后端实战:SpringBoot接口遇异常请求,轻量WAF兼顾安全与性能
java·spring boot·安全
稚辉君.MCA_P8_Java44 分钟前
Gemini永久会员 Java中的四边形不等式优化
java·后端·算法
DKPT1 小时前
ZGC和G1收集器相比哪个更好?
java·jvm·笔记·学习·spring
稚辉君.MCA_P8_Java1 小时前
通义 插入排序(Insertion Sort)
数据结构·后端·算法·架构·排序算法
n***F8751 小时前
修改表字段属性,SQL总结
java·数据库·sql
q***69771 小时前
【Spring Boot】统一数据返回
java·spring boot·后端
Hollis Chuang1 小时前
Spring Boot 4.0 正式发布,人麻了。。。
java·spring boot·后端·spring
用户9949481198251 小时前
拒绝“人工智障”:618大促背后的 MateChat 智能导购架构演进与性能极致优化
架构
用户9949481198251 小时前
定义未来的交互:基于 MateChat 实现 NL2UI(自然语言生成界面)的架构探索
架构