Files
cunkebao_v3/Moncter/app/utils/RedisHelper.php
2026-01-05 10:16:20 +08:00

268 lines
7.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\utils;
use Predis\Client;
use app\utils\LoggerHelper;
/**
* Redis 工具类
*
* 职责:
* - 封装 Redis 连接和基础操作
* - 提供分布式锁功能
*/
class RedisHelper
{
private static ?Client $client = null;
private static array $config = [];
/**
* 获取 Redis 客户端(单例模式)
*
* @return Client Redis 客户端
*/
public static function getClient(): Client
{
if (self::$client !== null) {
return self::$client;
}
// 从 session 配置中读取 Redis 配置(临时方案,后续可创建独立的 cache.php
$sessionConfig = config('session.config.redis', []);
self::$config = [
'host' => $sessionConfig['host'] ?? getenv('REDIS_HOST') ?: '127.0.0.1',
'port' => (int)($sessionConfig['port'] ?? getenv('REDIS_PORT') ?: 6379),
'password' => $sessionConfig['auth'] ?? getenv('REDIS_PASSWORD') ?: null,
'database' => (int)($sessionConfig['database'] ?? getenv('REDIS_DATABASE') ?: 0),
'timeout' => $sessionConfig['timeout'] ?? 2.0,
];
$parameters = [
'host' => self::$config['host'],
'port' => self::$config['port'],
];
if (!empty(self::$config['password'])) {
$parameters['password'] = self::$config['password'];
}
if (self::$config['database'] > 0) {
$parameters['database'] = self::$config['database'];
}
$options = [
'timeout' => self::$config['timeout'],
];
self::$client = new Client($parameters, $options);
// 测试连接
try {
self::$client->ping();
LoggerHelper::logBusiness('redis_connected', [
'host' => self::$config['host'],
'port' => self::$config['port'],
]);
} catch (\Throwable $e) {
LoggerHelper::logError($e, [
'component' => 'RedisHelper',
'action' => 'getClient',
]);
throw $e;
}
return self::$client;
}
/**
* 获取分布式锁
*
* @param string $key 锁的键
* @param int $ttl 锁的过期时间(秒)
* @param int $retryTimes 重试次数
* @param int $retryDelay 重试延迟(毫秒)
* @return bool 是否获取成功
*/
public static function acquireLock(string $key, int $ttl = 300, int $retryTimes = 3, int $retryDelay = 1000): bool
{
$client = self::getClient();
$lockKey = "lock:{$key}";
$lockValue = uniqid(gethostname() . '_', true); // 唯一值,用于安全释放锁
for ($i = 0; $i <= $retryTimes; $i++) {
// 尝试获取锁SET key value NX EX ttl
$result = $client->set($lockKey, $lockValue, 'EX', $ttl, 'NX');
if ($result) {
LoggerHelper::logBusiness('redis_lock_acquired', [
'key' => $key,
'ttl' => $ttl,
]);
return true;
}
// 如果还有重试机会,等待后重试
if ($i < $retryTimes) {
usleep($retryDelay * 1000); // 转换为微秒
}
}
LoggerHelper::logBusiness('redis_lock_failed', [
'key' => $key,
'retry_times' => $retryTimes,
]);
return false;
}
/**
* 释放分布式锁
*
* @param string $key 锁的键
* @return bool 是否释放成功
*/
public static function releaseLock(string $key): bool
{
$client = self::getClient();
$lockKey = "lock:{$key}";
try {
$result = $client->del([$lockKey]);
if ($result > 0) {
LoggerHelper::logBusiness('redis_lock_released', [
'key' => $key,
]);
return true;
}
return false;
} catch (\Throwable $e) {
LoggerHelper::logError($e, [
'component' => 'RedisHelper',
'action' => 'releaseLock',
'key' => $key,
]);
return false;
}
}
/**
* 设置键值对
*
* @param string $key 键
* @param mixed $value 值
* @param int|null $ttl 过期时间null 表示不过期
* @return bool 是否设置成功
*/
public static function set(string $key, $value, ?int $ttl = null): bool
{
try {
$client = self::getClient();
$serialized = is_string($value) ? $value : json_encode($value, JSON_UNESCAPED_UNICODE);
if ($ttl !== null) {
$client->setex($key, $ttl, $serialized);
} else {
$client->set($key, $serialized);
}
return true;
} catch (\Throwable $e) {
LoggerHelper::logError($e, [
'component' => 'RedisHelper',
'action' => 'set',
'key' => $key,
]);
return false;
}
}
/**
* 获取键值
*
* @param string $key 键
* @return mixed 值,不存在返回 null
*/
public static function get(string $key)
{
try {
$client = self::getClient();
$value = $client->get($key);
if ($value === null) {
return null;
}
// 尝试 JSON 解码
$decoded = json_decode($value, true);
return $decoded !== null ? $decoded : $value;
} catch (\Throwable $e) {
LoggerHelper::logError($e, [
'component' => 'RedisHelper',
'action' => 'get',
'key' => $key,
]);
return null;
}
}
/**
* 删除键
*
* @param string $key 键
* @return bool 是否删除成功
*/
public static function delete(string $key): bool
{
try {
$client = self::getClient();
$result = $client->del([$key]);
return $result > 0;
} catch (\Throwable $e) {
LoggerHelper::logError($e, [
'component' => 'RedisHelper',
'action' => 'delete',
'key' => $key,
]);
return false;
}
}
/**
* 检查键是否存在
*
* @param string $key 键
* @return bool 是否存在
*/
public static function exists(string $key): bool
{
try {
$client = self::getClient();
$result = $client->exists($key);
return $result > 0;
} catch (\Throwable $e) {
LoggerHelper::logError($e, [
'component' => 'RedisHelper',
'action' => 'exists',
'key' => $key,
]);
return false;
}
}
/**
* 删除键别名兼容del方法
*
* @param string $key 键
* @return bool 是否删除成功
*/
public static function del(string $key): bool
{
return self::delete($key);
}
}