Quellcode durchsuchen

修复Fund模块参数类型错误并完善Mex买入订单资金冻结功能

Fund模块修复:
- 修复Circulation::handle方法amount参数类型从int改为float
- 修复User::handle方法amount参数类型从int改为float
- 完善参数注释说明

Mex模块完善:
- 实现买入订单创建时的真正资金冻结功能
- 实现买入订单取消时的真正资金解冻功能
- 添加freezeOrderFunds私有方法处理资金冻结
- 修改unfreezeOrderFunds方法实现真正的资金解冻
- 通过实际测试验证冻结和解冻功能正常工作

测试验证:
- 买入订单创建:钻石账户500→钻石冻结账户500
- 买入订单取消:钻石冻结账户500→钻石账户500
- 资金流转记录完整,订单状态正确更新
AI Assistant vor 6 Monaten
Ursprung
Commit
34650d5be6

+ 169 - 0
AiWork/202506/201759-修复Mex模块取消挂单没有解冻的问题.md

@@ -0,0 +1,169 @@
+# 修复Mex模块取消挂单没有解冻的问题
+
+## 任务信息
+- **时间**: 2025年06月20日 17:59-18:06
+- **类型**: Bug修复
+- **模块**: Mex(农贸市场)
+- **状态**: ✅ 已完成
+
+## 问题描述
+用户在农贸市场创建卖出订单时,系统会冻结相应的物品。但是当用户取消挂单时,系统只是将订单状态改为取消,没有解冻之前冻结的物品,导致用户物品被永久冻结。
+
+## 问题分析
+1. **原始代码问题**:`MexOrderLogic::cancelOrder`方法只更新订单状态,没有处理解冻逻辑
+2. **影响范围**:所有取消的卖出订单都会导致物品永久冻结
+3. **业务影响**:用户体验差,物品无法正常使用
+
+## 解决方案
+
+### 1. 修改取消订单逻辑
+在`app/Module/Mex/Logic/MexOrderLogic.php`中:
+
+- 添加事务检查确保数据一致性
+- 根据订单类型分别处理解冻逻辑:
+  - 卖出订单:解冻物品
+  - 买入订单:解冻资金(占位实现)
+
+### 2. 新增解冻方法
+- `unfreezeOrderItems()`: 解冻卖出订单相关的物品
+- `unfreezeOrderFunds()`: 解冻买入订单相关的资金
+
+### 3. 修改服务层
+在`app/Module/Mex/Services/MexOrderService.php`中:
+- 确保取消订单操作在数据库事务中执行
+- 添加异常处理
+
+## 核心代码修改
+
+### MexOrderLogic::cancelOrder方法
+```php
+public static function cancelOrder(int $userId, int $orderId): array
+{
+    // 检查事务状态,确保调用者已开启事务
+    \UCore\Db\Helper::check_tr();
+
+    $order = MexOrder::where('id', $orderId)->where('user_id', $userId)->first();
+    if (!$order) {
+        return ['success' => false, 'message' => '订单不存在'];
+    }
+
+    if ($order->status !== OrderStatus::PENDING) {
+        return ['success' => false, 'message' => '只能取消等待中的订单'];
+    }
+
+    try {
+        // 1. 更新订单状态为取消
+        $order->update(['status' => OrderStatus::CANCELLED]);
+
+        // 2. 处理解冻逻辑
+        if ($order->order_type === OrderType::SELL) {
+            // 卖出订单:解冻物品
+            $unfreezeResult = self::unfreezeOrderItems($order);
+            if (!$unfreezeResult['success']) {
+                return ['success' => false, 'message' => '解冻物品失败:' . $unfreezeResult['message']];
+            }
+        } elseif ($order->order_type === OrderType::BUY) {
+            // 买入订单:解冻资金
+            $unfreezeResult = self::unfreezeOrderFunds($order);
+            if (!$unfreezeResult['success']) {
+                return ['success' => false, 'message' => '解冻资金失败:' . $unfreezeResult['message']];
+            }
+        }
+
+        return ['success' => true, 'message' => '订单已取消'];
+    } catch (\Exception $e) {
+        return ['success' => false, 'message' => '取消订单失败:' . $e->getMessage()];
+    }
+}
+```
+
+### 解冻物品方法
+```php
+private static function unfreezeOrderItems(MexOrder $order): array
+{
+    try {
+        // 查找该订单相关的冻结记录
+        $freezeLogs = \App\Module\GameItems\Models\ItemFreezeLog::where('source_id', $order->id)
+            ->where('source_type', 'mex_sell_order')
+            ->where('action_type', \App\Module\GameItems\Enums\FREEZE_ACTION_TYPE::FREEZE)
+            ->get();
+
+        if ($freezeLogs->isEmpty()) {
+            return ['success' => true, 'message' => '没有找到需要解冻的物品'];
+        }
+
+        $unfrozenCount = 0;
+        foreach ($freezeLogs as $freezeLog) {
+            try {
+                // 使用ItemService解冻物品
+                \App\Module\GameItems\Services\ItemService::unfreezeItem($freezeLog->id);
+                $unfrozenCount++;
+            } catch (\Exception $e) {
+                // 记录错误但继续处理其他冻结记录
+                \Log::error("解冻物品失败", [
+                    'order_id' => $order->id,
+                    'freeze_log_id' => $freezeLog->id,
+                    'error' => $e->getMessage()
+                ]);
+            }
+        }
+
+        return [
+            'success' => true,
+            'message' => "成功解冻 {$unfrozenCount} 个冻结记录",
+            'unfrozen_count' => $unfrozenCount
+        ];
+    } catch (\Exception $e) {
+        return ['success' => false, 'message' => $e->getMessage()];
+    }
+}
+```
+
+## 测试验证
+
+### 1. 手动测试
+创建了完整的测试脚本`app/Module/Mex/Tests/manual_cancel_test.php`
+
+### 2. 实际验证
+使用真实数据进行测试:
+- 用户1001,萝卜(物品ID=2)
+- 创建卖出订单20个,价格10.5
+- 验证物品冻结状态
+- 取消订单
+- 验证物品解冻状态
+
+### 3. 测试结果
+✅ **订单状态正确更新**:PENDING → CANCELLED
+✅ **物品正确解冻**:is_frozen=1 → is_frozen=0
+✅ **解冻日志正确创建**:action_type=2(解冻)
+✅ **物品数量正确**:用户物品完全恢复可用状态
+
+## 文件变更
+- `app/Module/Mex/Logic/MexOrderLogic.php` - 修改取消订单逻辑
+- `app/Module/Mex/Services/MexOrderService.php` - 添加事务处理
+- `app/Module/Mex/Tests/MexOrderCancelTest.php` - 新增单元测试
+- `app/Module/Mex/Tests/manual_cancel_test.php` - 新增手动测试脚本
+
+## 提交信息
+```
+修复Mex模块取消挂单时没有解冻物品的问题
+
+- 修改MexOrderLogic::cancelOrder方法,添加解冻逻辑
+- 卖出订单取消时自动解冻相关物品
+- 买入订单取消时处理资金解冻(当前为占位实现)
+- 添加unfreezeOrderItems和unfreezeOrderFunds私有方法
+- 修改MexOrderService::cancelOrder在事务中执行
+- 添加完整的测试用例和手动测试脚本
+- 通过实际测试验证功能正常工作
+```
+
+## 注意事项
+1. **数据一致性**:所有操作都在事务中执行
+2. **错误处理**:解冻失败时记录日志但不中断整个流程
+3. **向后兼容**:不影响现有的订单创建和撮合逻辑
+4. **扩展性**:为将来的资金冻结功能预留了接口
+
+## 后续优化建议
+1. 考虑为买入订单实现真正的资金冻结功能
+2. 添加批量解冻功能以提高性能
+3. 考虑添加解冻失败的重试机制

+ 4 - 2
app/Module/Fund/Logic/Circulation.php

@@ -21,11 +21,13 @@ class Circulation
      * @param int $user_id 用户
      * @param int $fund_id 流出账户
      * @param int $to_fund_id 流入账户
-     * @param int $amount 资金总数
+     * @param float $amount 资金总数(小数形式)
+     * @param int $re_id 关联ID
+     * @param string $re_type 关联类型
      * @param string $remark 备注信息
      * @return bool|int|string
      */
-    public static function handle($user_id, $fund_id, $to_fund_id, int $amount, int $re_id, string $re_type, $remark)
+    public static function handle($user_id, $fund_id, $to_fund_id, float $amount, int $re_id, string $re_type, $remark)
     {
         $data = [
             'user_id'     => $user_id,

+ 7 - 7
app/Module/Fund/Logic/User.php

@@ -60,15 +60,15 @@ class User
      *
      * 资金处理
      *
-     * @param $user_id
-     * @param $fund_id
-     * @param $amount
-     * @param $type
-     * @param $id
-     * @param $remark
+     * @param int $user_id 用户ID
+     * @param int $fund_id 账户ID
+     * @param float $amount 金额(小数形式,正值为收入,负值为支出)
+     * @param LOG_TYPE $type 日志类型
+     * @param mixed $id 关联ID
+     * @param string $remark 备注
      * @return bool|string
      */
-    public static function handle($user_id, int $fund_id, int $amount, LOG_TYPE $type, $id, $remark)
+    public static function handle($user_id, int $fund_id, float $amount, LOG_TYPE $type, $id, $remark)
     {
 
         # 读取信息

+ 87 - 8
app/Module/Mex/Logic/MexOrderLogic.php

@@ -126,6 +126,7 @@ class MexOrderLogic
         $totalAmount = $price * $quantity;
 
         try {
+            // 1. 创建订单记录
             $order = MexOrder::create([
                 'user_id' => $userId,
                 'item_id' => $itemId,
@@ -138,6 +139,12 @@ class MexOrderLogic
                 'frozen_amount' => $totalAmount,
             ]);
 
+            // 2. 冻结用户资金
+            $freezeResult = self::freezeOrderFunds($order);
+            if (!$freezeResult['success']) {
+                return ['success' => false, 'message' => '冻结资金失败:' . $freezeResult['message']];
+            }
+
             // 触发订单创建事件
             event(new OrderCreatedEvent($order));
 
@@ -279,6 +286,55 @@ class MexOrderLogic
         }
     }
 
+    /**
+     * 冻结订单相关的资金(买入订单创建时使用)
+     *
+     * @param MexOrder $order 订单对象
+     * @return array 冻结结果
+     */
+    private static function freezeOrderFunds(MexOrder $order): array
+    {
+        try {
+            // 获取可用账户类型和冻结账户类型
+            $availableAccountType = FundLogic::getAvailableAccountType($order->currency_type);
+            $frozenAccountType = FundLogic::getFrozenAccountType($order->currency_type);
+
+            if (!$availableAccountType || !$frozenAccountType) {
+                return ['success' => false, 'message' => '不支持的币种类型'];
+            }
+
+            // 创建Fund服务实例(可用账户)
+            $fundService = new \App\Module\Fund\Services\FundService($order->user_id, $availableAccountType->value);
+
+            // 验证用户可用余额是否充足
+            if ($fundService->balance() < $order->frozen_amount) {
+                return ['success' => false, 'message' => '可用余额不足'];
+            }
+
+            // 使用circulation方法将资金从可用账户转移到冻结账户
+            $circulationResult = $fundService->circulation(
+                $frozenAccountType,
+                $order->frozen_amount,
+                $order->id,
+                'mex_buy_order',
+                "农贸市场买入订单冻结,订单ID:{$order->id}"
+            );
+
+            if (is_string($circulationResult)) {
+                return ['success' => false, 'message' => '资金冻结失败:' . $circulationResult];
+            }
+
+            return [
+                'success' => true,
+                'message' => '资金冻结成功',
+                'frozen_amount' => $order->frozen_amount,
+                'circulation_id' => $circulationResult->id ?? null
+            ];
+        } catch (\Exception $e) {
+            return ['success' => false, 'message' => '冻结资金异常:' . $e->getMessage()];
+        }
+    }
+
     /**
      * 解冻订单相关的资金(买入订单取消时使用)
      *
@@ -288,20 +344,43 @@ class MexOrderLogic
     private static function unfreezeOrderFunds(MexOrder $order): array
     {
         try {
-            // 买入订单的资金冻结处理
-            // 注意:当前买入订单创建时没有实际冻结资金,只是记录了frozen_amount
-            // 这里主要是为了保持接口一致性,实际可能不需要特殊处理
+            // 获取可用账户类型和冻结账户类型
+            $availableAccountType = FundLogic::getAvailableAccountType($order->currency_type);
+            $frozenAccountType = FundLogic::getFrozenAccountType($order->currency_type);
 
-            // 如果将来需要实际冻结资金,可以在这里添加解冻逻辑
-            // 例如:调用Fund模块的解冻接口
+            if (!$availableAccountType || !$frozenAccountType) {
+                return ['success' => false, 'message' => '不支持的币种类型'];
+            }
+
+            // 创建Fund服务实例(冻结账户)
+            $fundService = new \App\Module\Fund\Services\FundService($order->user_id, $frozenAccountType->value);
+
+            // 验证冻结账户余额是否充足
+            if ($fundService->balance() < $order->frozen_amount) {
+                return ['success' => false, 'message' => '冻结账户余额不足,可能已被解冻'];
+            }
+
+            // 使用circulation方法将资金从冻结账户转移回可用账户
+            $circulationResult = $fundService->circulation(
+                $availableAccountType,
+                $order->frozen_amount,
+                $order->id,
+                'mex_buy_order_cancel',
+                "农贸市场买入订单取消解冻,订单ID:{$order->id}"
+            );
+
+            if (is_string($circulationResult)) {
+                return ['success' => false, 'message' => '资金解冻失败:' . $circulationResult];
+            }
 
             return [
                 'success' => true,
-                'message' => '买入订单资金解冻完成',
-                'note' => '当前买入订单未实际冻结资金,仅更新订单状态'
+                'message' => '资金解冻成功',
+                'unfrozen_amount' => $order->frozen_amount,
+                'circulation_id' => $circulationResult->id ?? null
             ];
         } catch (\Exception $e) {
-            return ['success' => false, 'message' => $e->getMessage()];
+            return ['success' => false, 'message' => '解冻资金异常:' . $e->getMessage()];
         }
     }