# 安全解冻逻辑设计文档 ## 概述 安全解冻是物品冻结系统中的核心功能,用于处理冻结物品被部分或完全消耗后的解冻操作。与普通解冻不同,安全解冻需要处理复杂的补足逻辑。 ## 业务场景 ### 典型场景 1. **用户冻结100个物品**(用于交易订单) 2. **冻结堆被部分消耗60个**(其他操作消耗了冻结物品) 3. **用户要求解冻**(取消交易订单) 4. **系统需要解冻100个物品**(恢复原始冻结数量) ### 问题分析 - 冻结堆当前只有40个,但用户期望解冻100个 - 需要从其他地方补足60个差额 - **关键原则**:解冻是状态变更,不是物品消耗 ## 核心设计原则 ### 1. 解冻 ≠ 消耗 - **解冻**:改变物品的冻结状态,从"冻结"变为"可用" - **消耗**:减少物品的总数量 - **补足来源**:只能从其他冻结堆中解冻,不能从可用物品中扣除 ### 2. 数量守恒 - 解冻前后,用户的物品总数量不变 - 只是冻结状态发生变化 ### 3. 日志类型 - **解冻操作**:只产生解冻日志(ItemFreezeLog) - **不产生**:物品交易日志(ItemTransactionLog) ## 安全解冻逻辑流程 ### 输入参数 - `freezeLogId`: 要解冻的冻结日志ID ### 处理流程 #### 第一步:验证和获取基础信息 ```php 1. 检查事务状态 2. 查找冻结日志记录 3. 验证冻结日志的有效性 4. 查找对应的冻结物品堆 ``` #### 第二步:计算补足需求 ```php $originalFrozenQuantity = $freezeLog->quantity; // 原始冻结数量 $currentQuantity = $frozenItem->quantity; // 当前剩余数量 $shortageQuantity = $originalFrozenQuantity - $currentQuantity; // 需要补足的数量 ``` #### 第三步:分支处理 ##### 分支A:无需补足(shortageQuantity == 0) ```php if ($shortageQuantity == 0) { // 冻结堆完整,直接解冻 return 正常解冻流程(); } ``` ##### 分支B:需要补足(shortageQuantity > 0) ```php if ($shortageQuantity > 0) { // 包含两种情况: // 1. 部分消耗:currentQuantity > 0,需要补足部分差额 // 2. 完全消耗:currentQuantity <= 0,需要补足全部数量 // 两种情况的处理逻辑相同:都是从其他冻结堆中解冻来补足 return 补足解冻流程(); } ``` ### 补足解冻详细流程 #### 1. 查找补足来源 ```php // 查找用户其他冻结物品(排除当前冻结堆) $otherFrozenItems = ItemUser::where('user_id', $userId) ->where('item_id', $itemId) ->where('instance_id', $instanceId) ->where('is_frozen', true) ->where('frozen_log_id', '!=', $freezeLogId) // 排除当前冻结堆 ->where('quantity', '>', 0) ->orderBy('expire_at') // 优先使用即将过期的 ->lockForUpdate() // 锁定防并发 ->get(); ``` #### 2. 验证补足能力 ```php $totalOtherFrozenQuantity = $otherFrozenItems->sum('quantity'); if ($totalOtherFrozenQuantity < $shortageQuantity) { throw new Exception("其他冻结数量不足以补足"); } ``` #### 3. 执行补足操作 ```php foreach ($otherFrozenItems as $otherFrozenItem) { $unfreezeQuantity = min($otherFrozenItem->quantity, $remainingShortage); // 从其他冻结堆中减少数量 $otherFrozenItem->quantity -= $unfreezeQuantity; $otherFrozenItem->save(); // 记录解冻日志 ItemFreezeLog::createLog( $userId, $itemId, $instanceId, $unfreezeQuantity, FREEZE_ACTION_TYPE::UNFREEZE, "补足解冻:从冻结堆{$otherFrozenItem->frozen_log_id}解冻{$unfreezeQuantity}个用于补足解冻日志{$freezeLogId}" ); // 触发事件 event(new ItemQuantityChanged(...)); $remainingShortage -= $unfreezeQuantity; if ($remainingShortage <= 0) break; } ``` #### 4. 恢复目标冻结堆 ```php // 将目标冻结堆恢复到原始数量 $frozenItem->quantity = $originalFrozenQuantity; $frozenItem->save(); ``` #### 5. 执行最终解冻 ```php // 创建解冻日志 $unfreezeLog = ItemFreezeLog::createLog( $userId, $itemId, $instanceId, $originalFrozenQuantity, FREEZE_ACTION_TYPE::UNFREEZE, "安全解冻操作,原冻结日志ID: {$freezeLogId},补足差额: {$shortageQuantity}" ); // 解冻物品 $frozenItem->is_frozen = false; $frozenItem->frozen_log_id = null; $frozenItem->save(); // 触发解冻事件 event(new ItemQuantityChanged(...)); ``` ## 异常处理 ### 1. 冻结日志不存在 ```php throw new Exception("冻结日志 {$freezeLogId} 不存在"); ``` ### 2. 冻结物品不存在 ```php throw new Exception("未找到冻结日志 {$freezeLogId} 对应的冻结物品"); ``` ### 3. 其他冻结数量不足 ```php throw new Exception("需要补足 {$shortageQuantity},但用户其他冻结数量只有 {$otherFrozenQuantity}"); ``` ### 4. 锁定后数量变化 ```php throw new Exception("锁定后用户其他冻结数量只有 {$actualOtherFrozenQuantity}"); ``` ## 返回结果 ### 成功返回 ```php return [ 'success' => true, 'user_id' => $userId, 'item_id' => $itemId, 'instance_id' => $instanceId, 'unfrozen_quantity' => $originalFrozenQuantity, // 实际解冻数量 'shortage_compensated' => $shortageQuantity, // 补足的差额 'user_item_id' => $frozenItem->id, 'unfreeze_log_id' => $unfreezeLog->id, 'compensation_details' => $unfreezeDetails, // 补足详情 ]; ``` ## 事件触发 ### 1. 补足过程事件 - **事件类型**:ItemQuantityChanged - **触发时机**:每次从其他冻结堆减少数量时 - **事件数据**:包含补足操作的详细信息 ### 2. 解冻完成事件 - **事件类型**:ItemQuantityChanged - **触发时机**:最终解冻操作完成时 - **事件数据**:包含解冻操作的完整信息 ## 并发安全 ### 锁定机制 - 使用 `lockForUpdate()` 锁定相关记录 - 锁定后重新验证数量,防止并发修改 - 确保补足操作的原子性 ### 事务保护 - 整个操作在数据库事务中执行 - 任何步骤失败都会回滚所有变更 - 保证数据一致性 ## 与普通解冻的区别 | 特性 | 普通解冻 | 安全解冻 | |------|----------|----------| | 处理场景 | 冻结堆完整 | 冻结堆被消耗 | | 补足机制 | 无需补足 | 从其他冻结堆补足 | | 异常处理 | 严格抛异常 | 智能处理各种情况 | | 返回信息 | 基础信息 | 详细补足信息 | | 性能开销 | 较低 | 较高(需要查找和处理其他冻结堆) | ## 使用建议 ### 何时使用安全解冻 1. **用户主动取消操作**:如取消交易订单 2. **系统自动回滚**:如订单超时自动取消 3. **异常恢复场景**:如系统故障后的数据恢复 ### 何时使用普通解冻 1. **确定冻结堆完整**:如刚冻结后立即解冻 2. **性能敏感场景**:如高频操作 3. **简单业务逻辑**:如临时冻结后快速解冻