技术茶馆公告

🍵 欢迎来到技术茶馆 🍵

这里是一个分享技术、交流学习的地方

技术札记 | 茶馆周刊 | 工具书签 | 作品展示

让我们一起品茗技术,共同成长

Skip to content

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

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

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

我所在的团队负责一个多版本拍卖业务系统,目前有十几个不同版本的拍卖模式,每个版本对应一个代码分支。每个版本都有自己独特的协议规范、业务流程和特殊要求。随着业务发展,系统逐渐的遇到了一系列问题:

核心问题

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

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

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

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

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

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

每次新增拍卖模式版本,都是一次"复制粘贴+魔改"的过程:

  • 复制一个相似版本的策略类
  • 对照需求文档,逐个方法修改参数、逻辑
  • 开发写入个性化逻辑

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

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

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

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

一、重构方案设计

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

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

三大技术策略

  1. 分层解耦:将协议层、业务层、数据层清晰分离,让新人快速定位代码
  2. 责任链模式:每个Handler职责单一,可独立开发、测试、复用
  3. 适配器模式:新版本只需实现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 原有架构的痛点分析

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

php
<?php

class KaVersionBusinessStrategy extends BaseVersionBusinessStrategy 
{
    /**
     * 获取竞价结果
     */
    public function getBiddingPrice(...$params): BiddingPriceResult 
    {
        // 1. 参数校验(50行)
        // 2. 解密验签(30行)
        // 3. 获取版本配置(40行)
        // 4. 调用竞价服务(60行)
        // 5. 计算佣金(50行)
        // 6. 组装响应(30行)
        // 7. 加密签名(20行)
        // ... 总计500+行
    }
    
    /**
     * 创建拍卖订单
     */
    public function createAuctionOrder(...$params): CreateOrderResult 
    {
        // 同样的模式,又是几百行...
    }
}

问题总结

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

3.1.2 新架构设计思路

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

┌─────────────────────────────────────────┐
│         Facade层(统一入口)              │
│    VersionUnifiedFacade                 │
└─────────────────┬───────────────────────┘

┌─────────────────▼───────────────────────┐
│         Adapter层(版本适配)             │
│    IVersionAdapter                      │
│    - KaVersionAdapter                   │
│    - AVersionAdapter                    │
└─────────────────┬───────────────────────┘

┌─────────────────▼───────────────────────┐
│         Handler层(责任链)               │
│    - DuplicationCheckHandler            │
│    - VersionConfigHandler               │
│    - ReportMappingHandler               │
│    - OrderParamAssemblyHandler          │
│    - OrderCreationHandler               │
└─────────────────────────────────────────┘

核心设计点

  1. 分层解耦

    • Facade层:统一入口,参数校验、异常处理
    • Adapter层:版本适配,协议转换
    • Handler层:责任链模式,每个Handler职责单一
  2. 可复用的Handler

    • DuplicationCheckHandler → 幂等性检查(通用)
    • VersionConfigHandler → 版本配置获取(通用)
    • ReportMappingHandler → 报告映射(通用)
    • OrderParamAssemblyHandler → 参数组装(可定制)
    • OrderCreationHandler → 订单创建(通用)
  3. 灵活的编排机制

php
// A版本:标准流程
$chain = new HandlerChain();
$chain->addHandler(new DuplicationCheckHandler())
      ->addHandler(new VersionConfigHandler())
      ->addHandler(new ReportMappingHandler())
      ->addHandler(new OrderParamAssemblyHandler())
      ->addHandler(new OrderCreationHandler());

// KA版本:额外增加权限校验
$chain = new HandlerChain();
$chain->addHandler(new DuplicationCheckHandler())
      ->addHandler(new KaAuthCheckHandler())  // KA特有
      ->addHandler(new VersionConfigHandler())
      ->addHandler(new ReportMappingHandler())
      ->addHandler(new OrderParamAssemblyHandler())
      ->addHandler(new OrderCreationHandler());

3.1.3 任务拆解

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

任务ID任务描述依赖关系
T1定义责任链上下文数据结构-
T2定义IVersionAdapter接口T1
T3定义BusinessHandler接口T1
T4实现5个通用HandlerT3
T5实现统一对外接口T2
T6实现VersionAdapterT2, 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、定义好接口和数据结构

php
<?php

/**
 * 版本上下文
 */
class VersionContext 
{
    private string $requestId;        // 请求ID
    private string $versionCode;      // 版本编码
    private string $action;           // 业务动作
    private $bizData;                 // 业务数据
    private array $handlerResults = [];  // Handler结果
    
    public function __construct(
        string $requestId,
        string $versionCode,
        string $action,
        $bizData
    ) {
        $this->requestId = $requestId;
        $this->versionCode = $versionCode;
        $this->action = $action;
        $this->bizData = $bizData;
    }
    
    public function getRequestId(): string 
    {
        return $this->requestId;
    }
    
    public function getVersionCode(): string 
    {
        return $this->versionCode;
    }
    
    public function getAction(): string 
    {
        return $this->action;
    }
    
    public function getBizData() 
    {
        return $this->bizData;
    }
    
    public function putHandlerResult(string $key, $value): void 
    {
        $this->handlerResults[$key] = $value;
    }
    
    public function getHandlerResult(string $key) 
    {
        return $this->handlerResults[$key] ?? null;
    }
    
    public function getHandlerResults(): array 
    {
        return $this->handlerResults;
    }
}

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

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

php
<?php

class DuplicationCheckBusinessHandler extends AbstractTypedBusinessHandler 
{
    private IAuctionOrderService $orderService;
    
    public function __construct(IAuctionOrderService $orderService) 
    {
        $this->orderService = $orderService;
    }
    
    protected function handleTyped(CreateOrderBizData $input, VersionContext $context): bool 
    {
        // 1. 根据outOrderNo查询订单
        $existOrder = $this->orderService->queryByOutOrderNo($input->getOutOrderNo());
        
        // 2. 如果订单已存在,返回true(跳过后续Handler)
        if ($existOrder !== null) {
            \Log::info("订单已存在,跳过创建流程", [
                'outOrderNo' => $input->getOutOrderNo()
            ]);
            $context->putHandlerResult('ORDER_EXISTS', $existOrder);
            return true;
        }
        
        return false;
    }
    
    public function getOrder(): int 
    {
        return 100;  // 最先执行
    }
    
    public function supports(string $action): bool 
    {
        return $action === 'CREATE_ORDER';
    }
}

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

3.2.2 与Cursor的第一次协作

我的提示词

参考DuplicationCheckBusinessHandler的实现方式,帮我实现VersionConfigBusinessHandler,用于获取版本配置。需要满足:

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

Cursor生成的代码

php
<?php

class VersionConfigBusinessHandler extends AbstractTypedBusinessHandler 
{
    private ExternalVersionConfigService $configService;
    private PricedSourceService $pricedSourceService;
    
    public function __construct(
        ExternalVersionConfigService $configService,
        PricedSourceService $pricedSourceService
    ) {
        $this->configService = $configService;
        $this->pricedSourceService = $pricedSourceService;
    }
    
    protected function handleTyped(CreateOrderBizData $input, VersionContext $context): ExternalVersionConfig 
    {
        $versionCode = $context->getVersionCode();
        
        // 1. 获取版本配置
        $versionConfig = $this->configService->getByVersionCode($versionCode);
        if ($versionConfig === null) {
            throw new VersionException('VERSION_NOT_FOUND', "版本配置不存在: {$versionCode}");
        }
        
        // 2. 获取竞价源配置
        $pricedSource = $this->pricedSourceService->getPricedSource(
            $versionConfig->getOrgId(),
            $input->getCategoryId()
        );
        
        // 3. 保存到上下文
        $context->putHandlerResult('VERSION_CONFIG', $versionConfig);
        $context->putHandlerResult('PRICED_SOURCE', $pricedSource);
        
        \Log::info("获取版本配置成功", [
            'versionCode' => $versionCode,
            'orgId' => $versionConfig->getOrgId()
        ]);
        
        return $versionConfig;
    }
    
    public function getOrder(): int 
    {
        return 200;
    }
    
    public function supports(string $action): bool 
    {
        return $action === 'CREATE_ORDER';
    }
}

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

3.2.3 快速迭代的秘诀

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

技巧1:更加明确的提示

不好的提示词:

帮我实现所有的Handler

好的提示词:

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

技巧2:提供参考代码

参考DuplicationCheckHandler的实现方式,生成VersionConfigHandler

技巧3:明确约束条件

要求:

  1. 继承AbstractTypedBusinessHandler
  2. order设置为200
  3. 必须包含日志
  4. 异常使用VersionException

技巧4:及时Check

每生成一个Handler,我都会:

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

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

3.3.1 多层次的检查机制

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

第一层:IDE即时检查

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

第二层:单元测试

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

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

  • 订单不存在的场景
  • 订单已存在的场景
  • 查询异常的场景

Cursor生成的测试

php
<?php

use PHPUnit\Framework\TestCase;

class DuplicationCheckBusinessHandlerTest extends TestCase 
{
    private DuplicationCheckBusinessHandler $handler;
    private IAuctionOrderService $orderService;
    
    protected function setUp(): void 
    {
        $this->orderService = $this->createMock(IAuctionOrderService::class);
        $this->handler = new DuplicationCheckBusinessHandler($this->orderService);
    }
    
    public function testHandle_OrderNotExists(): void 
    {
        // 构建测试数据
        $input = new CreateOrderBizData();
        $input->setOutOrderNo('OUT_ORDER_001');
        $input->setVersionCode('TEST_VERSION');
        $input->setProductId(123456);
        
        $context = new VersionContext(
            'REQ_001',
            'TEST_VERSION',
            'CREATE_ORDER',
            $input
        );
        
        // Mock:订单不存在
        $this->orderService->expects($this->once())
            ->method('queryByOutOrderNo')
            ->with('OUT_ORDER_001')
            ->willReturn(null);
        
        // 执行
        $result = $this->handler->handleTyped($input, $context);
        
        // 验证
        $this->assertFalse($result);
        $this->assertNull($context->getHandlerResult('ORDER_EXISTS'));
    }
    
    public function testHandle_OrderExists(): void 
    {
        // 构建测试数据 - 订单已存在的情况
        $input = new CreateOrderBizData();
        $input->setOutOrderNo('OUT_ORDER_002');
        $input->setVersionCode('TEST_VERSION');
        $input->setProductId(123456);
        
        $context = new VersionContext(
            'REQ_002',
            'TEST_VERSION',
            'CREATE_ORDER',
            $input
        );
        
        // Mock:订单已存在
        $existOrder = new AuctionOrderVO();
        $existOrder->setId(999);
        $existOrder->setOutOrderNo('OUT_ORDER_002');
        
        $this->orderService->expects($this->once())
            ->method('queryByOutOrderNo')
            ->with('OUT_ORDER_002')
            ->willReturn($existOrder);
        
        // 执行
        $result = $this->handler->handleTyped($input, $context);
        
        // 验证
        $this->assertTrue($result);
        $this->assertNotNull($context->getHandlerResult('ORDER_EXISTS'));
        $this->assertEquals(999, $context->getHandlerResult('ORDER_EXISTS')->getId());
    }
}

第三层:集成测试

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

php
<?php

use PHPUnit\Framework\TestCase;

class VersionUnifiedFacadeTest extends TestCase 
{
    private IVersionUnifiedFacade $versionUnifiedFacade;
    private IAuctionOrderService $orderService;
    
    protected function setUp(): void 
    {
        // 初始化服务...
    }
    
    public function testCreateOrder_FullProcess(): void 
    {
        // 构建完整的版本请求
        $bizData = new CreateOrderBizData();
        $bizData->setOutOrderNo('TEST_OUT_ORDER_' . time());
        $bizData->setVersionCode('KA');
        $bizData->setProductId(123456);
        $bizData->setCategoryId(1);
        $bizData->setSkuId(1001);
        $bizData->setUserId(888888);
        $bizData->setPicList([
            new Pic('https://test.com/pic1.jpg')
        ]);
        
        $request = new VersionRequest();
        $request->setRequestId('REQ_' . time());
        $request->setVersionCode('KA');
        $request->setAction('CREATE_ORDER');
        $request->setBizData($bizData);
        
        // 执行完整流程
        $response = $this->versionUnifiedFacade->process($request);
        
        // 打印结果
        echo "=== 集成测试结果 ===\n";
        echo "请求成功: " . ($response->isSuccess() ? 'true' : 'false') . "\n";
        echo "订单ID: " . $response->getData() . "\n";
        echo "响应码: " . $response->getCode() . "\n";
        echo "响应信息: " . $response->getMessage() . "\n";
        
        // 验证订单确实创建成功
        if ($response->getData() !== null) {
            $order = $this->orderService->getById($response->getData());
            echo "订单详情: " . json_encode($order, JSON_UNESCAPED_UNICODE) . "\n";
            echo "订单状态: " . $order->getOrderState() . "\n";
            echo "外部订单号: " . $order->getOutOrderNo() . "\n";
        }
    }
}

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

3.4.1 沉淀提示词模板

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

模板1:生成Handler

角色:你是一个PHP后端工程师,熟悉责任链模式

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

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

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

模板2:生成Adapter

角色:你是一个PHP架构师,熟悉适配器模式和责任链模式

任务:实现[版本名]VersionAdapter

要求:
1. 继承AbstractIVersionAdapter
2. 注入以下Handler:[列出Handler列表]
3. 根据action构建不同的Handler链:
   - CREATE_ORDER: [Handler顺序]
   - UPDATE_ORDER: [Handler顺序]
4. 添加完善的日志

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

3.4.2 持续优化

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

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

php
// DuplicationCheckHandler
if (empty($input->getOutOrderNo())) {
    throw new VersionException('PARAM_ERROR', '外部订单号不能为空');
}

// VersionConfigHandler
if (empty($input->getVersionCode())) {
    throw new VersionException('PARAM_ERROR', '版本编码不能为空');
}

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

php
<?php

class ParamValidationBusinessHandler extends AbstractTypedBusinessHandler 
{
    protected function handleTyped($input, VersionContext $context): void 
    {
        // 根据action和input类型进行参数校验
        $action = $context->getAction();
        
        if ($action === 'CREATE_ORDER' && $input instanceof CreateOrderBizData) {
            $this->validateCreateOrder($input);
        } elseif ($action === 'UPDATE_ORDER' && $input instanceof UpdateOrderBizData) {
            $this->validateUpdateOrder($input);
        }
    }
    
    private function validateCreateOrder(CreateOrderBizData $input): void 
    {
        if (empty($input->getOutOrderNo())) {
            throw new VersionException('PARAM_ERROR', '外部订单号不能为空');
        }
        
        if (empty($input->getVersionCode())) {
            throw new VersionException('PARAM_ERROR', '版本编码不能为空');
        }
        
        // ... 其他校验
    }
    
    public function getOrder(): int 
    {
        return 50;  // 最先执行
    }
}

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

最后的话

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

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

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

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