SystemCrontabServices.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. <?php
  2. namespace app\services\system\crontab;
  3. use app\dao\system\crontab\SystemCrontabDao;
  4. use app\services\BaseServices;
  5. use crmeb\exceptions\AdminException;
  6. use think\helper\Str;
  7. use Workerman\Crontab\Crontab;
  8. class SystemCrontabServices extends BaseServices
  9. {
  10. public function __construct(SystemCrontabDao $dao)
  11. {
  12. $this->dao = $dao;
  13. }
  14. /**
  15. * 定时任务列表
  16. * @param array $where
  17. * @return array
  18. * @throws \think\db\exception\DataNotFoundException
  19. * @throws \think\db\exception\DbException
  20. * @throws \think\db\exception\ModelNotFoundException
  21. */
  22. public function getTimerList(array $where = [])
  23. {
  24. [$page, $limit] = $this->getPageValue();
  25. $list = $this->dao->selectList($where, '*', $page, $limit, 'id desc', [], true);
  26. foreach ($list as &$item) {
  27. $item['next_execution_time'] = date('Y-m-d H:i:s', $item['next_execution_time']);
  28. $item['last_execution_time'] = $item['last_execution_time'] != 0 ? date('Y-m-d H:i:s', $item['last_execution_time']) : '暂未执行';
  29. }
  30. $count = $this->dao->count($where);
  31. return compact('list', 'count');
  32. }
  33. /**
  34. * 定时任务详情
  35. * @param $id
  36. * @return array
  37. * @throws \think\db\exception\DataNotFoundException
  38. * @throws \think\db\exception\DbException
  39. * @throws \think\db\exception\ModelNotFoundException
  40. */
  41. public function getTimerInfo($id)
  42. {
  43. $info = $this->dao->get($id);
  44. $info['customCode'] = "<?php\n\n" . json_decode($info['customCode']);
  45. if (!$info) throw new AdminException(100026);
  46. return $info->toArray();
  47. }
  48. /**
  49. * 定时任务类型
  50. * @return string[]
  51. */
  52. public function getMarkList(): array
  53. {
  54. return app()->make(CrontabRunServices::class)->markList;
  55. }
  56. /**
  57. * 保存定时任务
  58. * @param array $data
  59. * @return bool
  60. */
  61. public function saveTimer(array $data = [])
  62. {
  63. if (!$data['id'] && $this->dao->getCount(['mark' => $data['mark'], 'is_del' => 0]) && $data['mark'] != 'customTimer') {
  64. throw new AdminException('该定时任务已存在,请勿重复添加');
  65. }
  66. if ($data['mark'] != 'customTimer') $data['name'] = $this->getMarkList()[$data['mark']];
  67. $data['add_time'] = time();
  68. $data['customCode'] = json_encode(preg_replace('/<\?php\s*\n/', '', $data['customCode']));
  69. $data['timeStr'] = $this->getTimerStr([
  70. 'type' => $data['type'],
  71. 'month' => $data['month'],
  72. 'week' => $data['week'],
  73. 'day' => $data['day'],
  74. 'hour' => $data['hour'],
  75. 'minute' => $data['minute'],
  76. 'second' => $data['second'],
  77. ]);
  78. if (!$data['id']) {
  79. unset($data['id']);
  80. $res = $this->dao->save($data);
  81. } else {
  82. $res = $this->dao->update(['id' => $data['id']], $data);
  83. }
  84. if (!$res) throw new AdminException(100006);
  85. return true;
  86. }
  87. /**
  88. * 删除定时任务
  89. * @param $id
  90. * @return bool
  91. */
  92. public function delTimer($id)
  93. {
  94. $res = $this->dao->update(['id' => $id], ['is_del' => 1]);
  95. if (!$res) throw new AdminException(100008);
  96. return true;
  97. }
  98. /**
  99. * 设置定时任务状态
  100. * @param $id
  101. * @param $is_open
  102. * @return bool
  103. */
  104. public function setTimerStatus($id, $is_open)
  105. {
  106. $res = $this->dao->update(['id' => $id], ['is_open' => $is_open]);
  107. if (!$res) throw new AdminException(100014);
  108. return true;
  109. }
  110. /**
  111. * 计算定时任务下次执行时间
  112. * @param $data
  113. * @param int $time
  114. * @return false|float|int|mixed
  115. */
  116. public function getTimerCycleTime($data, $time = 0)
  117. {
  118. if (!$time) $time = time();
  119. switch ($data['type']) {
  120. case 1: // 每隔几秒
  121. $cycle_time = $time + $data['second'];
  122. break;
  123. case 2: // 每隔几分
  124. $cycle_time = $time + ($data['minute'] * 60);
  125. break;
  126. case 3: // 每隔几时
  127. $cycle_time = $time + ($data['hour'] * 3600) + ($data['minute'] * 60);
  128. break;
  129. case 4: // 每隔几日
  130. $cycle_time = $time + ($data['day'] * 86400) + ($data['hour'] * 3600) + ($data['minute'] * 60);
  131. break;
  132. case 5: // 每日几时几分几秒
  133. $cycle_time = strtotime(date('Y-m-d ' . $data['hour'] . ':' . $data['minute'] . ':' . $data['second'], time()));
  134. if ($time >= $cycle_time) {
  135. $cycle_time = $cycle_time + 86400;
  136. }
  137. break;
  138. case 6: // 每周周几几时几分几秒
  139. $todayStart = strtotime(date('Y-m-d 00:00:00', time()));
  140. $w = date("w");
  141. if ($w > $data['week']) {
  142. $cycle_time = $todayStart + ((7 - $w + $data['week']) * 86400) + ($data['hour'] * 3600) + ($data['minute'] * 60) + $data['second'];
  143. } else if ($w == $data['week']) {
  144. $cycle_time = $todayStart + ($data['hour'] * 3600) + ($data['minute'] * 60) + $data['second'];
  145. if ($time >= $cycle_time) {
  146. $cycle_time = $cycle_time + (7 * 86400);
  147. }
  148. } else {
  149. $cycle_time = $todayStart + (($data['week'] - $w) * 86400) + ($data['hour'] * 3600) + ($data['minute'] * 60) + $data['second'];
  150. }
  151. break;
  152. case 7: // 每月几日几时几分几秒
  153. $currentMonth = date("n");
  154. $currentYear = date("Y");
  155. if ($currentMonth == 12) {
  156. $nextMonth = 1;
  157. $nextYear = $currentYear + 1;
  158. } else {
  159. $nextMonth = $currentMonth + 1;
  160. $nextYear = $currentYear;
  161. }
  162. $cycle_time = mktime($data['hour'], $data['minute'], $data['second'], $nextMonth, $data['day'], $nextYear);
  163. break;
  164. case 8: // 每年几月几日几时几分几秒
  165. $cycle_time = mktime($data['hour'], $data['minute'], $data['second'], $data['month'], $data['day'], date("Y") + 1);
  166. break;
  167. default:
  168. $cycle_time = 0;
  169. break;
  170. }
  171. return $cycle_time;
  172. }
  173. /**
  174. * 接口执行执行任务
  175. * @throws \think\db\exception\DataNotFoundException
  176. * @throws \think\db\exception\DbException
  177. * @throws \think\db\exception\ModelNotFoundException
  178. * @author 吴汐
  179. * @email 442384644@qq.com
  180. * @date 2023/02/17
  181. */
  182. public function crontabApiRun()
  183. {
  184. $crontabRunServices = app()->make(CrontabRunServices::class);
  185. $time = time();
  186. file_put_contents(root_path() . 'runtime/.timer', $time); //检测定时任务是否正常
  187. $list = $this->dao->selectList(['is_open' => 1, 'is_del' => 0])->toArray();
  188. foreach ($list as $item) {
  189. if ($item['next_execution_time'] < $time) {
  190. //转化小驼峰方法名
  191. $functionName = Str::camel($item['mark']);
  192. //执行定时任务
  193. $crontabRunServices->$functionName();
  194. //写入本次执行时间和下次执行时间
  195. $this->dao->update(['mark' => $item['mark']], ['last_execution_time' => $time, 'next_execution_time' => $this->getTimerCycleTime($item)]);
  196. }
  197. }
  198. }
  199. /**
  200. * 命令执行定时任务
  201. * @throws \ReflectionException
  202. * @throws \think\db\exception\DataNotFoundException
  203. * @throws \think\db\exception\DbException
  204. * @throws \think\db\exception\ModelNotFoundException
  205. * @author wuhaotian
  206. * @email 442384644@qq.com
  207. * @date 2024/6/5
  208. */
  209. public function crontabCommandRun()
  210. {
  211. $crontabRunServices = app()->make(CrontabRunServices::class);
  212. //自动写入文件方便检测是否启动定时任务命令
  213. new Crontab('*/6 * * * * *', function () {
  214. file_put_contents(root_path() . 'runtime/.timer', time());
  215. });
  216. $list = $this->dao->selectList(['is_del' => 0, 'is_open' => 1])->toArray();
  217. foreach ($list as &$item) {
  218. //转化小驼峰
  219. $functionName = Str::camel($item['mark']);
  220. //获取自定义定时任务code
  221. $customCode = json_decode($item['customCode']);
  222. //获取定时任务时间字符串
  223. $timeStr = $item['timeStr'] != '' ? $item['timeStr'] : $this->getTimerStr($item);
  224. new Crontab($timeStr, function () use ($crontabRunServices, $functionName, $customCode) {
  225. if ($functionName == 'customTimer') {
  226. $crontabRunServices->customTimer($customCode);
  227. } else {
  228. $crontabRunServices->$functionName();
  229. }
  230. });
  231. }
  232. }
  233. /**
  234. * 获取定时任务时间表达式
  235. * 0 1 2 3 4 5
  236. * | | | | | |
  237. * | | | | | +------ day of week (0 - 6) (Sunday=0)
  238. * | | | | +------ month (1 - 12)
  239. * | | | +-------- day of month (1 - 31)
  240. * | | +---------- hour (0 - 23)
  241. * | +------------ min (0 - 59)
  242. * +-------------- sec (0-59)[可省略,如果没有0位,则最小时间粒度是分钟]
  243. * @param $data
  244. * @return string
  245. */
  246. public function getTimerStr($data): string
  247. {
  248. $timeStr = '';
  249. switch ($data['type']) {
  250. case 1:// 每隔几秒
  251. $timeStr = '*/' . $data['second'] . ' * * * * *';
  252. break;
  253. case 2:// 每隔几分
  254. $timeStr = '0 */' . $data['minute'] . ' * * * *';
  255. break;
  256. case 3:// 每隔几时第几分钟执行
  257. $timeStr = '0 ' . $data['minute'] . ' */' . $data['hour'] . ' * * *';
  258. break;
  259. case 4:// 每隔几日第几小时第几分钟执行
  260. $timeStr = '0 ' . $data['minute'] . ' ' . $data['hour'] . ' */' . $data['day'] . ' * *';
  261. break;
  262. case 5:// 每日几时几分几秒
  263. $timeStr = $data['second'] . ' ' . $data['minute'] . ' ' . $data['hour'] . ' * * *';
  264. break;
  265. case 6:// 每周周几几时几分几秒
  266. $timeStr = $data['second'] . ' ' . $data['minute'] . ' ' . $data['hour'] . ' * * ' . ($data['week'] == 7 ? 0 : $data['week']);
  267. break;
  268. case 7:// 每月几日几时几分几秒
  269. $timeStr = $data['second'] . ' ' . $data['minute'] . ' ' . $data['hour'] . ' ' . $data['day'] . ' * *';
  270. break;
  271. case 8:// 每年几月几日几时几分几秒
  272. $timeStr = $data['second'] . ' ' . $data['minute'] . ' ' . $data['hour'] . ' ' . $data['day'] . ' ' . $data['month'] . ' *';
  273. break;
  274. }
  275. return $timeStr;
  276. }
  277. }