竞价逻辑应抽象至模型层,用事务+状态机+策略模式实现:定义BidStrategyInterface及各类策略,服务层用AR事务保障多表一致性,状态驱动生命周期校验,前后端双重校验防超卖与频控。

竞价逻辑在后台不能靠控制器硬写一堆SQL或if-else堆砌,核心是把规则抽象进模型层,用事务+状态机+策略模式组合落地。
把竞价规则拆成可配置的策略类
不要在Controller里写“如果出价>底价且时间未截止就成交”这种裸逻辑。应定义BidStrategyInterface,实现不同场景策略:
- 普通竞价策略:按价格优先、时间优先排序,取最高有效出价
- 阶梯加价策略:强制每次加价不得低于当前最高价+步长(从数据库读取)
-
保证金校验策略:调用
UserAccount::hasEnoughDeposit()前置验证 -
黑名单拦截策略:检查用户是否在
bid_blacklist表中
用AR事务封装多表一致性操作
一次竞价可能涉及更新标的、生成报价记录、扣减用户余额、写入成交日志四张表。必须用事务包裹,且放在BidService这类服务类中,而非控制器:
- 在
common\services\BidService.php中定义placeBid()方法 - 方法内开启
Yii::$app->db->beginTransaction() - 失败时统一
$transaction->rollBack(),成功则commit() - 控制器只负责接收参数、调用
BidService::placeBid()、返回JSON结果
状态驱动竞价生命周期
标的(如拍卖品)应有明确状态字段:status(draft/published/closed/aborted),所有竞价动作需校验当前状态是否允许:
- 用户提交报价前,查
AuctionItem::findOne($id)->canAcceptBid() - 该方法内部判断:
status === 'published'且start_time ≤ now ≤ end_time - 成交触发后,自动调用
$item->markAsClosed()并广播事件
前端交互与后端校验双保险
前端显示的“当前最高价”“剩余时间”等信息,不能直接用于后端判断——必须以服务端时间+数据库实时快照为准:
- 提交竞价时,后端重新查一次标的最新状态和历史最高出价
- 用
SELECT ... FOR UPDATE锁定标的行,防止超卖 - 对用户IP、设备指纹、会话ID做轻量级频控(如5秒内仅允1次提交)


















