ソースを参照

修复URS推广奖励组机制和种植收益发放

- 推广奖励:集成真正的奖励组系统,从达人配置读取奖励组ID
- 种植收益:重构为物品发放模式,使用ItemService发放物品奖励
- 数量处理:支持整数输入,按比例计算并向下取整
- 事务管理:添加完善的事务处理确保发放原子性
- 测试验证:推广奖励和种植收益发放均正常工作
- 文档更新:记录奖励组机制集成和修复过程
notfff 7 ヶ月 前
コミット
a762cd11df

+ 1 - 1
app/Module/UrsPromotion/Commands/TestFarmIntegrationCommand.php

@@ -170,7 +170,7 @@ class TestFarmIntegrationCommand extends Command
             $testData['mock_land'],
             $testData['mock_crop'],
             $testData['mock_harvest_log'],
-            1001, // 输出物品ID
+            19, // 输出物品ID(普通化肥)
             $testData['output_amount']
         );
 

+ 231 - 0
app/Module/UrsPromotion/Commands/TestPromotionRewardCommand.php

@@ -0,0 +1,231 @@
+<?php
+
+namespace App\Module\UrsPromotion\Commands;
+
+use Illuminate\Console\Command;
+use App\Module\UrsPromotion\Models\UrsUserReferral;
+use App\Module\UrsPromotion\Models\UrsUserTalent;
+use App\Module\UrsPromotion\Models\UrsProfit;
+use App\Module\UrsPromotion\Services\UrsProfitService;
+use App\Module\UrsPromotion\Enums\UrsProfitType;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * 测试推广奖励发放功能
+ */
+class TestPromotionRewardCommand extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'urs:test-promotion-reward {--clean : 清理测试数据}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '测试URS推广奖励发放功能(集成奖励组系统)';
+
+    /**
+     * 执行命令
+     */
+    public function handle(): int
+    {
+        if ($this->option('clean')) {
+            $this->cleanTestData();
+            $this->info('测试数据清理完成');
+            return 0;
+        }
+
+        $this->info('开始测试URS推广奖励发放功能...');
+
+        try {
+            // 1. 准备测试数据
+            $this->info('1. 准备测试数据...');
+            $testData = $this->prepareTestData();
+
+            // 2. 测试推广奖励发放
+            $this->info('2. 测试推广奖励发放...');
+            $this->testPromotionReward($testData);
+
+            // 3. 验证奖励发放结果
+            $this->info('3. 验证奖励发放结果...');
+            $this->verifyRewardResults($testData);
+
+            $this->info('✅ URS推广奖励发放功能测试通过!');
+            return 0;
+
+        } catch (\Exception $e) {
+            $this->error('❌ 测试失败: ' . $e->getMessage());
+            $this->error('错误详情: ' . $e->getTraceAsString());
+            return 1;
+        }
+    }
+
+    /**
+     * 准备测试数据
+     */
+    private function prepareTestData(): array
+    {
+        DB::beginTransaction();
+
+        try {
+            // 创建测试用户推荐关系
+            $newUserId = 4001; // 新注册用户
+            $directReferrerId = 4002; // 直推(初级达人)
+            $indirectReferrerId = 4003; // 间推(中级达人)
+            $thirdReferrerId = 4004; // 三推(高级达人)
+
+            // 清理可能存在的测试数据
+            UrsUserReferral::whereIn('user_id', [$newUserId, $directReferrerId, $indirectReferrerId])->delete();
+            UrsUserTalent::whereIn('user_id', [$directReferrerId, $indirectReferrerId, $thirdReferrerId])->delete();
+            UrsProfit::where('source_type', 'user_register_test')->delete();
+
+            // 创建推荐关系:4004 -> 4003 -> 4002 -> 4001
+            UrsUserReferral::create([
+                'user_id' => $indirectReferrerId,
+                'referrer_id' => $thirdReferrerId,
+                'referral_code' => 'TEST_' . $thirdReferrerId,
+                'direct_count' => 1,
+                'indirect_count' => 0,
+                'third_count' => 0,
+            ]);
+
+            UrsUserReferral::create([
+                'user_id' => $directReferrerId,
+                'referrer_id' => $indirectReferrerId,
+                'referral_code' => 'TEST_' . $indirectReferrerId,
+                'direct_count' => 1,
+                'indirect_count' => 0,
+                'third_count' => 0,
+            ]);
+
+            UrsUserReferral::create([
+                'user_id' => $newUserId,
+                'referrer_id' => $directReferrerId,
+                'referral_code' => 'TEST_' . $directReferrerId,
+                'direct_count' => 0,
+                'indirect_count' => 0,
+                'third_count' => 0,
+            ]);
+
+            // 创建达人等级
+            UrsUserTalent::create([
+                'user_id' => $directReferrerId,
+                'talent_level' => 1, // 初级达人
+                'direct_count' => 1,
+                'indirect_count' => 0,
+                'third_count' => 0,
+                'total_count' => 1,
+            ]);
+
+            UrsUserTalent::create([
+                'user_id' => $indirectReferrerId,
+                'talent_level' => 2, // 中级达人
+                'direct_count' => 1,
+                'indirect_count' => 1,
+                'third_count' => 0,
+                'total_count' => 2,
+            ]);
+
+            UrsUserTalent::create([
+                'user_id' => $thirdReferrerId,
+                'talent_level' => 3, // 高级达人
+                'direct_count' => 1,
+                'indirect_count' => 1,
+                'third_count' => 1,
+                'total_count' => 3,
+            ]);
+
+            DB::commit();
+
+            $this->info("   - 创建推荐关系: {$thirdReferrerId}(高级) -> {$indirectReferrerId}(中级) -> {$directReferrerId}(初级) -> {$newUserId}(新用户)");
+
+            return [
+                'new_user_id' => $newUserId,
+                'direct_referrer_id' => $directReferrerId,
+                'indirect_referrer_id' => $indirectReferrerId,
+                'third_referrer_id' => $thirdReferrerId,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            throw $e;
+        }
+    }
+
+    /**
+     * 测试推广奖励发放
+     */
+    private function testPromotionReward(array $testData): void
+    {
+        // 模拟新用户注册,触发推广奖励发放
+        $profits = UrsProfitService::distributePromotionReward(
+            $testData['new_user_id'],
+            'user_register_test',
+            1001
+        );
+
+        $this->info("   - 触发推广奖励发放: 新用户{$testData['new_user_id']}注册");
+        $this->info("   - 生成收益记录数量: " . count($profits));
+    }
+
+    /**
+     * 验证奖励发放结果
+     */
+    private function verifyRewardResults(array $testData): void
+    {
+        // 等待奖励发放完成
+        sleep(1);
+
+        // 查询生成的收益记录
+        $profits = UrsProfit::where('source_type', 'user_register_test')
+            ->where('source_id', 1001)
+            ->where('profit_type', UrsProfitType::PROMOTION_REWARD->value)
+            ->get();
+
+        $this->info("   - 实际生成收益记录数量: " . $profits->count());
+
+        if ($profits->isEmpty()) {
+            throw new \Exception('未生成任何推广收益记录');
+        }
+
+        foreach ($profits as $profit) {
+            $this->info("   - 收益记录: 用户{$profit->user_id}, 层级{$profit->relation_level}, 金额{$profit->profit_amount}, 奖励组{$profit->reward_group_id}");
+        }
+
+        // 验证收益记录的正确性
+        $directProfit = $profits->where('user_id', $testData['direct_referrer_id'])->first();
+        $indirectProfit = $profits->where('user_id', $testData['indirect_referrer_id'])->first();
+        $thirdProfit = $profits->where('user_id', $testData['third_referrer_id'])->first();
+
+        if (!$directProfit) {
+            throw new \Exception('直推收益记录不存在');
+        }
+
+        if (!$indirectProfit) {
+            throw new \Exception('间推收益记录不存在');
+        }
+
+        if (!$thirdProfit) {
+            throw new \Exception('三推收益记录不存在');
+        }
+
+        $this->info("   ✅ 直推收益: {$directProfit->profit_amount} (奖励组: {$directProfit->reward_group_id})");
+        $this->info("   ✅ 间推收益: {$indirectProfit->profit_amount} (奖励组: {$indirectProfit->reward_group_id})");
+        $this->info("   ✅ 三推收益: {$thirdProfit->profit_amount} (奖励组: {$thirdProfit->reward_group_id})");
+    }
+
+    /**
+     * 清理测试数据
+     */
+    private function cleanTestData(): void
+    {
+        UrsUserReferral::whereIn('user_id', [4001, 4002, 4003])->delete();
+        UrsUserTalent::whereIn('user_id', [4002, 4003, 4004])->delete();
+        UrsProfit::where('source_type', 'user_register_test')->delete();
+    }
+}

+ 3 - 1
app/Module/UrsPromotion/Listeners/CropHarvestedListener.php

@@ -33,11 +33,13 @@ class CropHarvestedListener
 
             // 调用URS收益分发服务
             // 使用收获记录ID作为source_id,便于追溯
+            // 传递物品ID和数量,用于按比例发放物品奖励
             $profits = UrsProfitService::distributePlantingReward(
                 $event->userId,
                 'farm_harvest',
                 $event->harvestLog->id,
-                (string)$event->outputAmount
+                $event->outputAmount,
+                $event->outputItemId
             );
 
             Log::info('URS种植收益分发完成', [

+ 197 - 77
app/Module/UrsPromotion/Logics/UrsProfitLogic.php

@@ -8,6 +8,9 @@ use App\Module\UrsPromotion\Models\UrsProfit;
 use App\Module\UrsPromotion\Models\UrsTalentConfig;
 use App\Module\UrsPromotion\Enums\UrsProfitType;
 use App\Module\UrsPromotion\Enums\UrsPromotionRelationLevel;
+use App\Module\Game\Services\RewardService;
+use App\Module\GameItems\Services\ItemService;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
 /**
@@ -84,14 +87,16 @@ class UrsProfitLogic
      * @param int $userId 产生收益的用户ID
      * @param string $sourceType 收益来源类型
      * @param int $sourceId 收益来源ID
-     * @param string $originalAmount 原始收益金额
+     * @param int $originalAmount 原始收益数量(整数)
+     * @param int $itemId 收获的物品ID
      * @return array 分成记录
      */
     public function distributePlantingReward(
         int $userId,
         string $sourceType,
         int $sourceId,
-        string $originalAmount
+        int $originalAmount,
+        int $itemId
     ): array {
         $profits = [];
         
@@ -115,8 +120,9 @@ class UrsProfitLogic
                     $sourceType,
                     $sourceId,
                     $level,
-                    $originalAmount,
-                    $talentConfigs
+                    $originalAmount, // 传递原始整数数量
+                    $talentConfigs,
+                    $itemId // 传递物品ID
                 );
 
                 if ($profit) {
@@ -227,25 +233,58 @@ class UrsProfitLogic
             return null;
         }
 
-        // TODO: 这里需要根据奖励组ID获取具体的奖励金额
-        // 暂时使用固定金额作为示例
-        $rewardAmount = $this->getRewardAmountByGroupId($rewardGroupId);
-
-        // 创建收益记录
-        $profit = UrsProfit::create([
-            'user_id' => $referrerId,
-            'promotion_member_id' => $memberId,
-            'source_id' => $sourceId,
-            'source_type' => $sourceType,
-            'profit_type' => UrsProfitType::PROMOTION_REWARD->value,
-            'relation_level' => $relationLevel,
-            'original_amount' => '0', // 推广收益无原始金额概念
-            'profit_amount' => $rewardAmount,
-            'profit_rate' => 0, // 推广收益无比例概念
-            'reward_group_id' => $rewardGroupId,
-            'talent_level' => $talentLevel,
-            'status' => UrsProfit::STATUS_NORMAL,
-        ]);
+        // 开启事务(奖励组系统要求在事务中执行)
+        DB::beginTransaction();
+
+        try {
+            // 使用奖励组系统发放奖励
+            $rewardResult = RewardService::grantReward(
+                $referrerId,
+                $rewardGroupId,
+                $sourceType,
+                $sourceId
+            );
+
+            if (!$rewardResult->success) {
+                DB::rollBack();
+                Log::error("推广奖励发放失败", [
+                    'referrer_id' => $referrerId,
+                    'reward_group_id' => $rewardGroupId,
+                    'error' => $rewardResult->errorMessage
+                ]);
+                return null;
+            }
+
+            // 计算奖励总金额(用于记录)
+            $totalRewardAmount = $this->calculateTotalRewardAmount($rewardResult->items);
+
+            // 创建收益记录
+            $profit = UrsProfit::create([
+                'user_id' => $referrerId,
+                'promotion_member_id' => $memberId,
+                'source_id' => $sourceId,
+                'source_type' => $sourceType,
+                'profit_type' => UrsProfitType::PROMOTION_REWARD->value,
+                'relation_level' => $relationLevel,
+                'original_amount' => '0', // 推广收益无原始金额概念
+                'profit_amount' => $totalRewardAmount,
+                'profit_rate' => 0, // 推广收益无比例概念
+                'reward_group_id' => $rewardResult->groupId,
+                'talent_level' => $talentLevel,
+                'status' => UrsProfit::STATUS_NORMAL,
+            ]);
+
+            DB::commit();
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error("推广奖励发放事务失败", [
+                'referrer_id' => $referrerId,
+                'reward_group_id' => $rewardGroupId,
+                'error' => $e->getMessage()
+            ]);
+            return null;
+        }
 
         Log::info("URS推广收益记录创建", [
             'profit_id' => $profit->id,
@@ -254,22 +293,23 @@ class UrsProfitLogic
             'relation_level' => $relationLevel,
             'talent_level' => $talentLevel,
             'reward_group_id' => $rewardGroupId,
-            'reward_amount' => $rewardAmount
+            'reward_amount' => $totalRewardAmount
         ]);
 
         return $profit;
     }
 
     /**
-     * 计算种植收益分成(按比例)
+     * 计算种植收益分成(按比例发放物品
      *
      * @param int $referrerId 推荐人ID
      * @param int $memberId 团队成员ID
      * @param string $sourceType 收益来源类型
      * @param int $sourceId 收益来源ID
      * @param int $relationLevel 推荐层级
-     * @param string $originalAmount 原始收益金额
+     * @param int $originalAmount 原始收益数量(整数)
      * @param array $talentConfigs 达人等级配置
+     * @param int $itemId 收获的物品ID
      * @return UrsProfit|null
      */
     private function calculatePlantingReward(
@@ -278,8 +318,9 @@ class UrsProfitLogic
         string $sourceType,
         int $sourceId,
         int $relationLevel,
-        string $originalAmount,
-        array $talentConfigs
+        int $originalAmount,
+        array $talentConfigs,
+        int $itemId
     ): ?UrsProfit {
         // 获取推荐人的达人等级
         $talent = UrsUserTalent::where('user_id', $referrerId)->first();
@@ -299,24 +340,74 @@ class UrsProfitLogic
             return null;
         }
 
-        // 计算分成金额
-        $profitAmount = bcmul($originalAmount, (string)$profitRate, 10);
+        // 计算应该奖励的物品数量(向下取整)
+        $rewardQuantity = (int)floor($originalAmount * $profitRate);
+        if ($rewardQuantity <= 0) {
+            Log::debug("推荐人 {$referrerId} 计算出的奖励数量为0", [
+                'original_amount' => $originalAmount,
+                'profit_rate' => $profitRate,
+                'calculated_quantity' => $originalAmount * $profitRate
+            ]);
+            return null;
+        }
 
-        // 创建收益记录
-        $profit = UrsProfit::create([
-            'user_id' => $referrerId,
-            'promotion_member_id' => $memberId,
-            'source_id' => $sourceId,
-            'source_type' => $sourceType,
-            'profit_type' => UrsProfitType::PLANTING_REWARD->value,
-            'relation_level' => $relationLevel,
-            'original_amount' => $originalAmount,
-            'profit_amount' => $profitAmount,
-            'profit_rate' => $profitRate,
-            'reward_group_id' => null, // 种植收益不使用奖励组
-            'talent_level' => $talentLevel,
-            'status' => UrsProfit::STATUS_NORMAL,
-        ]);
+        // 开启事务发放物品奖励
+        DB::beginTransaction();
+
+        try {
+            // 使用物品模块服务发放物品
+            $addResult = ItemService::addItem($referrerId, $itemId, $rewardQuantity, [
+                'source_type' => $sourceType,
+                'source_id' => $sourceId,
+                'source' => 'urs_planting_reward',
+                'details' => [
+                    'member_id' => $memberId,
+                    'relation_level' => $relationLevel,
+                    'talent_level' => $talentLevel,
+                    'profit_rate' => $profitRate,
+                    'original_amount' => $originalAmount
+                ]
+            ]);
+
+            if (!$addResult['success']) {
+                DB::rollBack();
+                Log::error("种植收益物品发放失败", [
+                    'referrer_id' => $referrerId,
+                    'item_id' => $itemId,
+                    'quantity' => $rewardQuantity,
+                    'error' => $addResult['message'] ?? '未知错误'
+                ]);
+                return null;
+            }
+
+            // 创建收益记录
+            $profit = UrsProfit::create([
+                'user_id' => $referrerId,
+                'promotion_member_id' => $memberId,
+                'source_id' => $sourceId,
+                'source_type' => $sourceType,
+                'profit_type' => UrsProfitType::PLANTING_REWARD->value,
+                'relation_level' => $relationLevel,
+                'original_amount' => (string)$originalAmount,
+                'profit_amount' => (string)$rewardQuantity, // 记录实际发放的物品数量
+                'profit_rate' => $profitRate,
+                'reward_group_id' => null, // 种植收益不使用奖励组
+                'talent_level' => $talentLevel,
+                'status' => UrsProfit::STATUS_NORMAL,
+            ]);
+
+            DB::commit();
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error("种植收益发放事务失败", [
+                'referrer_id' => $referrerId,
+                'item_id' => $itemId,
+                'quantity' => $rewardQuantity,
+                'error' => $e->getMessage()
+            ]);
+            return null;
+        }
 
         Log::info("URS种植收益记录创建", [
             'profit_id' => $profit->id,
@@ -325,7 +416,9 @@ class UrsProfitLogic
             'relation_level' => $relationLevel,
             'talent_level' => $talentLevel,
             'profit_rate' => $profitRate,
-            'profit_amount' => $profitAmount
+            'item_id' => $itemId,
+            'reward_quantity' => $rewardQuantity,
+            'original_amount' => $originalAmount
         ]);
 
         return $profit;
@@ -333,6 +426,64 @@ class UrsProfitLogic
 
 
     
+    /**
+     * 计算奖励总金额(用于记录)
+     *
+     * @param array $rewardItems 奖励项列表
+     * @return string
+     */
+    private function calculateTotalRewardAmount(array $rewardItems): string
+    {
+        $totalAmount = '0';
+
+        foreach ($rewardItems as $item) {
+            // 计算货币类型的奖励金额
+            if (in_array($item->rewardType, ['fund', 'currency', 'fund_config'])) {
+                $totalAmount = bcadd($totalAmount, (string)$item->quantity, 10);
+            }
+            // 计算物品类型的奖励价值(按物品售价计算,如果没有售价则按数量计算)
+            elseif ($item->rewardType === 'item') {
+                // 获取物品售价作为价值参考
+                $itemValue = $this->getItemValue($item->targetId);
+                $itemTotalValue = bcmul((string)$itemValue, (string)$item->quantity, 10);
+                $totalAmount = bcadd($totalAmount, $itemTotalValue, 10);
+            }
+            // 其他类型奖励按数量计算基础价值
+            else {
+                $totalAmount = bcadd($totalAmount, (string)$item->quantity, 10);
+            }
+        }
+
+        return $totalAmount;
+    }
+
+    /**
+     * 获取物品价值(用于奖励金额计算)
+     *
+     * @param int $itemId 物品ID
+     * @return string
+     */
+    private function getItemValue(int $itemId): string
+    {
+        try {
+            // 查询物品售价
+            $item = \App\Module\GameItems\Models\Item::find($itemId);
+            if ($item && $item->sell_price > 0) {
+                return (string)$item->sell_price;
+            }
+
+            // 如果没有售价,返回默认价值(可以根据物品类型调整)
+            return '1.0000000000';
+
+        } catch (\Exception $e) {
+            Log::warning("获取物品价值失败", [
+                'item_id' => $itemId,
+                'error' => $e->getMessage()
+            ]);
+            return '1.0000000000';
+        }
+    }
+
     /**
      * 获取推广收益奖励组ID
      *
@@ -377,38 +528,7 @@ class UrsProfitLogic
         }
     }
 
-    /**
-     * 根据奖励组ID获取奖励金额
-     *
-     * TODO: 这里需要集成奖励组系统,暂时使用固定金额
-     *
-     * @param int $rewardGroupId 奖励组ID
-     * @return string
-     */
-    private function getRewardAmountByGroupId(int $rewardGroupId): string
-    {
-        // 暂时使用固定金额映射,实际应该从奖励组系统获取
-        $rewardAmounts = [
-            1001 => '50.0000000000',   // 初级达人直推奖励
-            1002 => '30.0000000000',   // 初级达人间推奖励
-            1003 => '10.0000000000',   // 初级达人三推奖励
-            1004 => '80.0000000000',   // 中级达人直推奖励
-            1005 => '50.0000000000',   // 中级达人间推奖励
-            1006 => '20.0000000000',   // 中级达人三推奖励
-            1007 => '120.0000000000',  // 高级达人直推奖励
-            1008 => '80.0000000000',   // 高级达人间推奖励
-            1009 => '40.0000000000',   // 高级达人三推奖励
-            1010 => '200.0000000000',  // 资深达人直推奖励
-            1011 => '120.0000000000',  // 资深达人间推奖励
-            1012 => '60.0000000000',   // 资深达人三推奖励
-            1013 => '300.0000000000',  // 顶级达人直推奖励
-            1014 => '200.0000000000',  // 顶级达人间推奖励
-            1015 => '100.0000000000',  // 顶级达人三推奖励
-        ];
 
-        return $rewardAmounts[$rewardGroupId] ?? '0.0000000000';
-    }
-    
     /**
      * 获取用户的收益统计
      * 

+ 1 - 0
app/Module/UrsPromotion/Providers/UrsPromotionServiceProvider.php

@@ -35,6 +35,7 @@ class UrsPromotionServiceProvider extends ServiceProvider
                 \App\Module\UrsPromotion\Commands\InsertUrsPromotionAdminMenuCommand::class,
                 \App\Module\UrsPromotion\Commands\UrsPromotionIntegrationTestCommand::class,
                 \App\Module\UrsPromotion\Commands\TestFarmIntegrationCommand::class,
+                \App\Module\UrsPromotion\Commands\TestPromotionRewardCommand::class,
             ]);
         }
     }

+ 12 - 9
app/Module/UrsPromotion/Services/UrsProfitService.php

@@ -38,28 +38,31 @@ class UrsProfitService
     
     /**
      * 分发种植收益
-     * 
+     *
      * 当下级用户收获作物时调用此方法分发种植收益
-     * 
+     *
      * @param int $userId 产生收益的用户ID
      * @param string $sourceType 收益来源类型
      * @param int $sourceId 收益来源ID
-     * @param string $originalAmount 原始收益金额
+     * @param int $originalAmount 原始收益数量(整数)
+     * @param int $itemId 收获的物品ID
      * @return array 分成记录
      */
     public static function distributePlantingReward(
-        int $userId, 
-        string $sourceType, 
-        int $sourceId, 
-        string $originalAmount
+        int $userId,
+        string $sourceType,
+        int $sourceId,
+        int $originalAmount,
+        int $itemId
     ): array {
         $logic = new UrsProfitLogic();
-        
+
         return $logic->distributePlantingReward(
             $userId,
             $sourceType,
             $sourceId,
-            $originalAmount
+            $originalAmount,
+            $itemId
         );
     }