PetService.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <?php
  2. namespace App\Module\Pet\Services;
  3. use App\Module\Pet\Dtos\PetDataDto;
  4. use App\Module\Pet\Enums\PetStatus;
  5. use App\Module\Pet\Factories\PetDtoFactory;
  6. use App\Module\Pet\Logic\PetLogic;
  7. use App\Module\Pet\Models\PetUser;
  8. use App\Module\Pet\Models\PetSkill;
  9. use App\Module\Pet\Models\PetLevelConfig;
  10. use Exception;
  11. use Illuminate\Support\Collection;
  12. use Illuminate\Support\Facades\DB;
  13. use Illuminate\Support\Facades\Log;
  14. use UCore\Db\Helper;
  15. use UCore\Dto\Res;
  16. use UCore\Exception\LogicException;
  17. /**
  18. * 宠物服务类
  19. *
  20. * 提供宠物相关的服务,包括获取用户宠物列表、创建宠物、宠物升级、
  21. * 宠物喂养、宠物洗髓、宠物技能使用等功能。该类是宠物模块对外提供
  22. * 服务的主要入口,封装了宠物操作的复杂逻辑。
  23. *
  24. * 注意:本服务类不负责事务管理,调用方需要自行开启和管理事务。
  25. * 对于涉及数据修改的方法,强烈建议在调用前开启事务,以确保数据一致性。
  26. */
  27. class PetService
  28. {
  29. /**
  30. * 获取用户宠物列表
  31. *
  32. * @param int $userId 用户ID
  33. * @param array $filters 过滤条件
  34. * @return Collection|PetUser[]
  35. */
  36. public static function getUserPets(int $userId, array $filters = []): Collection
  37. {
  38. $query = PetUser::where('user_id', $userId);
  39. // 应用过滤条件
  40. if (isset($filters['status'])) {
  41. $query->where('status', $filters['status']);
  42. }
  43. if (isset($filters['grade'])) {
  44. $query->where('grade', $filters['grade']);
  45. }
  46. if (isset($filters['min_level'])) {
  47. $query->where('level', '>=', $filters['min_level']);
  48. }
  49. if (isset($filters['max_level'])) {
  50. $query->where('level', '<=', $filters['max_level']);
  51. }
  52. return $query->get();
  53. }
  54. /**
  55. * 创建宠物
  56. *
  57. * 注意:调用此方法前,请确保已开启数据库事务
  58. *
  59. * @param int $userId 用户ID
  60. * @param string $name 宠物名称
  61. * @param int|null $grade 宠物品阶,如果为null则随机生成(1一品,2二品,3三品,4四品)
  62. * @param array $options 其他选项
  63. * @return array 创建结果
  64. * @throws Exception
  65. */
  66. public static function createPet(int $userId, string $name, ?int $grade = null, array $options = []): array
  67. {
  68. // 验证事务是否已开启
  69. Helper::check_tr();
  70. try {
  71. // 创建宠物逻辑
  72. $petLogic = new PetLogic();
  73. // 创建宠物
  74. $result = $petLogic->createPet($userId, $name, $grade, $options);
  75. return [
  76. 'success' => true,
  77. 'pet_id' => $result['pet_id'],
  78. 'name' => $name,
  79. 'grade' => $result['grade'],
  80. 'level' => 1,
  81. 'message' => '宠物创建成功'
  82. ];
  83. } catch (Exception $e) {
  84. Log::error('创建宠物失败', [
  85. 'user_id' => $userId,
  86. 'name' => $name,
  87. 'error' => $e->getMessage()
  88. ]);
  89. throw $e;
  90. }
  91. }
  92. /**
  93. * 宠物喂养
  94. *
  95. * 注意:调用此方法前,请确保已开启数据库事务
  96. *
  97. * @param int $userId 用户ID
  98. * @param int $petId 宠物ID
  99. * @param int $itemId 物品ID(狗粮)
  100. * @param int $amount 数量
  101. * @return array 喂养结果
  102. * @throws Exception
  103. */
  104. public static function feedPet(int $userId, int $petId, int $itemId, int $amount = 1): Res
  105. {
  106. // 验证事务是否已开启
  107. Helper::check_tr();
  108. // 宠物逻辑
  109. $petLogic = new PetLogic();
  110. // 获取宠物信息
  111. $pet = PetUser::where('id', $petId)
  112. ->where('user_id', $userId)
  113. ->first();
  114. if (!$pet) {
  115. throw new LogicException("宠物不存在或不属于该用户");
  116. }
  117. // 检查宠物状态
  118. if ($pet->status !== PetStatus::NORMAL) {
  119. throw new LogicException("宠物当前状态不允许喂养");
  120. }
  121. // 执行喂养逻辑
  122. $result = $petLogic->feedPet($petId, $itemId, $amount);
  123. return Res::success('', [
  124. 'pet_id' => $petId,
  125. 'item_id' => $itemId,
  126. 'amount' => $amount
  127. ]);
  128. }
  129. /**
  130. * 宠物洗髓
  131. *
  132. * 注意:调用此方法前,请确保已开启数据库事务
  133. *
  134. * @param int $userId 用户ID
  135. * @param int $petId 宠物ID
  136. * @param int $itemId 洗髓道具ID,如果为0则使用钻石
  137. * @return array 洗髓结果
  138. * @throws Exception
  139. */
  140. public static function remouldPet(int $userId, int $petId, int $itemId = 0): array
  141. {
  142. // 验证事务是否已开启
  143. Helper::check_tr();
  144. try {
  145. // 宠物逻辑
  146. $petLogic = new PetLogic();
  147. // 获取宠物信息
  148. $pet = PetUser::where('id', $petId)
  149. ->where('user_id', $userId)
  150. ->first();
  151. if (!$pet) {
  152. throw new Exception("宠物不存在或不属于该用户");
  153. }
  154. // 检查宠物状态
  155. if ($pet->status !== PetStatus::NORMAL) {
  156. throw new Exception("宠物当前状态不允许洗髓");
  157. }
  158. // 执行洗髓逻辑
  159. $result = $petLogic->remouldPet($petId, $itemId);
  160. return [
  161. 'success' => true,
  162. 'pet_id' => $petId,
  163. 'old_grade' => $result['old_grade'],
  164. 'new_grade' => $result['new_grade'],
  165. 'item_id' => $itemId,
  166. 'message' => '宠物洗髓成功'
  167. ];
  168. } catch (Exception $e) {
  169. Log::error('宠物洗髓失败', [
  170. 'user_id' => $userId,
  171. 'pet_id' => $petId,
  172. 'item_id' => $itemId,
  173. 'error' => $e->getMessage()
  174. ]);
  175. throw $e;
  176. }
  177. }
  178. /**
  179. * 使用宠物技能
  180. *
  181. * 注意:调用此方法前,请确保已开启数据库事务
  182. *
  183. * @param int $userId 用户ID
  184. * @param int $petId 宠物ID
  185. * @param int $skillId 技能ID
  186. * @param array $params 技能参数
  187. * @return array 技能使用结果
  188. * @throws Exception
  189. */
  190. public static function useSkill(int $userId, int $petId, int $skillId, array $params = []): array
  191. {
  192. // 验证事务是否已开启
  193. Helper::check_tr();
  194. try {
  195. // 宠物逻辑
  196. $petLogic = new PetLogic();
  197. // 获取宠物信息
  198. $pet = PetUser::where('id', $petId)
  199. ->where('user_id', $userId)
  200. ->first();
  201. if (!$pet) {
  202. throw new Exception("宠物不存在或不属于该用户");
  203. }
  204. // 获取技能信息
  205. /**
  206. * @var PetSkill $skill
  207. */
  208. $skill = PetSkill::find($skillId);
  209. if (!$skill) {
  210. throw new Exception("技能不存在");
  211. }
  212. // 检查宠物状态
  213. if ($pet->status !== PetStatus::NORMAL) {
  214. throw new Exception("宠物当前状态不允许使用技能");
  215. }
  216. // 检查宠物当前等级是否可以使用该技能(基于等级配置表)
  217. if (!self::checkPetSkillAvailable($pet, $skillId)) {
  218. throw new Exception("该技能在当前等级不可用");
  219. }
  220. // 检查宠物体力是否足够
  221. if ($pet->stamina < $skill->stamina_cost) {
  222. throw new Exception("宠物体力不足,无法使用该技能");
  223. }
  224. // 执行技能使用逻辑
  225. $result = $petLogic->useSkill($petId, $skillId, $params);
  226. return [
  227. 'success' => true,
  228. 'pet_id' => $petId,
  229. 'skill_id' => $skillId,
  230. 'stamina_cost' => $skill->stamina_cost,
  231. 'effect_result' => $result['effect_result'],
  232. 'message' => '技能使用成功'
  233. ];
  234. } catch (Exception $e) {
  235. Log::error('使用宠物技能失败', [
  236. 'user_id' => $userId,
  237. 'pet_id' => $petId,
  238. 'skill_id' => $skillId,
  239. 'params' => $params,
  240. 'error' => $e->getMessage()
  241. ]);
  242. throw $e;
  243. }
  244. }
  245. /**
  246. * 获取宠物状态
  247. *
  248. * @param int $userId 用户ID
  249. * @param int $petId 宠物ID
  250. * @return PetDataDto 宠物状态信息,参考DataPet结构,包括技能情况,不包含争霸赛情况
  251. * @throws Exception
  252. */
  253. public static function getPetStatus(int $userId, int $petId): PetDataDto
  254. {
  255. // 获取宠物信息
  256. $pet = PetUser::where('id', $petId)
  257. ->where('user_id', $userId)
  258. ->first();
  259. if (!$pet) {
  260. throw new Exception("宠物不存在或不属于该用户");
  261. }
  262. // 计算宠物战力
  263. $petLogic = new PetLogic();
  264. $fightingCapacity = $petLogic->calculatePower($petId);
  265. // 使用工厂类创建宠物DTO
  266. $petDto = PetDtoFactory::createPetDataDto($pet, $fightingCapacity);
  267. // 直接返回DTO对象
  268. return $petDto;
  269. }
  270. /**
  271. * 更新宠物状态
  272. *
  273. * 注意:调用此方法前,请确保已开启数据库事务
  274. *
  275. * @param int $userId 用户ID
  276. * @param int $petId 宠物ID
  277. * @param PetStatus $status 新状态
  278. * @param string $reason 变更原因
  279. * @return bool 是否更新成功
  280. * @throws Exception
  281. */
  282. public static function updatePetStatus(int $userId, int $petId, PetStatus $status, string $reason = ''): bool
  283. {
  284. // 验证事务是否已开启
  285. Helper::check_tr();
  286. try {
  287. // 宠物逻辑
  288. $petLogic = new PetLogic();
  289. // 获取宠物信息
  290. $pet = PetUser::where('id', $petId)
  291. ->where('user_id', $userId)
  292. ->first();
  293. if (!$pet) {
  294. throw new Exception("宠物不存在或不属于该用户");
  295. }
  296. // 记录旧状态
  297. $oldStatus = $pet->status;
  298. // 执行状态变更逻辑
  299. $result = $petLogic->changeStatus($petId, $status, $reason);
  300. return true;
  301. } catch (Exception $e) {
  302. Log::error('更新宠物状态失败', [
  303. 'user_id' => $userId,
  304. 'pet_id' => $petId,
  305. 'status' => $status->value,
  306. 'reason' => $reason,
  307. 'error' => $e->getMessage()
  308. ]);
  309. throw $e;
  310. }
  311. }
  312. /**
  313. * 恢复宠物体力
  314. *
  315. * 注意:调用此方法前,请确保已开启数据库事务
  316. *
  317. * @param int $userId 用户ID
  318. * @param int $petId 宠物ID
  319. * @param int $minutes 经过的分钟数
  320. * @return array 恢复结果
  321. * @throws Exception
  322. */
  323. public static function recoverStamina(int $userId, int $petId, int $minutes): array
  324. {
  325. // 验证事务是否已开启
  326. Helper::check_tr();
  327. try {
  328. // 宠物逻辑
  329. $petLogic = new PetLogic();
  330. // 获取宠物信息
  331. $pet = PetUser::where('id', $petId)
  332. ->where('user_id', $userId)
  333. ->first();
  334. if (!$pet) {
  335. throw new Exception("宠物不存在或不属于该用户");
  336. }
  337. // 执行体力恢复逻辑
  338. $recoveredStamina = $petLogic->recoverStamina($petId, $minutes);
  339. return [
  340. 'success' => true,
  341. 'pet_id' => $petId,
  342. 'recovered_stamina' => $recoveredStamina,
  343. 'current_stamina' => $pet->fresh()->stamina,
  344. 'message' => '宠物体力恢复成功'
  345. ];
  346. } catch (Exception $e) {
  347. Log::error('恢复宠物体力失败', [
  348. 'user_id' => $userId,
  349. 'pet_id' => $petId,
  350. 'minutes' => $minutes,
  351. 'error' => $e->getMessage()
  352. ]);
  353. throw $e;
  354. }
  355. }
  356. /**
  357. * 获取宠物可用技能列表
  358. *
  359. * @param int $userId 用户ID
  360. * @param int $petId 宠物ID
  361. * @return array 技能列表
  362. * @throws Exception
  363. */
  364. public static function getPetSkills(int $userId, int $petId): array
  365. {
  366. // 获取宠物信息
  367. $pet = PetUser::where('id', $petId)
  368. ->where('user_id', $userId)
  369. ->first();
  370. if (!$pet) {
  371. throw new Exception("宠物不存在或不属于该用户");
  372. }
  373. // 获取所有技能
  374. $allSkills = PetSkill::all();
  375. // 筛选出宠物等级可用的技能(基于等级配置表)
  376. $availableSkills = $allSkills->filter(function ($skill) use ($pet) {
  377. // 检查等级配置表中的可用技能
  378. return self::checkPetSkillAvailable($pet, $skill->id);
  379. });
  380. // 获取技能使用记录,计算冷却时间
  381. $skillLogs = $pet->skillLogs()
  382. ->orderBy('used_at', 'desc')
  383. ->get()
  384. ->groupBy('skill_id');
  385. $result = [];
  386. foreach ($availableSkills as $skill) {
  387. $lastUsed = null;
  388. $cooldownRemaining = 0;
  389. if (isset($skillLogs[$skill->id]) && $skillLogs[$skill->id]->count() > 0) {
  390. $lastUsed = $skillLogs[$skill->id][0]->used_at;
  391. $cooldownSeconds = $skill->cool_down;
  392. $now = now();
  393. // 确保时间计算的正确性
  394. if ($lastUsed instanceof \Carbon\Carbon) {
  395. $secondsSinceLastUse = $now->diffInSeconds($lastUsed, false);
  396. } else {
  397. // 如果不是Carbon对象,尝试解析
  398. $lastUsed = \Carbon\Carbon::parse($lastUsed);
  399. $secondsSinceLastUse = $now->diffInSeconds($lastUsed, false);
  400. }
  401. // 如果secondsSinceLastUse为负数,说明lastUsed时间在未来,这是异常情况
  402. if ($secondsSinceLastUse < 0) {
  403. $secondsSinceLastUse = 0;
  404. }
  405. $cooldownRemaining = max(0, $cooldownSeconds - $secondsSinceLastUse);
  406. }
  407. $result[] = [
  408. 'skill_id' => $skill->id,
  409. 'skill_name' => $skill->skill_name,
  410. 'stamina_cost' => $skill->stamina_cost,
  411. 'cool_down' => $skill->cool_down,
  412. 'effect_desc' => $skill->effect_desc,
  413. 'last_used' => $lastUsed ? $lastUsed->format('Y-m-d H:i:s') : null,
  414. 'cooldown_remaining' => $cooldownRemaining,
  415. 'is_available' => $cooldownRemaining === 0 && $pet->stamina >= $skill->stamina_cost
  416. ];
  417. }
  418. return $result;
  419. }
  420. /**
  421. * 检查宠物当前等级是否可以使用指定技能
  422. *
  423. * 基于宠物等级配置表中的 skills 字段进行检查
  424. *
  425. * @param PetUser $pet 宠物对象
  426. * @param int $skillId 技能ID
  427. * @return bool 是否可以使用该技能
  428. */
  429. private static function checkPetSkillAvailable(PetUser $pet, int $skillId): bool
  430. {
  431. try {
  432. // 获取宠物当前等级的配置
  433. // 注意:这里假设所有宠物都使用 pet_id=1 的配置,如果有多种宠物类型,需要修改
  434. $levelConfig = PetLevelConfig::where('pet_id', 1)
  435. ->where('level', $pet->level)
  436. ->first();
  437. if (!$levelConfig) {
  438. Log::warning('宠物等级配置不存在', [
  439. 'pet_id' => $pet->id,
  440. 'pet_level' => $pet->level,
  441. 'skill_id' => $skillId
  442. ]);
  443. // 如果没有配置,默认允许使用(向后兼容)
  444. return true;
  445. }
  446. // 检查技能是否在可用技能列表中
  447. $availableSkills = $levelConfig->skills ?? [];
  448. $isAvailable = in_array($skillId, $availableSkills);
  449. Log::info('宠物技能可用性检查', [
  450. 'pet_id' => $pet->id,
  451. 'pet_level' => $pet->level,
  452. 'skill_id' => $skillId,
  453. 'available_skills' => $availableSkills,
  454. 'is_available' => $isAvailable
  455. ]);
  456. return $isAvailable;
  457. } catch (\Exception $e) {
  458. Log::error('检查宠物技能可用性时发生错误', [
  459. 'pet_id' => $pet->id,
  460. 'pet_level' => $pet->level,
  461. 'skill_id' => $skillId,
  462. 'error' => $e->getMessage()
  463. ]);
  464. // 发生错误时默认允许使用(向后兼容)
  465. return true;
  466. }
  467. }
  468. }