Primer commit del sistema separado falta mejorar mucho
This commit is contained in:
374
Sistema_discord/CommandLocker.php
Executable file
374
Sistema_discord/CommandLocker.php
Executable file
@@ -0,0 +1,374 @@
|
||||
<?php
|
||||
|
||||
class CommandLocker {
|
||||
private $pdo;
|
||||
private $lockTimeout = 300; // 5 minutos de tiempo de espera para el bloqueo
|
||||
private $debug = true;
|
||||
private $dbConnection;
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
// Configurar PDO para que lance excepciones
|
||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
||||
|
||||
// Inicializar la conexión a la base de datos
|
||||
$this->getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene una conexión activa a la base de datos
|
||||
*
|
||||
* @return PDO La conexión a la base de datos
|
||||
*/
|
||||
private function getConnection() {
|
||||
try {
|
||||
// Verificar si la conexión actual es válida
|
||||
if ($this->pdo === null || !$this->isConnectionAlive()) {
|
||||
$this->log("Estableciendo nueva conexión a la base de datos");
|
||||
$this->pdo = $this->createNewConnection();
|
||||
}
|
||||
return $this->pdo;
|
||||
} catch (Exception $e) {
|
||||
$this->log("Error al obtener conexión a la base de datos", [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si la conexión actual está activa
|
||||
*
|
||||
* @return bool True si la conexión está activa, false en caso contrario
|
||||
*/
|
||||
private function isConnectionAlive() {
|
||||
try {
|
||||
$this->pdo->query('SELECT 1');
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
$this->log("La conexión a la base de datos no está activa", [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea una nueva conexión a la base de datos
|
||||
*
|
||||
* @return PDO La nueva conexión
|
||||
*/
|
||||
private function createNewConnection() {
|
||||
// Obtener la configuración de la base de datos del archivo de configuración
|
||||
require_once __DIR__ . '/../includes/db.php';
|
||||
|
||||
// Obtener la instancia de la conexión del archivo db.php
|
||||
global $pdo;
|
||||
|
||||
if (!($pdo instanceof PDO)) {
|
||||
throw new Exception("No se pudo establecer una conexión a la base de datos");
|
||||
}
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
private function log($message, $data = []) {
|
||||
if ($this->debug) {
|
||||
$logMessage = date('[Y-m-d H:i:s] ') . $message;
|
||||
if (!empty($data)) {
|
||||
$logMessage .= ' ' . json_encode($data);
|
||||
}
|
||||
error_log($logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intenta adquirir un bloqueo para un comando
|
||||
*
|
||||
* @param string $command El comando a bloquear
|
||||
* @param int $chatId El ID del chat donde se ejecutó el comando
|
||||
* @param string $type Tipo de bloqueo ('command' o 'translation')
|
||||
* @param array $data Datos adicionales a almacenar con el bloqueo
|
||||
* @return array|false Retorna el ID del bloqueo si se adquiere, false si ya existe un bloqueo activo
|
||||
*/
|
||||
public function acquireLock($command, $chatId, $type = 'command', $data = []) {
|
||||
$this->log("Intentando adquirir bloqueo", [
|
||||
'command' => $command,
|
||||
'chatId' => $chatId,
|
||||
'type' => $type,
|
||||
'data' => $data
|
||||
]);
|
||||
|
||||
$this->cleanupExpiredLocks();
|
||||
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
// Buscar cualquier bloqueo existente para este chat y comando (sin filtrar por estado)
|
||||
$query = "
|
||||
SELECT id, status, expires_at, data, created_at
|
||||
FROM command_locks
|
||||
WHERE command = ?
|
||||
AND chat_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1 FOR UPDATE
|
||||
";
|
||||
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
|
||||
$this->log("Ejecutando consulta de bloqueo existente", [
|
||||
'query' => $stmt->queryString,
|
||||
'params' => [$command, $chatId, $type]
|
||||
]);
|
||||
|
||||
$stmt->execute([$command, $chatId]);
|
||||
$existingLock = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($existingLock) {
|
||||
$this->log("Bloqueo existente encontrado", $existingLock);
|
||||
|
||||
// Si hay un bloqueo activo y reciente, bloquear nueva ejecución
|
||||
if ($existingLock['status'] === 'processing' && strtotime($existingLock['created_at']) > strtotime('-5 minutes')) {
|
||||
$this->log("Bloqueo ya en proceso, rechazando nueva solicitud");
|
||||
$this->pdo->rollBack();
|
||||
return false; // Ya hay un bloqueo activo
|
||||
}
|
||||
|
||||
// Reutilizar el mismo registro para evitar violar la única (chat_id,command)
|
||||
$expiresAt = (new DateTime('+5 minutes'))->format('Y-m-d H:i:s');
|
||||
$dataJson = !empty($data) ? json_encode($data) : null;
|
||||
$upd = $this->pdo->prepare("UPDATE command_locks SET type = ?, status='processing', data = COALESCE(?, data), message_id = NULL, expires_at = ?, updated_at = NOW() WHERE id = ?");
|
||||
$upd->execute([$type, $dataJson, $expiresAt, $existingLock['id']]);
|
||||
$this->pdo->commit();
|
||||
$this->log("Bloqueo reutilizado y pasado a processing", ['lockId' => $existingLock['id']]);
|
||||
return (int)$existingLock['id'];
|
||||
} else {
|
||||
$this->log("No se encontraron bloqueos existentes");
|
||||
}
|
||||
|
||||
// Insertar un nuevo bloqueo
|
||||
$expiresAt = (new DateTime('+5 minutes'))->format('Y-m-d H:i:s');
|
||||
$dataJson = !empty($data) ? json_encode($data) : null;
|
||||
|
||||
$query = "
|
||||
INSERT INTO command_locks
|
||||
(chat_id, command, type, data, status, expires_at, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, 'processing', ?, NOW(), NOW())
|
||||
";
|
||||
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
|
||||
$this->log("Insertando nuevo bloqueo", [
|
||||
'chat_id' => $chatId,
|
||||
'command' => $command,
|
||||
'type' => $type,
|
||||
'has_data' => !empty($data),
|
||||
'expires_at' => $expiresAt
|
||||
]);
|
||||
|
||||
$stmt->execute([$chatId, $command, $type, $dataJson, $expiresAt]);
|
||||
|
||||
$lockId = $this->pdo->lastInsertId();
|
||||
$this->pdo->commit();
|
||||
|
||||
$this->log("Bloqueo adquirido exitosamente", ['lockId' => $lockId]);
|
||||
|
||||
return (int)$lockId;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->log("Error al adquirir bloqueo", [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
try {
|
||||
if ($this->pdo->inTransaction()) {
|
||||
$this->pdo->rollBack();
|
||||
}
|
||||
} catch (Exception $rollbackException) {
|
||||
$this->log("Error al hacer rollback", [
|
||||
'error' => $rollbackException->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza el estado de un bloqueo
|
||||
*
|
||||
* @param int $lockId ID del bloqueo a actualizar
|
||||
* @param string $status Nuevo estado ('processing', 'completed', 'failed')
|
||||
* @param string|null $messageId ID del mensaje asociado (opcional)
|
||||
* @return bool True si se actualizó correctamente, false en caso contrario
|
||||
*/
|
||||
public function updateLockStatus($lockId, $status, $messageId = null) {
|
||||
$this->log("Actualizando estado de bloqueo", [
|
||||
'lockId' => $lockId,
|
||||
'status' => $status,
|
||||
'messageId' => $messageId
|
||||
]);
|
||||
|
||||
try {
|
||||
// Asegurarse de que tenemos una conexión válida
|
||||
$pdo = $this->getConnection();
|
||||
|
||||
$query = "
|
||||
UPDATE command_locks
|
||||
SET status = ?,
|
||||
message_id = COALESCE(?, message_id),
|
||||
expires_at = CASE WHEN ? = 'completed' THEN DATE_ADD(NOW(), INTERVAL 1 MINUTE) ELSE expires_at END,
|
||||
updated_at = NOW()
|
||||
WHERE id = ?
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($query);
|
||||
$result = $stmt->execute([$status, $messageId, $status, $lockId]);
|
||||
$rowCount = $stmt->rowCount();
|
||||
|
||||
$this->log("Resultado de actualización de estado", [
|
||||
'lockId' => $lockId,
|
||||
'status' => $status,
|
||||
'rowCount' => $rowCount,
|
||||
'result' => $result
|
||||
]);
|
||||
|
||||
// Si no se actualizó ninguna fila, verificar si el bloqueo existe
|
||||
if ($rowCount === 0) {
|
||||
$checkStmt = $pdo->prepare("SELECT id FROM command_locks WHERE id = ?");
|
||||
$checkStmt->execute([$lockId]);
|
||||
$exists = $checkStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$this->log("Verificación de existencia de bloqueo", [
|
||||
'lockId' => $lockId,
|
||||
'exists' => (bool)$exists
|
||||
]);
|
||||
|
||||
if (!$exists) {
|
||||
$this->log("Error: El bloqueo no existe", ['lockId' => $lockId]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $result && $rowCount > 0;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->log("Error al actualizar estado de bloqueo", [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Libera un bloqueo marcándolo como completado
|
||||
*
|
||||
* @param int $lockId ID del bloqueo a liberar
|
||||
* @param string|null $messageId ID del mensaje asociado (opcional)
|
||||
* @return bool True si se actualizó correctamente, false en caso contrario
|
||||
*/
|
||||
public function releaseLock($lockId, $messageId = null) {
|
||||
$this->log("Liberando bloqueo", [
|
||||
'lockId' => $lockId,
|
||||
'messageId' => $messageId
|
||||
]);
|
||||
|
||||
$result = $this->updateLockStatus($lockId, 'completed', $messageId);
|
||||
|
||||
$this->log("Resultado de liberación de bloqueo", [
|
||||
'lockId' => $lockId,
|
||||
'success' => $result
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marca un bloqueo como fallido
|
||||
*
|
||||
* @param int $lockId ID del bloqueo a marcar como fallido
|
||||
* @param string $errorMessage Mensaje de error (opcional)
|
||||
* @return bool True si se actualizó correctamente, false en caso contrario
|
||||
*/
|
||||
public function failLock($lockId, $errorMessage = '') {
|
||||
$this->log("Marcando bloqueo como fallido", [
|
||||
'lockId' => $lockId,
|
||||
'errorMessage' => $errorMessage
|
||||
]);
|
||||
|
||||
try {
|
||||
// Asegurarse de que tenemos una conexión válida
|
||||
$pdo = $this->getConnection();
|
||||
|
||||
$query = "
|
||||
UPDATE command_locks
|
||||
SET status = 'failed',
|
||||
data = JSON_MERGE_PATCH(COALESCE(data, '{}'), ?),
|
||||
expires_at = DATE_ADD(NOW(), INTERVAL 5 MINUTE),
|
||||
updated_at = NOW()
|
||||
WHERE id = ?
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($query);
|
||||
$result = $stmt->execute([json_encode(['error' => $errorMessage, 'failed_at' => date('Y-m-d H:i:s')]), $lockId]);
|
||||
$rowCount = $stmt->rowCount();
|
||||
|
||||
$this->log("Resultado de marcar bloqueo como fallido", [
|
||||
'lockId' => $lockId,
|
||||
'rowCount' => $rowCount,
|
||||
'result' => $result
|
||||
]);
|
||||
|
||||
return $result && $rowCount > 0;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->log("Error al marcar bloqueo como fallido", [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia los bloqueos expirados
|
||||
*/
|
||||
private function cleanupExpiredLocks() {
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
DELETE FROM command_locks
|
||||
WHERE (status = 'completed' AND expires_at <= NOW())
|
||||
OR (status = 'processing' AND created_at < DATE_SUB(NOW(), INTERVAL 1 HOUR))
|
||||
");
|
||||
} catch (Exception $e) {
|
||||
error_log("Error al limpiar bloqueos expirados: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si un comando está siendo procesado actualmente
|
||||
*
|
||||
* @param string $command Comando a verificar
|
||||
* @param int $chatId ID del chat
|
||||
* @param string $type Tipo de bloqueo ('command' o 'translation')
|
||||
* @return bool True si el comando está siendo procesado, false en caso contrario
|
||||
*/
|
||||
public function isCommandProcessing($command, $chatId, $type = 'command') {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM command_locks
|
||||
WHERE command = ?
|
||||
AND chat_id = ?
|
||||
AND type = ?
|
||||
AND status = 'processing'
|
||||
AND created_at > DATE_SUB(NOW(), INTERVAL 5 MINUTE)
|
||||
");
|
||||
$stmt->execute([$command, $chatId, $type]);
|
||||
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user