162 lines
6.0 KiB
PHP
162 lines
6.0 KiB
PHP
<?php
|
||
/**
|
||
* Класс для логирования событий в BI Analytics (outbox)
|
||
*
|
||
* Используется для отправки событий из JoyWork в ClickHouse
|
||
* через таблицу bi_analytics_events_outbox
|
||
*/
|
||
class BiAnalyticsLogger {
|
||
|
||
/**
|
||
* @var MysqlPdo Подключение к базе данных
|
||
*/
|
||
private $db;
|
||
|
||
/**
|
||
* @var int ID компании (agency_id)
|
||
*/
|
||
private $companyId;
|
||
|
||
/**
|
||
* Конструктор
|
||
*
|
||
* @param MysqlPdo $db Подключение к БД
|
||
* @param int $companyId ID компании
|
||
*/
|
||
public function __construct($db, $companyId) {
|
||
$this->db = $db;
|
||
$this->companyId = (int)$companyId;
|
||
}
|
||
|
||
/**
|
||
* Создать событие в outbox
|
||
*
|
||
* @param string $entityType Тип сущности: 'client' или 'requisition'
|
||
* @param int $entityId ID сущности
|
||
* @param string $eventType Тип события
|
||
* @param int $actorUserId ID пользователя
|
||
* @param array $before Данные до изменения
|
||
* @param array $after Данные после изменения
|
||
*
|
||
* @return string UUID созданного события
|
||
* @throws Exception
|
||
*/
|
||
public function logEvent($entityType, $entityId, $eventType, $actorUserId, $before = array(), $after = array()) {
|
||
|
||
// Валидация
|
||
if (!in_array($entityType, array('client', 'requisition'))) {
|
||
throw new Exception("Invalid entity type: " . $entityType);
|
||
}
|
||
|
||
$entityId = (int)$entityId;
|
||
$actorUserId = (int)$actorUserId;
|
||
|
||
// Определяем изменённые поля
|
||
$changedFields = array_keys(array_diff_assoc($after, $before));
|
||
|
||
// Формируем payload
|
||
$payload = array(
|
||
'schema_version' => 1,
|
||
'entity' => array(
|
||
'type' => $entityType,
|
||
'id' => $entityId,
|
||
'company_id' => $this->companyId
|
||
),
|
||
'occurred_at' => date('c'),
|
||
'actor_user_id' => $actorUserId,
|
||
'event' => array(
|
||
'type' => $eventType,
|
||
'changed_fields' => $changedFields
|
||
),
|
||
'before' => $before,
|
||
'after' => $after
|
||
);
|
||
|
||
// Генерируем UUID v4
|
||
$eventUuid = $this->generateUuid();
|
||
|
||
// Вставляем в outbox с prepared statement
|
||
$payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||
|
||
// Экранируем строковые значения для безопасной вставки
|
||
$escapedEventUuid = $this->db->quote($eventUuid);
|
||
$escapedCompanyId = (int)$this->companyId; // целое число не требует экранирования
|
||
$escapedEntityType = $this->db->quote($entityType);
|
||
$escapedEntityId = (int)$entityId;
|
||
$escapedEventType = $this->db->quote($eventType);
|
||
$escapedActorUserId = (int)$actorUserId;
|
||
$escapedPayloadJson = $this->db->quote($payloadJson);
|
||
|
||
$sql = "INSERT INTO bi_analytics_events_outbox
|
||
(event_uuid, company_id, entity_type, entity_id, event_type, occurred_at, actor_user_id, payload_json)
|
||
VALUES ($escapedEventUuid, $escapedCompanyId, $escapedEntityType, $escapedEntityId, $escapedEventType, NOW(3), $escapedActorUserId, $escapedPayloadJson)";
|
||
|
||
try {
|
||
$this->db->query($sql);
|
||
return $eventUuid;
|
||
} catch (Exception $e) {
|
||
throw new Exception("Failed to insert event: " . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Создать событие: клиент создан
|
||
*/
|
||
public function logClientCreated($clientId, $actorUserId, $clientData) {
|
||
return $this->logEvent('client', $clientId, 'client.created', $actorUserId, array(), $clientData);
|
||
}
|
||
|
||
/**
|
||
* Создать событие: клиент обновлён
|
||
*/
|
||
public function logClientUpdated($clientId, $actorUserId, $before, $after) {
|
||
return $this->logEvent('client', $clientId, 'client.updated', $actorUserId, $before, $after);
|
||
}
|
||
|
||
/**
|
||
* Создать событие: заявка создана
|
||
*/
|
||
public function logRequisitionCreated($requisitionId, $actorUserId, $requisitionData) {
|
||
return $this->logEvent('requisition', $requisitionId, 'requisition.created', $actorUserId, array(), $requisitionData);
|
||
}
|
||
|
||
/**
|
||
* Создать событие: заявка обновлена
|
||
*/
|
||
public function logRequisitionUpdated($requisitionId, $actorUserId, $before, $after) {
|
||
return $this->logEvent('requisition', $requisitionId, 'requisition.updated', $actorUserId, $before, $after);
|
||
}
|
||
|
||
/**
|
||
* Создать событие: заявка перешла на этап
|
||
*/
|
||
public function logRequisitionMovedToStep($requisitionId, $actorUserId, $fromStepId, $toStepId) {
|
||
$before = array('step_id' => $fromStepId);
|
||
$after = array('step_id' => $toStepId);
|
||
return $this->logEvent('requisition', $requisitionId, 'requisition.moved_to_step', $actorUserId, $before, $after);
|
||
}
|
||
|
||
/**
|
||
* Создать событие: смена ответственного
|
||
*/
|
||
public function logResponsibleChanged($entityType, $entityId, $actorUserId, $fromUserId, $toUserId) {
|
||
$before = array('who_work' => $fromUserId);
|
||
$after = array('who_work' => $toUserId);
|
||
return $this->logEvent($entityType, $entityId, 'responsible_changed', $actorUserId, $before, $after);
|
||
}
|
||
|
||
/**
|
||
* Сгенерировать UUID v4
|
||
*
|
||
* @return string
|
||
*/
|
||
private function generateUuid() {
|
||
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||
mt_rand(0, 0xffff),
|
||
mt_rand(0, 0xffff) & 0x0fff | 0x4000,
|
||
mt_rand(0, 0x3fff) | 0x8000,
|
||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||
);
|
||
}
|
||
} |