with(['item', 'instance']); // 应用过滤条件 if (isset($filters['item_id'])) { $query->where('item_id', $filters['item_id']); } if (isset($filters['category_id'])) { $query->whereHas('item', function ($q) use ($filters) { $q->where('category_id', $filters['category_id']); }); } if (isset($filters['type'])) { $query->whereHas('item', function ($q) use ($filters) { $q->where('type', $filters['type']); }); } // 排除过期物品 if (!$includeExpired) { $now = now(); $query->where(function ($q) use ($now) { $q->whereNull('expire_at') ->orWhere('expire_at', '>', $now); })->whereHas('item', function ($q) use ($now) { $q->where(function ($subQ) use ($now) { $subQ->whereNull('global_expire_at') ->orWhere('global_expire_at', '>', $now); }); }); } // 获取模型集合 $itemUsers = $query->get(); // 转换为DTO集合 return $itemUsers->map(function (ItemUser $itemUser) { return ItemUserDto::fromModel($itemUser); }); } /** * 添加物品到用户背包 * * @param int $userId 用户ID * @param int $itemId 物品ID * @param int $quantity 数量 * @param array $options 选项 * @return array 添加结果 * @throws Exception */ public static function addItem(int $userId, int $itemId, int $quantity, array $options = []): array { // 获取物品信息 $item = Item::findOrFail($itemId); // 检查物品是否已过期(全局过期) if (ItemLogic::isExpired($item)) { throw new Exception("物品 {$itemId} 已全局过期"); } // 处理单独属性物品 if ($item->is_unique) { return ItemLogic::addUniqueItem($userId, $itemId, $options); } // 处理统一属性物品 return ItemLogic::addNormalItem($userId, $itemId, $quantity, $options); } /** * 消耗用户物品 * * @param int $userId 用户ID * @param int $itemId 物品ID * @param int|null $instanceId 物品实例ID(单独属性物品) * @param int $quantity 数量 * @param array $options 选项 * @return array 消耗结果 * @throws Exception */ public static function consumeItem(int $userId, int $itemId, ?int $instanceId, int $quantity, array $options = []): array { Helper::check_tr(); // 获取物品信息(确保物品存在) Item::findOrFail($itemId); if ($instanceId) { // 消耗单独属性物品 return ItemLogic::consumeUniqueItem($userId, $itemId, $instanceId, $options); } else { // 消耗统一属性物品 return ItemLogic::consumeNormalItem($userId, $itemId, $quantity, $options); } } /** * 获取物品信息 * * @param int $itemId 物品ID * @return ItemDto|null 物品信息DTO */ public static function getItemInfo(int $itemId): ?ItemDto { $item = Item::find($itemId); if (!$item) { return null; } return ItemDto::fromModel($item); } /** * 获取物品数值属性 * * @param int $itemId 物品ID * @param string $attributeName 属性名称 * @param int $defaultValue 默认值 * @return int 属性值 */ public static function getItemNumericAttribute(int $itemId, string $attributeName, int $defaultValue = 0): int { /** * @var Item $item */ $item = Item::find($itemId); if (!$item) { return $defaultValue; } $numericAttributes = (array)$item->numeric_attributes; return $numericAttributes[$attributeName] ?? $defaultValue; } /** * 验证用户是否拥有足够数量的物品 * * @param int $userId 用户ID * @param int $itemId 物品ID * @param int $quantity 需要的数量 * @param int|null $instanceId 物品实例ID(可选,用于验证特定实例物品) * @param bool $includeAllTypes 是否包含所有类型的物品(普通物品和实例物品) * @param bool $includeExpired 是否包含已过期物品 * @return Res 验证结果 */ public static function checkItemQuantity(int $userId, int $itemId, int $quantity, ?int $instanceId = null, bool $includeAllTypes = false, bool $includeExpired = false): Res { try { // 获取物品信息 $item = Item::find($itemId); if (!$item) { return Res::error("物品不存在(ID: {$itemId})"); } // 检查物品是否已过期(全局过期) if (ItemLogic::isExpired($item)) { return Res::error("物品已全局过期(ID: {$itemId})"); } // 根据不同情况进行验证 if ($instanceId ) { // 验证特定实例物品 $hasInstance = ItemQuantity::hasInstanceItem($userId, $itemId, $instanceId, $includeExpired); if (!$hasInstance) { return Res::error("用户不拥有指定的实例物品(ID: {$instanceId})"); } return Res::success("用户拥有指定的实例物品"); } else if ($includeAllTypes) { // 验证所有类型物品(普通物品和实例物品)的总数量 $totalQuantity = ItemQuantity::getUserTotalItemQuantity($userId, $itemId, $includeExpired); if ($totalQuantity < $quantity) { return Res::error("物品总数量不足,需要{$quantity}个,但只有{$totalQuantity}个", [ 'required' => $quantity, 'available' => $totalQuantity ]); } return Res::success("物品总数量足够", [ 'quantity' => $totalQuantity, 'required' => $quantity ]); } else { // 只验证普通物品数量 $normalQuantity = ItemQuantity::getUserItemQuantity($userId, $itemId, $includeExpired); if ($normalQuantity < $quantity) { return Res::error("普通物品数量不足,需要{$quantity}个,但只有{$normalQuantity}个", [ 'required' => $quantity, 'available' => $normalQuantity ]); } return Res::success("普通物品数量足够", [ 'quantity' => $normalQuantity, 'required' => $quantity ]); } } catch (Exception $e) { return Res::error("验证物品数量时发生错误: " . $e->getMessage(), [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); } } /** * 获取用户拥有的种子物品 * * @param int $userId 用户ID * @param bool $includeExpired 是否包含已过期物品 * @return array 种子物品数组,键为物品ID,值为数量 */ public static function getUserSeedItems(int $userId, bool $includeExpired = false): array { try { // 首先获取所有种子的物品ID $seedItemIds = \App\Module\Farm\Models\FarmSeed::pluck('item_id')->toArray(); if (empty($seedItemIds)) { return []; } // 查询用户拥有的种子物品 $query = ItemUser::where('user_id', $userId) ->whereIn('item_id', $seedItemIds) ->where('quantity', '>', 0); // 排除过期物品 if (!$includeExpired) { $now = now(); $query->where(function ($q) use ($now) { $q->whereNull('expire_at') ->orWhere('expire_at', '>', $now); })->whereHas('item', function ($q) use ($now) { $q->where(function ($subQ) use ($now) { $subQ->whereNull('global_expire_at') ->orWhere('global_expire_at', '>', $now); }); }); } $userItems = $query->get(); // 按物品ID分组并汇总数量 $seedItems = []; foreach ($userItems as $userItem) { $itemId = $userItem->item_id; if (!isset($seedItems[$itemId])) { $seedItems[$itemId] = 0; } $seedItems[$itemId] += $userItem->quantity; } return $seedItems; } catch (Exception $e) { \Illuminate\Support\Facades\Log::error('获取用户种子物品失败', [ 'user_id' => $userId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return []; } } /** * 冻结物品 * * @param int $userId 用户ID * @param int $itemId 物品ID * @param int|null $instanceId 物品实例ID(单独属性物品) * @param int $quantity 数量 * @param string $reason 冻结原因 * @param array $options 选项 * @return array 冻结结果 * @throws Exception */ public static function freezeItem( int $userId, int $itemId, ?int $instanceId, int $quantity, string $reason, array $options = [] ): array { Helper::check_tr(); // 解析冻结原因 $reasonEnum = FREEZE_REASON_TYPE::tryFrom($options['reason_type'] ?? FREEZE_REASON_TYPE::SYSTEM_FREEZE->value); if (!$reasonEnum) { $reasonEnum = FREEZE_REASON_TYPE::SYSTEM_FREEZE; } $sourceId = $options['source_id'] ?? null; $sourceType = $options['source_type'] ?? null; $operatorId = $options['operator_id'] ?? null; if ($instanceId) { // 冻结单独属性物品 return ItemFreeze::freezeUniqueItem( $userId, $itemId, $instanceId, $reasonEnum, $sourceId, $sourceType, $operatorId ); } else { // 冻结统一属性物品 return ItemFreeze::freezeNormalItem( $userId, $itemId, $quantity, $reasonEnum, $sourceId, $sourceType, $operatorId ); } } /** * 解冻物品 * * @param int $freezeLogId 冻结日志ID * @return array 解冻结果 * @throws Exception */ public static function unfreezeItem(int $freezeLogId): array { Helper::check_tr(); return ItemFreeze::unfreezeByLogId($freezeLogId); } /** * 安全解冻物品(处理已被消耗的冻结堆) * * 与unfreezeItem不同,此方法会检查冻结物品是否已被消耗: * - 如果冻结物品数量为0,则标记为已处理,不抛出异常 * - 如果冻结物品数量大于0,则正常解冻 * - 返回详细的处理结果信息 * * @param int $freezeLogId 冻结日志ID * @return array 解冻结果 * @throws Exception */ public static function safeUnfreezeItem(int $freezeLogId): array { Helper::check_tr(); return ItemFreeze::safeUnfreezeByLogId($freezeLogId); } /** * 获取已被消耗的冻结物品统计 * * @param int|null $userId 用户ID,为null时统计所有用户 * @return array 统计结果 */ public static function getConsumedFrozenItemsStatistics(?int $userId = null): array { return ItemFreeze::getConsumedFrozenItemsStatistics($userId); } /** * 获取用户可用物品数量(排除冻结的) * * @param int $userId 用户ID * @param int $itemId 物品ID * @param int|null $instanceId 实例ID * @return int 可用数量 */ public static function getAvailableQuantity(int $userId, int $itemId, ?int $instanceId = null): int { return ItemFreeze::getAvailableQuantity($userId, $itemId, $instanceId); } /** * 获取用户冻结物品列表 * * @param int $userId 用户ID * @param array $filters 过滤条件 * @return SupportCollection 冻结物品集合 */ public static function getFrozenItems(int $userId, array $filters = []): SupportCollection { return ItemFreeze::getFrozenItems($userId, $filters); } /** * 批量冻结物品 * * @param int $userId 用户ID * @param array $items 物品列表 * @param string $reason 冻结原因 * @param array $options 选项 * @return array 冻结结果 * @throws Exception */ public static function batchFreezeItems( int $userId, array $items, string $reason, array $options = [] ): array { Helper::check_tr(); // 解析冻结原因 $reasonEnum = FREEZE_REASON_TYPE::tryFrom($options['reason_type'] ?? FREEZE_REASON_TYPE::SYSTEM_FREEZE->value); if (!$reasonEnum) { $reasonEnum = FREEZE_REASON_TYPE::SYSTEM_FREEZE; } $sourceId = $options['source_id'] ?? null; $sourceType = $options['source_type'] ?? null; $operatorId = $options['operator_id'] ?? null; return ItemFreeze::batchFreezeItems( $userId, $items, $reasonEnum, $sourceId, $sourceType, $operatorId ); } /** * 获取冻结统计信息 * * @param int $userId 用户ID * @return array 统计信息 */ public static function getFreezeStatistics(int $userId): array { return ItemFreeze::getFreezeStatistics($userId); } }