mm_redis
Version:
高效的Redis客户端封装库,提供连接池管理、数据类型操作、错误处理和批量操作优化
2,166 lines (1,993 loc) • 111 kB
JavaScript
/**
* @fileOverview redis缓存帮助类函数
* @author <a href="http://qww.elins.cn">邱文武</a>
* @version 1.5.0
*/
const {
BaseService
} = require('mm_base_service');
const {
createClient
} = require('redis');
// Redis缓存帮助类函数
/* 创建Redis帮助类函数 */
/// 构造函数
class Redis extends BaseService {
/**
* @description 创建Redis帮助类函数 (构造函数)
* @param {Object} config 配置参数
* @constructor
*/
constructor(config) {
super();
// 配置参数
this.config = {
// 服务器地址
"host": "localhost",
// 端口号
"port": 6379,
// 密码
"password": "asd159357",
// 数据库
"database": 0,
// 前缀
"prefix": "",
// 连接超时时间(ms)
"connect_timeout": 10000,
// 重试次数
"retry_count": 3,
// 重试间隔(ms)
"retry_delay": 1000,
// 连接池配置
"pool": {
"max_clients": 100, // 最大连接数
"min_idle": 5, // 最小空闲连接数
"max_idle": 20, // 最大空闲连接数
"idle_timeout": 30000 // 空闲超时时间(ms)
}
};
this.setConfig(config);
// 初始化连接池
this._initPool();
// 唯一标识符
this.identifier = this.config.host + "/" + this.config.database;
// 数据库连接器
this.conn = null;
// 连接状态
this.connected = false;
// 重试次数计数器
this.retry_count = 0;
// 订阅回调函数
this.func_list = {};
}
}
/**
* 应用键前缀
* @param {String} key 键
* @return {String} 处理后的键
*/
Redis.prototype.applyPrefix = function(key) {
return this.config.prefix ? this.config.prefix + key : key;
};
/**
* 移除键前缀
* @param {String} key 带前缀的键
* @return {String} 处理后的键
*/
Redis.prototype.removePrefix = function(key) {
if (this.config.prefix && key.startsWith(this.config.prefix)) {
return key.slice(this.config.prefix.length);
}
return key;
};
/**
* 消息订阅
* @param {String} channel 订阅的频道
* @param {Function} func 操作回调函数,可判断成功与否
* @return {Promise<Boolean>} 订阅结果
*/
Redis.prototype.subscribe = async function(channel, func) {
try {
await this.conn.subscribe(this.applyPrefix(channel));
this.func_list[channel] = func;
return true;
} catch (err) {
$.log.error(`[Redis] 订阅频道 ${channel} 失败:`, err);
return false;
}
};
/**
* 取消订阅
* @param {String} channel 订阅的频道
* @return {Promise<Boolean>} 取消订阅结果
*/
Redis.prototype.unsubscribe = async function(channel) {
try {
await this.conn.unsubscribe(this.applyPrefix(channel));
delete this.func_list[channel];
return true;
} catch (err) {
$.log.error(`[Redis] 取消订阅频道 ${channel} 失败:`, err);
return false;
}
};
/**
* 发布消息
* @param {String} channel 频道
* @param {String} message 订阅的消息
* @return {Promise<Number>} 接收消息的客户端数量
*/
Redis.prototype.publish = async function(channel, message) {
try {
return await this.conn.publish(this.applyPrefix(channel), message);
} catch (err) {
$.log.error(`[Redis] 发布消息到频道 ${channel} 失败:`, err);
$.log.error('[Redis] 操作失败:', err);
return false;
}
};
/**
* 订阅消息处理, 如果没有订阅函数,则其他的订阅消息会进入该函数
* @param {String} channel
* @param {String} message
*/
Redis.prototype.main = async function(channel, message) {
// 默认实现,可由子类重写
$.log.debug(`[Redis] 收到未处理的消息 [${channel}]:`, message);
};
/**
* 消息通知
* @param {String} channel 频道
* @param {String} message 消息
*/
Redis.prototype.message = function(channel, message) {
// 移除前缀
channel = this.removePrefix(channel);
var func = this.func_list[channel];
if (func) {
try {
func(message);
} catch (err) {
$.log.error(`[Redis] 处理频道 ${channel} 的消息时出错:`, err);
}
} else {
this.main(channel, message);
}
};
/**
* @description 设置配置
* @param {Object} config 配置对象或配置路径
*/
Redis.prototype.setConfig = function(config) {
let obj = {};
if (typeof config === 'string') {
obj = config.loadJson(__dirname);
} else if (config) {
// 合并配置参数
if (config.pool) {
// 确保连接池配置的合并不会覆盖所有默认值
if (!this.config.pool) {
this.config.pool = {};
}
Object.assign(this.config.pool, config.pool);
// 删除config中的pool属性,避免后续Object.assign重复处理
const configCopy = {
...config
};
delete configCopy.pool;
Object.assign(this.config, configCopy);
} else {
Object.assign(this.config, config);
}
} else {
return;
}
// 确保连接池配置存在且包含所有必要字段
if (!this.config.pool) {
this.config.pool = {};
}
// 设置默认连接池配置,只覆盖未定义的字段
const defaultPoolConfig = {
max_clients: 100,
min_idle: 5,
max_idle: 20,
idle_timeout: 30000
};
for (const key in defaultPoolConfig) {
if (this.config.pool[key] === undefined) {
this.config.pool[key] = defaultPoolConfig[key];
}
}
// 如果连接池已经初始化,更新其配置
if (this.connection_pool) {
this.connection_pool.config = this.config.pool;
}
// 初始化连接池
this._initPool();
$.log.debug('[Redis] 配置已更新');
this.identifier = this.config.host + "/" + this.config.database;
return this;
};
// 添加连接池初始化方法
Redis.prototype._initPool = function() {
// 确保连接池配置存在
if (!this.config.pool) {
this.config.pool = {
max_clients: 100,
min_idle: 5,
max_idle: 20,
idle_timeout: 30000
};
}
// 初始化连接池相关属性
this.connection_pool = {
available: [],
in_use: new Set(),
config: this.config.pool // 直接引用配置对象,确保同步更新
};
// 记录连接池初始化
// $.log.debug('[Redis] 连接池已初始化,配置:', this.config.pool);
};
/**
* @description 连接Redis数据库
* @return {Promise<Boolean>} 连接结果
*/
Redis.prototype.open = async function() {
if (this.conn && this.connected) {
return true;
}
try {
const cg = this.config;
this.retry_count = 0;
// 创建客户端
this.conn = createClient({
socket: {
host: cg.host,
port: cg.port,
timeout: cg.connect_timeout
},
password: cg.password,
database: cg.database
});
// 监听错误事件
this.conn.on('error', (err) => {
$.log.error('[Redis] Redis连接错误:', err);
this.connected = false;
// 尝试重连
this._reconnect();
});
// 监听连接成功事件
this.conn.on('connect', () => {
// $.log.debug('[Redis] Redis已连接');
this.connected = true;
this.retry_count = 0;
});
// 监听断开连接事件
this.conn.on('end', () => {
// $.log.debug('[Redis] Redis连接已断开');
this.connected = false;
});
// 监听订阅成功事件
this.conn.on('subscribe', (channel, count) => {
$.log.debug(`[Redis] 频道${this.removePrefix(channel)}订阅成功, 当前总计订阅数:${count}`);
});
// 监听取消订阅事件
this.conn.on('unsubscribe', (channel, count) => {
$.log.debug(`[Redis] 频道${this.removePrefix(channel)}取消订阅, 当前总计订阅数:${count}`);
});
// 收到消息后执行回调
this.conn.on('message', (channel, message) => {
this.message(channel, message);
});
// 连接Redis
await this.conn.connect();
this.connected = true;
return true;
} catch (err) {
$.log.error('[Redis] Redis连接失败:', err);
// 尝试重连
this._reconnect();
return false;
}
};
/**
* 内部重连方法
* @private
*/
Redis.prototype._reconnect = function() {
if (this.retry_count >= this.config.retry_count) {
$.log.error(`[Redis] 达到最大重连次数 ${this.config.retry_count},停止重连`);
return;
}
this.retry_count++;
$.log.debug(`[Redis] 尝试第 ${this.retry_count} 次重连...`);
setTimeout(async () => {
try {
await this.open();
} catch (err) {
$.log.error(`[Redis] 重连失败:`, err);
}
}, this.config.retry_delay);
};
/**
* @description 关闭连接
* @return {Promise<Boolean>} 关闭结果
*/
// 修复close方法,正确处理连接池
Redis.prototype.close = function() {
try {
// $.log.debug('[Redis] 关闭所有连接...');
// 关闭所有使用中的连接
if (this.connection_pool && this.connection_pool.in_use) {
for (const connection of this.connection_pool.in_use) {
try {
connection.close();
} catch (e) {
$.log.error('[Redis] 关闭连接失败:', e);
}
}
this.connection_pool.in_use.clear();
}
// 关闭所有可用连接
if (this.connection_pool && this.connection_pool.available) {
for (const connection of this.connection_pool.available) {
try {
// 检查连接对象是否有close方法
if (typeof connection.close === 'function') {
connection.close();
} else if (typeof connection.end === 'function') {
// 某些Redis客户端可能使用end方法
connection.end();
} else {
$.log.debug('[Redis] 连接对象没有可用的关闭方法');
}
} catch (e) {
$.log.error('[Redis] 关闭连接失败:', e);
}
}
this.connection_pool.available = [];
}
this.connected = false;
$.log.debug('[Redis] Redis连接已断开');
return Promise.resolve(true);
} catch (error) {
$.log.error('[Redis] 关闭连接时发生错误:', error);
return Promise.resolve(false);
}
};
// 添加Pipeline支持
Redis.prototype.pipeline = function() {
const redisInstance = this;
const commands = [];
return {
// 设置值
set: function(key, value, seconds) {
commands.push(['set', key, value, seconds]);
return this;
},
// 获取值
get: function(key) {
commands.push(['get', key]);
return this;
},
// 删除
del: function(key) {
commands.push(['del', key]);
return this;
},
// 判断存在
has: function(key) {
commands.push(['has', key]);
return this;
},
// 设置哈希字段
hset: function(key, field, value) {
commands.push(['hset', key, field, value]);
return this;
},
// 过期时间设置
expire: function(key, seconds) {
commands.push(['expire', key, seconds]);
return this;
},
// 执行所有命令
exec: async function() {
try {
// 使用Redis实例的方法获取连接
const connection = await redisInstance._getConnection();
const results = [];
// 依次执行所有命令
for (const cmd of commands) {
try {
let result;
const [command, ...args] = cmd;
// 根据命令类型直接调用对应的方法
switch (command) {
case 'set': {
// 键前缀处理和值序列化
const [key, value, seconds] = args;
const prefixedKey = redisInstance.applyPrefix(key);
let processedValue = value;
if (typeof value === 'object' && value !== null) {
processedValue = JSON.stringify(value);
}
result = await connection.set(prefixedKey, processedValue);
// 设置过期时间
if (seconds && seconds > 0) {
await connection.expire(prefixedKey, seconds);
}
break;
}
case 'get': {
const [key] = args;
const prefixedKey = redisInstance.applyPrefix(key);
result = await connection.get(prefixedKey);
// 尝试解析JSON
if (typeof result === 'string') {
try {
if (result.startsWith('{') && result.endsWith('}') ||
result.startsWith('[') && result.endsWith(']')) {
result = JSON.parse(result);
}
} catch (e) {
// 解析失败,保持原样
}
}
break;
}
case 'del': {
const [key] = args;
const prefixedKey = redisInstance.applyPrefix(key);
result = await connection.del(prefixedKey);
break;
}
case 'has': {
const [key] = args;
const prefixedKey = redisInstance.applyPrefix(key);
result = await connection.exists(prefixedKey) > 0;
break;
}
case 'hset': {
const [key, field, value] = args;
const prefixedKey = redisInstance.applyPrefix(key);
result = await connection.hSet(prefixedKey, field, value);
break;
}
case 'expire': {
const [key, seconds] = args;
const prefixedKey = redisInstance.applyPrefix(key);
result = await connection.expire(prefixedKey, seconds);
break;
}
default:
// 对于其他命令,直接返回null
result = null;
break;
}
// 直接返回结果值,符合测试期望的格式
results.push(result);
} catch (err) {
$.log.error(`[Redis] Pipeline命令执行失败 [${cmd[0]}]:`, err);
results.push(null);
}
}
// 释放连接回连接池
redisInstance._recycleConnection(connection);
return results;
} catch (error) {
$.log.error('[Redis] Pipeline执行失败:', error);
return [];
}
}
};
};
/**
* @description 检查连接状态
* @return {Boolean} 连接状态
*/
Redis.prototype.isConnected = function() {
return this.connected && this.conn && this.conn.isReady;
};
/**
* @description 查询或是设置缓存过期时间
* @param {String} key 键
* @param {Number} seconds 秒, 为空则查询有效时长
* @return {Promise<Number|Boolean>} 查询时长或设置结果, 时长为-1表示永不过期
*/
/**
* 获取或设置键的过期时间
* @param {String} key 键名
* @param {Number} [seconds] 过期时间(秒),不传此参数则获取剩余过期时间
* @return {Promise<Number|Boolean>} 不传seconds时返回剩余过期时间(-1表示无过期设置,-2表示已过期),传seconds时返回是否设置成功
*/
Redis.prototype.ttl = async function(key, seconds) {
try {
const connection = await this._getConnection();
try {
key = this.applyPrefix(key);
// 如果传入了seconds参数,则设置过期时间
if (seconds !== undefined) {
if (typeof seconds !== 'number' || isNaN(seconds) || seconds < 0) {
return false;
}
// 设置过期时间
if (typeof connection.expire === 'function') {
await connection.expire(key, seconds);
return true;
} else if (typeof connection.execute === 'function') {
await connection.execute('EXPIRE', [key, seconds]);
return true;
} else {
$.log.error('[Redis] 连接对象不支持expire操作');
return false;
}
}
// 否则获取剩余过期时间
if (typeof connection.ttl === 'function') {
return await connection.ttl(key);
} else if (typeof connection.execute === 'function') {
return await connection.execute('TTL', [key]);
} else {
$.log.error('[Redis] 连接对象不支持ttl操作');
return -2; // 默认返回键不存在
}
} finally {
this._recycleConnection(connection);
}
} catch (err) {
$.log.error(`[Redis] ${seconds !== undefined ? '设置' : '获取'}过期时间失败 [${key}]:`, err);
return seconds !== undefined ? false : -2;
}
};
/**
* @description 增加整数值(负数为减)(兼容方法)
* @param {String} key 键
* @param {Number} num 数值
* @param {Number} [seconds=0] 过期时间(秒)
* @return {Promise<Number>} 计算后的结果
*/
Redis.prototype.addInt = async function(key, num, seconds = 0) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
$.log.error('[Redis] 参数错误: 键必须是非空字符串');
return false;
}
if (typeof num !== 'number' || isNaN(num) || !Number.isInteger(num)) {
$.log.error('[Redis] 参数错误: 数值必须是有效整数');
return false;
}
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value: num,
seconds,
cancel: false
};
this.emit('addInt_before', beforeEventData);
// 如果事件处理器标记为取消,则返回false
if (beforeEventData.cancel) {
$.log.debug(`[Redis] addInt操作被事件处理器取消 [${key}]`);
return false;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalNum,
seconds: finalSeconds
} = beforeEventData;
const connection = await this._getConnection();
try {
const prefixedKey = this.applyPrefix(finalKey);
let result;
if (finalNum > 0) {
// 增加整数值
if (typeof connection.incrBy === 'function') {
result = await connection.incrBy(prefixedKey, finalNum);
} else if (typeof connection.execute === 'function') {
result = await connection.execute('INCRBY', [prefixedKey, finalNum]);
} else {
// 如果出错,回退到先获取再设置的方式
$.log.warn(`[Redis] 使用原子操作增加整数值失败,使用回退方案`);
let value = await this.get(prefixedKey);
let n = 0;
if (value) {
n = Number(value);
}
n += finalNum;
await this.set(prefixedKey, n, finalSeconds);
result = n;
}
} else {
// 减少整数值
if (typeof connection.decrBy === 'function') {
result = await connection.decrBy(prefixedKey, -finalNum);
} else if (typeof connection.execute === 'function') {
result = await connection.execute('DECRBY', [prefixedKey, -finalNum]);
} else {
// 如果出错,回退到先获取再设置的方式
$.log.warn(`[Redis] 使用原子操作减少整数值失败,使用回退方案`);
let value = await this.get(prefixedKey);
let n = 0;
if (value) {
n = Number(value);
}
n += finalNum;
await this.set(prefixedKey, n, finalSeconds);
result = n;
}
}
// 如果设置了过期时间
if (finalSeconds > 0) {
if (typeof connection.expire === 'function') {
await connection.expire(prefixedKey, finalSeconds);
} else if (typeof connection.execute === 'function') {
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
}
}
// 触发后置事件
this.emit('addInt_after', {
key: finalKey,
value: finalNum,
seconds: finalSeconds,
result,
success: true
});
return result;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('addInt_error', {
key,
value: num,
seconds,
error: err
});
$.log.error(`[Redis] 增加整数值失败 [${key}]:`, err);
// 与CacheBase保持一致,错误时返回默认值而不是抛出异常
return 0;
}
};
/**
* @description 增加浮点数值(负数为减)(兼容方法)
* @param {String} key 键
* @param {Number} num 数值
* @param {Number} seconds 过期时间,0表示永不过期
* @return {Promise<Number>} 计算后的结果
*/
Redis.prototype.addFloat = async function(key, num, seconds = 0) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
$.log.warn(`[Redis] addFloat: key参数必须是非空字符串`);
return 0;
}
// 验证num参数
num = parseFloat(num);
if (isNaN(num)) {
$.log.warn(`[Redis] addFloat: num参数必须是有效的数值`);
return 0;
}
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value: num,
seconds,
cancel: false
};
this.emit('addFloat_before', beforeEventData);
// 如果事件处理器标记为取消,则返回默认值
if (beforeEventData.cancel) {
$.log.debug(`[Redis] addFloat操作被事件处理器取消 [${key}]`);
return 0;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalNum,
seconds: finalSeconds
} = beforeEventData;
let result;
const connection = await this._getConnection();
try {
const prefixedKey = this.applyPrefix(finalKey);
// 优先使用原生方法
if (typeof connection.incrByFloat === 'function') {
result = parseFloat(await connection.incrByFloat(prefixedKey, finalNum));
}
// 尝试使用execute通用方法
else if (typeof connection.execute === 'function') {
result = parseFloat(await connection.execute('INCRBYFLOAT', [prefixedKey, finalNum]));
}
// 回退方案
else {
$.log.warn(`[Redis] 连接对象不支持incrByFloat操作,使用回退方案`);
let value = await this.get(prefixedKey);
let n = 0;
if (value) {
n = Number(value);
}
n += finalNum;
await this.set(prefixedKey, n, finalSeconds);
result = n;
}
// 设置过期时间
if (finalSeconds > 0) {
if (typeof connection.expire === 'function') {
await connection.expire(prefixedKey, finalSeconds);
} else if (typeof connection.execute === 'function') {
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
}
}
// 触发后置事件
this.emit('addFloat_after', {
key: finalKey,
value: finalNum,
seconds: finalSeconds,
result,
success: true
});
return result;
} catch (err) {
// 如果出错,回退到先获取再设置的方式
$.log.warn(`[Redis] 使用原子操作增加浮点数值失败,使用回退方案:`, err);
let value = await this.get(prefixedKey);
let n = 0;
if (value) {
n = Number(value);
}
n += finalNum;
await this.set(prefixedKey, n, finalSeconds);
// 触发后置事件
this.emit('addFloat_after', {
key: finalKey,
value: finalNum,
seconds: finalSeconds,
result: n,
success: true
});
return n;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('addFloat_error', {
key,
value: num,
seconds,
error: err
});
$.log.error(`[Redis] 增加浮点数值失败 [${key}]:`, err);
// 与CacheBase保持一致,错误时返回默认值而不是抛出异常
return 0;
}
};
/**
* @description 增加字符串值到指定缓存(兼容方法)
* @param {String} key 键
* @param {String} str 添加的字符串
* @param {Number} seconds 过期时间,0表示永不过期
* @return {Promise<Number>} 添加后的字符串长度
*/
Redis.prototype.addStr = async function(key, str, seconds = 0) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
$.log.warn(`[Redis] addStr: key参数必须是非空字符串`);
return 0;
}
// 验证str参数
if (str === undefined || str === null) {
$.log.warn(`[Redis] addStr: str参数不能为undefined或null`);
return 0;
}
str = String(str);
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value: str,
seconds,
cancel: false
};
this.emit('addStr_before', beforeEventData);
// 如果事件处理器标记为取消,则返回0
if (beforeEventData.cancel) {
$.log.debug(`[Redis] addStr操作被事件处理器取消 [${key}]`);
return 0;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalStr,
seconds: finalSeconds
} = beforeEventData;
let result;
const connection = await this._getConnection();
try {
const prefixedKey = this.applyPrefix(finalKey);
// 优先使用原生方法
if (typeof connection.append === 'function') {
result = await connection.append(prefixedKey, finalStr);
}
// 尝试使用execute通用方法
else if (typeof connection.execute === 'function') {
result = await connection.execute('APPEND', [prefixedKey, finalStr]);
}
// 回退方案
else {
$.log.warn(`[Redis] 连接对象不支持append操作,使用回退方案`);
let value = await this.get(prefixedKey) || '';
value += finalStr;
await this.set(prefixedKey, value, finalSeconds);
result = value.length;
}
// 设置过期时间
if (finalSeconds > 0) {
if (typeof connection.expire === 'function') {
await connection.expire(prefixedKey, finalSeconds);
} else if (typeof connection.execute === 'function') {
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
}
}
// 触发后置事件
this.emit('addStr_after', {
key: finalKey,
value: finalStr,
seconds: finalSeconds,
result,
success: true
});
return result;
} catch (err) {
$.log.error('[Redis] 追加字符串失败:', err);
// 回退方案:先获取再设置
let value = await this.get(prefixedKey) || '';
value += finalStr;
await this.set(prefixedKey, value, finalSeconds);
// 触发后置事件
this.emit('addStr_after', {
key: finalKey,
value: finalStr,
seconds: finalSeconds,
result: value.length,
success: true
});
return value.length;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('addStr_error', {
key,
value: str,
seconds,
error: err
});
$.log.error(`[Redis] 追加字符串失败 [${key}]:`, err);
return 0;
}
};
/**
* @description 删除缓存(兼容方法)
* @param {String|Array<String>} key 键或键数组
* @return {Promise<Boolean>} 成功返回true,失败返回false
*/
Redis.prototype.del = async function(key) {
try {
// 键名必须是非空字符串或非空字符串数组
if (!key) {
$.log.warn('[Redis] del: key必须是非空字符串或非空字符串数组');
return false;
}
// 如果是单个键,验证是否为非空字符串
if (typeof key === 'string') {
if (!key) {
$.log.warn('[Redis] del: key必须是非空字符串');
return false;
}
}
// 如果是数组,验证每个元素是否为非空字符串
else if (Array.isArray(key)) {
for (const k of key) {
if (!k || typeof k !== 'string') {
$.log.warn('[Redis] del: key必须是非空字符串或非空字符串数组');
return false;
}
}
} else {
$.log.warn('[Redis] del: key必须是非空字符串或非空字符串数组');
return false;
}
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
cancel: false
};
this.emit('del_before', beforeEventData);
// 如果事件处理器标记为取消,则返回false
if (beforeEventData.cancel) {
$.log.debug(`[Redis] del操作被事件处理器取消 [${key}]`);
return false;
}
// 使用可能被修改的值
let {
key: finalKey
} = beforeEventData;
const connection = await this._getConnection();
try {
let success;
if (Array.isArray(finalKey)) {
// 批量删除 - 分别删除每个键以确保正确处理
success = true;
const deleted = [];
for (const k of finalKey) {
const prefixedKey = this.applyPrefix(k);
const result = await connection.del(prefixedKey);
if (result !== 1) {
success = false;
} else {
deleted.push(k);
}
}
// 触发后置事件
this.emit('del_after', {
key: finalKey,
success,
deleted
});
} else {
// 单个删除
const prefixedKey = this.applyPrefix(finalKey);
const n = await connection.del(prefixedKey);
success = n === 1;
// 触发后置事件
this.emit('del_after', {
key: finalKey,
success,
deleted: success ? [finalKey] : []
});
}
return success;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('del_error', {
key,
error: err
});
$.log.error(`[Redis] 删除缓存失败 [${key}]:`, err);
return false;
}
};
/**
* @description 修改缓存
* @param {String} key 键
* @param {Object} value 值
* @param {Number} seconds 秒
* @return {Promise<Boolean>} 操作结果
*/
/**
* @description 设置缓存(兼容方法)
* @param {String} key 键
* @param {*} value 值
* @param {Number} seconds 过期时间,0表示永不过期
* @return {Promise<Boolean>} 成功返回true,失败返回false
*/
Redis.prototype.set = async function(key, value, seconds = 0) {
try {
// 键名必须是非空字符串
if (!key || typeof key !== 'string') {
$.log.error('[Redis] 参数错误: key必须是非空字符串');
return false;
}
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value,
seconds,
cancel: false
};
this.emit('set_before', beforeEventData);
// 如果事件处理器标记为取消,则返回false
if (beforeEventData.cancel) {
$.log.debug(`[Redis] set操作被事件处理器取消 [${key}]`);
return false;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalValue,
seconds: finalSeconds
} = beforeEventData;
const connection = await this._getConnection();
try {
const prefixedKey = this.applyPrefix(finalKey);
if (typeof(finalValue) === "object") {
finalValue = JSON.stringify(finalValue);
}
if (finalSeconds > 0) {
await connection.set(prefixedKey, finalValue, {
EX: finalSeconds
});
} else {
await connection.set(prefixedKey, finalValue);
}
// 触发后置事件
this.emit('set_after', {
key,
value,
seconds,
success: true
});
return true;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
$.log.error(`[Redis] 设置缓存失败 [${key}]:`, err);
// 触发错误事件
this.emit('set_error', {
key,
value,
seconds,
error: err
});
return false;
}
};
/**
* @description 仅当键不存在时设置缓存
* @param {String} key 键
* @param {Object} value 值
* @param {Number} seconds 过期时间(秒)
* @return {Promise<Boolean>} 操作结果,键不存在时设置成功返回true,键已存在返回false
*/
Redis.prototype.add = async function(key, value, seconds) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value,
ttl: seconds,
cancel: false
};
this.emit('add_before', beforeEventData);
// 如果事件处理器标记为取消,则返回false
if (beforeEventData.cancel) {
$.log.debug(`[Redis] add操作被事件处理器取消 [${key}]`);
return false;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalValue,
ttl: finalSeconds
} = beforeEventData;
const connection = await this._getConnection();
try {
finalKey = this.applyPrefix(finalKey);
if (typeof(finalValue) === "object") {
finalValue = JSON.stringify(finalValue);
}
// 使用NX选项实现SETNX操作 - 仅在键不存在时设置
const options = finalSeconds ? {
NX: true,
EX: finalSeconds
} : {
NX: true
};
const result = await connection.set(finalKey, finalValue, options);
const success = result === "OK";
// 触发后置事件
this.emit('add_after', {
key: finalKey,
value: finalValue,
ttl: finalSeconds,
success
});
return success;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
$.log.error(`[Redis] 添加缓存失败 [${key}]:`, err);
// 触发错误事件
this.emit('add_error', {
key,
value,
ttl: seconds,
error: err
});
return false;
}
};
/**
* @description 查询缓存
* @param {String} key 键
* @return {Promise<Object>} 查询值
*/
/**
* @description 获取缓存(兼容方法)
* @param {String} key 键
* @return {Promise<*>} 获取到的值,不存在时返回null
*/
Redis.prototype.get = async function(key) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
$.log.warn(`[Redis] get: key参数必须是非空字符串`);
return null;
}
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
cancel: false
};
this.emit('get_before', beforeEventData);
// 如果事件处理器标记为取消,则返回null
if (beforeEventData.cancel) {
$.log.debug(`[Redis] get操作被事件处理器取消 [${key}]`);
return null;
}
// 使用可能被修改的值
const finalKey = beforeEventData.key;
const connection = await this._getConnection();
try {
const prefixedKey = this.applyPrefix(finalKey);
const value = await connection.get(prefixedKey);
let result = value;
if (typeof(value) === "string") {
// 尝试解析JSON
try {
if (value.startsWith("{") && value.endsWith("}") ||
value.startsWith("[") && value.endsWith("]")) {
result = JSON.parse(value);
}
} catch (e) {
// 解析失败,返回原始字符串
}
}
// 触发后置事件
this.emit('get_after', {
key: finalKey,
value: result,
success: true
});
return result;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('get_error', {
key,
error: err
});
$.log.error(`[Redis] 获取缓存失败 [${key}]:`, err);
return null;
}
};
/**
* @description 判断键是否存在(兼容方法)
* @param {String} key 键
* @return {Promise<Boolean>} 有返回true, 没有返回false
*/
Redis.prototype.has = async function(key) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
$.log.warn(`[Redis] has: key参数必须是非空字符串`);
return false;
}
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
cancel: false
};
this.emit('has_before', beforeEventData);
// 如果事件处理器标记为取消,则返回false
if (beforeEventData.cancel) {
$.log.debug(`[Redis] has操作被事件处理器取消 [${key}]`);
return false;
}
// 使用可能被修改的值
const finalKey = beforeEventData.key;
const connection = await this._getConnection();
try {
const prefixedKey = this.applyPrefix(finalKey);
const result = await connection.exists(prefixedKey);
const exists = result > 0;
// 触发后置事件
this.emit('has_after', {
key: finalKey,
result: exists,
success: true
});
return exists;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('has_error', {
key,
error: err
});
$.log.error(`[Redis] 检查键是否存在失败 [${key}]:`, err);
return false;
}
};
/**
* @description 检查键是否存在,返回存在的键数量(兼容方法)
* @param {String|Array<String>} key 键或键数组
* @return {Promise<Number>} 存在的键数量
*/
Redis.prototype.exists = async function(key) {
try {
// 参数验证
if (!key) {
$.log.warn('[Redis] exists: key必须是非空字符串或非空字符串数组');
return 0;
}
// 如果是单个键,验证是否为非空字符串
if (typeof key === 'string') {
if (!key) {
$.log.warn('[Redis] exists: key必须是非空字符串');
return 0;
}
}
// 如果是数组,验证每个元素是否为非空字符串
else if (Array.isArray(key)) {
for (const k of key) {
if (!k || typeof k !== 'string') {
$.log.warn('[Redis] exists: key必须是非空字符串或非空字符串数组');
return 0;
}
}
} else {
$.log.warn('[Redis] exists: key必须是非空字符串或非空字符串数组');
return 0;
}
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
cancel: false
};
this.emit('exists_before', beforeEventData);
// 如果事件处理器标记为取消,则返回0
if (beforeEventData.cancel) {
$.log.debug(`[Redis] exists操作被事件处理器取消 [${key}]`);
return 0;
}
// 使用可能被修改的值
const finalKey = beforeEventData.key;
const connection = await this._getConnection();
try {
let result;
if (typeof finalKey === 'string') {
const prefixedKey = this.applyPrefix(finalKey);
result = await connection.exists(prefixedKey);
} else if (Array.isArray(finalKey)) {
const prefixedKeys = finalKey.map(k => this.applyPrefix(k));
result = await connection.exists(...prefixedKeys);
}
// 触发后置事件
this.emit('exists_after', {
key: finalKey,
result,
success: true
});
return result;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('exists_error', {
key,
error: err
});
$.log.error(`[Redis] 检查键存在性失败 [${key}]:`, err);
return 0;
}
};
/**
* @description 查询缓存的字符串中的一段字符串
* @param {String} key 键
* @param {Number} start 开始位置
* @param {Number} end 结束位置
* @return {Promise<String>} 查询值
*/
Redis.prototype.getrange = async function(key, start, end) {
try {
const connection = await this._getConnection();
try {
key = this.applyPrefix(key);
return await connection.getRange(key, start, end);
} catch (err) {
$.log.error('[Redis] 获取字符串范围失败:', err);
// 回退方案
const value = await this.get(key);
if (value && typeof value === 'string') {
return value.substring(start, end + 1);
}
return '';
} finally {
this._recycleConnection(connection);
}
} catch (err) {
$.log.error(`[Redis] 获取字符串范围失败 [${key}]:`, err);
return '';
}
};
/**
* 在指定位置插入文本
* @param {String} str 原字符串
* @param {Number} index 索引
* @param {String} str_sub 插入的字符串
* @return {String} 插入后的字符串
*/
Redis.prototype.insertString = function(str, index, str_sub) {
return str.substring(0, index) + str_sub + str.substring(index);
};
/**
* @description 在值的指定位置开始增加一段字符串
* @param {String} key 键
* @param {Number} index 开始位置
* @param {String} value 变更的值
* @return {Promise<Number>} 字符串长度
*/
Redis.prototype.setrange = async function(key, index, value) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
index,
value,
cancel: false
};
this.emit('setrange_before', beforeEventData);
// 如果事件处理器标记为取消,则返回0
if (beforeEventData.cancel) {
$.log.debug(`[Redis] setrange操作被事件处理器取消 [${key}]`);
return 0;
}
// 使用可能被修改的值
let {
key: finalKey,
index: finalIndex,
value: finalValue
} = beforeEventData;
let result;
const connection = await this._getConnection();
try {
finalKey = this.applyPrefix(finalKey);
// 优先使用原生方法
if (typeof connection.setRange === 'function') {
result = await connection.setRange(finalKey, finalIndex, finalValue);
}
// 尝试使用execute通用方法
else if (typeof connection.execute === 'function') {
result = await connection.execute('SETRANGE', [finalKey, finalIndex, finalValue]);
}
// 回退方案
else {
$.log.warn(`[Redis] 连接对象不支持setRange操作,使用回退方案`);
let str = await this.get(finalKey) || '';
if (str.length < finalIndex) {
// 填充空格到指定索引
str = str.padEnd(finalIndex, ' ');
}
str = this.insertString(str, finalIndex, finalValue);
await this.set(finalKey, str);
result = str.length;
}
// 触发后置事件
this.emit('setrange_after', {
key: finalKey,
index: finalIndex,
value: finalValue,
result,
success: true
});
return result;
} catch (err) {
$.log.error('[Redis] 设置字符串范围失败:', err);
// 回退方案
let str = await this.get(finalKey) || '';
if (str.length < finalIndex) {
// 填充空格到指定索引
str = str.padEnd(finalIndex, ' ');
}
str = this.insertString(str, finalIndex, finalValue);
await this.set(finalKey, str);
// 触发后置事件
this.emit('setrange_after', {
key: finalKey,
index: finalIndex,
value: finalValue,
result: str.length,
success: true
});
return str.length;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('setrange_error', {
key,
index,
value,
error: err
});
$.log.error(`[Redis] 设置字符串范围失败 [${key}]:`, err);
return 0;
}
};
/**
* @description 清空缓存
* @param {String} key 键, 为空则清空所有
* @return {Promise<Boolean>} 执行结果
*/
Redis.prototype.clear = async function(key) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
cancel: false
};
this.emit('clear_before', beforeEventData);
// 如果事件处理器标记为取消,则返回false
if (beforeEventData.cancel) {
$.log.debug(`[Redis] clear操作被事件处理器取消`);
return false;
}
// 使用可能被修改的值
let {
key: finalKey
} = beforeEventData;
let success = false;
const connection = await this._getConnection();
try {
if (finalKey) {
// 清空指定键
success = await this.del(finalKey);
} else {
// 清空当前数据库
if (typeof connection.flushDb === 'function') {
await connection.flushDb();
success = true;
} else if (typeof connection.execute === 'function') {
await connection.execute('FLUSHDB');
success = true;
} else {
$.log.error('[Redis] 连接对象不支持flushDb操作');
success = false;
}
}
// 触发后置事件
this.emit('clear_after', {
key: finalKey,
success
});
return success;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('clear_error', {
key,
error: err
});
$.log.error('[Redis] 清空缓存失败:', err);
return false;
}
};
/**
* @description 排序
* @param {String} key 键
* @param {Object} options 排序选项
* @return {Promise<Array>} 排序后的数组
*/
Redis.prototype.sort = async function(key, options = {}) {
try {
const connection = await this._getConnection();
try {
key = this.applyPrefix(key);
// 默认升序
let sortOptions = [];
if (options.by) {
sortOptions.push('BY', options.by);
}
if (options.limit) {
sortOptions.push('LIMIT', options.limit.offset || 0, options.limit.count || -1);
}
if (options.order === 'desc') {
sortOptions.push('DESC');
} else {
sortOptions.push('ASC');
}
if (options.alpha) {
sortOptions.push('ALPHA');
}
if (options.get) {
if (Array.isArray(options.get)) {
options.get.forEach(getKey => {
sortOptions.push('GET', getKey);
});
} else {
sortOptions.push('GET', options.get);
}
}
return await connection.sort(key, sortOptions);
} catch (err) {
$.log.error('[Redis] 排序失败:', err);
// 回退方案:获取数据后在内存中排序
const data = await this.getForList(key);
return data.sort((a, b) => {
if (options.alpha) {
return options.order === 'desc' ? b.localeCompare(a) : a.localeCompare(b);
} else {
return options.order === 'desc' ? Number(b) - Number(a) : Number(a) - Number(b);
}
});
} finally {
this._recycleConnection(connection);
}
} catch (err) {
$.log.error(`[Redis] 排序失败 [${key}]:`, err);
return [];
}
};
/**
* @description 获取所有键名
* @param {String} pattern 键模式 支持*号
* @return {Promise<Array>} 键数组
*/
Redis.prototype.keys = async function(pattern = '*') {
try {
const connection = await this._getConnection();
try {
// 应用前缀到模式
const fullPattern = this.config.prefix ? this.config.prefix + pattern : pattern;
const keys = await connection.keys(fullPattern);
// 返回移除前缀的键
return keys.map(key => this.removePrefix(key));
} finally {
this._recycleConnection(connection);
}
} catch (err) {
$.log.error('[Redis] 获取键列表失败:', err);
return [];
}
};
/**
* @description 修改数组缓存
* @param {String} key 键
* @param {Object|Array} value 值
* @param {Number} seconds 秒
* @return {Promise<Number>} 数组长度
*/
Redis.prototype.setForList = async function(key, value, seconds) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value,
ttl: seconds,
cancel: false
};
this.emit('setForList_before', beforeEventData);
// 如果事件处理器标记为取消,则返回0
if (beforeEventData.cancel) {
$.log.debug(`[Redis] setForList操作被事件处理器取消 [${key}]`);
return 0;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalValue,
ttl: finalSeconds
} = beforeEventData;
let len = 0;
const connection = await this._getConnection();
try {
finalKey = this.applyPrefix(finalKey);
// 先清空再添加
if (typeof connection.del === 'function') {
await connection.del(finalKey);
} else if (typeof connection.execute === 'function') {
await connection.execute('DEL', [finalKey]);
}
if (Array.isArray(finalValue)) {
if (finalValue.length > 0) {
// 添加数组元素
if (typeof connection.rPush === 'function') {
len = await connection.rPush(finalKey, finalValue);
} else if (typeof connection.execute === 'function') {
len = await connection.execute('RPUSH', [finalKey, ...finalValue]);
} else {
$.log.error('[Redis] 连接对象不支持rPush操作');
len = 0;
}
}
} else {
// 添加单个元素
if (typeof connection.rPush === 'function') {
len = await connection.rPush(finalKey, finalValue);
} else if (typeof connection.execute === 'function') {
len = await connection.execute('RPUSH', [finalKey, finalValue]);
} else {
$.log.error('[Redis] 连接对象不支持rPush操作');
len = 0;
}
}
if (finalSeconds) {
// 设置过期时间
if (typeof connection.expire === 'function') {
await connection.expire(finalKey, finalSeconds);
} else if (typeof connection.execute === 'function') {
await connection.execute('EXPIRE', [finalKey, finalSeconds]);
} else {
$.log.error('[Redis] 连接对象不支持expire操作');
}
}
// 触发后置事件
this.emit('setForList_after', {
key: finalKey,
value: finalValue,
ttl: finalSeconds,
result: len,
success: len > 0
});
return len;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('setForList_error', {
key,
value,
error: err
});
$.log.error(`[Redis] 设置列表失败 [${key}]:`, err);
return 0;
}
};
/**
* @description 数组缓存追加对象
* @param {String} key 键
* @param {Object|Array} value 值
* @param {Number} seconds 秒
* @return {Promise<Number>} 追加后的数组长度
*/
Redis.prototype.addForList = async function(key, value, seconds = 0) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value,
ttl: seconds,
cancel: false
};
this.emit('addForList_before', beforeEventData);
// 如果事件处理器标记为取消,则返回0
if (beforeEventData.cancel) {
$.log.debug(`[Redis] addForList操作被事件处理器取消 [${key}]`);
return 0;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalValue,
ttl: finalSeconds
} = beforeEventData;
let len = 0;
const connection = await this._getConnection();
try {
finalKey = this.applyPrefix(finalKey);
if (Array.isArray(finalValue)) {
if (finalValue.length > 0) {
// 添加数组元素
if (typeof connection.rPush === 'function') {
len = await connection.rPush(finalKey, finalValue);
} else if (typeof connection.execute === 'function') {
len = await connection.execute('RPUSH', [finalKey, ...finalValue]);
} else {
$.log.error('[Redis] 连接对象不支持rPush操作');
len = 0;
}
}
} else {
// 添加单个元素
if (typeof connection.rPush === 'function') {
len = await connection.rPush(finalKey, finalValue);
} else if (typeof connection.execute === 'function') {
len = await connection.execute('RPUSH', [finalKey, finalValue]);
} else {
$.log.error('[Redis] 连接对象不支持rPush操作');
len = 0;
}
}
if (finalSeconds) {
// 设置过期时间
if (typeof connection.expire === 'function') {
await connection.expire(finalKey, finalSeconds);
} else if (typeof connection.execute === 'function') {
await connection.execute('EXPIRE', [finalKey, finalSeconds]);
} else {
$.log.error('[Redis] 连接对象不支持expire操作');
}
}
// 触发后置事件
this.emit('addForList_after', {
key: finalKey,
value: finalValue,
ttl: finalSeconds,
result: len,
success: len > 0
});
return len;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('addForList_error', {
key,
value,
error: err
});
$.log.error(`[Redis] 添加列表项失败 [${key}]:`, err);
return 0;
}
};
/**
* @description 判断成员是否存在
* @param {String} key 键
* @param {Object} value 值
* @return {Promise<Boolean>} 存在返回true, 否则返回false
*/
Redis.prototype.hasForList = async function(key, value) {
if (!value) {
$.log.error('[Redis] 参数错误: 值不能为空');
return false;
}
try {
// 尝试使用Redis的LREM命令来检查是否存在
const keyWithPrefix = this.applyPrefix(key);
// 转换value为字符串以便比较
const valueStr = JSON.stringify(value);
// 使用scan来查找,避免在大列表上的性能问题
const arr = await this.getForList(key);
return arr.some(item => {
const itemStr = JSON.stringify(item);
return itemStr === valueStr || item === value;
});
} catch (err) {
$.log.error(`[Redis] 检查列表成员是否存在失败 [${key}]:`, err);
return false;
}
};
/**
* @description 查询数组缓存
* @param {String} key 键
* @param {Number} start 起始位置
* @param {Number} end 结束位置
* @return {Promise<Array>} 查询到的数组
*/
Redis.prototype.getForList = async function(key, start = 0, end = -1) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
start,
end,
cancel: false
};
this.emit('getForList_before', beforeEventData);
// 如果事件处理器标记为取消,则返回空数组
if (beforeEventData.cancel) {
$.log.debug(`[Redis] getForList操作被事件处理器取消 [${key}]`);
return [];
}
// 使用可能被修改的值
let {
key: finalKey,
start: finalStart,
end: finalEnd
} = beforeEventData;
const connection = await this._getConnection();
try {
finalKey = this.applyPrefix(finalKey);
let result;
// 检查连接是否支持lRange方法,Redis v4 API
if (typeof connection.lRange === 'function') {
result = await connection.lRange(finalKey, finalStart, finalEnd);
} else if (typeof connection.lrange === 'function') {
// 兼容旧版API
result = await connection.lrange(finalKey, finalStart, finalEnd);
} else if (typeof connection.execute === 'function') {
// 回退到通用execute方法
result = await connection.execute('LRANGE', [finalKey, finalStart, finalEnd]);
} else {
$.log.error('[Redis] 连接对象不支持lRange或lrange操作');
return [];
}
// 确保结果是数组
if (!Array.isArray(result)) {
$.log.error('[Redis] 列表查询结果不是数组格式');
return [];
}
// 尝试解析每个元素为JSON
const parsedResult = result.map(item => {
if (typeof item === "string") {
try {
// 尝试识别并解析JSON字符串
const trimmedItem = item.trim();
if ((trimmedItem.startsWith("{") && trimmedItem.endsWith("}")) ||
(trimmedItem.startsWith("[") && trimmedItem.endsWith("]"))) {
return JSON.parse(trimmedItem);
}
} catch (e) {
// 解析失败,返回原始字符串
}
}
return item;
});
// 触发后置事件
this.emit('getForList_after', {
key: finalKey,
start: finalStart,
end: finalEnd,
result: parsedResult
});
return parsedResult;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('getForList_error', {
key,
start,
end,
error: err
});
$.log.error(`[Redis] 获取列表失败 [${key}]:`, err);
return [];
}
};
/**
* @description 清空列表,保留键和过期时间
* @param {String} key 键
* @return {Promise<Boolean>} 成功返回true,失败返回false
*/
Redis.prototype.clearForList = async function(key) {
if (!key) {
$.log.error('[Redis] 参数错误: 键不能为空');
return false;
}
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
cancel: false
};
this.emit('clearForList_before', beforeEventData);
// 如果事件处理器标记为取消,则返回false
if (beforeEventData.cancel) {
$.log.debug(`[Redis] clearForList操作被事件处理器取消 [${key}]`);
return false;
}
// 使用可能被修改的值
let finalKey = beforeEventData.key;
// 直接使用set方法设置为空数组,这样更可靠且与其他模块行为一致
// 这样可以确保返回值是一个空数组,而不是null或undefined
finalKey = this.applyPrefix(finalKey);
await this.set(finalKey, []);
// 触发后置事件
this.emit('clearForList_after', {
key: finalKey,
success: true
});
return true;
} catch (err) {
// 触发错误事件
this.emit('clearForList_error', {
key,
error: err
});
$.log.error(`[Redis] 清空列表失败 [${key}]:`, err);
return false;
}
};
/**
* @description 批量获取缓存
* @param {Array<String>} keys 键数组
* @return {Promise<Object>} 键值对对象
*/
/**
* @description 批量获取缓存(兼容方法)
* @param {Array<String>} keys 键数组
* @return {Promise<Array>} 返回值数组,顺序与键数组一致
*/
Redis.prototype.mget = async function(keys) {
try {
// 参数验证
if (!keys || !Array.isArray(keys)) {
$.log.warn('[Redis] mget: keys必须是非空数组');
return [];
}
// 验证数组中的每个元素是否为非空字符串
for (const key of keys) {
if (!key || typeof key !== 'string') {
$.log.warn('[Redis] mget: keys数组中的每个元素必须是非空字符串');
return [];
}
}
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
keys,
cancel: false
};
this.emit('mget_before', beforeEventData);
// 如果事件处理器标记为取消,则返回空数组
if (beforeEventData.cancel) {
$.log.debug(`[Redis] mget操作被事件处理器取消 [${keys}]`);
return [];
}
// 使用可能被修改的值
const finalKeys = beforeEventData.keys;
const connection = await this._getConnection();
try {
// 直接使用Redis v4的mGet方法,应用键前缀
const keysWithPrefix = finalKeys.map(k => this.applyPrefix(k));
const values = await connection.mGet(keysWithPrefix);
// 处理值的格式,保持数组返回格式
const result = [];
values.forEach((value, index) => {
if (typeof(value) === "string") {
try {
if (value.startsWith("{") && value.endsWith("}") ||
value.startsWith("[") && value.endsWith("]")) {
value = JSON.parse(value);
}
} catch (e) {
// 解析失败,返回原始字符串
}
}
result[index] = value;
});
// 触发后置事件
this.emit('mget_after', {
keys: finalKeys,
result,
success: true
});
return result;
} finally {
this._recycleConnection(connection);
}
} catch (err) {
// 触发错误事件
this.emit('mget_error', {
keys,
error: err
});
$.log.error(`[Redis] 批量获取缓存失败 [${keys}]:`, err);
return [];
}
}
/**
* @description 批量设置缓存(兼容方法)
* @param {Ob