回收系統架構演進實戰:與Cursor結對掃清系統混沌
"最好的代碼不是一次寫對的,而是不斷重構出來的。" —— Martin Fowler
前言:技術債務帶來的效能瓶頸
我所在的團隊負責一個多渠道回收業務系統,接入了十幾個外部渠道。每個渠道都有自己獨特的協議規范、業務流程和特殊要求。隨著業務發展,系統逐漸的遇到了一系列問題:
- 代碼膨脹:單個策略類代碼超過800行,包含大量if-else判斷,可讀性極差
- 重復代碼:不同渠道間有70%的代碼邏輯相似,有很多都是邏輯類似,但參數名稱、結構不一致,難以復用
- 難以測試:業務邏輯與協議處理耦合,單元測試覆蓋率不到30%,主要依賴人工測試
- 維護困難:改一個渠道的邏輯,需要理解整個類的800行代碼,調研成本極高
技術債務不僅影響代碼質量,更直接沖擊了團隊的研發效能。我們在日常開發中遭遇了三個核心瓶頸:
① 新人上手困難,培養周期長
新同學加入團隊后,需要2-3個月才能理解現有代碼邏輯:
- 單個策略類800+行,找一個業務邏輯點要翻半天
- 協議處理、業務邏輯、數據操作混在一起,理不清頭緒
- 沒有清晰的架構文檔,只能靠"師傅帶徒弟"口口相傳
② 需求承接效率低,開發成本高
每次接入新渠道,都是一次"復制粘貼+魔改"的過程:
- 復制一個相似渠道的策略類
- 對照需求文檔,逐個方法修改參數、邏輯
- 開發寫入個性化邏輯
③ 測試回歸成本高,質量保障困難
代碼耦合導致"牽一發而動全身":
- 修改一個渠道的邏輯,可能影響其他渠道,測試范圍難以評估
- 單元測試覆蓋率低(30%),主要靠人工回歸測試
- 每次發版前,測試同學要回歸受影響渠道的核心流程
此時我的腦子里出現了一個想法:為了早點下班~ 必須重構了!
一、 重構方案設計
針對上述的三大痛點,我們很快制定了初步的重構規劃:
痛點 | 重構目標 | 預期效果 |
新人上手慢 | 架構清晰化、職責單一化 | 培養周期從3周降到1周,調研成本降低70% |
需求效率低 | 通用邏輯可復用、新增渠道可配置化 | 新渠道接入成本降低70% |
測試成本高 | 解耦分層、單元測試覆蓋率≥80% | 回歸測試時間減半,Code Review時間降低80% |
三大技術策略:
- 分層解耦:將協議層、業務層、數據層清晰分離,讓新人快速定位代碼
- 責任鏈模式:每個Handler職責單一,可獨立開發、測試、復用
- 適配器模式:新渠道只需實現Adapter,組裝Handler即可
目標有了,問題也來了:這是一個對接10余個活躍渠道的業務系統,系統復雜,且每日有數百萬請求打進系統,如何在不影響線上業務的前提下進行重構?
漸進式落地,而非一步到位
出于對生產環境的敬畏之心,我們不敢"一刀切"地推倒重來。經過評估,我們選擇了漸進式重構策略: 整體改進是一個持續的過程,目標清晰但落地需要時間。我們會逐步驗證架構可行性,再慢慢進行遷移,確保每一步都是穩定可控的。
階段規劃:
階段 | 時間 | 目標 |
第一階段 | 1-2周 | 架構驗證:完成1個渠道的規范化改造 |
第二階段 | 1-2個月 | 核心遷移:完成50%渠道的遷移 |
第三階段 | 3-6個月 | 全量遷移:剩余渠道全部遷移 |
第四階段 | 持續優化 | 補充協議Handler、完善監控 |
關鍵原則
- 新老并行:新架構與舊代碼并存,通過配置開關灰度切換
- 小步驗證:每完成一個渠道,立即上線驗證,確保穩定后再推進下一個
- 數據對比:新老架構同時執行,對比結果一致性,發現問題及時回滾
二、 基于PDCA思想驅動的人機協作模式
PDCA(Plan-Do-Check-Act)是戴明環的核心思想,特別適合用于:
- 復雜系統的漸進式改進:將大目標拆解為小任務
- 風險可控的迭代驗證:每個小步驟都可驗證
- 知識沉淀與持續優化:從單次需求到全局改進
在與AI協作時,PDCA的價值更加明顯:
PDCA階段 | 人的職責 | AI的職責 | 協作模式 |
Plan | 架構設計、任務拆解、確定路徑 | 提供設計建議、分析技術方案 | 人主導 ,AI輔助 |
Do | 編寫核心邏輯、Review代碼 | 生成模板代碼、實現細節 | 結對編程 |
Check | 驗證業務邏輯、集成測試 | 生成單元測試、代碼檢查 | 人驗證 ,AI執行 |
Act | 總結經驗、沉淀規則 | 整理文檔、提取模式 | 共同迭代 |
三、 實戰案例:渠道統一Handler架構重構
3.1 Plan階段:架構設計與任務拆解
3.1.1 原有架構的痛點分析
重構前,我們的代碼結構是這樣的:
@Service("kaChannelBusinessStrategy")
publicclass KaChannelBusinessStrategy extends BaseChannelBusinessStrategy {
public EnquiryPriceResult getEnquiryPrice(...) {
// 1. 參數校驗(50行)
// 2. 解密驗簽(30行)
// 3. 獲取渠道配置(40行)
// 4. 調用報價服務(60行)
// 5. 計算傭金(50行)
// 6. 組裝響應(30行)
// 7. 加密簽名(20行)
// ... 總計500+行
}
public CreateOrderResult createRecycleOrder(...) {
// 同樣的模式,又是幾百行...
}
}問題總結:
- 職責不清:一個類既處理協議,又處理業務,還管數據
- 代碼重復:AChannelBusinessStrategy、BChannelBusinessStrategy里有大量相似代碼
- 難以擴展:新增一個渠道,需要復制粘貼800行代碼
- 測試困難:依賴過多,mock成本高
3.1.2 新架構設計思路
本次我們的設計,是一個基于責任鏈模式的新架構:
圖片
核心設計點:
- 分層解耦
Facade層:統一入口,參數校驗、異常處理
Adapter層:渠道適配,協議轉換
Handler層:責任鏈模式,每個Handler職責單一
- 可復用的Handler
DuplicationCheckHandler → 冪等性檢查(通用)
ChannelConfigHandler → 渠道配置獲取(通用)
ReportMappingHandler → 報告映射(通用)
OrderParamAssemblyHandler → 參數組裝(可定制)
OrderCreationHandler → 訂單創建(通用)- 靈活的編排機制
// 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、 定義好接口和數據結構
/**
* 渠道上下文
*/
@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的參考模板:
@Component
publicclass 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);
returntrue;
}
returnfalse;
}
@Override
public int getOrder() {
return100; // 最先執行
}
}完成了上述步驟后,我開始與Cursor結對編程。
參考DuplicationCheckBusinessHandler,幫我實現ChannelConfigBusinessHandler,用于獲取渠道配置。需要滿足:
- 從ExternalChannelConfigService獲取配置
- 從PricedSourceService獲取報價源配置
- 將結果保存到context的handlerResults中
- order設置為200
Cursor生成的代碼:
@Component
publicclass 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) {
thrownew 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() {
return200;
}
}可以看到,代碼結構完全符合預期,異常處理到位,日志也很規范。
3.2.3 快速迭代的秘訣
在Do階段,我總結出了幾個與Cursor高效協作的技巧:
技巧1:更加明確的提示
不好的提示詞:
幫我實現所有的Handler
好的提示詞:
先實現DuplicationCheckHandler,用于檢查訂單是否已存在
技巧2:提供參考代碼
參考DuplicationCheckHandler的實現方式,生成ChannelConfigHandler技巧3:明確約束條件
要求:
1. 繼承AbstractTypedBusinessHandler
2. order設置為200
3. 必須包含日志
4. 異常使用ChannelException技巧4:及時Check
每生成一個Handler,我都會:
- 檢查是否符合接口規范
- 運行單元測試
- 代碼Review(看命名、異常處理、日志)
3.3 Check階段:代碼審查與驗證
3.3.1 多層次的檢查機制
在Check階段,我建立了三層驗證機制:
第一層:IDE即時檢查
- Cursor的智能提示會提醒語法錯誤
- SonarLint插件實時檢查代碼質量
第二層:單元測試
我讓Cursor幫我生成了單元測試:
為DuplicationCheckHandler生成單元測試,需要覆蓋:
- 訂單不存在的場景
- 訂單已存在的場景
- 查詢異常的場景
Cursor生成的測試:
@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鏈的協作:
@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
角色:你是一個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
角色:你是一個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都自己做參數校驗:
// DuplicationCheckHandler
if (StringUtils.isBlank(input.getOutOrderNo())) {
throw new ChannelException("PARAM_ERROR", "外部訂單號不能為空");
}
// ChannelConfigHandler
if (StringUtils.isBlank(input.getChannelCode())) {
throw new ChannelException("PARAM_ERROR", "渠道編碼不能為空");
}在Act階段,我提取了一個通用的ParamValidationHandler:
@Component
publicclass ParamValidationBusinessHandler
extends AbstractTypedBusinessHandler<Object, Void> {
@Override
protected Void handleTyped(Object input, ChannelContext context) {
//.....
returnnull;
}
@Override
public int getOrder() {
return50;
}
}現在,只需要在Handler鏈中加入這個Handler,就能統一處理參數校驗了。
最后的話
這篇文章分享的是我們正在進行的探索,而非一個 "完美的成功案例" 。
我們的重構之旅才走了一小步,還有很多坑要踩,很多問題要解決。但我們選擇邊做邊分享,因為:
真實的經驗比完美的故事更有價值。
愿你在AI時代,既能擁抱新工具提升效率,又能保持對代碼質量的追求、對架構美感的堅持。
關于作者,于天奇,俠客匯Java開發工程師。 想了解更多轉轉公司的業務實踐。


































