where('crontab_id', $crontabId) ->where('status', 'open') ->update([ 'status' => 'resolved', 'resolved_at' => $now, 'update_time' => $now, ]); } public static function upsertCommandException(array $item, string $errorMessage, int $crontabLogId = 0): void { self::ensureTable(); $now = time(); $dedupeKey = self::buildDedupeKey((int) ($item['id'] ?? 0), 'command_exception'); $detail = json_encode([ 'summary_text' => mb_substr($errorMessage, 0, 1000), 'error_message' => mb_substr($errorMessage, 0, 1000), 'output_excerpt' => mb_substr($errorMessage, 0, 2000), ], JSON_UNESCAPED_UNICODE); $existing = Db::name('crontab_alert')->where('dedupe_key', $dedupeKey)->find(); if ($existing) { $update = [ 'crontab_log_id' => $crontabLogId, 'task_name' => mb_substr((string) ($item['name'] ?? ''), 0, 100), 'command' => mb_substr((string) ($item['command'] ?? ''), 0, 100), 'params' => mb_substr((string) ($item['params'] ?? ''), 0, 255), 'alert_type' => 'command_exception', 'http_status' => 0, 'detail' => $detail, 'last_seen_at' => $now, 'update_time' => $now, ]; if (($existing['status'] ?? '') === 'resolved') { $update['status'] = 'open'; $update['first_seen_at'] = $now; $update['last_notified_at'] = 0; $update['resolved_at'] = 0; $update['notify_count'] = 0; } Db::name('crontab_alert')->where('id', $existing['id'])->update($update); return; } Db::name('crontab_alert')->insert([ 'crontab_id' => (int) ($item['id'] ?? 0), 'crontab_log_id' => $crontabLogId, 'task_name' => mb_substr((string) ($item['name'] ?? ''), 0, 100), 'command' => mb_substr((string) ($item['command'] ?? ''), 0, 100), 'params' => mb_substr((string) ($item['params'] ?? ''), 0, 255), 'alert_type' => 'command_exception', 'http_status' => 0, 'detail' => $detail, 'dedupe_key' => $dedupeKey, 'status' => 'open', 'first_seen_at' => $now, 'last_seen_at' => $now, 'create_time' => $now, 'update_time' => $now, ]); } private static function buildDedupeKey(int $crontabId, string $alertType): string { return 'crontab:' . $crontabId . ':' . $alertType; } }