CraftService.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <?php
  2. namespace App\Module\GameItems\Services;
  3. use App\Module\GameItems\Models\ItemRecipe;
  4. use App\Module\GameItems\Models\ItemCraftLog;
  5. use App\Module\GameItems\Models\ItemUserRecipe;
  6. use App\Module\Game\Services\ConsumeService;
  7. use App\Module\Game\Services\RewardService;
  8. use Exception;
  9. use Illuminate\Database\Eloquent\Collection;
  10. use Illuminate\Support\Facades\DB;
  11. use Illuminate\Support\Facades\Log;
  12. /**
  13. * 物品合成服务类
  14. *
  15. * 提供物品合成相关的服务,包括合成物品、获取用户可合成配方列表、解锁配方等功能。
  16. * 该类是物品合成模块对外提供服务的主要入口,封装了物品合成的复杂逻辑。
  17. *
  18. * 所有方法均为静态方法,可直接通过类名调用。
  19. */
  20. class CraftService
  21. {
  22. /**
  23. * 合成物品
  24. *
  25. * @param int $userId 用户ID
  26. * @param int $recipeId 配方ID
  27. * @param array $options 选项
  28. * @return array|bool 合成结果或失败标志
  29. */
  30. public static function craftItem(int $userId, int $recipeId, array $options = [])
  31. {
  32. try {
  33. // 获取配方信息
  34. $recipe = ItemRecipe::with(['consumeGroup', 'rewardGroup'])->findOrFail($recipeId);
  35. // 检查配方是否激活
  36. if (!$recipe->is_active) {
  37. throw new Exception("配方未激活");
  38. }
  39. // 检查用户是否可以合成该配方
  40. $canCraft = $recipe->canCraftByUser($userId);
  41. if (!$canCraft['can_craft']) {
  42. throw new Exception($canCraft['reason']);
  43. }
  44. // 检查消耗组是否存在
  45. if (!$recipe->consume_group_id || !$recipe->consumeGroup) {
  46. throw new Exception("配方消耗组不存在");
  47. }
  48. // 检查奖励组是否存在
  49. if (!$recipe->reward_group_id || !$recipe->rewardGroup) {
  50. throw new Exception("配方奖励组不存在");
  51. }
  52. // 获取合成数量
  53. $quantity = $options['quantity'] ?? 1;
  54. // 开启事务
  55. DB::beginTransaction();
  56. // 执行消耗组
  57. $consumeResult = ConsumeService::executeConsume(
  58. $userId,
  59. $recipe->consume_group_id,
  60. 'craft',
  61. $recipeId,
  62. true, // 检查消耗条件
  63. $quantity // 使用数量作为倍数
  64. );
  65. if (!$consumeResult['success']) {
  66. throw new Exception("消耗失败: " . $consumeResult['message']);
  67. }
  68. // 执行奖励组
  69. $rewardResult = RewardService::grantReward(
  70. $userId,
  71. $recipe->reward_group_id,
  72. 'craft',
  73. $recipeId
  74. );
  75. $isSuccess = $rewardResult->success;
  76. $rewardItems = $rewardResult->items;
  77. // 更新用户配方使用记录
  78. $userRecipe = ItemUserRecipe::firstOrCreate(
  79. ['user_id' => $userId, 'recipe_id' => $recipeId],
  80. ['is_unlocked' => true, 'unlock_time' => now(), 'craft_count' => 0]
  81. );
  82. $userRecipe->incrementCraftCount($quantity);
  83. // 创建合成记录
  84. $craftLog = new ItemCraftLog([
  85. 'user_id' => $userId,
  86. 'recipe_id' => $recipeId,
  87. 'materials' => $consumeResult['consumed'] ?? [],
  88. 'result_item_id' => $isSuccess && !empty($rewardItems) ? $rewardItems[0]->targetId : null,
  89. 'result_instance_id' => null,
  90. 'result_quantity' => $isSuccess && !empty($rewardItems) ? $rewardItems[0]->quantity : 0,
  91. 'is_success' => $isSuccess,
  92. 'craft_time' => now(),
  93. 'ip_address' => $options['ip_address'] ?? request()->ip(),
  94. 'device_info' => $options['device_info'] ?? request()->userAgent(),
  95. ]);
  96. $craftLog->save();
  97. // 提交事务
  98. DB::commit();
  99. // 返回结果
  100. return [
  101. 'success' => $isSuccess,
  102. 'consumed' => $consumeResult['consumed'] ?? [],
  103. 'rewards' => $isSuccess ? $rewardItems : []
  104. ];
  105. } catch (Exception $e) {
  106. // 回滚事务
  107. if (DB::transactionLevel() > 0) {
  108. DB::rollBack();
  109. }
  110. Log::error('合成物品失败', [
  111. 'user_id' => $userId,
  112. 'recipe_id' => $recipeId,
  113. 'error' => $e->getMessage(),
  114. 'trace' => $e->getTraceAsString()
  115. ]);
  116. return false;
  117. }
  118. }
  119. /**
  120. * 获取用户可合成配方列表
  121. *
  122. * @param int $userId 用户ID
  123. * @param array $filters 过滤条件
  124. * @return Collection 配方列表
  125. */
  126. public static function getUserAvailableRecipes(int $userId, array $filters = []): Collection
  127. {
  128. $query = ItemRecipe::with(['consumeGroup', 'rewardGroup', 'conditionGroup'])
  129. ->where('is_active', true)
  130. ->whereHas('userRecipes', function ($q) use ($userId) {
  131. $q->where('user_id', $userId)
  132. ->where('is_unlocked', true);
  133. });
  134. // 应用过滤条件
  135. if (isset($filters['category_id'])) {
  136. $query->where('category_id', $filters['category_id']);
  137. }
  138. return $query->orderBy('sort_order', 'asc')->get();
  139. }
  140. /**
  141. * 解锁用户配方
  142. *
  143. * @param int $userId 用户ID
  144. * @param int $recipeId 配方ID
  145. * @return bool 是否成功
  146. */
  147. public static function unlockRecipe(int $userId, int $recipeId): bool
  148. {
  149. try {
  150. $recipe = ItemRecipe::findOrFail($recipeId);
  151. // 检查配方是否激活
  152. if (!$recipe->is_active) {
  153. return false;
  154. }
  155. // 检查是否已解锁
  156. $userRecipe = ItemUserRecipe::where('user_id', $userId)
  157. ->where('recipe_id', $recipeId)
  158. ->first();
  159. if ($userRecipe && $userRecipe->is_unlocked) {
  160. return true; // 已经解锁
  161. }
  162. // 创建或更新用户配方记录
  163. if (!$userRecipe) {
  164. $userRecipe = new ItemUserRecipe([
  165. 'user_id' => $userId,
  166. 'recipe_id' => $recipeId,
  167. 'is_unlocked' => true,
  168. 'unlock_time' => now(),
  169. 'craft_count' => 0
  170. ]);
  171. $userRecipe->save();
  172. } else {
  173. $userRecipe->is_unlocked = true;
  174. $userRecipe->unlock_time = now();
  175. $userRecipe->save();
  176. }
  177. return true;
  178. } catch (Exception $e) {
  179. Log::error('解锁配方失败', [
  180. 'user_id' => $userId,
  181. 'recipe_id' => $recipeId,
  182. 'error' => $e->getMessage(),
  183. 'trace' => $e->getTraceAsString()
  184. ]);
  185. return false;
  186. }
  187. }
  188. }