ItemService.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <?php
  2. namespace App\Module\GameItems\Services;
  3. use App\Module\GameItems\Dtos\ItemDto;
  4. use App\Module\GameItems\Dtos\ItemUserDto;
  5. use App\Module\GameItems\Enums\FREEZE_REASON_TYPE;
  6. use App\Module\GameItems\Logics\Item as ItemLogic;
  7. use App\Module\GameItems\Logics\ItemFreeze;
  8. use App\Module\GameItems\Logics\ItemQuantity;
  9. use App\Module\GameItems\Models\Item;
  10. use App\Module\GameItems\Models\ItemUser;
  11. use Exception;
  12. use Illuminate\Support\Collection as SupportCollection;
  13. use UCore\Db\Helper;
  14. use UCore\Dto\Res;
  15. /**
  16. * 物品服务类
  17. *
  18. * 提供物品相关的服务,包括获取用户物品、添加物品到用户背包、消耗用户物品等功能。
  19. * 该类是物品模块对外提供服务的主要入口,封装了物品操作的复杂逻辑,
  20. * 通过调用ItemLogic类实现具体的业务逻辑处理。
  21. *
  22. * 所有方法均为静态方法,可直接通过类名调用。
  23. */
  24. class ItemService
  25. {
  26. /**
  27. * 获取用户物品列表
  28. *
  29. * @param int $userId 用户ID
  30. * @param array $filters 过滤条件
  31. * @param bool $includeExpired 是否包含已过期物品
  32. * @return SupportCollection|ItemUserDto[] 用户物品DTO集合
  33. */
  34. public static function getUserItems(int $userId, array $filters = [], bool $includeExpired = false): SupportCollection
  35. {
  36. $query = ItemUser::where('user_id', $userId)
  37. ->with(['item', 'instance']);
  38. // 应用过滤条件
  39. if (isset($filters['item_id'])) {
  40. $query->where('item_id', $filters['item_id']);
  41. }
  42. if (isset($filters['category_id'])) {
  43. $query->whereHas('item', function ($q) use ($filters) {
  44. $q->where('category_id', $filters['category_id']);
  45. });
  46. }
  47. if (isset($filters['type'])) {
  48. $query->whereHas('item', function ($q) use ($filters) {
  49. $q->where('type', $filters['type']);
  50. });
  51. }
  52. // 排除过期物品
  53. if (!$includeExpired) {
  54. $now = now();
  55. $query->where(function ($q) use ($now) {
  56. $q->whereNull('expire_at')
  57. ->orWhere('expire_at', '>', $now);
  58. })->whereHas('item', function ($q) use ($now) {
  59. $q->where(function ($subQ) use ($now) {
  60. $subQ->whereNull('global_expire_at')
  61. ->orWhere('global_expire_at', '>', $now);
  62. });
  63. });
  64. }
  65. // 获取模型集合
  66. $itemUsers = $query->get();
  67. // 转换为DTO集合
  68. return $itemUsers->map(function (ItemUser $itemUser) {
  69. return ItemUserDto::fromModel($itemUser);
  70. });
  71. }
  72. /**
  73. * 添加物品到用户背包
  74. *
  75. * @param int $userId 用户ID
  76. * @param int $itemId 物品ID
  77. * @param int $quantity 数量
  78. * @param array $options 选项
  79. * @return array 添加结果
  80. * @throws Exception
  81. */
  82. public static function addItem(int $userId, int $itemId, int $quantity, array $options = []): array
  83. {
  84. // 获取物品信息
  85. $item = Item::findOrFail($itemId);
  86. // 检查物品是否已过期(全局过期)
  87. if (ItemLogic::isExpired($item)) {
  88. throw new Exception("物品 {$itemId} 已全局过期");
  89. }
  90. // 处理单独属性物品
  91. if ($item->is_unique) {
  92. return ItemLogic::addUniqueItem($userId, $itemId, $options);
  93. }
  94. // 处理统一属性物品
  95. return ItemLogic::addNormalItem($userId, $itemId, $quantity, $options);
  96. }
  97. /**
  98. * 消耗用户物品
  99. *
  100. * @param int $userId 用户ID
  101. * @param int $itemId 物品ID
  102. * @param int|null $instanceId 物品实例ID(单独属性物品)
  103. * @param int $quantity 数量
  104. * @param array $options 选项
  105. * @return array 消耗结果
  106. * @throws Exception
  107. */
  108. public static function consumeItem(int $userId, int $itemId, ?int $instanceId, int $quantity, array $options = []): array
  109. {
  110. Helper::check_tr();
  111. // 获取物品信息(确保物品存在)
  112. Item::findOrFail($itemId);
  113. if ($instanceId) {
  114. // 消耗单独属性物品
  115. return ItemLogic::consumeUniqueItem($userId, $itemId, $instanceId, $options);
  116. } else {
  117. // 消耗统一属性物品
  118. return ItemLogic::consumeNormalItem($userId, $itemId, $quantity, $options);
  119. }
  120. }
  121. /**
  122. * 获取物品信息
  123. *
  124. * @param int $itemId 物品ID
  125. * @return ItemDto|null 物品信息DTO
  126. */
  127. public static function getItemInfo(int $itemId): ?ItemDto
  128. {
  129. $item = Item::find($itemId);
  130. if (!$item) {
  131. return null;
  132. }
  133. return ItemDto::fromModel($item);
  134. }
  135. /**
  136. * 获取物品数值属性
  137. *
  138. * @param int $itemId 物品ID
  139. * @param string $attributeName 属性名称
  140. * @param int $defaultValue 默认值
  141. * @return int 属性值
  142. */
  143. public static function getItemNumericAttribute(int $itemId, string $attributeName, int $defaultValue = 0): int
  144. {
  145. /**
  146. * @var Item $item
  147. */
  148. $item = Item::find($itemId);
  149. if (!$item) {
  150. return $defaultValue;
  151. }
  152. $numericAttributes = (array)$item->numeric_attributes;
  153. return $numericAttributes[$attributeName] ?? $defaultValue;
  154. }
  155. /**
  156. * 验证用户是否拥有足够数量的物品
  157. *
  158. * @param int $userId 用户ID
  159. * @param int $itemId 物品ID
  160. * @param int $quantity 需要的数量
  161. * @param int|null $instanceId 物品实例ID(可选,用于验证特定实例物品)
  162. * @param bool $includeAllTypes 是否包含所有类型的物品(普通物品和实例物品)
  163. * @param bool $includeExpired 是否包含已过期物品
  164. * @return Res 验证结果
  165. */
  166. public static function checkItemQuantity(int $userId, int $itemId, int $quantity, ?int $instanceId = null, bool $includeAllTypes = false, bool $includeExpired = false): Res
  167. {
  168. try {
  169. // 获取物品信息
  170. $item = Item::find($itemId);
  171. if (!$item) {
  172. return Res::error("物品不存在(ID: {$itemId})");
  173. }
  174. // 检查物品是否已过期(全局过期)
  175. if (ItemLogic::isExpired($item)) {
  176. return Res::error("物品已全局过期(ID: {$itemId})");
  177. }
  178. // 根据不同情况进行验证
  179. if ($instanceId ) {
  180. // 验证特定实例物品
  181. $hasInstance = ItemQuantity::hasInstanceItem($userId, $itemId, $instanceId, $includeExpired);
  182. if (!$hasInstance) {
  183. return Res::error("用户不拥有指定的实例物品(ID: {$instanceId})");
  184. }
  185. return Res::success("用户拥有指定的实例物品");
  186. } else if ($includeAllTypes) {
  187. // 验证所有类型物品(普通物品和实例物品)的总数量
  188. $totalQuantity = ItemQuantity::getUserTotalItemQuantity($userId, $itemId, $includeExpired);
  189. if ($totalQuantity < $quantity) {
  190. return Res::error("物品总数量不足,需要{$quantity}个,但只有{$totalQuantity}个", [
  191. 'required' => $quantity,
  192. 'available' => $totalQuantity
  193. ]);
  194. }
  195. return Res::success("物品总数量足够", [
  196. 'quantity' => $totalQuantity,
  197. 'required' => $quantity
  198. ]);
  199. } else {
  200. // 只验证普通物品数量
  201. $normalQuantity = ItemQuantity::getUserItemQuantity($userId, $itemId, $includeExpired);
  202. if ($normalQuantity < $quantity) {
  203. return Res::error("普通物品数量不足,需要{$quantity}个,但只有{$normalQuantity}个", [
  204. 'required' => $quantity,
  205. 'available' => $normalQuantity
  206. ]);
  207. }
  208. return Res::success("普通物品数量足够", [
  209. 'quantity' => $normalQuantity,
  210. 'required' => $quantity
  211. ]);
  212. }
  213. } catch (Exception $e) {
  214. return Res::error("验证物品数量时发生错误: " . $e->getMessage(), [
  215. 'error' => $e->getMessage(),
  216. 'trace' => $e->getTraceAsString()
  217. ]);
  218. }
  219. }
  220. /**
  221. * 获取用户拥有的种子物品
  222. *
  223. * @param int $userId 用户ID
  224. * @param bool $includeExpired 是否包含已过期物品
  225. * @return array 种子物品数组,键为物品ID,值为数量
  226. */
  227. public static function getUserSeedItems(int $userId, bool $includeExpired = false): array
  228. {
  229. try {
  230. // 首先获取所有种子的物品ID
  231. $seedItemIds = \App\Module\Farm\Models\FarmSeed::pluck('item_id')->toArray();
  232. if (empty($seedItemIds)) {
  233. return [];
  234. }
  235. // 查询用户拥有的种子物品
  236. $query = ItemUser::where('user_id', $userId)
  237. ->whereIn('item_id', $seedItemIds)
  238. ->where('quantity', '>', 0);
  239. // 排除过期物品
  240. if (!$includeExpired) {
  241. $now = now();
  242. $query->where(function ($q) use ($now) {
  243. $q->whereNull('expire_at')
  244. ->orWhere('expire_at', '>', $now);
  245. })->whereHas('item', function ($q) use ($now) {
  246. $q->where(function ($subQ) use ($now) {
  247. $subQ->whereNull('global_expire_at')
  248. ->orWhere('global_expire_at', '>', $now);
  249. });
  250. });
  251. }
  252. $userItems = $query->get();
  253. // 按物品ID分组并汇总数量
  254. $seedItems = [];
  255. foreach ($userItems as $userItem) {
  256. $itemId = $userItem->item_id;
  257. if (!isset($seedItems[$itemId])) {
  258. $seedItems[$itemId] = 0;
  259. }
  260. $seedItems[$itemId] += $userItem->quantity;
  261. }
  262. return $seedItems;
  263. } catch (Exception $e) {
  264. \Illuminate\Support\Facades\Log::error('获取用户种子物品失败', [
  265. 'user_id' => $userId,
  266. 'error' => $e->getMessage(),
  267. 'trace' => $e->getTraceAsString()
  268. ]);
  269. return [];
  270. }
  271. }
  272. /**
  273. * 冻结物品
  274. *
  275. * @param int $userId 用户ID
  276. * @param int $itemId 物品ID
  277. * @param int|null $instanceId 物品实例ID(单独属性物品)
  278. * @param int $quantity 数量
  279. * @param string $reason 冻结原因
  280. * @param array $options 选项
  281. * @return array 冻结结果
  282. * @throws Exception
  283. */
  284. public static function freezeItem(
  285. int $userId,
  286. int $itemId,
  287. ?int $instanceId,
  288. int $quantity,
  289. string $reason,
  290. array $options = []
  291. ): array {
  292. Helper::check_tr();
  293. // 解析冻结原因
  294. $reasonEnum = FREEZE_REASON_TYPE::tryFrom($options['reason_type'] ?? FREEZE_REASON_TYPE::SYSTEM_FREEZE->value);
  295. if (!$reasonEnum) {
  296. $reasonEnum = FREEZE_REASON_TYPE::SYSTEM_FREEZE;
  297. }
  298. $sourceId = $options['source_id'] ?? null;
  299. $sourceType = $options['source_type'] ?? null;
  300. $operatorId = $options['operator_id'] ?? null;
  301. if ($instanceId) {
  302. // 冻结单独属性物品
  303. return ItemFreeze::freezeUniqueItem(
  304. $userId,
  305. $itemId,
  306. $instanceId,
  307. $reasonEnum,
  308. $sourceId,
  309. $sourceType,
  310. $operatorId
  311. );
  312. } else {
  313. // 冻结统一属性物品
  314. return ItemFreeze::freezeNormalItem(
  315. $userId,
  316. $itemId,
  317. $quantity,
  318. $reasonEnum,
  319. $sourceId,
  320. $sourceType,
  321. $operatorId
  322. );
  323. }
  324. }
  325. /**
  326. * 解冻物品
  327. *
  328. * @param int $freezeLogId 冻结日志ID
  329. * @return array 解冻结果
  330. * @throws Exception
  331. */
  332. public static function unfreezeItem(int $freezeLogId): array
  333. {
  334. Helper::check_tr();
  335. return ItemFreeze::unfreezeByLogId($freezeLogId);
  336. }
  337. /**
  338. * 获取用户可用物品数量(排除冻结的)
  339. *
  340. * @param int $userId 用户ID
  341. * @param int $itemId 物品ID
  342. * @param int|null $instanceId 实例ID
  343. * @return int 可用数量
  344. */
  345. public static function getAvailableQuantity(int $userId, int $itemId, ?int $instanceId = null): int
  346. {
  347. return ItemFreeze::getAvailableQuantity($userId, $itemId, $instanceId);
  348. }
  349. /**
  350. * 获取用户冻结物品列表
  351. *
  352. * @param int $userId 用户ID
  353. * @param array $filters 过滤条件
  354. * @return SupportCollection 冻结物品集合
  355. */
  356. public static function getFrozenItems(int $userId, array $filters = []): SupportCollection
  357. {
  358. return ItemFreeze::getFrozenItems($userId, $filters);
  359. }
  360. /**
  361. * 批量冻结物品
  362. *
  363. * @param int $userId 用户ID
  364. * @param array $items 物品列表
  365. * @param string $reason 冻结原因
  366. * @param array $options 选项
  367. * @return array 冻结结果
  368. * @throws Exception
  369. */
  370. public static function batchFreezeItems(
  371. int $userId,
  372. array $items,
  373. string $reason,
  374. array $options = []
  375. ): array {
  376. Helper::check_tr();
  377. // 解析冻结原因
  378. $reasonEnum = FREEZE_REASON_TYPE::tryFrom($options['reason_type'] ?? FREEZE_REASON_TYPE::SYSTEM_FREEZE->value);
  379. if (!$reasonEnum) {
  380. $reasonEnum = FREEZE_REASON_TYPE::SYSTEM_FREEZE;
  381. }
  382. $sourceId = $options['source_id'] ?? null;
  383. $sourceType = $options['source_type'] ?? null;
  384. $operatorId = $options['operator_id'] ?? null;
  385. return ItemFreeze::batchFreezeItems(
  386. $userId,
  387. $items,
  388. $reasonEnum,
  389. $sourceId,
  390. $sourceType,
  391. $operatorId
  392. );
  393. }
  394. /**
  395. * 获取冻结统计信息
  396. *
  397. * @param int $userId 用户ID
  398. * @return array 统计信息
  399. */
  400. public static function getFreezeStatistics(int $userId): array
  401. {
  402. return ItemFreeze::getFreezeStatistics($userId);
  403. }
  404. }