mm_mongodb
Version:
MongoDB数据库操作模块,提供简洁易用的API、缓存功能以及完整的Redis兼容接口,支持事件系统、事务和连接池管理
2,244 lines (1,998 loc) • 95.2 kB
JavaScript
/**
* @fileOverview MongoDB帮助类函数
* @author <a href="http://qww.elins.cn">邱文武</a>
* @version 1.2.1
*/
const {
BaseService
} = require('mm_base_service');
const MongoClient = require('mongodb').MongoClient;
/**
* @description MongoDB帮助类
*/
class MongoDB extends BaseService {
/**
* @description 创建MongoDB帮助类函数 (构造函数)
* @param {Object} config 配置参数
* @constructor
*/
constructor(config) {
super();
// 合并配置,确保使用对象扩展运算符正确合并
this.config = {
// 服务器地址
host: "localhost",
// 端口号
port: 27017,
// 数据库名
database: "mm",
// 用户名
user: "admin",
// 密码
password: "",
// 连接选项
options: {
minPoolSize: 10,
maxPoolSize: 100,
connectTimeoutMS: 10000,
serverSelectionTimeoutMS: 5000,
retryWrites: true
},
// 重试配置
retry: {
maxAttempts: 3,
delayMS: 1000
}
};
// 连接地址
this.url = null;
// 数据库连接器
this.conn = null;
// 操作的数据库
this.db = null;
// 表名
this.table = "cache";
/**
* 页码
*/
this.page = 1;
/**
* 每页显示条数
*/
this.size = 0;
// 连接状态
this.connected = false;
// 连接Promise缓存
this.connectPromise = null;
// 连接重试次数
this.connectAttempts = 0;
this.maxConnectAttempts = 3;
this.setConfig(config);
// 验证配置
this._validateConfig();
}
}
/**
* @private
* @description 验证配置有效性
* @throws {Error} 配置无效时抛出错误
*/
MongoDB.prototype._validateConfig = function () {
if (!this.config.host || typeof this.config.host !== 'string') {
throw new Error('MongoDB配置无效:host必须是有效的字符串');
}
if (!Number.isInteger(this.config.port) || this.config.port < 1 || this.config.port > 65535) {
throw new Error('MongoDB配置无效:port必须是1-65535之间的整数');
}
if (!this.config.database || typeof this.config.database !== 'string') {
throw new Error('MongoDB配置无效:database必须是有效的字符串');
}
};
/**
* @private
* @description 执行带重试的操作
* @param {Function} operation 要执行的操作
* @param {String} operationName 操作名称
* @return {Promise} 操作结果
*/
MongoDB.prototype._withRetry = async function (operation, operationName) {
const maxAttempts = this.config.retry?.maxAttempts || 3;
const delayMS = this.config.retry?.delayMS || 1000;
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
$.log.error(`[MongoDB] ${operationName} 第${attempt}次尝试失败:`, error.message);
// 如果不是最后一次尝试,则等待后重试
if (attempt < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, delayMS));
}
}
}
// 所有重试都失败,抛出最后一个错误
throw lastError;
};
/**
* @description 确保数据库连接已建立
* @private
* @return {Promise<void>}
*/
MongoDB.prototype._ensureConnected = async function () {
// 如果已有连接Promise,等待其完成
if (this.connectPromise) {
return this.connectPromise;
}
// 创建新的连接Promise
this.connectPromise = (async () => {
try {
// 检查连接状态
if (!this.db || !this.connected) {
// 记录连接尝试
this.connectAttempts++;
// 尝试连接
await this.open();
// 检查连接是否成功
if (this.db && this.db.collection) {
$.log.debug(`[MongoDB] [_ensureConnected] 连接成功,db对象存在,开始初始化collection,table=${this.table}`);
this.connected = true;
this.connectAttempts = 0;
// 保存collection引用到实例变量
try {
this.collection = this.db.collection(this.table);
$.log.debug(`[MongoDB] [_ensureConnected] 成功初始化collection: ${this.table}`);
} catch (err) {
$.log.error(`[MongoDB] [_ensureConnected] 初始化collection失败:`, err.message);
this.collection = null;
}
// 确保索引已创建
if (this.collection) {
await this._ensureIndexes();
}
$.log.info(`[MongoDB] 成功连接到数据库,表名: ${this.table}`);
} else {
$.log.error(`[MongoDB] [_ensureConnected] 连接失败,db对象为空或不具有collection方法`);
throw new Error('数据库连接失败,db对象为空');
}
}
} catch (error) {
$.log.error(`[MongoDB] 连接失败 (尝试 ${this.connectAttempts}/${this.maxConnectAttempts}):`,
error.message);
// 重置连接Promise,允许下次重试
this.connectPromise = null;
// 如果尝试次数未超过限制,抛出错误让上层处理
if (this.connectAttempts < this.maxConnectAttempts) {
throw error;
} else {
// 超过重试次数,记录严重错误
$.log.error('[MongoDB] 达到最大连接重试次数,连接失败');
throw new Error('数据库连接失败,已达到最大重试次数');
}
}
})();
return this.connectPromise;
};
/**
* @description 确保索引已创建
* @private
* @return {Promise<void>}
*/
MongoDB.prototype._ensureIndexes = async function () {
try {
// 确保db对象有效
if (!this.db || !this.db.collection) {
throw new Error('数据库连接无效,无法创建索引');
}
// 创建TTL索引用于自动过期
await this.db.collection(this.table).createIndex({
expire_at: 1
}, {
expireAfterSeconds: 0
});
// 创建_id索引(通常自动创建,但显式指定更清晰)
await this.db.collection(this.table).createIndex({
_id: 1
});
} catch (error) {
// 忽略索引已存在的错误
if (error.code !== 85) {
$.log.error('[MongoDB] 创建索引失败:', error);
}
}
};
/**
* @description Redis兼容API - 获取缓存值
* @param {String} key 键名
* @return {Promise<any>} 返回键值,不存在返回undefined
*/
MongoDB.prototype.get = async function (key) {
try {
await this._ensureConnected();
const result = await this.findOne({
key: key
});
return result ? result.value : undefined;
} catch (error) {
$.log.error('[MongoDB] 获取缓存失败:', error);
throw error;
}
};
/**
* @description Redis兼容API - 清空所有缓存或匹配模式的缓存
* @param {string} pattern - 可选的键匹配模式
* @return {Promise<Boolean>} 是否成功
*/
MongoDB.prototype.clear = async function (pattern) {
try {
// 触发前置事件
this.emit('clear_before', {
pattern
});
await this._ensureConnected();
if (pattern) {
// 支持通配符匹配删除
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
const result = await this.delete({
key: {
$regex: regex
}
});
this.emit('clear_after', {
pattern,
result
});
return result.acknowledged;
} else {
// 清空所有数据,直接使用deleteMany操作
const result = await this.db.collection(this.table).deleteMany({});
this.emit('clear_after', {
result
});
return result.acknowledged;
}
} catch (error) {
this.emit('clear_error', {
error,
pattern
});
$.log.error('[MongoDB] 清空缓存失败:', error);
return false;
}
};
/**
* @description Redis兼容API - 获取所有键
* @param {String} pattern 匹配模式
* @return {Promise<Array>} 键名数组
*/
MongoDB.prototype.keys = async function (pattern = '*') {
try {
await this._ensureConnected();
let query = {};
if (pattern !== '*') {
// 简单的通配符支持
const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
query.key = {
$regex: new RegExp(`^${regexPattern}$`)
};
}
const results = await this.find(query, {
projection: {
key: 1
}
});
return results.map(item => item.key);
} catch (error) {
$.log.error('[MongoDB] 获取键列表失败:', error);
return [];
}
};
/**
* @description Redis兼容API - 获取或设置过期时间
* @param {String} key 键名
* @param {Number} [seconds] 过期时间(秒),不传此参数则获取剩余过期时间
* @return {Promise<Number|Boolean>} 不传seconds时返回剩余过期时间(-1表示无过期设置,-2表示已过期或不存在),传seconds时返回是否设置成功
*/
MongoDB.prototype.ttl = async function (key, seconds) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
return arguments.length === 1 ? -2 : false;
}
// 设置模式才需要触发事件
if (seconds !== undefined && seconds !== null) {
// 触发前置事件
const beforeEventData = {
key,
seconds,
cancel: false
};
this.emit('ttl_before', beforeEventData);
if (beforeEventData.cancel) {
return false;
}
let { key: finalKey, seconds: finalSeconds } = beforeEventData;
await this._ensureConnected();
// 确保连接和collection存在
if (!this.db) {
await this.open();
}
// 直接尝试获取collection,使用实例的table属性
if (!this.collection && this.db && this.db.collection) {
$.log.debug(`[MongoDB] [ttl] 设置模式:直接获取collection, table=${this.table}`);
try {
this.collection = this.db.collection(this.table);
$.log.debug(`[MongoDB] [ttl] 设置模式:成功获取collection`);
} catch (err) {
$.log.error(`[MongoDB] [ttl] 设置模式:获取collection失败:`, err.message);
this.collection = null;
}
}
// 检查collection是否存在
if (!this.collection) {
this.emit('ttl_after', {
key: finalKey,
seconds: finalSeconds,
result: false
});
return false;
}
// 检查键是否存在
const count = await this.collection.countDocuments({ key: finalKey });
if (count === 0) {
this.emit('ttl_after', {
key: finalKey,
seconds: finalSeconds,
result: false
});
return false;
}
// 设置过期时间
const updateData = {};
if (finalSeconds === 0 || finalSeconds === -1) {
// 移除过期时间
updateData.$unset = { expire_at: 1 };
} else if (finalSeconds > 0) {
// 设置新的过期时间
updateData.$set = { expire_at: new Date(Date.now() + finalSeconds * 1000) };
} else {
// 设置为已过期
updateData.$set = { expire_at: new Date(0) };
}
await this.collection.updateOne(
{ key: finalKey },
updateData,
{ upsert: false }
);
this.emit('ttl_after', {
key: finalKey,
seconds: finalSeconds,
result: true
});
return true;
}
// 获取模式:检查键的过期时间(不需要事件)
else {
await this._ensureConnected();
// 确保连接和collection存在
if (!this.db) {
await this.open();
}
// 直接尝试获取collection,使用实例的table属性
if (!this.collection && this.db && this.db.collection) {
$.log.debug(`[MongoDB] [ttl] 直接获取collection, table=${this.table}`);
try {
this.collection = this.db.collection(this.table);
$.log.debug(`[MongoDB] [ttl] 成功获取collection`);
} catch (err) {
$.log.error(`[MongoDB] [ttl] 获取collection失败:`, err.message);
this.collection = null;
}
}
// 再次检查collection
if (!this.collection) {
return -2;
}
// 查询键是否存在及过期时间
const item = await this.collection.findOne({ key });
$.log.debug(`[MongoDB] [ttl] 查询键结果: item=${JSON.stringify(item)}`);
if (!item) {
// 键不存在返回-2
$.log.debug(`[MongoDB] [ttl] 键 ${key} 不存在,返回-2`);
return -2;
}
if (!item.expire_at) {
// 没有过期时间返回-1
$.log.debug(`[MongoDB] [ttl] 键 ${key} 没有过期时间,返回-1`);
return -1;
}
// 计算剩余过期时间
const expireTime = new Date(item.expire_at).getTime();
const currentTime = Date.now();
const remainingSeconds = Math.ceil((expireTime - currentTime) / 1000);
if (remainingSeconds <= 0) {
// 已过期返回-2
return -2;
}
// 返回剩余过期时间
return remainingSeconds;
}
} catch (error) {
this.emit('ttl_error', { key, seconds, error });
$.log.error(`[MongoDB] [ttl] 操作失败:`, error.message);
return arguments.length === 1 ? -2 : false;
}
};
/**
* @description Redis兼容API - 移除键的过期时间
* @param {String} key 键名
* @return {Promise<boolean>} 移除成功返回true,键不存在或没有过期时间返回false
*/
MongoDB.prototype.persist = async function (key) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
return false;
}
// 触发前置事件
const beforeEventData = {
key,
cancel: false
};
this.emit('persist_before', beforeEventData);
if (beforeEventData.cancel) {
return false;
}
const { key: finalKey } = beforeEventData;
// 确保连接和collection存在
if (!this.db) {
await this.open();
}
// 直接尝试获取collection,不依赖_ensureConnected的复杂逻辑
if (!this.collection && this.db && this.db.collection) {
$.log.debug(`[MongoDB] [persist] 直接获取collection, table=${this.table}`);
try {
this.collection = this.db.collection(this.table);
$.log.debug(`[MongoDB] [persist] 成功获取collection`);
} catch (err) {
$.log.error(`[MongoDB] [persist] 获取collection失败:`, err.message);
this.collection = null;
}
}
// 再次检查collection
if (!this.collection) {
$.log.error('[MongoDB] persist: collection is undefined');
this.emit('persist_error', {
key: finalKey,
error: new Error('collection is undefined')
});
return false;
}
// 检查键是否存在
const item = await this.collection.findOne({ key: finalKey });
if (!item) {
// 键不存在返回false
this.emit('persist_after', {
key: finalKey,
success: false
});
return false;
}
// 如果没有过期时间,直接返回true
if (!item.expire_at) {
this.emit('persist_after', {
key: finalKey,
success: true
});
return true;
}
// 从文档中移除过期时间
const result = await this.collection.updateOne(
{ key: finalKey },
{ $unset: { expire_at: 1 } },
{ upsert: false }
);
// 检查更新是否成功
const success = result.modifiedCount > 0;
// 触发后置事件
this.emit('persist_after', {
key: finalKey,
success: success
});
return success;
} catch (error) {
$.log.error('[MongoDB] persist error:', error.message);
this.emit('persist_error', {
key,
error
});
return false;
}
};
/**
* @param {String} key 键名
* @param {*} value 值
* @param {Number} expire 过期时间(秒)
* @return {Promise<Boolean>} 是否成功
*/
MongoDB.prototype._add = async function (key, value, expire) {
try {
await this._ensureConnected();
const data = {
key: key,
value: value
};
if (expire) {
data.expire_at = new Date(Date.now() + expire * 1000);
}
// 检查是否已存在
const existing = await this.findOne({
key: key
});
if (existing) {
// 调用_set方法时传递过期时间参数
await this._set(key, value, expire);
} else {
await this.addObj(data);
}
return true;
} catch (error) {
$.log.error('[MongoDB] 添加缓存失败:', error);
return false;
}
};
/**
* @description 仅当键不存在时设置缓存
* @param {String} key 键
* @param {*} value 值
* @param {Number} seconds 过期时间(秒)
* @return {Promise<Boolean>} 操作结果,键不存在时设置成功返回true,键已存在返回false
*/
MongoDB.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(`[MongoDB] add操作被事件处理器取消 [${key}]`);
return false;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalValue,
ttl: finalSeconds
} = beforeEventData;
await this._ensureConnected();
// 检查键是否已存在
const existing = await this.findOne({
key: finalKey
});
if (existing) {
// 键已存在,返回false
this.emit('add_after', {
key: finalKey,
value: finalValue,
ttl: finalSeconds,
success: false
});
return false;
}
// 键不存在,设置值
const data = {
key: finalKey,
value: finalValue
};
if (finalSeconds) {
data.expire_at = new Date(Date.now() + finalSeconds * 1000);
}
await this.addObj(data);
// 触发后置事件
this.emit('add_after', {
key: finalKey,
value: finalValue,
ttl: finalSeconds,
success: true
});
return true;
} catch (error) {
$.log.error(`[MongoDB] 添加缓存失败 [${key}]:`, error);
// 触发错误事件
this.emit('add_error', {
key,
value,
ttl: seconds,
error
});
return false;
}
};
/**
* @description Redis兼容API - 增加整数值
* @param {String} key 键名
* @param {Number} value 增加值
* @return {Promise<Number>} 增加后的值
*/
MongoDB.prototype._addNum = async function (key, value = 1) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
let currentValue = 0;
if (item && typeof item.value === 'number') {
currentValue = item.value;
}
const newValue = currentValue + value;
await this._add(key, newValue);
return newValue;
} catch (error) {
$.log.error('[MongoDB] 增加整数值失败:', error);
return undefined;
}
};
/**
* @description Redis兼容API - 增加整数值
* @param {String} key 键名
* @param {Number} value 增加值
* @return {Promise<Number>} 增加后的值
*/
/**
* @description Redis兼容API - 增加整数值
* @param {String} key 键名
* @param {Number} value 增加值
* @return {Promise<Number>} 增加后的值
*/
MongoDB.prototype.addInt = async function (key, value = 1, seconds = 0) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value,
seconds,
cancel: false
};
this.emit('addInt_before', beforeEventData);
// 如果事件处理器标记为取消,则返回默认值
if (beforeEventData.cancel) {
$.log.debug('[MongoDB] addInt操作被事件处理器取消');
return 0;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalValue,
seconds: finalSeconds
} = beforeEventData;
// 参数验证
if (!finalKey || typeof finalKey !== 'string') {
$.log.warn('[MongoDB] addInt: key必须是非空字符串');
return 0;
}
finalValue = Number(finalValue) || 0;
await this._ensureConnected();
// 获取当前值
const item = await this.findOne({
key: finalKey
});
let currentValue = 0;
if (item) {
// 确保转换为数字
currentValue = Number(item.value) || 0;
}
const newValue = Math.floor(currentValue + finalValue); // 确保返回整数
await this._add(finalKey, newValue, finalSeconds);
// 触发后置事件
this.emit('addInt_after', {
key: finalKey,
value: finalValue,
seconds: finalSeconds,
result: newValue,
success: true
});
return newValue;
} catch (error) {
// 触发错误事件
this.emit('addInt_error', {
key,
value,
seconds,
error
});
$.log.error('[MongoDB] 增加整数值失败:', error);
// 与CacheBase保持一致,错误时返回默认值而不是抛出异常
return 0;
}
};
/**
* @description Redis兼容API - 增加浮点数值
* @param {String} key 键名
* @param {Number} value 增加值
* @return {Promise<Number>} 增加后的值
*/
MongoDB.prototype.addFloat = async function (key, value = 1.0, seconds = 0) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value,
seconds,
cancel: false
};
this.emit('addFloat_before', beforeEventData);
// 如果事件处理器标记为取消,则返回默认值
if (beforeEventData.cancel) {
$.log.debug('[MongoDB] addFloat操作被事件处理器取消');
return 0.0;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalValue,
seconds: finalSeconds
} = beforeEventData;
// 参数验证
if (!finalKey || typeof finalKey !== 'string') {
$.log.warn('[MongoDB] addFloat: key必须是非空字符串');
return 0.0;
}
finalValue = Number(finalValue) || 0.0;
await this._ensureConnected();
// 获取当前值
const item = await this.findOne({
key: finalKey
});
let currentValue = 0.0;
if (item) {
// 确保转换为数字
currentValue = Number(item.value) || 0.0;
}
const newValue = currentValue + finalValue;
await this._add(finalKey, newValue, finalSeconds);
// 触发后置事件
this.emit('addFloat_after', {
key: finalKey,
value: finalValue,
seconds: finalSeconds,
result: newValue,
success: true
});
return newValue;
} catch (error) {
// 触发错误事件
this.emit('addFloat_error', {
key,
value,
seconds,
error
});
$.log.error('[MongoDB] 增加浮点数值失败:', error);
// 与CacheBase保持一致,错误时返回默认值而不是抛出异常
return 0.0;
}
};
/**
* @description Redis兼容API - 添加字符串
* @param {String} key 键名
* @param {String} value 字符串值
* @return {Promise<Number>} 添加后的字符串长度
*/
MongoDB.prototype.addStr = async function (key, value) {
try {
// 触发前置事件
this.emit('addStr_before', {
key,
value
});
// 参数验证
if (!key || typeof key !== 'string') {
throw new Error('key必须是非空字符串');
}
if (value === undefined || value === null) {
value = '';
}
await this._ensureConnected();
// 获取当前值
const item = await this.findOne({
key: key
});
const currentValue = item ? String(item.value) : '';
const newValue = currentValue + String(value);
// 设置新值
await this._add(key, newValue);
// 触发后置事件
this.emit('addStr_after', {
key,
value,
result: newValue.length
});
// 返回添加后的字符串长度
return newValue.length;
} catch (error) {
// 触发错误事件
this.emit('addStr_error', {
key,
value,
error
});
$.log.error('[MongoDB] 添加字符串失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 排序
* @param {String} key 键名
* @param {Object} options 排序选项
* @return {Promise<Array>} 排序结果
*/
MongoDB.prototype.sort = async function (key, options = {}) {
try {
await this._ensureConnected();
// 假设value是数组
const item = await this.findOne({
key: key
});
if (!item || !Array.isArray(item.value)) return [];
const sorted = [...item.value];
// 处理排序选项
if (options.desc) {
sorted.sort((a, b) => b - a);
} else {
sorted.sort((a, b) => a - b);
}
// 处理限制
if (options.limit) {
const offset = options.offset || 0;
return sorted.slice(offset, offset + options.limit);
}
return sorted;
} catch (error) {
$.log.error('[MongoDB] 排序失败:', error);
return [];
}
};
/**
* @description Redis兼容API - 哈希表设置字段
* @param {String} key 键名
* @param {String} field 字段名
* @param {*} value 值
* @return {Promise<number>} 返回1表示新建字段,0表示更新字段
*/
MongoDB.prototype.hset = async function (key, field, value) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {};
// 检查字段是否存在,以确定是新建还是更新
const isNewField = !hash.hasOwnProperty(field);
hash[field] = value;
await this._add(key, hash);
return isNewField ? 1 : 0;
} catch (error) {
$.log.error('[MongoDB] 哈希表设置失败:', error);
throw error;
}
};
/**
* @description Redis兼容API - 哈希表获取字段
* @param {String} key 键名
* @param {String} field 字段名
* @return {Promise<any>} 返回字段值,不存在返回undefined
*/
MongoDB.prototype.hget = async function (key, field) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || typeof item.value !== 'object' || item.value === null) {
return undefined;
}
return item.value[field] !== undefined ? item.value[field] : undefined;
} catch (error) {
$.log.error('[MongoDB] 哈希表获取失败:', error);
throw error;
}
};
/**
* @description Redis兼容API - 哈希表获取所有字段和值
* @param {String} key 键名
* @return {Promise<Object>} 哈希表对象
*/
MongoDB.prototype.hgetall = async function (key) {
try {
const item = await this.findOne({
key: key
});
if (!item || typeof item.value !== 'object' || item.value === null) {
return {};
}
return item.value;
} catch (error) {
$.log.error('[MongoDB] 哈希表获取全部失败:', error);
return {};
}
};
/**
* @description Redis兼容API - 哈希表删除字段
* @param {String} key 键名
* @param {String|Array<String>} field 字段名或字段名数组
* @return {Promise<boolean>} 返回true表示成功删除,false表示未删除
*/
MongoDB.prototype.hdel = async function (key, fields) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
return 0;
}
// 统一将fields转换为数组格式
const fieldArray = Array.isArray(fields) ? fields : [fields];
if (fieldArray.length === 0) {
return 0;
}
await this._ensureConnected();
// 查找是否存在该键
const item = await this.findOne({ key: key });
// 如果键不存在或不是哈希表格式,返回0
if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) {
return 0;
}
// 计数删除的字段数量
let deletedCount = 0;
const updatedValue = { ...item.value };
// 遍历要删除的字段
fieldArray.forEach(field => {
if (field && Object.prototype.hasOwnProperty.call(updatedValue, field)) {
delete updatedValue[field];
deletedCount++;
}
});
// 如果删除了字段,更新数据库
if (deletedCount > 0) {
// 使用正确的方式更新数据库
await this.db.collection(this.table).updateOne(
{ key: key },
{ $set: { value: updatedValue, updated_at: new Date() } }
);
}
return deletedCount;
} catch (error) {
$.log.error('[MongoDB] hdel操作失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 检查键是否存在
* @param {String} key 键名
* @return {Promise<Boolean>} 是否存在
*/
MongoDB.prototype.has = async function (key) {
try {
const result = await this.findOne({
key: key
});
return result !== null;
} catch (error) {
$.log.error('[MongoDB] [has] 检查键是否存在失败:', error);
throw error;
}
};
/**
* @description Redis兼容API - 设置键过期时间(映射到ttl)
* @param {String} key 键名
* @param {Number} seconds 过期时间(秒)
* @return {Promise<Boolean>} 是否成功
*/
MongoDB.prototype.expire = async function (key, seconds) {
try {
// 参数验证
seconds = parseInt(seconds) || 0;
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
seconds,
cancel: false
};
this.emit('expire_before', beforeEventData);
// 如果事件处理器标记为取消,则返回默认值
if (beforeEventData.cancel) {
$.log.debug('[MongoDB] expire操作被事件处理器取消');
return false;
}
// 使用可能被修改的值
const {
key: finalKey,
seconds: finalSeconds
} = beforeEventData;
// 调用ttl方法设置过期时间
const result = await this.ttl(finalKey, finalSeconds);
// 触发后置事件
this.emit('expire_after', {
key: finalKey,
seconds: finalSeconds,
result
});
// 返回布尔值表示成功或失败
return result === true || (typeof result === 'number' && result >= -2);
} catch (error) {
// 记录错误日志
$.log.error('[MongoDB] expire方法执行错误', { error: error.message, key, seconds });
// 触发错误事件
this.emit('expire_error', {
key,
seconds,
error
});
return false;
}
};
/**
* @description Redis兼容API - 递增(通过addInt模拟)
* @param {String} key 键名
* @return {Promise<Number>} 递增后的值
*/
MongoDB.prototype.incr = async function (key, seconds = 0) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
seconds,
cancel: false
};
this.emit('incr_before', beforeEventData);
// 如果事件处理器标记为取消,则返回默认值
if (beforeEventData.cancel) {
$.log.debug('[MongoDB] incr操作被事件处理器取消');
return 0;
}
// 使用可能被修改的值
let {
key: finalKey,
seconds: finalSeconds
} = beforeEventData;
// 参数验证
if (!finalKey || typeof finalKey !== 'string') {
$.log.warn('[MongoDB] incr: key必须是非空字符串');
return 0;
}
await this._ensureConnected();
const result = await this.addInt(finalKey, 1, finalSeconds);
const returnResult = result !== undefined ? result : 0;
// 触发后置事件
this.emit('incr_after', {
key: finalKey,
seconds: finalSeconds,
result: returnResult,
success: true
});
return returnResult;
} catch (error) {
// 触发错误事件
this.emit('incr_error', {
key,
seconds,
error
});
$.log.error('[MongoDB] 递增操作失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 递减(通过addInt模拟)
* @param {String} key 键名
* @return {Promise<Number>} 递减后的值
*/
MongoDB.prototype.decr = async function (key, seconds = 0) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
seconds,
cancel: false
};
this.emit('decr_before', beforeEventData);
// 如果事件处理器标记为取消,则返回默认值
if (beforeEventData.cancel) {
$.log.debug('[MongoDB] decr操作被事件处理器取消');
return 0;
}
// 使用可能被修改的值
let {
key: finalKey,
seconds: finalSeconds
} = beforeEventData;
// 参数验证
if (!finalKey || typeof finalKey !== 'string') {
$.log.warn('[MongoDB] decr: key必须是非空字符串');
return 0;
}
await this._ensureConnected();
const result = await this.addInt(finalKey, -1, finalSeconds);
const returnResult = result !== undefined ? result : 0;
// 触发后置事件
this.emit('decr_after', {
key: finalKey,
seconds: finalSeconds,
result: returnResult,
success: true
});
return returnResult;
} catch (error) {
// 触发错误事件
this.emit('decr_error', {
key,
seconds,
error
});
$.log.error('[MongoDB] 递减操作失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 按指定值递增
* @param {String} key 键名
* @param {Number} value 增加值
* @return {Promise<Number>} 递增后的值
*/
MongoDB.prototype.incrby = async function (key, value = 1, seconds = 0) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value,
seconds,
cancel: false
};
this.emit('incrby_before', beforeEventData);
// 如果事件处理器标记为取消,则返回默认值
if (beforeEventData.cancel) {
$.log.debug('[MongoDB] incrby操作被事件处理器取消');
return 0;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalValue,
seconds: finalSeconds
} = beforeEventData;
// 参数验证
if (!finalKey || typeof finalKey !== 'string') {
$.log.warn('[MongoDB] incrby: key必须是非空字符串');
return 0;
}
// 确保value是数字
finalValue = Number(finalValue) || 0;
await this._ensureConnected();
const result = await this.addInt(finalKey, finalValue, finalSeconds);
const returnResult = result !== undefined ? result : 0;
// 触发后置事件
this.emit('incrby_after', {
key: finalKey,
value: finalValue,
seconds: finalSeconds,
result: returnResult,
success: true
});
return returnResult;
} catch (error) {
// 触发错误事件
this.emit('incrby_error', {
key,
value,
seconds,
error
});
$.log.error('[MongoDB] 递增操作失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 按指定值递减
* @param {String} key 键名
* @param {Number} value 减少值
* @return {Promise<Number>} 递减后的值
*/
MongoDB.prototype.decrby = async function (key, value = 1, seconds = 0) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value,
seconds,
cancel: false
};
this.emit('decrby_before', beforeEventData);
// 如果事件处理器标记为取消,则返回默认值
if (beforeEventData.cancel) {
$.log.debug('[MongoDB] decrby操作被事件处理器取消');
return 0;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalValue,
seconds: finalSeconds
} = beforeEventData;
// 参数验证
if (!finalKey || typeof finalKey !== 'string') {
$.log.warn('[MongoDB] decrby: key必须是非空字符串');
return 0;
}
// 确保value是数字
finalValue = Number(finalValue) || 0;
await this._ensureConnected();
const result = await this.addInt(finalKey, -finalValue, finalSeconds);
const returnResult = result !== undefined ? result : 0;
// 触发后置事件
this.emit('decrby_after', {
key: finalKey,
value: finalValue,
seconds: finalSeconds,
result: returnResult,
success: true
});
return returnResult;
} catch (error) {
// 触发错误事件
this.emit('decrby_error', {
key,
value,
seconds,
error
});
$.log.error('[MongoDB] 递减操作失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 按指定浮点值递增
* @param {String} key 键名
* @param {Number} value 浮点增加值
* @return {Promise<Number>} 递增后的值
*/
MongoDB.prototype.incrbyfloat = async function (key, value = 1.0, seconds = 0) {
try {
// 触发前置事件,允许修改或阻止操作
const beforeEventData = {
key,
value,
seconds,
cancel: false
};
this.emit('incrbyfloat_before', beforeEventData);
// 如果事件处理器标记为取消,则返回默认值
if (beforeEventData.cancel) {
$.log.debug('[MongoDB] incrbyfloat操作被事件处理器取消');
return 0.0;
}
// 使用可能被修改的值
let {
key: finalKey,
value: finalValue,
seconds: finalSeconds
} = beforeEventData;
// 参数验证
if (!finalKey || typeof finalKey !== 'string') {
$.log.warn('[MongoDB] incrbyfloat: key必须是非空字符串');
return 0.0;
}
// 确保value是数字
finalValue = Number(finalValue) || 0.0;
await this._ensureConnected();
const result = await this.addFloat(finalKey, finalValue, finalSeconds);
const returnResult = result !== undefined ? result : 0.0;
// 触发后置事件
this.emit('incrbyfloat_after', {
key: finalKey,
value: finalValue,
seconds: finalSeconds,
result: returnResult,
success: true
});
return returnResult;
} catch (error) {
// 触发错误事件
this.emit('incrbyfloat_error', {
key,
value,
seconds,
error
});
$.log.error('[MongoDB] 浮点递增操作失败:', error);
return 0.0;
}
};
/**
* @description Redis兼容API - 检查键是否存在
* @param {String|Array<String>} key 键名或键名数组
* @return {Promise<number>} 返回存在的键数量
*/
MongoDB.prototype.exists = async function (key) {
try {
await this._ensureConnected();
// 验证数组中的每个元素都是非空字符串
if (Array.isArray(key)) {
for (const k of key) {
if (typeof k !== 'string' || !k) {
throw new Error('all keys must be non-empty strings');
}
}
}
let existsCount = 0;
// 检查输入类型并处理
if (Array.isArray(key)) {
// 处理多键检查
const results = await this.find({
key: {
$in: key
}
});
existsCount = results.length;
} else {
// 处理单键检查
const result = await this.findOne({
key: key
});
existsCount = result !== null ? 1 : 0;
}
return existsCount;
} catch (error) {
$.log.error('[MongoDB] 检查键是否存在失败:', error);
throw error;
}
};
/**
* @description Redis兼容API - 设置键值
* @param {String} key 键名
* @param {*} value 值
* @return {Promise<Boolean>} 是否成功
*/
MongoDB.prototype._set = async function (key, value, expire) {
try {
await this._ensureConnected();
// 准备更新操作
const updateData = { $set: { value: value } };
// 如果提供了过期时间,添加expire_at字段
if (expire && expire > 0) {
updateData.$set.expire_at = new Date(Date.now() + expire * 1000);
}
// 直接更新数据库,避免调用_add方法形成循环引用
const result = await this.db.collection(this.table).updateOne(
{ key: key },
updateData,
{ upsert: true }
);
return true;
} catch (error) {
$.log.error('[MongoDB] 设置键值失败:', error);
return false; // 与CacheBase保持一致,返回false而不是抛出异常
}
};
/**
* @description Redis兼容API - 设置键值
* @param {String} key 键名
* @param {*} value 值
* @param {Number} seconds 过期时间(秒)
* @return {Promise<Boolean>} 是否成功
*/
MongoDB.prototype.set = async function (key, value, seconds = 0) {
return this._set(key, value, seconds);
};
/**
* @description Redis兼容API - 哈希表批量设置
* @param {String} key 键名
* @param {Array} fieldValues 字段值对数组,格式: [field1, value1, field2, value2, ...] 或对象格式 {field1: value1, field2: value2}
* @return {Promise<Number>} 设置的字段数量
*/
MongoDB.prototype.hmset = async function (key, ...fieldValues) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
$.log.warn('[MongoDB] hmset: 键名无效');
return 0;
}
// 处理可变参数或对象参数
let hashData = {};
if (fieldValues.length === 1 && typeof fieldValues[0] === 'object' && fieldValues[0] !== null && !Array
.isArray(fieldValues[0])) {
// 单个对象参数格式 {field1: value1, field2: value2}
hashData = fieldValues[0];
} else {
// 可变参数格式 field1, value1, field2, value2...
for (let i = 0; i < fieldValues.length; i += 2) {
if (i + 1 < fieldValues.length) {
hashData[fieldValues[i]] = fieldValues[i + 1];
}
}
}
const fieldsCount = Object.keys(hashData).length;
// 确保连接
await this._ensureConnected();
// 直接创建或更新哈希数据,不使用事件系统
const data = {
key: key,
value: hashData
};
const existing = await this.findOne({
key: key
});
if (existing) {
// 如果存在,合并现有数据
if (typeof existing.value === 'object' && existing.value !== null && !Array.isArray(existing
.value)) {
data.value = {
...existing.value,
...hashData
};
}
await this._set({
key: key
}, data);
} else {
await this.addObj(data);
}
return fieldsCount;
} catch (error) {
$.log.error('[MongoDB] [hmset] 哈希表批量设置失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 哈希表批量获取
* @param {String} key 键名
* @param {Array} fields 字段名数组
* @return {Promise<Array>} 与字段名顺序对应的数组值,不存在返回undefined
*/
MongoDB.prototype.hmget = async function (key, fields) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
return Array.isArray(fields) ? fields.map(() => null) : [null];
}
await this._ensureConnected();
const item = await this.findOne({ key: key });
// 确保fields是数组
const fieldArray = Array.isArray(fields) ? fields : [fields];
// 如果键不存在或不是哈希表格式,返回与字段数量相同的null数组
if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) {
return fieldArray.map(() => null);
}
// 返回与字段名顺序对应的数组值,不存在的字段返回null
const values = fieldArray.map(field => {
// 严格检查字段是否存在于对象中
return Object.prototype.hasOwnProperty.call(item.value, field) ? item.value[field] : null;
});
return values;
} catch (error) {
$.log.error('[MongoDB] hmget操作失败:', error);
// 错误时返回与fields长度相同的null数组
const fieldArray = Array.isArray(fields) ? fields : [fields];
return fieldArray.map(() => null);
}
};
/**
* @description Redis兼容API - 哈希表批量删除字段
* @param {String} key 键名
* @param {Array} fields 字段名数组
* @return {Promise<Boolean>} 是否成功
*/
MongoDB.prototype.hdelMulti = async function (key, fields) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || typeof item.value !== 'object' || item.value === null) {
return false;
}
let deleted = false;
fields.forEach(field => {
if (item.value[field] !== undefined) {
delete item.value[field];
deleted = true;
}
});
if (deleted) {
await this._set({
key: key
}, {
value: item.value
});
}
return deleted;
} catch (error) {
$.log.error('[MongoDB] 哈希表批量删除失败:', error);
return false;
}
};
/**
* @description Redis兼容API - 哈希表获取所有值
* @param {String} key 键名
* @return {Promise<Array>} 值数组
*/
MongoDB.prototype.hvals = async function (key) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || typeof item.value !== 'object' || item.value === null) {
return [];
}
return Object.values(item.value);
} catch (error) {
$.log.error('[MongoDB] 哈希表获取所有值失败:', error);
return [];
}
};
/**
* @description Redis兼容API - 哈希表获取所有字段名
* @param {String} key 键名
* @return {Promise<Array>} 字段名数组
*/
MongoDB.prototype.hkeys = async function (key) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || typeof item.value !== 'object' || item.value === null) {
return [];
}
return Object.keys(item.value);
} catch (error) {
$.log.error('[MongoDB] 哈希表获取所有字段名失败:', error);
return [];
}
};
/**
* @description Redis兼容API - 哈希表字段数量
* @param {String} key 键名
* @return {Promise<Number>} 字段数量
*/
MongoDB.prototype.hlen = async function (key) {
try {
// 参数验证
if (!key || typeof key !== 'string') {
return 0;
}
await this._ensureConnected();
const item = await this.findOne({ key: key });
// 如果键不存在或不是哈希表格式,返回0
if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) {
return 0;
}
// 使用Object.keys获取自有属性数量,这比for-in循环更高效
return Object.keys(item.value).length;
} catch (error) {
$.log.error('[MongoDB] hlen操作失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 哈希表字段是否存在
* @param {String} key 键名
* @param {String} field 字段名
* @return {Promise<Boolean>} 是否存在
*/
MongoDB.prototype.hexists = async function (key, field) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || typeof item.value !== 'object' || item.value === null) {
return false;
}
return field in item.value;
} catch (error) {
$.log.error('[MongoDB] 哈希表字段是否存在失败:', error);
return false;
}
};
/**
* @description Redis兼容API - 列表尾部添加元素
* @param {String} key 键名
* @param {*} value 值
* @return {Promise<Number>} 添加后列表长度
*/
MongoDB.prototype.rpush = async function (key, value) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
let list = item && Array.isArray(item.value) ? item.value : [];
// 将值转换为字符串,与Redis行为一致
list.push(value.toString());
await this._add(key, list);
return list.length;
} catch (error) {
$.log.error('[MongoDB] 列表尾部添加失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 列表头部添加元素
* @param {String} key 键名
* @param {*} value 值
* @return {Promise<Number>} 添加后列表长度
*/
MongoDB.prototype.lpush = async function (key, value) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
let list = item && Array.isArray(item.value) ? item.value : [];
// 将值转换为字符串,与Redis行为一致
list.unshift(value.toString());
await this._add(key, list);
return list.length;
} catch (error) {
$.log.error('[MongoDB] 列表头部添加失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 列表范围查询
* @param {String} key 键名
* @param {Number} start 起始索引
* @param {Number} stop 结束索引
* @return {Promise<Array>} 元素数组
*/
MongoDB.prototype.lrange = async function (key, start, stop) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || !Array.isArray(item.value)) {
return [];
}
// 处理负数索引
let startIndex = start < 0 ? item.value.length + start : start;
let endIndex = stop < 0 ? item.value.length + stop : stop;
// 确保索引有效
startIndex = Math.max(0, Math.min(startIndex, item.value.length - 1));
endIndex = Math.max(startIndex, Math.min(endIndex, item.value.length - 1));
return item.value.slice(startIndex, endIndex + 1);
} catch (error) {
$.log.error('[MongoDB] 列表范围查询失败:', error);
return [];
}
};
/**
* @description Redis兼容API - 获取列表指定索引元素
* @param {String} key 键名
* @param {Number} index 索引
* @return {Promise<any>} 元素值
*/
MongoDB.prototype.lindex = async function (key, index) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || !Array.isArray(item.value)) {
return null;
}
// 处理负数索引
const realIndex = index < 0 ? item.value.length + index : index;
if (realIndex < 0 || realIndex >= item.value.length) {
return null;
}
return item.value[realIndex];
} catch (error) {
$.log.error('[MongoDB] 获取列表索引元素失败:', error);
return null;
}
};
/**
* @description Redis兼容API - 设置列表指定索引元素
* @param {String} key 键名
* @param {Number} index 索引
* @param {*} value 值
* @return {Promise<Boolean>} 是否成功
*/
MongoDB.prototype.lset = async function (key, index, value) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || !Array.isArray(item.value)) {
return false;
}
// 处理负数索引
const realIndex = index < 0 ? item.value.length + index : index;
if (realIndex < 0 || realIndex >= item.value.length) {
return false;
}
item.value[realIndex] = value;
await this._set({
key: key
}, {
value: item.value
});
return true;
} catch (error) {
$.log.error('[MongoDB] 设置列表索引元素失败:', error);
return false;
}
};
/**
* @description Redis兼容API - 删除列表元素
* @param {String} key 键名
* @param {Number} count 删除数量
* @param {*} value 要删除的值
* @return {Promise<Number>} 删除的元素数量
*/
MongoDB.prototype.lrem = async function (key, count, value) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || !Array.isArray(item.value)) {
return 0;
}
let list = [...item.value];
let removedCount = 0;
if (count > 0) {
// 从头部删除count个等于value的元素
for (let i = 0; i < list.length && removedCount < count; i++) {
if (JSON.stringify(list[i]) === JSON.stringify(value)) {
list.splice(i, 1);
removedCount++;
i--; // 调整索引
}
}
} else if (count < 0) {
// 从尾部删除|count|个等于value的元素
count = Math.abs(count);
for (let i = list.length - 1; i >= 0 && removedCount < count; i--) {
if (JSON.stringify(list[i]) === JSON.stringify(value)) {
list.splice(i, 1);
removedCount++;
}
}
} else {
// 删除所有等于value的元素
list = list.filter(item => JSON.stringify(item) !== JSON.stringify(value));
removedCount = item.value.length - list.length;
}
if (removedCount > 0) {
await this._set({
key: key
}, {
value: list
});
}
return removedCount;
} catch (error) {
$.log.error('[MongoDB] 删除列表元素失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 裁剪列表
* @param {String} key 键名
* @param {Number} start 起始索引
* @param {Number} stop 结束索引
* @return {Promise<Boolean>} 是否成功
*/
MongoDB.prototype.ltrim = async function (key, start, stop) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || !Array.isArray(item.value)) {
return false;
}
// 使用lrange的逻辑获取裁剪后的列表
const trimmed = await this.lrange(key, start, stop);
await this._set({
key: key
}, {
value: trimmed
});
return true;
} catch (error) {
$.log.error('[MongoDB] 裁剪列表失败:', error);
return false;
}
};
/**
* @description Redis兼容API - 列表长度
* @param {String} key 键名
* @return {Promise<Number>} 列表长度
*/
MongoDB.prototype.llen = async function (key) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || !Array.isArray(item.value)) {
return 0;
}
return item.value.length;
} catch (error) {
$.log.error('[MongoDB] 获取列表长度失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 集合批量添加
* @param {String} key 键名
* @param {Array} members 成员数组
* @return {Promise<Number>} 添加的成员数量
*/
MongoDB.prototype.saddMulti = async function (key, members) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
let set = item && Array.isArray(item.value) ? [...new Set(item.value)] : [];
let addedCount = 0;
members.forEach(member => {
if (!set.some(existing => JSON.stringify(existing) === JSON.stringify(member))) {
set.push(member);
addedCount++;
}
});
if (addedCount > 0) {
await this._add(key, set);
}
return addedCount;
} catch (error) {
$.log.error('[MongoDB] 集合并发添加失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 集合批量删除
* @param {String} key 键名
* @param {Array} members 成员数组
* @return {Promise<Number>} 删除的成员数量
*/
MongoDB.prototype.sremMulti = async function (key, members) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || !Array.isArray(item.value)) {
return 0;
}
let set = [...new Set(item.value)];
const originalLength = set.length;
// 创建要删除的值的字符串表示集合用于快速查找
const membersToRemove = new Set(members.map(m => JSON.stringify(m)));
set = set.filter(member => !membersToRemove.has(JSON.stringify(member)));
const removedCount = originalLength - set.length;
if (removedCount > 0) {
await this._add(key, set);
}
return removedCount;
} catch (error) {
$.log.error('[MongoDB] 集合并发删除失败:', error);
return 0;
}
};
/**
* @description Redis兼容API - 集合差集
* @param {String} key1 第一个集合键
* @param {String} key2 第二个集合键
* @return {Promise<Array>} 差集数组
*/
MongoDB.prototype.sdiff = async function (key1, key2) {
try {
await this._ensureConnected();
const item1 = await this.findOne({
key: key1
});
const item2 = await this.findOne({
key: key2
});
const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
// 创建第二个集合值的字符串表示用于快速查找
const set2Str = new Set(set2.map(m => JSON.stringify(m)));
// 找出在set1中但不在set2中的元素
return set1.filter(member => !set2Str.has(JSON.stringify(member)));
} catch (error) {
$.log.error('[MongoDB] 集合差集失败:', error);
return [];
}
};
/**
* @description Redis兼容API - 集合交集
* @param {String} key1 第一个集合键
* @param {String} key2 第二个集合键
* @return {Promise<Array>} 交集数组
*/
MongoDB.prototype.sinter = async function (key1, key2) {
try {
await this._ensureConnected();
const item1 = await this.findOne({
key: key1
});
const item2 = await this.findOne({
key: key2
});
const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
// 创建较小集合的字符串表示用于快速查找,优化性能
let smallerSet, largerSet;
if (set1.length <= set2.length) {
smallerSet = set1;
largerSet = set2;
} else {
smallerSet = set2;
largerSet = set1;
}
const largerSetStr = new Set(largerSet.map(m => JSON.stringify(m)));
// 找出同时存在于两个集合中的元素
return smallerSet.filter(member => largerSetStr.has(JSON.stringify(member)));
} catch (error) {
$.log.error('[MongoDB] 集合并集失败:', error);
return [];
}
};
/**
* @description Redis兼容API - 集合并集
* @param {String} key1 第一个集合键
* @param {String} key2 第二个集合键
* @return {Promise<Array>} 并集数组
*/
MongoDB.prototype.sunion = async function (key1, key2) {
try {
await this._ensureConnected();
const item1 = await this.findOne({
key: key1
});
const item2 = await this.findOne({
key: key2
});
const set1 = item1 && Array.isArray(item1.value) ? item1.value : [];
const set2 = item2 && Array.isArray(item2.value) ? item2.value : [];
// 合并并去重
const combined = [...set1, ...set2];
// 使用Map进行对象去重,基于JSON字符串表示
const uniqueMap = new Map();
combined.forEach(item => {
const key = JSON.stringify(item);
if (!uniqueMap.has(key)) {
uniqueMap.set(key, item);
}
});
return Array.from(uniqueMap.values());
} catch (error) {
$.log.error('[MongoDB] 集合并集失败:', error);
return [];
}
};
/**
* @description Redis兼容API - 集合成员数量
* @param {String} key 键名
* @return {Promise<Number>} 成员数量
*/
MongoDB.prototype.scard = async function (key) {
try {
await this._ensureConnected();
const item = await this.findOne({
key: key
});
if (!item || !Array.isArray(item.value)) {