Forráskód Böngészése

feat(shop): 重构商店系统以支持消耗组和奖励组- 重新设计了商店商品的购买逻辑,支持使用消耗组和奖励组
- 新增 ShopItemDto、ShopCategoryDto 和 ShopPromotionDto 类用于数据传输
- 修改了 ShopLogic 和 ShopService以适应新的商品结构
- 更新了 QueryHandler 和 BuyHandler 以处理新的消耗组和奖励组- 为消耗组和奖励组添加了占位符逻辑,待后续完善

notfff 8 hónapja
szülő
commit
26143ec816

+ 52 - 33
app/Module/AppGame/Handler/Shop/BuyHandler.php

@@ -55,44 +55,63 @@ class BuyHandler extends BaseHandler
 
             // 从验证结果中获取商品信息
             $shopItem = $validation->shop_item;
-            $totalPrice = $shopItem->price * $number;
 
             // 验证通过后,开启事务
             DB::beginTransaction();
 
-            // 扣除用户货币
-            $fundResult = FundUser::handle(
-                $userId,
-                $shopItem->currency_id,
-                -$totalPrice,
-                LOG_TYPE::TRADE,
-                $goodId,
-                "购买商品:{$shopItem->name} x {$number}"
-            );
-
-            if (is_string($fundResult)) {
-                throw new LogicException("购买失败:" . $fundResult);
+            // TODO: 使用消耗组和奖励组系统进行购买
+            // 这里需要实现消耗组和奖励组的逻辑,暂时使用旧的逻辑作为占位符
+
+            // 暂时的处理:如果商品有消耗组,需要调用消耗组服务
+            if ($shopItem->consumeGroup) {
+                // TODO: 调用消耗组服务扣除资源
+                // $consumeResult = ConsumeGroupService::consume($userId, $shopItem->consume_group_id, $number);
+                // if (!$consumeResult['success']) {
+                //     throw new LogicException($consumeResult['message']);
+                // }
+
+                // 暂时的占位符逻辑 - 假设扣除100金币
+                $fundResult = FundUser::handle(
+                    $userId,
+                    1, // 假设货币类型1是金币
+                    -100 * $number, // 假设每个商品100金币
+                    LOG_TYPE::TRADE,
+                    $goodId,
+                    "购买商品:{$shopItem->name} x {$number}"
+                );
+
+                if (is_string($fundResult)) {
+                    throw new LogicException("购买失败:" . $fundResult);
+                }
             }
 
-            // 添加物品到用户背包
-            ItemService::addItem(
-                $userId,
-                $shopItem->item_id,
-                $number * $shopItem->item_quantity,
-                [
-                    'source_type' => 'shop_buy',
-                    'source_id' => $goodId,
-                    'details' => [
-                        'shop_item_id' => $goodId,
-                        'shop_item_name' => $shopItem->name,
-                        'price' => $shopItem->price,
-                        'quantity' => $number
+            // 暂时的处理:如果商品有奖励组,需要调用奖励组服务
+            if ($shopItem->rewardGroup) {
+                // TODO: 调用奖励组服务发放奖励
+                // $rewardResult = RewardGroupService::giveReward($userId, $shopItem->reward_group_id, $number);
+                // if (!$rewardResult['success']) {
+                //     throw new LogicException($rewardResult['message']);
+                // }
+
+                // 暂时的占位符逻辑 - 假设给予物品ID为1的物品
+                ItemService::addItem(
+                    $userId,
+                    1, // 假设物品ID为1
+                    $number, // 数量
+                    [
+                        'source_type' => 'shop_buy',
+                        'source_id' => $goodId,
+                        'details' => [
+                            'shop_item_id' => $goodId,
+                            'shop_item_name' => $shopItem->name,
+                            'quantity' => $number
+                        ]
                     ]
-                ]
-            );
+                );
+            }
 
-            // 记录购买记录
-            $shopItem->recordPurchase($userId, $number, $totalPrice);
+            // 记录购买记录(总价暂时设为0,因为不再有固定价格)
+            $shopItem->recordPurchase($userId, $number, 0);
 
             // 提交事务
             DB::commit();
@@ -106,9 +125,9 @@ class BuyHandler extends BaseHandler
                 'user_id' => $userId,
                 'good_id' => $goodId,
                 'number' => $number,
-                'total_price' => $totalPrice,
-                'item_id' => $shopItem->item_id,
-                'item_quantity' => $shopItem->item_quantity
+                'consume_group_id' => $shopItem->consume_group_id,
+                'reward_group_id' => $shopItem->reward_group_id,
+                'shop_item_name' => $shopItem->name
             ]);
 
         } catch (\UCore\Exception\ValidateException $e) {

+ 61 - 81
app/Module/AppGame/Handler/Shop/QueryHandler.php

@@ -6,9 +6,11 @@ use App\Module\AppGame\Handler\BaseHandler;
 use App\Module\Shop\Services\ShopService;
 use Google\Protobuf\Internal\Message;
 use Illuminate\Support\Facades\Log;
-use Uraus\Kku\Common\DataShopCategory;
-use Uraus\Kku\Common\DataShopItem;
-use Uraus\Kku\Common\LastData;
+use Uraus\Kku\Response\ShopGoodItem;
+use Uraus\Kku\Common\Deduct;
+use Uraus\Kku\Common\DeductCoin;
+use Uraus\Kku\Common\Reward;
+use Uraus\Kku\Common\RewardItem;
 use Uraus\Kku\Request\RequestShopQuery;
 use Uraus\Kku\Response\ResponseShopQuery;
 use UCore\Exception\LogicException;
@@ -37,91 +39,70 @@ class QueryHandler extends BaseHandler
 
         try {
             // 获取请求参数
-            $categoryId = $data->getCategoryId() ?? 0;
-            $promotionId = $data->getPromotionId() ?? 0;
-            $onlyPromotion = $data->getOnlyPromotion() ?? false;
+            $times = $data->getTimes();
             $userId = $this->user_id;
 
-            // 构建过滤条件
-            $filters = [];
-            if ($categoryId > 0) {
-                $filters['category_id'] = $categoryId;
-            }
-
-            if ($promotionId > 0) {
-                $filters['promotion_id'] = $promotionId;
-            }
-
-            if ($onlyPromotion) {
-                $filters['only_promotion'] = true;
-            }
-
-            // 获取商店商品列表
-            $shopItems = ShopService::getShopItems($filters);
+            // 获取商店商品列表(传入用户ID以获取购买数量)
+            $shopItemDtos = ShopService::getShopItems([], $userId);
 
-            // 获取商店分类列表
-            $shopCategories = ShopService::getShopCategories();
-
-            // 创建LastData对象,用于返回商店信息
-            $lastData = new LastData();
-
-            // 添加商品数据
+            // 创建商品列表
             $itemList = [];
-            foreach ($shopItems as $shopItem) {
-                $dataItem = new DataShopItem();
-                $dataItem->setId($shopItem->id);
-                $dataItem->setName($shopItem->name);
-                $dataItem->setDescription($shopItem->description);
-                $dataItem->setCategoryId($shopItem->category_id);
-                $dataItem->setItemId($shopItem->item_id);
-                $dataItem->setItemQuantity($shopItem->item_quantity);
-                $dataItem->setPrice($shopItem->price);
-                $dataItem->setCurrencyId($shopItem->currency_id);
-                $dataItem->setMaxBuy($shopItem->max_buy);
-                $dataItem->setImage($shopItem->image);
-
-                // 设置折扣价格信息
-                $dataItem->setOriginalPrice($shopItem->original_price ?? $shopItem->price);
-                $dataItem->setDiscountedPrice($shopItem->discounted_price ?? $shopItem->price);
-                $dataItem->setHasDiscount($shopItem->has_discount ?? false);
-                $dataItem->setDiscountPercentage($shopItem->discount_percentage ?? 0);
-
-                // 如果有促销活动,设置促销信息
-                $promotion = $shopItem->getActivePromotion();
-                if ($promotion) {
-                    $dataItem->setPromotionId($promotion->id);
-                    $dataItem->setPromotionName($promotion->name);
-                    $dataItem->setPromotionEndTime($promotion->end_time ? $promotion->end_time->timestamp : 0);
+            foreach ($shopItemDtos as $shopItemDto) {
+                $goodItem = new ShopGoodItem();
+                $goodItem->setId($shopItemDto->id);
+
+                // 设置分类名称
+                $categoryName = $shopItemDto->categoryName ?? '未分类';
+                $goodItem->setCategory($categoryName);
+
+                // 设置消耗组信息(作为价格)
+                if ($shopItemDto->consumeGroupId) {
+                    $deduct = new Deduct();
+                    // TODO: 这里需要根据消耗组的具体内容来设置Deduct对象
+                    // 暂时创建一个空的Deduct对象,实际需要解析消耗组的内容
+                    $deductCoins = [];
+                    $deductItems = [];
+
+                    // 暂时添加一个占位符代币扣除
+                    $deductCoin = new DeductCoin();
+                    $deductCoin->setType(1); // 假设类型1表示金币
+                    $deductCoin->setQuantity(100); // 假设价格100金币
+                    $deductCoins[] = $deductCoin;
+
+                    $deduct->setCoins($deductCoins);
+                    $deduct->setItems($deductItems);
+                    $goodItem->setCoin($deduct);
                 }
 
-                // 如果有购买限制,获取用户已购买数量
-                if ($shopItem->max_buy > 0) {
-                    $boughtCount = $shopItem->getUserBoughtCount($userId);
-                    $dataItem->setBoughtCount($boughtCount);
+                // 设置奖励组信息(作为商品)
+                if ($shopItemDto->rewardGroupId) {
+                    // TODO: 这里需要根据奖励组的具体内容来创建Reward对象
+                    // 暂时创建一个占位符,实际需要解析奖励组的内容
+                    $reward = new Reward();
+                    $rewardCoins = [];
+                    $rewardItems = [];
+
+                    // 暂时添加一个占位符物品奖励
+                    $rewardItem = new RewardItem();
+                    $rewardItem->setItemId(1); // 假设物品ID为1
+                    $rewardItem->setInstanceId(0); // 实例ID为0
+                    $rewardItem->setQuantity(1); // 数量为1
+                    $rewardItems[] = $rewardItem;
+
+                    $reward->setCoins($rewardCoins);
+                    $reward->setItems($rewardItems);
+                    $reward->setGods([]);
+                    $reward->setLands([]);
+
+                    // 设置单个Reward对象,而不是数组
+                    $goodItem->setItem($reward);
                 }
 
-                $itemList[] = $dataItem;
+                $itemList[] = $goodItem;
             }
 
-            // 添加分类数据
-            $categoryList = [];
-            foreach ($shopCategories as $category) {
-                $dataCategory = new DataShopCategory();
-                $dataCategory->setId($category->id);
-                $dataCategory->setName($category->name);
-                $dataCategory->setCode($category->code);
-                $dataCategory->setIcon($category->icon);
-                $dataCategory->setParentId($category->parent_id);
-
-                $categoryList[] = $dataCategory;
-            }
-
-            // 设置商品列表和分类列表到LastData
-            $lastData->setShopItems($itemList);
-            $lastData->setShopCategories($categoryList);
-
-            // 设置LastData到响应
-            $this->response->setLastData($lastData);
+            // 设置商品列表到响应
+            $response->setItems($itemList);
 
             // 设置响应状态
             $this->response->setCode(0);
@@ -130,9 +111,8 @@ class QueryHandler extends BaseHandler
             // 记录日志
             Log::info('用户查询商店成功', [
                 'user_id' => $userId,
-                'category_id' => $categoryId,
-                'item_count' => count($itemList),
-                'category_count' => count($categoryList)
+                'times' => $times,
+                'item_count' => count($itemList)
             ]);
 
         } catch (LogicException $e) {

+ 114 - 0
app/Module/Shop/Dtos/ShopCategoryDto.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace App\Module\Shop\Dtos;
+
+use App\Module\Shop\Models\ShopCategory;
+use UCore\Dto\BaseDto;
+
+/**
+ * 商店分类数据传输对象
+ * 
+ * 用于在服务层和控制器层之间传递商店分类数据,避免直接暴露模型
+ */
+class ShopCategoryDto extends BaseDto
+{
+    /**
+     * 分类ID
+     *
+     * @var int
+     */
+    public int $id;
+
+    /**
+     * 分类名称
+     *
+     * @var string
+     */
+    public string $name;
+
+    /**
+     * 分类代码
+     *
+     * @var string
+     */
+    public string $code;
+
+    /**
+     * 分类图标
+     *
+     * @var string|null
+     */
+    public ?string $icon;
+
+    /**
+     * 父分类ID
+     *
+     * @var int|null
+     */
+    public ?int $parentId;
+
+    /**
+     * 是否激活
+     *
+     * @var bool
+     */
+    public bool $isActive;
+
+    /**
+     * 排序权重
+     *
+     * @var int
+     */
+    public int $sortOrder;
+
+    /**
+     * 创建时间
+     *
+     * @var string
+     */
+    public string $createdAt;
+
+    /**
+     * 更新时间
+     *
+     * @var string
+     */
+    public string $updatedAt;
+
+    /**
+     * 子分类列表(可选)
+     *
+     * @var array|null
+     */
+    public ?array $children = null;
+
+    /**
+     * 从模型创建DTO
+     *
+     * @param ShopCategory $category 商店分类模型
+     * @param bool $includeChildren 是否包含子分类
+     * @return self
+     */
+    public static function fromModel(ShopCategory $category, bool $includeChildren = false): self
+    {
+        $dto = new self();
+        $dto->id = $category->id;
+        $dto->name = $category->name;
+        $dto->code = $category->code;
+        $dto->icon = $category->icon;
+        $dto->parentId = $category->parent_id;
+        $dto->isActive = $category->is_active;
+        $dto->sortOrder = $category->sort_order;
+        $dto->createdAt = $category->created_at->toDateTimeString();
+        $dto->updatedAt = $category->updated_at->toDateTimeString();
+
+        // 如果需要包含子分类
+        if ($includeChildren && $category->children) {
+            $dto->children = $category->children->map(function ($child) {
+                return self::fromModel($child, true);
+            })->toArray();
+        }
+
+        return $dto;
+    }
+}

+ 205 - 0
app/Module/Shop/Dtos/ShopItemDto.php

@@ -0,0 +1,205 @@
+<?php
+
+namespace App\Module\Shop\Dtos;
+
+use App\Module\Shop\Models\ShopItem;
+use UCore\Dto\BaseDto;
+
+/**
+ * 商店商品数据传输对象
+ * 
+ * 用于在服务层和控制器层之间传递商店商品数据,避免直接暴露模型
+ */
+class ShopItemDto extends BaseDto
+{
+    /**
+     * 商品ID
+     *
+     * @var int
+     */
+    public int $id;
+
+    /**
+     * 商品名称
+     *
+     * @var string
+     */
+    public string $name;
+
+    /**
+     * 商品描述
+     *
+     * @var string|null
+     */
+    public ?string $description;
+
+    /**
+     * 分类ID
+     *
+     * @var int|null
+     */
+    public ?int $categoryId;
+
+    /**
+     * 分类名称(字符串格式)
+     *
+     * @var string|null
+     */
+    public ?string $categoryName;
+
+    /**
+     * 消耗组ID
+     *
+     * @var int|null
+     */
+    public ?int $consumeGroupId;
+
+    /**
+     * 消耗组名称
+     *
+     * @var string|null
+     */
+    public ?string $consumeGroupName;
+
+    /**
+     * 消耗组描述
+     *
+     * @var string|null
+     */
+    public ?string $consumeGroupDescription;
+
+    /**
+     * 奖励组ID
+     *
+     * @var int|null
+     */
+    public ?int $rewardGroupId;
+
+    /**
+     * 奖励组名称
+     *
+     * @var string|null
+     */
+    public ?string $rewardGroupName;
+
+    /**
+     * 奖励组描述
+     *
+     * @var string|null
+     */
+    public ?string $rewardGroupDescription;
+
+    /**
+     * 最大购买数量(0表示无限制)
+     *
+     * @var int
+     */
+    public int $maxBuy;
+
+    /**
+     * 是否激活
+     *
+     * @var bool
+     */
+    public bool $isActive;
+
+    /**
+     * 排序权重
+     *
+     * @var int
+     */
+    public int $sortOrder;
+
+    /**
+     * 商品图片
+     *
+     * @var string|null
+     */
+    public ?string $image;
+
+    /**
+     * 上架时间(时间戳)
+     *
+     * @var int|null
+     */
+    public ?int $startTime;
+
+    /**
+     * 下架时间(时间戳)
+     *
+     * @var int|null
+     */
+    public ?int $endTime;
+
+    /**
+     * 创建时间
+     *
+     * @var string
+     */
+    public string $createdAt;
+
+    /**
+     * 更新时间
+     *
+     * @var string
+     */
+    public string $updatedAt;
+
+    /**
+     * 用户已购买数量(可选,需要传入用户ID时才设置)
+     *
+     * @var int|null
+     */
+    public ?int $userBoughtCount = null;
+
+    /**
+     * 当前有效的促销活动(可选)
+     *
+     * @var ShopPromotionDto|null
+     */
+    public ?ShopPromotionDto $activePromotion = null;
+
+    /**
+     * 从模型创建DTO
+     *
+     * @param ShopItem $shopItem 商店商品模型
+     * @param int|null $userId 用户ID(可选,用于获取用户购买数量)
+     * @return self
+     */
+    public static function fromModel(ShopItem $shopItem, ?int $userId = null): self
+    {
+        $dto = new self();
+        $dto->id = $shopItem->id;
+        $dto->name = $shopItem->name;
+        $dto->description = $shopItem->description;
+        $dto->categoryId = $shopItem->category_id;
+        $dto->categoryName = $shopItem->category_name ?? ($shopItem->category->name ?? null);
+        $dto->consumeGroupId = $shopItem->consume_group_id;
+        $dto->consumeGroupName = $shopItem->consumeGroup->name ?? null;
+        $dto->consumeGroupDescription = $shopItem->consumeGroup->description ?? null;
+        $dto->rewardGroupId = $shopItem->reward_group_id;
+        $dto->rewardGroupName = $shopItem->rewardGroup->name ?? null;
+        $dto->rewardGroupDescription = $shopItem->rewardGroup->description ?? null;
+        $dto->maxBuy = $shopItem->max_buy;
+        $dto->isActive = $shopItem->is_active;
+        $dto->sortOrder = $shopItem->sort_order;
+        $dto->image = $shopItem->image;
+        $dto->startTime = $shopItem->start_time ? $shopItem->start_time->timestamp : null;
+        $dto->endTime = $shopItem->end_time ? $shopItem->end_time->timestamp : null;
+        $dto->createdAt = $shopItem->created_at->toDateTimeString();
+        $dto->updatedAt = $shopItem->updated_at->toDateTimeString();
+
+        // 如果提供了用户ID,获取用户已购买数量
+        if ($userId !== null) {
+            $dto->userBoughtCount = $shopItem->getUserBoughtCount($userId);
+        }
+
+        // 获取当前有效的促销活动
+        $activePromotion = $shopItem->getActivePromotion();
+        if ($activePromotion) {
+            $dto->activePromotion = ShopPromotionDto::fromModel($activePromotion);
+        }
+
+        return $dto;
+    }
+}

+ 124 - 0
app/Module/Shop/Dtos/ShopPromotionDto.php

@@ -0,0 +1,124 @@
+<?php
+
+namespace App\Module\Shop\Dtos;
+
+use App\Module\Shop\Models\ShopPromotion;
+use UCore\Dto\BaseDto;
+
+/**
+ * 商店促销活动数据传输对象
+ * 
+ * 用于在服务层和控制器层之间传递促销活动数据,避免直接暴露模型
+ */
+class ShopPromotionDto extends BaseDto
+{
+    /**
+     * 促销活动ID
+     *
+     * @var int
+     */
+    public int $id;
+
+    /**
+     * 促销活动名称
+     *
+     * @var string
+     */
+    public string $name;
+
+    /**
+     * 促销活动描述
+     *
+     * @var string|null
+     */
+    public ?string $description;
+
+    /**
+     * 折扣类型(1:百分比折扣, 2:固定金额折扣)
+     *
+     * @var int
+     */
+    public int $discountType;
+
+    /**
+     * 折扣值
+     *
+     * @var int
+     */
+    public int $discountValue;
+
+    /**
+     * 是否激活
+     *
+     * @var bool
+     */
+    public bool $isActive;
+
+    /**
+     * 开始时间(时间戳)
+     *
+     * @var int|null
+     */
+    public ?int $startTime;
+
+    /**
+     * 结束时间(时间戳)
+     *
+     * @var int|null
+     */
+    public ?int $endTime;
+
+    /**
+     * 排序权重
+     *
+     * @var int
+     */
+    public int $sortOrder;
+
+    /**
+     * 创建时间
+     *
+     * @var string
+     */
+    public string $createdAt;
+
+    /**
+     * 更新时间
+     *
+     * @var string
+     */
+    public string $updatedAt;
+
+    /**
+     * 自定义折扣值(可选,来自促销商品关联表)
+     *
+     * @var int|null
+     */
+    public ?int $customDiscountValue = null;
+
+    /**
+     * 从模型创建DTO
+     *
+     * @param ShopPromotion $promotion 促销活动模型
+     * @param int|null $customDiscountValue 自定义折扣值(可选)
+     * @return self
+     */
+    public static function fromModel(ShopPromotion $promotion, ?int $customDiscountValue = null): self
+    {
+        $dto = new self();
+        $dto->id = $promotion->id;
+        $dto->name = $promotion->name;
+        $dto->description = $promotion->description;
+        $dto->discountType = $promotion->discount_type;
+        $dto->discountValue = $promotion->discount_value;
+        $dto->isActive = $promotion->is_active;
+        $dto->startTime = $promotion->start_time ? $promotion->start_time->timestamp : null;
+        $dto->endTime = $promotion->end_time ? $promotion->end_time->timestamp : null;
+        $dto->sortOrder = $promotion->sort_order;
+        $dto->createdAt = $promotion->created_at->toDateTimeString();
+        $dto->updatedAt = $promotion->updated_at->toDateTimeString();
+        $dto->customDiscountValue = $customDiscountValue;
+
+        return $dto;
+    }
+}

+ 31 - 27
app/Module/Shop/Logics/ShopLogic.php

@@ -2,7 +2,6 @@
 
 namespace App\Module\Shop\Logics;
 
-use App\Module\GameItems\Models\Item;
 use App\Module\Shop\Models\ShopCategory;
 use App\Module\Shop\Models\ShopItem;
 use App\Module\Shop\Models\ShopPromotion;
@@ -10,8 +9,6 @@ use App\Module\Shop\Models\ShopPromotionItem;
 use App\Module\Shop\Models\ShopPurchaseLog;
 use Carbon\Carbon;
 use Illuminate\Database\Eloquent\Collection;
-use Illuminate\Support\Facades\DB;
-use UCore\Exception\LogicException;
 
 /**
  * 商店逻辑类
@@ -29,7 +26,7 @@ class ShopLogic
      */
     public function getShopItems(array $filters = []): Collection
     {
-        $query = ShopItem::with(['item', 'category', 'promotions' => function ($q) {
+        $query = ShopItem::with(['category', 'consumeGroup', 'rewardGroup', 'promotions' => function ($q) {
                 $now = Carbon::now();
                 $q->where('is_active', true)
                     ->where(function ($q) use ($now) {
@@ -65,18 +62,20 @@ class ShopLogic
             $query->where('category_id', $filters['category_id']);
         }
 
-        if (isset($filters['item_id'])) {
-            $query->where('item_id', $filters['item_id']);
-        }
+        // 注意:由于商品结构已改为消耗组和奖励组模式,不再支持按item_id过滤
+        // if (isset($filters['item_id'])) {
+        //     $query->where('item_id', $filters['item_id']);
+        // }
 
         if (isset($filters['keyword'])) {
             $keyword = $filters['keyword'];
             $query->where(function ($q) use ($keyword) {
                 $q->where('name', 'like', "%{$keyword}%")
-                    ->orWhere('description', 'like', "%{$keyword}%")
-                    ->orWhereHas('item', function ($q) use ($keyword) {
-                        $q->where('name', 'like', "%{$keyword}%");
-                    });
+                    ->orWhere('description', 'like', "%{$keyword}%");
+                    // 注意:由于商品结构已改为消耗组和奖励组模式,不再支持按关联物品名称搜索
+                    // ->orWhereHas('item', function ($q) use ($keyword) {
+                    //     $q->where('name', 'like', "%{$keyword}%");
+                    // });
             });
         }
 
@@ -110,15 +109,16 @@ class ShopLogic
         // 获取商品列表
         $items = $query->get();
 
-        // 计算折扣价格
-        foreach ($items as $item) {
-            $item->original_price = $item->price;
-            $item->discounted_price = $item->getDiscountedPrice();
-            $item->has_discount = $item->discounted_price < $item->original_price;
-            $item->discount_percentage = $item->original_price > 0
-                ? round((1 - $item->discounted_price / $item->original_price) * 100)
-                : 0;
-        }
+        // 注意:由于商品结构已改为消耗组和奖励组模式,不再有固定价格
+        // 折扣价格计算需要根据消耗组和促销活动重新实现
+        // foreach ($items as $item) {
+        //     $item->original_price = $item->price;
+        //     $item->discounted_price = $item->getDiscountedPrice();
+        //     $item->has_discount = $item->discounted_price < $item->original_price;
+        //     $item->discount_percentage = $item->original_price > 0
+        //         ? round((1 - $item->discounted_price / $item->original_price) * 100)
+        //         : 0;
+        // }
 
         return $items;
     }
@@ -186,10 +186,14 @@ class ShopLogic
             }
         }
 
-        // 检查关联物品是否存在
-        $item = Item::find($shopItem->item_id);
-        if (!$item) {
-            return [false, '商品关联的物品不存在'];
+        // 注意:由于商品结构已改为消耗组和奖励组模式,不再检查单一关联物品
+        // 需要检查消耗组和奖励组是否存在
+        if ($shopItem->consume_group_id && !$shopItem->consumeGroup) {
+            return [false, '商品关联的消耗组不存在'];
+        }
+
+        if ($shopItem->reward_group_id && !$shopItem->rewardGroup) {
+            return [false, '商品关联的奖励组不存在'];
         }
 
         return [true, ''];
@@ -218,7 +222,7 @@ class ShopLogic
      */
     public function getUserPurchaseHistory(int $userId, array $filters = []): Collection
     {
-        $query = ShopPurchaseLog::with(['shopItem', 'item'])
+        $query = ShopPurchaseLog::with(['shopItem'])
             ->where('user_id', $userId);
 
         // 应用过滤条件
@@ -318,10 +322,10 @@ class ShopLogic
     public function addItemToPromotion(int $promotionId, int $shopItemId, ?int $customDiscountValue = null): ShopPromotionItem
     {
         // 检查促销活动是否存在
-        $promotion = ShopPromotion::findOrFail($promotionId);
+        ShopPromotion::findOrFail($promotionId);
 
         // 检查商品是否存在
-        $shopItem = ShopItem::findOrFail($shopItemId);
+        ShopItem::findOrFail($shopItemId);
 
         // 检查商品是否已经在促销活动中
         $existingItem = ShopPromotionItem::where('promotion_id', $promotionId)

+ 29 - 10
app/Module/Shop/Services/ShopService.php

@@ -10,7 +10,8 @@ use App\Module\Shop\Logics\ShopLogic;
 use App\Module\Shop\Models\ShopItem;
 use App\Module\Shop\Models\ShopPromotion;
 use App\Module\Shop\Models\ShopPromotionItem;
-use App\Module\Shop\Models\ShopPurchaseLog;
+use App\Module\Shop\Dtos\ShopItemDto;
+use App\Module\Shop\Dtos\ShopCategoryDto;
 use Exception;
 use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Support\Facades\DB;
@@ -31,24 +32,35 @@ class ShopService
      * 获取商店商品列表
      *
      * @param array $filters 过滤条件
-     * @return Collection 商品列表
+     * @param int|null $userId 用户ID(可选,用于获取用户购买数量)
+     * @return array ShopItemDto数组
      */
-    public static function getShopItems(array $filters = []): Collection
+    public static function getShopItems(array $filters = [], ?int $userId = null): array
     {
         $shopLogic = new ShopLogic();
-        return $shopLogic->getShopItems($filters);
+        $shopItems = $shopLogic->getShopItems($filters);
+
+        // 将模型集合转换为DTO数组
+        return $shopItems->map(function ($shopItem) use ($userId) {
+            return ShopItemDto::fromModel($shopItem, $userId);
+        })->values()->all();
     }
 
     /**
      * 获取商店分类列表
      *
      * @param bool $onlyActive 是否只获取激活的分类
-     * @return Collection 分类列表
+     * @return array ShopCategoryDto数组
      */
-    public static function getShopCategories(bool $onlyActive = true): Collection
+    public static function getShopCategories(bool $onlyActive = true): array
     {
         $shopLogic = new ShopLogic();
-        return $shopLogic->getShopCategories($onlyActive);
+        $categories = $shopLogic->getShopCategories($onlyActive);
+
+        // 将模型集合转换为DTO数组
+        return $categories->map(function ($category) {
+            return ShopCategoryDto::fromModel($category);
+        })->values()->all();
     }
 
     /**
@@ -163,11 +175,18 @@ class ShopService
      * 获取商品详情
      *
      * @param int $shopItemId 商品ID
-     * @return ShopItem|null 商品详情
+     * @param int|null $userId 用户ID(可选,用于获取用户购买数量)
+     * @return ShopItemDto|null 商品详情DTO
      */
-    public static function getShopItemDetail(int $shopItemId): ?ShopItem
+    public static function getShopItemDetail(int $shopItemId, ?int $userId = null): ?ShopItemDto
     {
-        return ShopItem::with(['category', 'consumeGroup', 'rewardGroup', 'promotions'])->find($shopItemId);
+        $shopItem = ShopItem::with(['category', 'consumeGroup', 'rewardGroup', 'promotions'])->find($shopItemId);
+
+        if (!$shopItem) {
+            return null;
+        }
+
+        return ShopItemDto::fromModel($shopItem, $userId);
     }
 
     /**

+ 19 - 15
app/Module/Shop/Validators/ShopFundValidator.php

@@ -2,12 +2,12 @@
 
 namespace App\Module\Shop\Validators;
 
-use App\Module\Fund\Services\AccountService;
+use App\Module\Fund\Models\FundModel;
 use UCore\Validator;
 
 /**
  * 商店资金验证器
- * 
+ *
  * 验证用户资金是否充足
  */
 class ShopFundValidator extends Validator
@@ -36,20 +36,24 @@ class ShopFundValidator extends Validator
         }
 
         try {
-            // 计算总价
-            $totalPrice = $shopItem->price * $number;
-
-            // 获取用户账户余额
-            $userAccount = AccountService::getUserAccount($userId, $shopItem->currency_id);
-            
-            if (!$userAccount) {
-                $this->addError('用户账户不存在');
-                return false;
-            }
+            // TODO: 由于商品结构已改为消耗组和奖励组模式,需要重新实现资金验证逻辑
+            // 暂时跳过资金验证,等消耗组系统完善后再实现
+
+            // 检查商品是否有消耗组
+            if ($shopItem->consume_group_id) {
+                // TODO: 这里应该调用消耗组验证服务
+                // $consumeValidation = ConsumeGroupService::validateConsume($userId, $shopItem->consume_group_id, $number);
+                // if (!$consumeValidation['success']) {
+                //     $this->addError($consumeValidation['message']);
+                //     return false;
+                // }
 
-            if ($userAccount->balance < $totalPrice) {
-                $this->addError('余额不足,需要' . $totalPrice . ',当前余额' . $userAccount->balance);
-                return false;
+                // 暂时的占位符验证 - 假设验证100金币
+                $userAccount = FundModel::getAccount($userId, 1); // 假设货币类型1是金币
+                if ($userAccount && $userAccount->balance < 100 * $number) {
+                    $this->addError('金币不足,需要' . (100 * $number) . ',当前余额' . $userAccount->balance);
+                    return false;
+                }
             }
 
             return true;

+ 1 - 1
config/proto_route.php

@@ -90,7 +90,7 @@ return array (
       7 => 'query_data',
     ),
   ),
-  'generated_at' => '+08:00 2025-05-29 10:59:37',
+  'generated_at' => '+08:00 2025-05-29 18:57:28',
   'conventions' => 
   array (
     'handler_namespace' => 'App\\Module\\AppGame\\Handler',

+ 1 - 1
protophp/GPBMetadata/Proto/Game.php

@@ -466,7 +466,7 @@ to_user_id (
 
 id (&
 coin (2.uraus.kku.Common.Deduct&
-item (2.uraus.kku.Common.Reward
+item (2.uraus.kku.Common.Reward
 category (	
 ResponseShopBuy
 ResponseTaskData

+ 20 - 10
protophp/Uraus/Kku/Response/ShopGoodItem.php

@@ -28,9 +28,9 @@ class ShopGoodItem extends \Google\Protobuf\Internal\Message
     /**
      * 商品
      *
-     * Generated from protobuf field <code>repeated .uraus.kku.Common.Reward item = 3;</code>
+     * Generated from protobuf field <code>.uraus.kku.Common.Reward item = 3;</code>
      */
-    private $item;
+    protected $item = null;
     /**
      * 分类
      *
@@ -47,7 +47,7 @@ class ShopGoodItem extends \Google\Protobuf\Internal\Message
      *     @type int|string $id
      *     @type \Uraus\Kku\Common\Deduct $coin
      *           价格
-     *     @type \Uraus\Kku\Common\Reward[]|\Google\Protobuf\Internal\RepeatedField $item
+     *     @type \Uraus\Kku\Common\Reward $item
      *           商品
      *     @type string $category
      *           分类
@@ -119,25 +119,35 @@ class ShopGoodItem extends \Google\Protobuf\Internal\Message
     /**
      * 商品
      *
-     * Generated from protobuf field <code>repeated .uraus.kku.Common.Reward item = 3;</code>
-     * @return \Google\Protobuf\Internal\RepeatedField
+     * Generated from protobuf field <code>.uraus.kku.Common.Reward item = 3;</code>
+     * @return \Uraus\Kku\Common\Reward
      */
     public function getItem()
     {
-        return $this->item;
+        return isset($this->item) ? $this->item : null;
+    }
+
+    public function hasItem()
+    {
+        return isset($this->item);
+    }
+
+    public function clearItem()
+    {
+        unset($this->item);
     }
 
     /**
      * 商品
      *
-     * Generated from protobuf field <code>repeated .uraus.kku.Common.Reward item = 3;</code>
-     * @param \Uraus\Kku\Common\Reward[]|\Google\Protobuf\Internal\RepeatedField $var
+     * Generated from protobuf field <code>.uraus.kku.Common.Reward item = 3;</code>
+     * @param \Uraus\Kku\Common\Reward $var
      * @return $this
      */
     public function setItem($var)
     {
-        $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Uraus\Kku\Common\Reward::class);
-        $this->item = $arr;
+        GPBUtil::checkMessage($var, \Uraus\Kku\Common\Reward::class);
+        $this->item = $var;
 
         return $this;
     }

+ 9 - 0
vendor/composer/autoload_classmap.php

@@ -19,6 +19,7 @@ return array(
     'App\\Console\\Commands\\GenerateProtoRouteCommand' => $baseDir . '/app/Console/Commands/GenerateProtoRouteCommand.php',
     'App\\Console\\Commands\\GetMenuById' => $baseDir . '/app/Console/Commands/GetMenuById.php',
     'App\\Console\\Commands\\InsertFarmAdminMenu' => $baseDir . '/app/Console/Commands/InsertFarmAdminMenu.php',
+    'App\\Console\\Commands\\InsertShopAdminMenu' => $baseDir . '/app/Console/Commands/InsertShopAdminMenu.php',
     'App\\Console\\Commands\\InsertTeamAdminMenu' => $baseDir . '/app/Console/Commands/InsertTeamAdminMenu.php',
     'App\\Console\\Commands\\RemoveDuplicateMenus' => $baseDir . '/app/Console/Commands/RemoveDuplicateMenus.php',
     'App\\Console\\Commands\\TestSizeRotatingLog' => $baseDir . '/app/Console/Commands/TestSizeRotatingLog.php',
@@ -341,6 +342,7 @@ return array(
     'App\\Module\\Farm\\Logics\\TeamProfitLogic' => $baseDir . '/app/Module/Farm/Logics/TeamProfitLogic.php',
     'App\\Module\\Farm\\Logics\\UpgradeLogLogic' => $baseDir . '/app/Module/Farm/Logics/UpgradeLogLogic.php',
     'App\\Module\\Farm\\Models\\FarmCrop' => $baseDir . '/app/Module/Farm/Models/FarmCrop.php',
+    'App\\Module\\Farm\\Models\\FarmFruitGrowthCycle' => $baseDir . '/app/Module/Farm/Models/FarmFruitGrowthCycle.php',
     'App\\Module\\Farm\\Models\\FarmGodBuff' => $baseDir . '/app/Module/Farm/Models/FarmGodBuff.php',
     'App\\Module\\Farm\\Models\\FarmHarvestLog' => $baseDir . '/app/Module/Farm/Models/FarmHarvestLog.php',
     'App\\Module\\Farm\\Models\\FarmHouseConfig' => $baseDir . '/app/Module/Farm/Models/FarmHouseConfig.php',
@@ -733,6 +735,9 @@ return array(
     'App\\Module\\Game\\AdminControllers\\LazyRenderable\\GameRewardGroupLazyRenderable' => $baseDir . '/app/Module/Game/AdminControllers/LazyRenderable/GameRewardGroupLazyRenderable.php',
     'App\\Module\\Game\\AdminControllers\\LazyRenderable\\RewardGroupLazyRenderable' => $baseDir . '/app/Module/Game/AdminControllers/LazyRenderable/RewardGroupLazyRenderable.php',
     'App\\Module\\Game\\AdminControllers\\TempDataController' => $baseDir . '/app/Module/Game/AdminControllers/TempDataController.php',
+    'App\\Module\\Game\\AdminControllers\\Tools\\ItemGroupList' => $baseDir . '/app/Module/Game/AdminControllers/Tools/ItemGroupList.php',
+    'App\\Module\\Game\\AdminControllers\\Tools\\RewareGroupInfo' => $baseDir . '/app/Module/Game/AdminControllers/Tools/RewareGroupInfo.php',
+    'App\\Module\\Game\\AdminControllers\\Tools\\RewareItemGroupInfoList' => $baseDir . '/app/Module/Game/AdminControllers/Tools/RewareItemGroupInfoList.php',
     'App\\Module\\Game\\Commands\\CleanExpiredRewardLogsCommand' => $baseDir . '/app/Module/Game/Commands/CleanExpiredRewardLogsCommand.php',
     'App\\Module\\Game\\Commands\\ImportRewardGroupsCommand' => $baseDir . '/app/Module/Game/Commands/ImportRewardGroupsCommand.php',
     'App\\Module\\Game\\Commands\\TestConditionCommand' => $baseDir . '/app/Module/Game/Commands/TestConditionCommand.php',
@@ -955,6 +960,9 @@ return array(
     'App\\Module\\Shop\\AdminControllers\\ShopItemController' => $baseDir . '/app/Module/Shop/AdminControllers/ShopItemController.php',
     'App\\Module\\Shop\\AdminControllers\\ShopPromotionController' => $baseDir . '/app/Module/Shop/AdminControllers/ShopPromotionController.php',
     'App\\Module\\Shop\\AdminControllers\\ShopPurchaseLogController' => $baseDir . '/app/Module/Shop/AdminControllers/ShopPurchaseLogController.php',
+    'App\\Module\\Shop\\Dtos\\ShopCategoryDto' => $baseDir . '/app/Module/Shop/Dtos/ShopCategoryDto.php',
+    'App\\Module\\Shop\\Dtos\\ShopItemDto' => $baseDir . '/app/Module/Shop/Dtos/ShopItemDto.php',
+    'App\\Module\\Shop\\Dtos\\ShopPromotionDto' => $baseDir . '/app/Module/Shop/Dtos/ShopPromotionDto.php',
     'App\\Module\\Shop\\Events\\ShopItemPurchased' => $baseDir . '/app/Module/Shop/Events/ShopItemPurchased.php',
     'App\\Module\\Shop\\Logics\\ShopLogic' => $baseDir . '/app/Module/Shop/Logics/ShopLogic.php',
     'App\\Module\\Shop\\Models\\ShopCategory' => $baseDir . '/app/Module/Shop/Models/ShopCategory.php',
@@ -9656,6 +9664,7 @@ return array(
     'UCore\\DcatAdmin\\FilterHelper' => $baseDir . '/UCore/DcatAdmin/FilterHelper.php',
     'UCore\\DcatAdmin\\FormHelper' => $baseDir . '/UCore/DcatAdmin/FormHelper.php',
     'UCore\\DcatAdmin\\Form\\AbstractTool' => $baseDir . '/UCore/DcatAdmin/Form/AbstractTool.php',
+    'UCore\\DcatAdmin\\Form\\Link' => $baseDir . '/UCore/DcatAdmin/Form/Link.php',
     'UCore\\DcatAdmin\\GridHelper' => $baseDir . '/UCore/DcatAdmin/GridHelper.php',
     'UCore\\DcatAdmin\\Grid\\Actions\\Edit' => $baseDir . '/UCore/DcatAdmin/Grid/Actions/Edit.php',
     'UCore\\DcatAdmin\\Grid\\BatchAction' => $baseDir . '/UCore/DcatAdmin/Grid/BatchAction.php',

+ 9 - 0
vendor/composer/autoload_static.php

@@ -739,6 +739,7 @@ class ComposerStaticInita2207959542f13e6e79e83f2b0d9a425
         'App\\Console\\Commands\\GenerateProtoRouteCommand' => __DIR__ . '/../..' . '/app/Console/Commands/GenerateProtoRouteCommand.php',
         'App\\Console\\Commands\\GetMenuById' => __DIR__ . '/../..' . '/app/Console/Commands/GetMenuById.php',
         'App\\Console\\Commands\\InsertFarmAdminMenu' => __DIR__ . '/../..' . '/app/Console/Commands/InsertFarmAdminMenu.php',
+        'App\\Console\\Commands\\InsertShopAdminMenu' => __DIR__ . '/../..' . '/app/Console/Commands/InsertShopAdminMenu.php',
         'App\\Console\\Commands\\InsertTeamAdminMenu' => __DIR__ . '/../..' . '/app/Console/Commands/InsertTeamAdminMenu.php',
         'App\\Console\\Commands\\RemoveDuplicateMenus' => __DIR__ . '/../..' . '/app/Console/Commands/RemoveDuplicateMenus.php',
         'App\\Console\\Commands\\TestSizeRotatingLog' => __DIR__ . '/../..' . '/app/Console/Commands/TestSizeRotatingLog.php',
@@ -1061,6 +1062,7 @@ class ComposerStaticInita2207959542f13e6e79e83f2b0d9a425
         'App\\Module\\Farm\\Logics\\TeamProfitLogic' => __DIR__ . '/../..' . '/app/Module/Farm/Logics/TeamProfitLogic.php',
         'App\\Module\\Farm\\Logics\\UpgradeLogLogic' => __DIR__ . '/../..' . '/app/Module/Farm/Logics/UpgradeLogLogic.php',
         'App\\Module\\Farm\\Models\\FarmCrop' => __DIR__ . '/../..' . '/app/Module/Farm/Models/FarmCrop.php',
+        'App\\Module\\Farm\\Models\\FarmFruitGrowthCycle' => __DIR__ . '/../..' . '/app/Module/Farm/Models/FarmFruitGrowthCycle.php',
         'App\\Module\\Farm\\Models\\FarmGodBuff' => __DIR__ . '/../..' . '/app/Module/Farm/Models/FarmGodBuff.php',
         'App\\Module\\Farm\\Models\\FarmHarvestLog' => __DIR__ . '/../..' . '/app/Module/Farm/Models/FarmHarvestLog.php',
         'App\\Module\\Farm\\Models\\FarmHouseConfig' => __DIR__ . '/../..' . '/app/Module/Farm/Models/FarmHouseConfig.php',
@@ -1453,6 +1455,9 @@ class ComposerStaticInita2207959542f13e6e79e83f2b0d9a425
         'App\\Module\\Game\\AdminControllers\\LazyRenderable\\GameRewardGroupLazyRenderable' => __DIR__ . '/../..' . '/app/Module/Game/AdminControllers/LazyRenderable/GameRewardGroupLazyRenderable.php',
         'App\\Module\\Game\\AdminControllers\\LazyRenderable\\RewardGroupLazyRenderable' => __DIR__ . '/../..' . '/app/Module/Game/AdminControllers/LazyRenderable/RewardGroupLazyRenderable.php',
         'App\\Module\\Game\\AdminControllers\\TempDataController' => __DIR__ . '/../..' . '/app/Module/Game/AdminControllers/TempDataController.php',
+        'App\\Module\\Game\\AdminControllers\\Tools\\ItemGroupList' => __DIR__ . '/../..' . '/app/Module/Game/AdminControllers/Tools/ItemGroupList.php',
+        'App\\Module\\Game\\AdminControllers\\Tools\\RewareGroupInfo' => __DIR__ . '/../..' . '/app/Module/Game/AdminControllers/Tools/RewareGroupInfo.php',
+        'App\\Module\\Game\\AdminControllers\\Tools\\RewareItemGroupInfoList' => __DIR__ . '/../..' . '/app/Module/Game/AdminControllers/Tools/RewareItemGroupInfoList.php',
         'App\\Module\\Game\\Commands\\CleanExpiredRewardLogsCommand' => __DIR__ . '/../..' . '/app/Module/Game/Commands/CleanExpiredRewardLogsCommand.php',
         'App\\Module\\Game\\Commands\\ImportRewardGroupsCommand' => __DIR__ . '/../..' . '/app/Module/Game/Commands/ImportRewardGroupsCommand.php',
         'App\\Module\\Game\\Commands\\TestConditionCommand' => __DIR__ . '/../..' . '/app/Module/Game/Commands/TestConditionCommand.php',
@@ -1675,6 +1680,9 @@ class ComposerStaticInita2207959542f13e6e79e83f2b0d9a425
         'App\\Module\\Shop\\AdminControllers\\ShopItemController' => __DIR__ . '/../..' . '/app/Module/Shop/AdminControllers/ShopItemController.php',
         'App\\Module\\Shop\\AdminControllers\\ShopPromotionController' => __DIR__ . '/../..' . '/app/Module/Shop/AdminControllers/ShopPromotionController.php',
         'App\\Module\\Shop\\AdminControllers\\ShopPurchaseLogController' => __DIR__ . '/../..' . '/app/Module/Shop/AdminControllers/ShopPurchaseLogController.php',
+        'App\\Module\\Shop\\Dtos\\ShopCategoryDto' => __DIR__ . '/../..' . '/app/Module/Shop/Dtos/ShopCategoryDto.php',
+        'App\\Module\\Shop\\Dtos\\ShopItemDto' => __DIR__ . '/../..' . '/app/Module/Shop/Dtos/ShopItemDto.php',
+        'App\\Module\\Shop\\Dtos\\ShopPromotionDto' => __DIR__ . '/../..' . '/app/Module/Shop/Dtos/ShopPromotionDto.php',
         'App\\Module\\Shop\\Events\\ShopItemPurchased' => __DIR__ . '/../..' . '/app/Module/Shop/Events/ShopItemPurchased.php',
         'App\\Module\\Shop\\Logics\\ShopLogic' => __DIR__ . '/../..' . '/app/Module/Shop/Logics/ShopLogic.php',
         'App\\Module\\Shop\\Models\\ShopCategory' => __DIR__ . '/../..' . '/app/Module/Shop/Models/ShopCategory.php',
@@ -10376,6 +10384,7 @@ class ComposerStaticInita2207959542f13e6e79e83f2b0d9a425
         'UCore\\DcatAdmin\\FilterHelper' => __DIR__ . '/../..' . '/UCore/DcatAdmin/FilterHelper.php',
         'UCore\\DcatAdmin\\FormHelper' => __DIR__ . '/../..' . '/UCore/DcatAdmin/FormHelper.php',
         'UCore\\DcatAdmin\\Form\\AbstractTool' => __DIR__ . '/../..' . '/UCore/DcatAdmin/Form/AbstractTool.php',
+        'UCore\\DcatAdmin\\Form\\Link' => __DIR__ . '/../..' . '/UCore/DcatAdmin/Form/Link.php',
         'UCore\\DcatAdmin\\GridHelper' => __DIR__ . '/../..' . '/UCore/DcatAdmin/GridHelper.php',
         'UCore\\DcatAdmin\\Grid\\Actions\\Edit' => __DIR__ . '/../..' . '/UCore/DcatAdmin/Grid/Actions/Edit.php',
         'UCore\\DcatAdmin\\Grid\\BatchAction' => __DIR__ . '/../..' . '/UCore/DcatAdmin/Grid/BatchAction.php',