DismantleService.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <?php
  2. namespace App\Module\GameItems\Services;
  3. use App\Module\Game\Enums\REWARD_SOURCE_TYPE;
  4. use App\Module\GameItems\Models\Item;
  5. use App\Module\GameItems\Models\ItemDismantleRule;
  6. use App\Module\GameItems\Models\ItemDismantleLog;
  7. use App\Module\GameItems\Models\ItemUser;
  8. use Exception;
  9. use Illuminate\Support\Facades\DB;
  10. use Illuminate\Support\Facades\Log;
  11. /**
  12. * 物品分解服务类
  13. *
  14. * 提供物品分解相关的服务,包括分解物品、获取分解预览等功能。
  15. * 该类是物品分解模块对外提供服务的主要入口,封装了物品分解的复杂逻辑。
  16. *
  17. * 所有方法均为静态方法,可直接通过类名调用。
  18. */
  19. class DismantleService
  20. {
  21. /**
  22. * 分解物品
  23. *
  24. * @param int $userId 用户ID
  25. * @param int $itemId 物品ID
  26. * @param int|null $instanceId 物品实例ID(单独属性物品)
  27. * @param int $quantity 数量
  28. * @param array $options 选项
  29. * @return array|bool 分解结果或失败标志
  30. */
  31. public static function dismantleItem(int $userId, int $itemId, ?int $instanceId, int $quantity, array $options = [])
  32. {
  33. try {
  34. // 获取物品信息
  35. $item = Item::findOrFail($itemId);
  36. // 检查物品是否可分解
  37. if (!$item->can_dismantle) {
  38. throw new Exception("该物品不可分解");
  39. }
  40. // 检查用户是否拥有该物品
  41. $userItem = ItemUser::where('user_id', $userId)
  42. ->where('item_id', $itemId);
  43. if ($instanceId) {
  44. $userItem->where('instance_id', $instanceId);
  45. }
  46. $userItem = $userItem->first();
  47. if (!$userItem || $userItem->quantity < $quantity) {
  48. throw new Exception("物品数量不足");
  49. }
  50. // 获取分解规则
  51. $rule = self::getDismantleRule($itemId);
  52. if (!$rule) {
  53. throw new Exception("没有找到适用的分解规则");
  54. }
  55. // 开启事务
  56. DB::beginTransaction();
  57. // 消耗物品
  58. ItemService::consumeItem($userId, $itemId, $instanceId, $quantity, [
  59. 'source_type' => 'dismantle',
  60. 'source_id' => $rule->id,
  61. 'details' => [
  62. 'rule_id' => $rule->id,
  63. 'rule_name' => $rule->name
  64. ]
  65. ]);
  66. // 获取分解结果
  67. $dismantleResults = [];
  68. for ($i = 0; $i < $quantity; $i++) {
  69. $results = $rule->getDismantleResults();
  70. foreach ($results as $result) {
  71. // 添加到分解结果
  72. if (isset($dismantleResults[$result['item_id']])) {
  73. $dismantleResults[$result['item_id']]['quantity'] += $result['quantity'];
  74. } else {
  75. $dismantleResults[$result['item_id']] = $result;
  76. }
  77. }
  78. }
  79. // 添加分解结果物品到用户背包
  80. $addedItems = [];
  81. foreach ($dismantleResults as $resultItemId => $resultData) {
  82. $resultQuantity = $resultData['quantity'];
  83. if ($resultQuantity > 0) {
  84. // 添加物品到用户背包
  85. $addResult = ItemService::addItem($userId, $resultItemId, $resultQuantity, [
  86. 'source_type' => REWARD_SOURCE_TYPE::DISMANTLE,
  87. 'source_id' => $rule->id,
  88. 'details' => [
  89. 'rule_id' => $rule->id,
  90. 'rule_name' => $rule->name,
  91. 'original_item_id' => $itemId,
  92. 'original_item_name' => $item->name
  93. ]
  94. ]);
  95. $addedItems[] = [
  96. 'item_id' => $resultItemId,
  97. 'item_name' => $resultData['item_name'],
  98. 'quantity' => $resultQuantity,
  99. 'instance_id' => $addResult['instance_id'] ?? null
  100. ];
  101. }
  102. }
  103. // 计算返还金币
  104. $coinReturn = 0;
  105. if ($rule->coin_return_rate > 0 && $item->sell_price > 0) {
  106. $coinReturn = floor($item->sell_price * $rule->coin_return_rate * $quantity);
  107. if ($coinReturn > 0) {
  108. // TODO: 添加金币到用户账户
  109. // 这里需要调用Fund模块的服务来添加金币
  110. }
  111. }
  112. // 创建分解记录
  113. $dismantleLog = new ItemDismantleLog([
  114. 'user_id' => $userId,
  115. 'rule_id' => $rule->id,
  116. 'item_id' => $itemId,
  117. 'instance_id' => $instanceId,
  118. 'quantity' => $quantity,
  119. 'results' => $addedItems,
  120. 'coin_return' => $coinReturn,
  121. 'dismantle_time' => now(),
  122. 'ip_address' => $options['ip_address'] ?? request()->ip(),
  123. 'device_info' => $options['device_info'] ?? request()->userAgent(),
  124. ]);
  125. $dismantleLog->save();
  126. // 提交事务
  127. DB::commit();
  128. // 返回结果
  129. return [
  130. 'success' => true,
  131. 'dismantled_item' => [
  132. 'item_id' => $itemId,
  133. 'item_name' => $item->name,
  134. 'quantity' => $quantity,
  135. 'instance_id' => $instanceId
  136. ],
  137. 'results' => $addedItems,
  138. 'coin_return' => $coinReturn
  139. ];
  140. } catch (Exception $e) {
  141. // 回滚事务
  142. if (DB::transactionLevel() > 0) {
  143. DB::rollBack();
  144. }
  145. Log::error('分解物品失败', [
  146. 'user_id' => $userId,
  147. 'item_id' => $itemId,
  148. 'instance_id' => $instanceId,
  149. 'quantity' => $quantity,
  150. 'error' => $e->getMessage(),
  151. 'trace' => $e->getTraceAsString()
  152. ]);
  153. return false;
  154. }
  155. }
  156. /**
  157. * 获取物品分解预览
  158. *
  159. * @param int $userId 用户ID
  160. * @param int $itemId 物品ID
  161. * @param int|null $instanceId 物品实例ID(单独属性物品)
  162. * @return array 分解预览
  163. */
  164. public static function getDismantlePreview(int $userId, int $itemId, ?int $instanceId = null): array
  165. {
  166. try {
  167. // 获取物品信息
  168. $item = Item::findOrFail($itemId);
  169. // 检查物品是否可分解
  170. if (!$item->can_dismantle) {
  171. return [
  172. 'can_dismantle' => false,
  173. 'reason' => '该物品不可分解'
  174. ];
  175. }
  176. // 获取分解规则
  177. $rule = self::getDismantleRule($itemId);
  178. if (!$rule) {
  179. return [
  180. 'can_dismantle' => false,
  181. 'reason' => '没有找到适用的分解规则'
  182. ];
  183. }
  184. // 获取分解结果预览(使用新的奖励组系统)
  185. // 旧的分解结果表已废弃,现在通过奖励组系统获取分解奖励
  186. $results = [];
  187. // TODO: 实现通过奖励组获取分解预览的逻辑
  188. // if ($rule->reward_group_id) {
  189. // $rewardService = app(\App\Module\Game\Services\RewardService::class);
  190. // $results = $rewardService->getRewardPreview($rule->reward_group_id);
  191. // }
  192. // 计算返还金币
  193. $coinReturn = 0;
  194. if ($rule->coin_return_rate > 0 && $item->sell_price > 0) {
  195. $coinReturn = floor($item->sell_price * $rule->coin_return_rate);
  196. }
  197. return [
  198. 'can_dismantle' => true,
  199. 'item' => [
  200. 'item_id' => $itemId,
  201. 'item_name' => $item->name,
  202. 'instance_id' => $instanceId
  203. ],
  204. 'possible_results' => $results,
  205. 'coin_return' => $coinReturn
  206. ];
  207. } catch (Exception $e) {
  208. Log::error('获取分解预览失败', [
  209. 'user_id' => $userId,
  210. 'item_id' => $itemId,
  211. 'instance_id' => $instanceId,
  212. 'error' => $e->getMessage(),
  213. 'trace' => $e->getTraceAsString()
  214. ]);
  215. return [
  216. 'can_dismantle' => false,
  217. 'reason' => '系统错误'
  218. ];
  219. }
  220. }
  221. /**
  222. * 获取物品的分解规则
  223. *
  224. * @param int $itemId 物品ID
  225. * @return ItemDismantleRule|null 分解规则
  226. */
  227. private static function getDismantleRule(int $itemId): ?ItemDismantleRule
  228. {
  229. // 获取物品信息
  230. $item = Item::find($itemId);
  231. if (!$item) {
  232. return null;
  233. }
  234. // 获取物品稀有度(从numeric_attributes中获取,如果没有则默认为1)
  235. $rarity = 1;
  236. if ($item->numeric_attributes && isset($item->numeric_attributes['rarity'])) {
  237. $rarity = (int)$item->numeric_attributes['rarity'];
  238. }
  239. // 1. 优先查找针对特定物品的规则(包含稀有度匹配)
  240. $rule = ItemDismantleRule::where('item_id', $itemId)
  241. ->where('is_active', true)
  242. ->where('min_rarity', '<=', $rarity)
  243. ->where('max_rarity', '>=', $rarity)
  244. ->orderBy('priority', 'desc')
  245. ->first();
  246. if ($rule) {
  247. return $rule;
  248. }
  249. // 2. 查找针对物品分类的规则(包含稀有度匹配)
  250. $rule = ItemDismantleRule::where('category_id', $item->category_id)
  251. ->whereNull('item_id')
  252. ->where('is_active', true)
  253. ->where('min_rarity', '<=', $rarity)
  254. ->where('max_rarity', '>=', $rarity)
  255. ->orderBy('priority', 'desc')
  256. ->first();
  257. if ($rule) {
  258. return $rule;
  259. }
  260. // 3. 查找通用规则(包含稀有度匹配)
  261. $rule = ItemDismantleRule::whereNull('item_id')
  262. ->whereNull('category_id')
  263. ->where('is_active', true)
  264. ->where('min_rarity', '<=', $rarity)
  265. ->where('max_rarity', '>=', $rarity)
  266. ->orderBy('priority', 'desc')
  267. ->first();
  268. return $rule;
  269. }
  270. }