✨ Features: - Migrated ALL files to new S3 structure (Projects, Contacts, Accounts, HelpDesk, Invoice, etc.) - Added Nextcloud folder buttons to ALL modules - Fixed Nextcloud editor integration - WebSocket server for real-time updates - Redis Pub/Sub integration - File path manager for organized storage - Redis caching for performance (Functions.php) 📁 New Structure: Documents/Project/ProjectName_ID/file_docID.ext Documents/Contacts/FirstName_LastName_ID/file_docID.ext Documents/Accounts/AccountName_ID/file_docID.ext 🔧 Technical: - FilePathManager for standardized paths - S3StorageService integration - WebSocket server (Node.js + Docker) - Redis cache for getBasicModuleInfo() - Predis library for Redis connectivity 📝 Scripts: - Migration scripts for all modules - Test pages for WebSocket/SSE/Polling - Documentation (MIGRATION_*.md, REDIS_*.md) 🎯 Result: 15,000+ files migrated successfully!
256 lines
7.5 KiB
PHP
256 lines
7.5 KiB
PHP
<?php
|
||
/**
|
||
* Redis Cache для ускорения CRM
|
||
*
|
||
* Кеширует:
|
||
* - Метаданные модулей (табиды, поля)
|
||
* - Права доступа пользователей
|
||
* - Списки picklist значений
|
||
* - Настройки модулей
|
||
*/
|
||
|
||
class RedisCache {
|
||
private $redis;
|
||
private $enabled = false;
|
||
private $prefix = 'crm:cache:';
|
||
private $defaultTTL = 3600; // 1 час
|
||
|
||
public function __construct() {
|
||
try {
|
||
if (class_exists('Redis')) {
|
||
// Используем расширение Redis
|
||
$this->redis = new Redis();
|
||
$this->redis->connect('127.0.0.1', 6379);
|
||
$this->redis->auth('CRM_Redis_Pass_2025_Secure!');
|
||
$this->enabled = true;
|
||
} else {
|
||
// Используем Predis
|
||
require_once __DIR__ . '/../vendor/autoload.php';
|
||
$this->redis = new Predis\Client([
|
||
'scheme' => 'tcp',
|
||
'host' => '127.0.0.1',
|
||
'port' => 6379,
|
||
'password' => 'CRM_Redis_Pass_2025_Secure!',
|
||
]);
|
||
$this->enabled = true;
|
||
}
|
||
} catch (Exception $e) {
|
||
error_log("Redis cache disabled: " . $e->getMessage());
|
||
$this->enabled = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Получить значение из кеша
|
||
*/
|
||
public function get($key) {
|
||
if (!$this->enabled) {
|
||
return null;
|
||
}
|
||
|
||
try {
|
||
$value = $this->redis->get($this->prefix . $key);
|
||
if ($value === false || $value === null) {
|
||
return null;
|
||
}
|
||
return json_decode($value, true);
|
||
} catch (Exception $e) {
|
||
error_log("Redis get error: " . $e->getMessage());
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Сохранить значение в кеш
|
||
*/
|
||
public function set($key, $value, $ttl = null) {
|
||
if (!$this->enabled) {
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
$ttl = $ttl ?? $this->defaultTTL;
|
||
$this->redis->setex(
|
||
$this->prefix . $key,
|
||
$ttl,
|
||
json_encode($value)
|
||
);
|
||
return true;
|
||
} catch (Exception $e) {
|
||
error_log("Redis set error: " . $e->getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Удалить значение из кеша
|
||
*/
|
||
public function delete($key) {
|
||
if (!$this->enabled) {
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
$this->redis->del($this->prefix . $key);
|
||
return true;
|
||
} catch (Exception $e) {
|
||
error_log("Redis delete error: " . $e->getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Очистить весь кеш
|
||
*/
|
||
public function flush() {
|
||
if (!$this->enabled) {
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
// Удаляем все ключи с нашим префиксом
|
||
$keys = $this->redis->keys($this->prefix . '*');
|
||
if (!empty($keys)) {
|
||
$this->redis->del($keys);
|
||
}
|
||
return true;
|
||
} catch (Exception $e) {
|
||
error_log("Redis flush error: " . $e->getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Получить или установить значение (если не существует)
|
||
*/
|
||
public function remember($key, $callback, $ttl = null) {
|
||
$value = $this->get($key);
|
||
|
||
if ($value !== null) {
|
||
return $value;
|
||
}
|
||
|
||
// Вызываем callback для получения значения
|
||
$value = $callback();
|
||
$this->set($key, $value, $ttl);
|
||
|
||
return $value;
|
||
}
|
||
|
||
/**
|
||
* Кешировать результат SQL запроса
|
||
*/
|
||
public function cacheQuery($key, $query, $params = [], $ttl = null) {
|
||
return $this->remember($key, function() use ($query, $params) {
|
||
global $adb;
|
||
$result = $adb->pquery($query, $params);
|
||
|
||
$data = [];
|
||
while ($row = $adb->fetch_array($result)) {
|
||
$data[] = $row;
|
||
}
|
||
|
||
return $data;
|
||
}, $ttl);
|
||
}
|
||
|
||
/**
|
||
* Кешировать tabid модуля
|
||
*/
|
||
public function getTabId($moduleName) {
|
||
return $this->remember("tabid:{$moduleName}", function() use ($moduleName) {
|
||
global $adb;
|
||
$result = $adb->pquery("SELECT tabid FROM vtiger_tab WHERE name=?", [$moduleName]);
|
||
return $adb->query_result($result, 0, 'tabid');
|
||
}, 86400); // 24 часа
|
||
}
|
||
|
||
/**
|
||
* Кешировать поля модуля
|
||
*/
|
||
public function getModuleFields($moduleName) {
|
||
return $this->remember("fields:{$moduleName}", function() use ($moduleName) {
|
||
global $adb;
|
||
$tabid = getTabid($moduleName);
|
||
|
||
$query = "SELECT fieldname, fieldlabel, uitype, columnname, tablename, typeofdata
|
||
FROM vtiger_field
|
||
WHERE tabid=? AND presence IN (0,2)
|
||
ORDER BY sequence";
|
||
|
||
$result = $adb->pquery($query, [$tabid]);
|
||
|
||
$fields = [];
|
||
while ($row = $adb->fetch_array($result)) {
|
||
$fields[] = $row;
|
||
}
|
||
|
||
return $fields;
|
||
}, 3600); // 1 час
|
||
}
|
||
|
||
/**
|
||
* Кешировать picklist значения
|
||
*/
|
||
public function getPicklistValues($fieldName) {
|
||
return $this->remember("picklist:{$fieldName}", function() use ($fieldName) {
|
||
global $adb;
|
||
|
||
$query = "SELECT DISTINCT vtiger_$fieldName.*
|
||
FROM vtiger_$fieldName
|
||
ORDER BY sortorderid";
|
||
|
||
$result = $adb->query($query);
|
||
|
||
$values = [];
|
||
while ($row = $adb->fetch_array($result)) {
|
||
$values[] = $row;
|
||
}
|
||
|
||
return $values;
|
||
}, 3600); // 1 час
|
||
}
|
||
|
||
/**
|
||
* Кешировать права доступа пользователя
|
||
*/
|
||
public function getUserPrivileges($userId) {
|
||
return $this->remember("privileges:user:{$userId}", function() use ($userId) {
|
||
require_once('include/utils/UserInfoUtil.php');
|
||
$privileges = getAllUserPrivileges($userId);
|
||
return $privileges;
|
||
}, 1800); // 30 минут
|
||
}
|
||
|
||
/**
|
||
* Проверить включен ли кеш
|
||
*/
|
||
public function isEnabled() {
|
||
return $this->enabled;
|
||
}
|
||
|
||
/**
|
||
* Получить статистику кеша
|
||
*/
|
||
public function getStats() {
|
||
if (!$this->enabled) {
|
||
return ['enabled' => false];
|
||
}
|
||
|
||
try {
|
||
$info = $this->redis->info();
|
||
return [
|
||
'enabled' => true,
|
||
'keys' => $this->redis->dbsize(),
|
||
'memory' => $info['used_memory_human'] ?? 'unknown',
|
||
'hits' => $info['keyspace_hits'] ?? 0,
|
||
'misses' => $info['keyspace_misses'] ?? 0,
|
||
];
|
||
} catch (Exception $e) {
|
||
return ['enabled' => false, 'error' => $e->getMessage()];
|
||
}
|
||
}
|
||
}
|
||
|
||
|