PetActiveSkillService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. <?php
  2. namespace App\Module\Pet\Services;
  3. use App\Module\Pet\Models\PetActiveSkill;
  4. use App\Module\Pet\Models\PetSkillProcessLog;
  5. use App\Module\Pet\Logic\PetAutoSkillLogic;
  6. use Illuminate\Support\Facades\Log;
  7. use Illuminate\Support\Facades\DB;
  8. use Illuminate\Support\Collection;
  9. /**
  10. * 宠物激活技能服务类
  11. *
  12. * 提供宠物激活技能处理的服务,包括批量处理激活技能、单个技能处理等功能。
  13. * 该类是宠物激活技能处理的主要入口,封装了技能处理的复杂逻辑。
  14. */
  15. class PetActiveSkillService
  16. {
  17. /**
  18. * 批量处理激活技能
  19. *
  20. * @param int $limit 处理数量限制,默认100
  21. * @return array 处理结果统计
  22. */
  23. public static function processActiveSkills(int $limit = 100): array
  24. {
  25. Log::info('开始处理宠物激活技能定时任务');
  26. try {
  27. // 获取所有生效中的技能,按最后检查时间排序(优先处理长时间未检查的)
  28. $activeSkills = self::getActiveSkills($limit);
  29. Log::info('找到激活技能数量', ['count' => $activeSkills->count()]);
  30. $processedCount = 0;
  31. $expiredCount = 0;
  32. $skippedCount = 0;
  33. foreach ($activeSkills as $activeSkill) {
  34. try {
  35. $result = self::processSkill($activeSkill);
  36. switch ($result['status']) {
  37. case 'processed':
  38. $processedCount++;
  39. break;
  40. case 'expired':
  41. $expiredCount++;
  42. break;
  43. case 'skipped':
  44. $skippedCount++;
  45. break;
  46. }
  47. } catch (\Exception $e) {
  48. Log::error('处理单个激活技能失败', [
  49. 'active_skill_id' => $activeSkill->id,
  50. 'pet_id' => $activeSkill->pet_id,
  51. 'skill_name' => $activeSkill->skill_name,
  52. 'error' => $e->getMessage(),
  53. 'trace' => $e->getTraceAsString()
  54. ]);
  55. }
  56. }
  57. $result = [
  58. 'total_skills' => $activeSkills->count(),
  59. 'processed_count' => $processedCount,
  60. 'expired_count' => $expiredCount,
  61. 'skipped_count' => $skippedCount
  62. ];
  63. Log::info('宠物激活技能定时任务完成', $result);
  64. return $result;
  65. } catch (\Exception $e) {
  66. Log::error('处理宠物激活技能定时任务失败', [
  67. 'error' => $e->getMessage(),
  68. 'trace' => $e->getTraceAsString()
  69. ]);
  70. throw $e;
  71. }
  72. }
  73. /**
  74. * 处理单个激活技能
  75. *
  76. * @param PetActiveSkill $activeSkill 激活的技能
  77. * @return array 处理结果 ['status' => 'processed|expired|skipped', 'reason' => string]
  78. */
  79. public static function processSkill(PetActiveSkill $activeSkill): array
  80. {
  81. // 检查技能是否已过期
  82. if ($activeSkill->isExpired()) {
  83. $activeSkill->markAsExpired();
  84. // 记录过期日志
  85. PetSkillProcessLog::createExpiredLog($activeSkill, '技能已过期,自动标记为过期状态');
  86. Log::info('技能已过期', [
  87. 'active_skill_id' => $activeSkill->id,
  88. 'pet_id' => $activeSkill->pet_id,
  89. 'skill_name' => $activeSkill->skill_name
  90. ]);
  91. return ['status' => 'expired', 'reason' => '技能已过期'];
  92. }
  93. // 检查是否需要执行检查
  94. if (!$activeSkill->shouldCheck()) {
  95. $skipReason = self::getSkipReason($activeSkill);
  96. PetSkillProcessLog::createSkippedLog($activeSkill, $skipReason['message'], $skipReason['data']);
  97. Log::debug('shouldCheck false', [
  98. 'active_skill_id' => $activeSkill->id,
  99. 'pet_id' => $activeSkill->pet_id,
  100. 'skill_name' => $activeSkill->skill_name,
  101. 'skip_reason' => $skipReason['message']
  102. ]);
  103. return ['status' => 'skipped', 'reason' => $skipReason['message']];
  104. }
  105. // 处理技能效果(事务在方法内部管理)
  106. self::processSkillEffect($activeSkill);
  107. return ['status' => 'processed', 'reason' => '技能处理成功'];
  108. }
  109. /**
  110. * 获取激活中的技能列表
  111. *
  112. * @param int $limit 数量限制
  113. * @return Collection|PetActiveSkill[]
  114. */
  115. protected static function getActiveSkills(int $limit): Collection
  116. {
  117. return PetActiveSkill::where('status', PetActiveSkill::STATUS_ACTIVE)
  118. ->where('end_time', '>', now())
  119. ->orderBy('last_check_time', 'asc')
  120. ->limit($limit)
  121. ->get();
  122. }
  123. /**
  124. * 处理技能效果
  125. *
  126. * @param PetActiveSkill $activeSkill 激活的技能
  127. * @return void
  128. */
  129. protected static function processSkillEffect(PetActiveSkill $activeSkill): void
  130. {
  131. $startTime = microtime(true);
  132. $autoSkillLogic = new PetAutoSkillLogic();
  133. $processData = [];
  134. $processStatus = 'success';
  135. $processReason = '技能处理成功';
  136. try {
  137. $result = self::executeSkillByType($autoSkillLogic, $activeSkill);
  138. $processData = self::extractProcessData($result, $activeSkill->skill_name);
  139. } catch (\Exception $e) {
  140. $processStatus = 'failed';
  141. $processReason = '技能处理异常: ' . $e->getMessage();
  142. $processData['error'] = [
  143. 'message' => $e->getMessage(),
  144. 'file' => $e->getFile(),
  145. 'line' => $e->getLine()
  146. ];
  147. }
  148. $executionTime = microtime(true) - $startTime;
  149. // 记录处理日志
  150. if ($processStatus === 'success') {
  151. PetSkillProcessLog::createSuccessLog($activeSkill, $processData, $executionTime, $processReason);
  152. } else {
  153. PetSkillProcessLog::createFailedLog($activeSkill, $processReason, $processData, $executionTime);
  154. }
  155. // 更新最后检查时间
  156. $activeSkill->updateLastCheckTime();
  157. }
  158. /**
  159. * 根据技能类型执行对应的技能逻辑
  160. *
  161. * @param PetAutoSkillLogic $autoSkillLogic 自动技能逻辑实例
  162. * @param PetActiveSkill $activeSkill 激活的技能
  163. * @return mixed 技能执行结果
  164. */
  165. protected static function executeSkillByType(PetAutoSkillLogic $autoSkillLogic, PetActiveSkill $activeSkill)
  166. {
  167. switch ($activeSkill->skill_name) {
  168. case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_HARVESTING->value:
  169. return self::processAutoHarvestWithSeparateTransactions($autoSkillLogic, $activeSkill);
  170. case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_PLANTING->value:
  171. return DB::transaction(function () use ($autoSkillLogic, $activeSkill) {
  172. return $autoSkillLogic->processAutoPlant($activeSkill);
  173. });
  174. case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_WEEDING->value:
  175. return DB::transaction(function () use ($autoSkillLogic, $activeSkill) {
  176. return $autoSkillLogic->processAutoWeeding($activeSkill);
  177. });
  178. case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_WATERING->value:
  179. return DB::transaction(function () use ($autoSkillLogic, $activeSkill) {
  180. return $autoSkillLogic->processAutoWatering($activeSkill);
  181. });
  182. case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_PEST_CONTROL->value:
  183. return DB::transaction(function () use ($autoSkillLogic, $activeSkill) {
  184. return $autoSkillLogic->processAutoPestControl($activeSkill);
  185. });
  186. case \App\Module\Pet\Enums\PET_SKILL_NAME::AUTO_FERTILIZING->value:
  187. return DB::transaction(function () use ($autoSkillLogic, $activeSkill) {
  188. return $autoSkillLogic->processAutoFertilizing($activeSkill);
  189. });
  190. default:
  191. Log::warning('未知的技能类型', [
  192. 'active_skill_id' => $activeSkill->id,
  193. 'skill_name' => $activeSkill->skill_name
  194. ]);
  195. throw new \Exception('未知的技能类型: ' . $activeSkill->skill_name);
  196. }
  197. }
  198. /**
  199. * 提取处理数据用于日志记录
  200. *
  201. * @param mixed $result 处理结果
  202. * @param string $skillName 技能名称
  203. * @return array
  204. */
  205. protected static function extractProcessData($result, string $skillName): array
  206. {
  207. $processData = [
  208. 'skill_name' => $skillName,
  209. 'processed_at' => now()->toDateTimeString()
  210. ];
  211. // 如果结果是数组,直接合并
  212. if (is_array($result)) {
  213. $processData = array_merge($processData, $result);
  214. }
  215. return $processData;
  216. }
  217. /**
  218. * 获取跳过原因
  219. *
  220. * @param PetActiveSkill $activeSkill 激活的技能
  221. * @return array ['message' => string, 'data' => array]
  222. */
  223. protected static function getSkipReason(PetActiveSkill $activeSkill): array
  224. {
  225. $lastCheckTime = $activeSkill->getLastCheckTime();
  226. $config = $activeSkill->config;
  227. $checkInterval = is_array($config) ? ($config['check_interval'] ?? 60) : 60;
  228. $diffSeconds = $lastCheckTime ? $lastCheckTime->diffInSeconds(now()) : 0;
  229. $message = sprintf(
  230. '未到检查间隔时间,距离上次检查仅%d秒,需要间隔%d秒',
  231. $diffSeconds,
  232. $checkInterval
  233. );
  234. $data = [
  235. 'last_check_time' => $lastCheckTime ? $lastCheckTime->toDateTimeString() : null,
  236. 'check_interval' => $checkInterval,
  237. 'diff_seconds' => $diffSeconds
  238. ];
  239. return ['message' => $message, 'data' => $data];
  240. }
  241. /**
  242. * 处理自动收获技能,使用分离的事务
  243. *
  244. * 将收获和铲除功能分别在不同的事务中执行
  245. *
  246. * @param PetAutoSkillLogic $autoSkillLogic 自动技能逻辑实例
  247. * @param PetActiveSkill $activeSkill 激活的技能
  248. * @return array 技能执行结果
  249. */
  250. protected static function processAutoHarvestWithSeparateTransactions(PetAutoSkillLogic $autoSkillLogic, PetActiveSkill $activeSkill): array
  251. {
  252. $pet = $activeSkill->pet;
  253. $userId = $pet->user_id;
  254. Log::info('开始处理自动收菜技能(分离事务)', [
  255. 'active_skill_id' => $activeSkill->id,
  256. 'pet_id' => $pet->id,
  257. 'user_id' => $userId
  258. ]);
  259. // 执行收获逻辑
  260. // 注意:这里直接调用原有的processAutoHarvest方法,它已经包含了收获和铲除逻辑
  261. // 但是铲除部分会在收获事务内执行,这符合用户要求的分离事务
  262. DB::transaction(function () use ($autoSkillLogic, $activeSkill) {
  263. $autoSkillLogic->processAutoHarvest($activeSkill);
  264. });
  265. // 清理已存在的枯萎作物
  266. $witheredClearCount = 0;
  267. try {
  268. $witheredClearCount = DB::transaction(function () use ($autoSkillLogic, $userId) {
  269. return $autoSkillLogic->clearAllWitheredCrops($userId);
  270. });
  271. } catch (\Exception $e) {
  272. Log::warning('清理枯萎作物失败', [
  273. 'user_id' => $userId,
  274. 'error' => $e->getMessage()
  275. ]);
  276. }
  277. $finalResult = [
  278. 'withered_cleared_count' => $witheredClearCount,
  279. 'harvest_completed' => true
  280. ];
  281. Log::info('自动收菜技能处理完成(分离事务)', [
  282. 'active_skill_id' => $activeSkill->id,
  283. 'pet_id' => $pet->id,
  284. 'user_id' => $userId,
  285. 'withered_cleared_count' => $witheredClearCount
  286. ]);
  287. return $finalResult;
  288. }
  289. }