292 lines
8.9 KiB
PHP
292 lines
8.9 KiB
PHP
<?php
|
||
|
||
namespace App\Services\System;
|
||
|
||
use App\Models\System\Task;
|
||
use App\Services\System\NotificationService;
|
||
use Illuminate\Support\Facades\Validator;
|
||
use Illuminate\Support\Facades\Log;
|
||
|
||
class TaskService
|
||
{
|
||
/**
|
||
* @var NotificationService
|
||
*/
|
||
protected $notificationService;
|
||
|
||
/**
|
||
* TaskService constructor
|
||
*/
|
||
public function __construct(NotificationService $notificationService)
|
||
{
|
||
$this->notificationService = $notificationService;
|
||
}
|
||
|
||
public function getList(array $params): array
|
||
{
|
||
$query = Task::query();
|
||
|
||
if (!empty($params['keyword'])) {
|
||
$query->where(function ($q) use ($params) {
|
||
$q->where('name', 'like', '%' . $params['keyword'] . '%')
|
||
->orWhere('command', 'like', '%' . $params['keyword'] . '%');
|
||
});
|
||
}
|
||
|
||
if (isset($params['is_active']) && $params['is_active'] !== '') {
|
||
$query->where('is_active', $params['is_active']);
|
||
}
|
||
|
||
if (!empty($params['type'])) {
|
||
$query->where('type', $params['type']);
|
||
}
|
||
|
||
$query->orderBy('id');
|
||
|
||
$pageSize = $params['page_size'] ?? 20;
|
||
$list = $query->paginate($pageSize);
|
||
|
||
return [
|
||
'list' => $list->items(),
|
||
'total' => $list->total(),
|
||
'page' => $list->currentPage(),
|
||
'page_size' => $list->perPage(),
|
||
];
|
||
}
|
||
|
||
public function getAll(): array
|
||
{
|
||
return Task::all()->toArray();
|
||
}
|
||
|
||
public function getById(int $id): ?Task
|
||
{
|
||
return Task::find($id);
|
||
}
|
||
|
||
public function create(array $data): Task
|
||
{
|
||
Validator::make($data, [
|
||
'name' => 'required|string|max:100',
|
||
'command' => 'required|string|max:255',
|
||
'type' => 'required|string|in:command,job,closure',
|
||
'expression' => 'required|string',
|
||
])->validate();
|
||
|
||
return Task::create($data);
|
||
}
|
||
|
||
public function update(int $id, array $data): Task
|
||
{
|
||
$task = Task::findOrFail($id);
|
||
|
||
Validator::make($data, [
|
||
'name' => 'sometimes|required|string|max:100',
|
||
'command' => 'sometimes|required|string|max:255',
|
||
'type' => 'sometimes|required|string|in:command,job,closure',
|
||
'expression' => 'sometimes|required|string',
|
||
])->validate();
|
||
|
||
$task->update($data);
|
||
return $task;
|
||
}
|
||
|
||
public function delete(int $id): bool
|
||
{
|
||
$task = Task::findOrFail($id);
|
||
return $task->delete();
|
||
}
|
||
|
||
public function batchDelete(array $ids): bool
|
||
{
|
||
Task::whereIn('id', $ids)->delete();
|
||
return true;
|
||
}
|
||
|
||
public function batchUpdateStatus(array $ids, bool $status): bool
|
||
{
|
||
Task::whereIn('id', $ids)->update(['is_active' => $status]);
|
||
return true;
|
||
}
|
||
|
||
public function run(int $id): array
|
||
{
|
||
$task = Task::findOrFail($id);
|
||
|
||
$startTime = microtime(true);
|
||
$output = '';
|
||
$status = 'success';
|
||
$errorMessage = '';
|
||
|
||
try {
|
||
switch ($task->type) {
|
||
case 'command':
|
||
$command = $task->command;
|
||
if ($task->run_in_background) {
|
||
$command .= ' > /dev/null 2>&1 &';
|
||
}
|
||
exec($command, $output, $statusCode);
|
||
$output = implode("\n", $output);
|
||
if ($statusCode !== 0) {
|
||
throw new \Exception('Command failed with status code: ' . $statusCode);
|
||
}
|
||
break;
|
||
|
||
case 'job':
|
||
$jobClass = $task->command;
|
||
if (class_exists($jobClass)) {
|
||
dispatch(new $jobClass());
|
||
} else {
|
||
throw new \Exception('Job class not found: ' . $jobClass);
|
||
}
|
||
break;
|
||
|
||
case 'closure':
|
||
throw new \Exception('Closure type tasks cannot be run directly');
|
||
}
|
||
|
||
$task->increment('run_count');
|
||
} catch (\Exception $e) {
|
||
$status = 'error';
|
||
$errorMessage = $e->getMessage();
|
||
$task->increment('failed_count');
|
||
}
|
||
|
||
$executionTime = round((microtime(true) - $startTime) * 1000);
|
||
|
||
$task->update([
|
||
'last_run_at' => now(),
|
||
'last_output' => substr($output, 0, 10000),
|
||
]);
|
||
|
||
// 发送任务执行结果通知
|
||
$this->sendTaskNotification($task, $status, $errorMessage, $executionTime);
|
||
|
||
return [
|
||
'status' => $status,
|
||
'output' => $output,
|
||
'error_message' => $errorMessage,
|
||
'execution_time' => $executionTime,
|
||
];
|
||
}
|
||
|
||
public function getStatistics(): array
|
||
{
|
||
$total = Task::count();
|
||
$active = Task::where('is_active', true)->count();
|
||
$inactive = Task::where('is_active', false)->count();
|
||
|
||
return [
|
||
'total' => $total,
|
||
'active' => $active,
|
||
'inactive' => $inactive,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 发送任务执行结果通知
|
||
*
|
||
* @param Task $task
|
||
* @param string $status
|
||
* @param string|null $errorMessage
|
||
* @param int $executionTime
|
||
* @return void
|
||
*/
|
||
protected function sendTaskNotification(Task $task, string $status, ?string $errorMessage, int $executionTime): void
|
||
{
|
||
try {
|
||
// 只对失败的任务或重要的成功任务发送通知
|
||
// 这里可以根据实际需求调整通知策略
|
||
|
||
if ($status === 'error') {
|
||
// 任务失败通知
|
||
$title = '任务执行失败: ' . $task->name;
|
||
$content = sprintf(
|
||
"任务 %s 执行失败,错误信息:%s\n执行时间:%d 毫秒",
|
||
$task->name,
|
||
$errorMessage ?: '未知错误',
|
||
$executionTime
|
||
);
|
||
|
||
// 获取管理员用户ID列表
|
||
$adminUserIds = $this->getAdminUserIds();
|
||
|
||
if (!empty($adminUserIds)) {
|
||
$this->notificationService->sendToUsers(
|
||
$adminUserIds,
|
||
$title,
|
||
$content,
|
||
\App\Models\System\Notification::TYPE_ERROR,
|
||
\App\Models\System\Notification::CATEGORY_TASK,
|
||
[
|
||
'task_id' => $task->id,
|
||
'task_name' => $task->name,
|
||
'command' => $task->command,
|
||
'error_message' => $errorMessage,
|
||
'execution_time' => $executionTime,
|
||
'last_run_at' => $task->last_run_at?->toDateTimeString()
|
||
]
|
||
);
|
||
}
|
||
} elseif ($executionTime > 60000) {
|
||
// 执行时间超过1分钟的成功任务,发送通知
|
||
$title = '任务执行完成: ' . $task->name;
|
||
$content = sprintf(
|
||
"任务 %s 执行成功,耗时:%.2f 秒",
|
||
$task->name,
|
||
$executionTime / 1000
|
||
);
|
||
|
||
$adminUserIds = $this->getAdminUserIds();
|
||
|
||
if (!empty($adminUserIds)) {
|
||
$this->notificationService->sendToUsers(
|
||
$adminUserIds,
|
||
$title,
|
||
$content,
|
||
\App\Models\System\Notification::TYPE_SUCCESS,
|
||
\App\Models\System\Notification::CATEGORY_TASK,
|
||
[
|
||
'task_id' => $task->id,
|
||
'task_name' => $task->name,
|
||
'execution_time' => $executionTime,
|
||
'last_run_at' => $task->last_run_at?->toDateTimeString()
|
||
]
|
||
);
|
||
}
|
||
}
|
||
} catch (\Exception $e) {
|
||
Log::error('发送任务通知失败', [
|
||
'task_id' => $task->id,
|
||
'error' => $e->getMessage()
|
||
]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取管理员用户ID列表
|
||
*
|
||
* @return array
|
||
*/
|
||
protected function getAdminUserIds(): array
|
||
{
|
||
try {
|
||
// 获取拥有管理员权限的用户
|
||
// 这里可以根据实际业务逻辑调整,例如获取特定角色的用户
|
||
$adminUserIds = \App\Models\Auth\User::where('status', 1)
|
||
->whereHas('roles', function ($query) {
|
||
$query->where('name', 'admin');
|
||
})
|
||
->pluck('id')
|
||
->toArray();
|
||
|
||
return $adminUserIds;
|
||
} catch (\Exception $e) {
|
||
Log::error('获取管理员用户列表失败', [
|
||
'error' => $e->getMessage()
|
||
]);
|
||
return [];
|
||
}
|
||
}
|
||
}
|