Sfoglia il codice sorgente

修复货币(Currency)和代币账户(Fund)概念使用混淆问题

notfff 7 mesi fa
parent
commit
28776d695e

+ 192 - 0
app/Module/Fund/Docs/货币与账户概念使用指南.md

@@ -0,0 +1,192 @@
+# 货币与账户概念使用指南
+
+## 1. 概述
+
+在系统中,我们有三个核心概念:
+
+1. **币种(Currency)**:指货币的种类,如美元、人民币等,由 `FundCurrencyModel` 表示
+2. **账户种类(Fund Config)**:指资金的不同用途或状态,如可用账户、冻结账户,由 `FundConfigModel` 表示
+3. **用户账户(Fund)**:用户实际持有的资金账户,由 `FundModel` 表示
+
+本文档旨在明确这些概念的正确使用方式,避免混淆。
+
+## 2. 枚举定义
+
+### 2.1 消耗类型枚举(CONSUME_TYPE)
+
+```php
+enum CONSUME_TYPE: int
+{
+    /**
+     * 物品消耗
+     * 消耗用户背包中的物品
+     */
+    case ITEM = 1;
+
+    /**
+     * 账户种类消耗
+     * 消耗用户特定账户种类的资金
+     * 注意:这里的target_id指向fund_config表的id(账户种类ID)
+     */
+    case FUND_CONFIG = 2;
+
+    /**
+     * 币种消耗
+     * 消耗用户特定币种的资金
+     * 注意:这里的target_id指向fund_currency表的id(币种ID)
+     */
+    case CURRENCY = 3;
+}
+```
+
+### 2.2 奖励类型枚举(REWARD_TYPE)
+
+```php
+enum REWARD_TYPE: int
+{
+    /**
+     * 物品奖励
+     * 奖励用户背包中的物品
+     */
+    case ITEM = 1;
+
+    /**
+     * 账户种类奖励
+     * 奖励用户特定账户种类的资金
+     * 注意:这里的target_id指向fund_config表的id(账户种类ID)
+     */
+    case FUND_CONFIG = 2;
+
+    /**
+     * 宠物经验奖励
+     */
+    case PET_EXP = 3;
+
+    /**
+     * 宠物体力奖励
+     */
+    case PET_ENERGY = 4;
+
+    /**
+     * 币种奖励
+     * 奖励用户特定币种的资金
+     * 注意:这里的target_id指向fund_currency表的id(币种ID)
+     */
+    case CURRENCY = 5;
+
+    /**
+     * 其他奖励
+     */
+    case OTHER = 99;
+}
+```
+
+## 3. 正确使用方式
+
+### 3.1 消耗账户种类资金
+
+当需要消耗用户特定账户种类的资金时,应使用 `CONSUME_TYPE::FUND_CONFIG`:
+
+```php
+// 创建消耗项
+$consumeItem = new GameConsumeItem();
+$consumeItem->consume_type = CONSUME_TYPE::FUND_CONFIG->value;
+$consumeItem->target_id = $fundConfigId; // 账户种类ID,来自fund_config表
+$consumeItem->quantity = $amount;
+
+// 检查消耗
+$result = ConsumeService::checkConsumeItem($userId, $consumeItem);
+
+// 执行消耗
+$result = ConsumeService::executeConsumeItem($userId, $consumeItem, $source, $sourceId);
+```
+
+### 3.2 消耗币种资金
+
+当需要消耗用户特定币种的资金(可能涉及多个账户)时,应使用 `CONSUME_TYPE::CURRENCY`:
+
+```php
+// 创建消耗项
+$consumeItem = new GameConsumeItem();
+$consumeItem->consume_type = CONSUME_TYPE::CURRENCY->value;
+$consumeItem->target_id = $currencyId; // 币种ID,来自fund_currency表
+$consumeItem->quantity = $amount;
+
+// 检查消耗
+$result = ConsumeService::checkConsumeItem($userId, $consumeItem);
+
+// 执行消耗
+$result = ConsumeService::executeConsumeItem($userId, $consumeItem, $source, $sourceId);
+```
+
+### 3.3 奖励账户种类资金
+
+当需要奖励用户特定账户种类的资金时,应使用 `REWARD_TYPE::FUND_CONFIG`:
+
+```php
+// 创建奖励项
+$rewardItem = new GameRewardItem();
+$rewardItem->reward_type = REWARD_TYPE::FUND_CONFIG->value;
+$rewardItem->target_id = $fundConfigId; // 账户种类ID,来自fund_config表
+$rewardItem->quantity = $amount;
+
+// 发放奖励
+$result = RewardService::grantReward($userId, $rewardItem, $source, $sourceId);
+```
+
+### 3.4 奖励币种资金
+
+当需要奖励用户特定币种的资金时,应使用 `REWARD_TYPE::CURRENCY`:
+
+```php
+// 创建奖励项
+$rewardItem = new GameRewardItem();
+$rewardItem->reward_type = REWARD_TYPE::CURRENCY->value;
+$rewardItem->target_id = $currencyId; // 币种ID,来自fund_currency表
+$rewardItem->quantity = $amount;
+
+// 发放奖励
+$result = RewardService::grantReward($userId, $rewardItem, $source, $sourceId);
+```
+
+## 4. 常见错误
+
+### 4.1 混淆账户种类ID和币种ID
+
+错误示例:
+```php
+// 错误:使用币种ID作为账户种类消耗的target_id
+$consumeItem->consume_type = CONSUME_TYPE::FUND_CONFIG->value;
+$consumeItem->target_id = $currencyId; // 错误!应该使用账户种类ID
+```
+
+正确示例:
+```php
+// 正确:使用账户种类ID作为账户种类消耗的target_id
+$consumeItem->consume_type = CONSUME_TYPE::FUND_CONFIG->value;
+$consumeItem->target_id = $fundConfigId; // 正确!使用账户种类ID
+```
+
+### 4.2 混淆消耗类型
+
+错误示例:
+```php
+// 错误:想消耗特定账户种类的资金,但使用了币种消耗类型
+$consumeItem->consume_type = CONSUME_TYPE::CURRENCY->value;
+$consumeItem->target_id = $fundConfigId; // 错误!CURRENCY类型应该使用币种ID
+```
+
+正确示例:
+```php
+// 正确:消耗特定账户种类的资金
+$consumeItem->consume_type = CONSUME_TYPE::FUND_CONFIG->value;
+$consumeItem->target_id = $fundConfigId; // 正确!
+```
+
+## 5. 总结
+
+- 使用 `CONSUME_TYPE::FUND_CONFIG` 和 `REWARD_TYPE::FUND_CONFIG` 操作特定账户种类的资金
+- 使用 `CONSUME_TYPE::CURRENCY` 和 `REWARD_TYPE::CURRENCY` 操作特定币种的资金
+- 始终确保 `target_id` 与消耗/奖励类型匹配:
+  - 对于 `FUND_CONFIG` 类型,`target_id` 应该是账户种类ID(`fund_config.id`)
+  - 对于 `CURRENCY` 类型,`target_id` 应该是币种ID(`fund_currency.id`)

+ 13 - 6
app/Module/Game/Enums/CONSUME_TYPE.php

@@ -8,6 +8,8 @@ use UCore\Enum\EnumToInt;
 
 /**
  * 消耗类型枚举
+ *
+ * 定义了系统中支持的各种消耗类型
  */
 enum CONSUME_TYPE: int
 {
@@ -15,18 +17,23 @@ enum CONSUME_TYPE: int
 
     /**
      * 物品消耗
+     * 消耗用户背包中的物品
      */
     case ITEM = 1;
 
     /**
-     * 货币消耗
+     * 账户种类消耗
+     * 消耗用户特定账户种类的资金
+     * 注意:这里的target_id指向fund_config表的id(账户种类ID)
      */
-    case CURRENCY = 2;
+    case FUND_CONFIG = 2;
 
     /**
-     * 代币(账户)消耗
+     * 币种消耗
+     * 消耗用户特定币种的资金
+     * 注意:这里的target_id指向fund_currency表的id(币种ID)
      */
-    case FUND = 3;
+    case CURRENCY = 3;
 
 
     /**
@@ -38,8 +45,8 @@ enum CONSUME_TYPE: int
     {
         return [
             self::ITEM->value => '物品',
-            self::CURRENCY->value => '货币',
-            self::FUND->value => '代币',
+            self::FUND_CONFIG->value => '账户种类',
+            self::CURRENCY->value => '币种',
         ];
     }
 

+ 13 - 6
app/Module/Game/Enums/REWARD_TYPE.php

@@ -7,6 +7,8 @@ use UCore\Enum\EnumToInt;
 
 /**
  * 奖励类型枚举
+ *
+ * 定义了系统中支持的各种奖励类型
  */
 enum REWARD_TYPE: int
 {
@@ -14,13 +16,16 @@ enum REWARD_TYPE: int
 
     /**
      * 物品奖励
+     * 奖励用户背包中的物品
      */
     case ITEM = 1;
 
     /**
-     * 货币奖励
+     * 账户种类奖励
+     * 奖励用户特定账户种类的资金
+     * 注意:这里的target_id指向fund_config表的id(账户种类ID)
      */
-    case CURRENCY = 2;
+    case FUND_CONFIG = 2;
 
     /**
      * 宠物经验奖励
@@ -33,9 +38,11 @@ enum REWARD_TYPE: int
     case PET_ENERGY = 4;
 
     /**
-     * 代币(账户)奖励
+     * 币种奖励
+     * 奖励用户特定币种的资金
+     * 注意:这里的target_id指向fund_currency表的id(币种ID)
      */
-    case FUND = 5;
+    case CURRENCY = 5;
 
     /**
      * 其他奖励
@@ -51,11 +58,11 @@ enum REWARD_TYPE: int
     {
         return [
             self::ITEM->value => '物品',
-            self::CURRENCY->value => '货币',
+            self::FUND_CONFIG->value => '账户种类',
             self::PET_EXP->value => '宠物经验',
             self::PET_ENERGY->value => '宠物体力',
+            self::CURRENCY->value => '币种',
             self::OTHER->value => '其他',
-            self::FUND->value => '代币(账户)',
         ];
     }
 

+ 186 - 58
app/Module/Game/Services/ConsumeService.php

@@ -158,12 +158,12 @@ class ConsumeService
             case CONSUME_TYPE::ITEM->value:
                 return self::checkItemConsume($userId, $consumeItem);
 
+            case CONSUME_TYPE::FUND_CONFIG->value:
+                return self::checkFundConfigConsume($userId, $consumeItem);
+
             case CONSUME_TYPE::CURRENCY->value:
                 return self::checkCurrencyConsume($userId, $consumeItem);
 
-            case CONSUME_TYPE::FUND->value:
-                return self::checkFundConsume($userId, $consumeItem);
-
             default:
                 return [
                     'success' => false,
@@ -187,12 +187,12 @@ class ConsumeService
             case CONSUME_TYPE::ITEM->value:
                 return self::executeItemConsume($userId, $consumeItem, $source, $sourceId);
 
+            case CONSUME_TYPE::FUND_CONFIG->value:
+                return self::executeFundConfigConsume($userId, $consumeItem, $source, $sourceId);
+
             case CONSUME_TYPE::CURRENCY->value:
                 return self::executeCurrencyConsume($userId, $consumeItem, $source, $sourceId);
 
-            case CONSUME_TYPE::FUND->value:
-                return self::executeFundConsume($userId, $consumeItem, $source, $sourceId);
-
             default:
                 return [
                     'success' => false,
@@ -240,26 +240,28 @@ class ConsumeService
     }
 
     /**
-     * 检查货币消耗
+     * 检查账户种类消耗
+     *
+     * 注意:这里的target_id指向fund_config表的id(账户种类ID)
      *
      * @param int $userId 用户ID
      * @param GameConsumeItem $consumeItem 消耗项
      * @return array 检查结果
      */
-    protected static function checkCurrencyConsume(int $userId, GameConsumeItem $consumeItem): array
+    protected static function checkFundConfigConsume(int $userId, GameConsumeItem $consumeItem): array
     {
-        $currencyId = $consumeItem->target_id;
+        $fundConfigId = $consumeItem->target_id;
         $amount = $consumeItem->quantity;
 
-        // 获取用户货币账户
-        $account = FundLogic::get_account($userId, $currencyId);
+        // 获取用户账户
+        $account = FundLogic::get_account($userId, $fundConfigId);
 
         // 检查账户是否存在
         if ($account === false) {
             return [
                 'success' => false,
-                'message' => "用户没有该货币账户",
-                'currency_id' => $currencyId
+                'message' => "用户没有该账户种类",
+                'fund_config_id' => $fundConfigId
             ];
         }
 
@@ -267,8 +269,8 @@ class ConsumeService
         if ($account->balance < $amount) {
             return [
                 'success' => false,
-                'message' => "货币余额不足,需要 {$amount},实际 {$account->balance}",
-                'currency_id' => $currencyId,
+                'message' => "账户余额不足,需要 {$amount},实际 {$account->balance}",
+                'fund_config_id' => $fundConfigId,
                 'required' => $amount,
                 'actual' => $account->balance
             ];
@@ -276,7 +278,7 @@ class ConsumeService
 
         return [
             'success' => true,
-            'message' => '货币余额足够'
+            'message' => '账户余额足够'
         ];
     }
 
@@ -373,7 +375,9 @@ class ConsumeService
     }
 
     /**
-     * 执行货币消耗
+     * 执行账户种类消耗
+     *
+     * 注意:这里的target_id指向fund_config表的id(账户种类ID)
      *
      * @param int $userId 用户ID
      * @param GameConsumeItem $consumeItem 消耗项
@@ -381,9 +385,9 @@ class ConsumeService
      * @param int $sourceId 消耗来源ID
      * @return array 执行结果
      */
-    protected static function executeCurrencyConsume(int $userId, GameConsumeItem $consumeItem, string $source, int $sourceId): array
+    protected static function executeFundConfigConsume(int $userId, GameConsumeItem $consumeItem, string $source, int $sourceId): array
     {
-        $currencyId = $consumeItem->target_id;
+        $fundConfigId = $consumeItem->target_id;
         $amount = -$consumeItem->quantity; // 负数表示消耗
 
         // 构建备注
@@ -392,10 +396,10 @@ class ConsumeService
             $remark .= ",ID:{$sourceId}";
         }
 
-        // 消耗货币
+        // 消耗账户资金
         $result = FundLogic::handle(
             $userId,
-            $currencyId,
+            $fundConfigId,
             $amount,
             FUND_LOG_TYPE::TRADE, // 使用TRADE类型,因为CONSUME可能不存在
             $sourceId,
@@ -405,72 +409,196 @@ class ConsumeService
         if ($result !== true) {
             return [
                 'success' => false,
-                'message' => is_string($result) ? $result : '货币消耗失败',
-                'currency_id' => $currencyId,
+                'message' => is_string($result) ? $result : '账户资金消耗失败',
+                'fund_config_id' => $fundConfigId,
                 'amount' => abs($amount)
             ];
         }
 
         return [
             'success' => true,
-            'message' => '货币消耗成功',
-            'currency_id' => $currencyId,
+            'message' => '账户资金消耗成功',
+            'fund_config_id' => $fundConfigId,
             'amount' => abs($amount)
         ];
     }
 
     /**
-     * 执行代币账户消耗
+     * 检查币种消耗
+     *
+     * 注意:这里的target_id指向fund_currency表的id(币种ID)
+     * 这个方法会检查用户所有与该币种相关的账户,并计算总余额
      *
      * @param int $userId 用户ID
      * @param GameConsumeItem $consumeItem 消耗项
-     * @param string $source 消耗来源
-     * @param int $sourceId 消耗来源ID
-     * @return array 执行结果
+     * @return array 检查结果
      */
-    protected static function executeFundConsume(int $userId, GameConsumeItem $consumeItem, string $source, int $sourceId): array
+    protected static function checkCurrencyConsume(int $userId, GameConsumeItem $consumeItem): array
     {
-        $fundId = $consumeItem->target_id;
-        $amount = -$consumeItem->quantity; // 负数表示消耗
-
-        // 构建备注
-        $remark = "消耗组:{$consumeItem->group_id},来源:{$source}";
-        if ($sourceId > 0) {
-            $remark .= ",ID:{$sourceId}";
-        }
+        $currencyId = $consumeItem->target_id;
+        $amount = $consumeItem->quantity;
 
         try {
-            // 消耗代币账户
-            $fundService = new \App\Module\Fund\Services\FundService($userId, $fundId);
-            $result = $fundService->trade(
-                0, // 系统账户
-                abs($amount), // 正数金额
-                $source,
-                $sourceId,
-                $remark
-            );
-
-            if (is_string($result)) {
+            // 获取该币种的所有账户种类
+            $fundConfigs = \App\Module\Fund\Models\FundConfigModel::where('currency_id', $currencyId)->get();
+
+            if ($fundConfigs->isEmpty()) {
                 return [
                     'success' => false,
-                    'message' => $result,
-                    'fund_id' => $fundId,
-                    'amount' => abs($amount)
+                    'message' => "币种不存在或没有关联的账户种类",
+                    'currency_id' => $currencyId
+                ];
+            }
+
+            // 获取用户所有与该币种相关的账户
+            $fundConfigIds = $fundConfigs->pluck('id')->toArray();
+            $accounts = \App\Module\Fund\Models\FundModel::where('user_id', $userId)
+                ->whereIn('fund_id', $fundConfigIds)
+                ->get();
+
+            if ($accounts->isEmpty()) {
+                return [
+                    'success' => false,
+                    'message' => "用户没有该币种的账户",
+                    'currency_id' => $currencyId
+                ];
+            }
+
+            // 计算总余额
+            $totalBalance = $accounts->sum('balance');
+
+            // 检查余额是否足够
+            if ($totalBalance < $amount) {
+                return [
+                    'success' => false,
+                    'message' => "币种总余额不足,需要 {$amount},实际 {$totalBalance}",
+                    'currency_id' => $currencyId,
+                    'required' => $amount,
+                    'actual' => $totalBalance
                 ];
             }
 
             return [
                 'success' => true,
-                'message' => '代币账户消耗成功',
-                'fund_id' => $fundId,
-                'amount' => abs($amount)
+                'message' => '币种总余额足够',
+                'accounts' => $accounts->toArray()
             ];
         } catch (\Exception $e) {
             return [
                 'success' => false,
-                'message' => '代币账户消耗异常: ' . $e->getMessage(),
-                'fund_id' => $fundId,
-                'amount' => abs($amount)
+                'message' => '检查币种消耗异常: ' . $e->getMessage(),
+                'currency_id' => $currencyId
+            ];
+        }
+    }
+
+    /**
+     * 执行币种消耗
+     *
+     * 注意:这里的target_id指向fund_currency表的id(币种ID)
+     * 这个方法会优先从用户的可用账户中扣除,如果不足则依次从其他账户扣除
+     *
+     * @param int $userId 用户ID
+     * @param GameConsumeItem $consumeItem 消耗项
+     * @param string $source 消耗来源
+     * @param int $sourceId 消耗来源ID
+     * @return array 执行结果
+     */
+    protected static function executeCurrencyConsume(int $userId, GameConsumeItem $consumeItem, string $source, int $sourceId): array
+    {
+        $currencyId = $consumeItem->target_id;
+        $amountToConsume = $consumeItem->quantity;
+
+        try {
+            // 先检查是否有足够的余额
+            $checkResult = self::checkCurrencyConsume($userId, $consumeItem);
+            if (!$checkResult['success']) {
+                return $checkResult;
+            }
+
+            // 获取该币种的所有账户种类
+            $fundConfigs = \App\Module\Fund\Models\FundConfigModel::where('currency_id', $currencyId)->get();
+            $fundConfigIds = $fundConfigs->pluck('id')->toArray();
+
+            // 获取用户所有与该币种相关的账户
+            $accounts = \App\Module\Fund\Models\FundModel::where('user_id', $userId)
+                ->whereIn('fund_id', $fundConfigIds)
+                ->orderBy('fund_id') // 按账户种类ID排序,通常可用账户ID较小
+                ->get();
+
+            // 开始事务
+            \Illuminate\Support\Facades\DB::beginTransaction();
+
+            $remainingAmount = $amountToConsume;
+            $consumedAccounts = [];
+
+            // 依次从各个账户中扣除
+            foreach ($accounts as $account) {
+                if ($remainingAmount <= 0) {
+                    break;
+                }
+
+                $accountBalance = $account->balance;
+                $amountToDeduct = min($accountBalance, $remainingAmount);
+
+                if ($amountToDeduct > 0) {
+                    // 构建备注
+                    $remark = "币种消耗:{$currencyId},消耗组:{$consumeItem->group_id},来源:{$source}";
+                    if ($sourceId > 0) {
+                        $remark .= ",ID:{$sourceId}";
+                    }
+
+                    // 从当前账户扣除
+                    $result = FundLogic::handle(
+                        $userId,
+                        $account->fund_id,
+                        -$amountToDeduct, // 负数表示消耗
+                        FUND_LOG_TYPE::TRADE,
+                        $sourceId,
+                        $remark
+                    );
+
+                    if ($result !== true) {
+                        \Illuminate\Support\Facades\DB::rollBack();
+                        return [
+                            'success' => false,
+                            'message' => is_string($result) ? $result : "从账户 {$account->fund_id} 扣除失败",
+                            'currency_id' => $currencyId,
+                            'fund_config_id' => $account->fund_id,
+                            'amount' => $amountToDeduct
+                        ];
+                    }
+
+                    $consumedAccounts[] = [
+                        'fund_config_id' => $account->fund_id,
+                        'amount' => $amountToDeduct
+                    ];
+
+                    $remainingAmount -= $amountToDeduct;
+                }
+            }
+
+            // 提交事务
+            \Illuminate\Support\Facades\DB::commit();
+
+            return [
+                'success' => true,
+                'message' => '币种消耗成功',
+                'currency_id' => $currencyId,
+                'amount' => $amountToConsume,
+                'consumed_accounts' => $consumedAccounts
+            ];
+        } catch (\Exception $e) {
+            // 回滚事务
+            if (\Illuminate\Support\Facades\DB::transactionLevel() > 0) {
+                \Illuminate\Support\Facades\DB::rollBack();
+            }
+
+            return [
+                'success' => false,
+                'message' => '币种消耗异常: ' . $e->getMessage(),
+                'currency_id' => $currencyId,
+                'amount' => $amountToConsume
             ];
         }
     }