使用 Node.js、ioredis 和 TS 封装 Redis 操作.md
Redis 是一个高性能的内存数据库,广泛应用于缓存和消息队列等场景。本文将介绍如何使用 Node.js、ioredis 和 TypeScript 封装 Redis 操作,使其更易于在项目中使用。
数据类型 | 描述 | 常用场景 |
---|---|---|
字符串(String) | 最基本的类型,存储简单的键值对,值可以是字符串、整数或浮点数。 | 缓存、计数器、会话数据、配置项 |
哈希(Hash) | 键值对集合,适合存储对象(如用户信息),可以通过字段访问和修改。 | 用户信息、对象存储、配置项 |
列表(List) | 有序链表,支持从两端插入和删除元素,适合实现消息队列等。 | 消息队列、任务列表、最新消息 |
集合(Set) | 无序集合,自动去重,适合存储唯一值集合,支持集合运算(交集、并集等)。 | 标签、好友关系、推荐系统、去重操作 |
有序集合(ZSet) | 带有分数的有序集合,按分数排序,适合实现排行榜等。 | 排行榜、延迟队列、带权重的消息 |
位图(Bitmap) | 位数组,适合进行位操作和状态记录,如用户签到、在线状态等。 | 用户签到、在线状态、特征标记 |
HyperLogLog | 基数估计算法,适合统计大规模数据的基数(如独立用户数),占用内存小。 | 独立用户统计、去重计数 |
地理空间索引(Geo) | 存储地理位置信息,支持半径查询和距离计算,适合实现地理位置服务。 | 附近的人/地点搜索、距离计算、地理围栏 |
创建基础缓存类
为了方便管理和使用 Redis,我们首先创建一个基础缓存类 BaseCache
,它包含了一些通用的 Redis 操作方法:
import { ChainableCommander, Redis as RedisClient } from "ioredis";
import { CacheOption } from "./type";
/**
* 基础缓存类
*/
export class BaseCache {
protected option: CacheOption; // 缓存选项
protected redis: RedisClient; // Redis 客户端实例
protected prefix: string; // 缓存键前缀
/**
* 构造函数
* @param prefix - 缓存键前缀
* @param option - 缓存选项
* @param redisClient - Redis 客户端实例
*/
constructor(prefix: string, option: CacheOption, redisClient: RedisClient) {
this.option = option;
this.redis = redisClient;
this.prefix = prefix;
}
/**
* 创建缓存键
* @param value - 可选的键值
* @returns 生成的缓存键
*/
protected createKey(value?: string | number | boolean): string {
let key = `${this.prefix}-${this.option.appName}-${this.option.funcName}`;
if (value !== undefined && value !== null) {
key = `${key}-${value}`;
}
return key;
}
/**
* 将数据转换为字符串
* @param data - 要转换的数据
* @returns 转换后的字符串
*/
protected dataToString(data: any): string {
switch (typeof data) {
case "object":
return JSON.stringify(data);
case "boolean":
return data ? "true" : "false";
default:
return String(data);
}
}
/**
* 创建管道操作
* @returns 创建的管道实例
*/
createPipeline() {
return this.redis.pipeline();
}
/**
* 执行事务操作
* @param commands - 包含事务命令的回调函数
* @returns 事务执行结果的 Promise
*/
async executeTransaction(
commands: (pipeline: ChainableCommander) => void
): Promise<any> {
const pipeline = this.redis.multi();
commands(pipeline);
return pipeline.exec();
}
}
封装字符串操作
接下来,我们创建一个 CacheString
类来封装 Redis 的字符串操作:
import { BaseCache } from "./base";
export class CacheString extends BaseCache {
/**
* 设置字符串值
* @param key - 缓存键
* @param value - 缓存值
* @param EX - 过期时间(秒),可选
* @returns 设置结果
*/
async set(key: string, value: any, EX: number = -1): Promise<string | null> {
const redisKey = this.createKey(key);
const redisValue = this.dataToString(value);
if (EX > 0) {
return await this.redis.set(redisKey, redisValue, "EX", EX);
} else {
return await this.redis.set(redisKey, redisValue);
}
}
/**
* 获取字符串值
* @param key - 缓存键
* @returns 缓存值
*/
async get(key: string): Promise<string | null> {
return await this.redis.get(this.createKey(key));
}
/**
* 检查键是否存在
* @param key - 缓存键
* @returns 键是否存在
*/
async exists(key: string): Promise<number> {
return await this.redis.exists(this.createKey(key));
}
/**
* 删除键
* @param key - 缓存键
* @returns 删除的键数量
*/
async delete(key: string): Promise<number> {
return await this.redis.del(this.createKey(key));
}
/**
* 增加键的值
* @param key - 缓存键
* @param increment - 增加的值
* @returns 增加后的值
*/
async increment(key: string, increment: number = 1): Promise<number> {
return await this.redis.incrby(this.createKey(key), increment);
}
/**
* 减少键的值
* @param key - 缓存键
* @param decrement - 减少的值
* @returns 减少后的值
*/
async decrement(key: string, decrement: number = 1): Promise<number> {
return await this.redis.decrby(this.createKey(key), decrement);
}
/**
* 获取并设置新值
* @param key - 缓存键
* @param value - 新值
* @returns 旧值
*/
async getSet(key: string, value: any): Promise<string | null> {
const redisKey = this.createKey(key);
const redisValue = this.dataToString(value);
return await this.redis.getset(redisKey, redisValue);
}
/**
* 设置多个键值对
* @param keyValuePairs - 键值对数组
* @returns 设置结果
*/
async setMultiple(
keyValuePairs: { key: string; value: any }[]
): Promise<string> {
const flattenedPairs = keyValuePairs.flatMap(({ key, value }) => [
this.createKey(key),
this.dataToString(value),
]);
return await this.redis.mset(flattenedPairs);
}
/**
* 获取多个键值对
* @param keys - 键数组
* @returns 值数组
*/
async getMultiple(keys: string[]): Promise<(string | null)[]> {
const redisKeys = keys.map((key) => this.createKey(key));
return await this.redis.mget(redisKeys);
}
/**
* 设置键值对,如果键不存在
* @param key - 缓存键
* @param value - 缓存值
* @returns 是否成功设置
*/
async setnx(key: string, value: any): Promise<number> {
const redisKey = this.createKey(key);
const redisValue = this.dataToString(value);
return await this.redis.setnx(redisKey, redisValue);
}
/**
* 设置键值对,如果键不存在,并设置过期时间
* @param key - 缓存键
* @param value - 缓存值
* @param EX - 过期时间(秒)
* @returns 是否成功设置
*/
async setnxWithExpire(
key: string,
value: any,
EX: number
): Promise<string | null> {
const redisKey = this.createKey(key);
const redisValue = this.dataToString(value);
return await this.redis.set(redisKey, redisValue, "EX", EX, "NX");
} // 重载方案 set(key: RedisKey, value: string | Buffer | number, secondsToken: "EX", seconds: number | string, nx: "NX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>;
}
封装集合操作
然后,我们创建一个 CacheSet
类来封装 Redis 的集合操作:
import { BaseCache } from "./base";
export class CacheSet extends BaseCache {
/**
* 添加一个元素到集合
* @param value - 要添加的元素
* @param suffix - 可选的键后缀
* @returns 是否成功添加
*/
async add(value: any, suffix: string = ""): Promise<boolean> {
const result = await this.redis.sadd(
this.createKey(suffix),
this.dataToString(value)
);
return result > 0;
}
/**
* 批量添加元素到集合
* @param values - 要添加的元素数组
* @param suffix - 可选的键后缀
* @returns 是否成功添加
*/
async addBatch(values: any[], suffix: string = ""): Promise<boolean> {
const args = values.map((value) => this.dataToString(value));
const result = await this.redis.sadd(this.createKey(suffix), ...args);
return result > 0;
}
/**
* 从集合中移除一个元素
* @param value - 要移除的元素
* @param suffix - 可选的键后缀
* @returns 是否成功移除
*/
async remove(value: any, suffix: string = ""): Promise<boolean> {
const result = await this.redis.srem(
this.createKey(suffix),
this.dataToString(value)
);
return result > 0;
}
/**
* 批量移除集合中的元素
* @param values - 要移除的元素数组
* @param suffix - 可选的键后缀
* @returns 是否成功移除
*/
async removeBatch(values: any[], suffix: string = ""): Promise<boolean> {
const args = values.map((value) => this.dataToString(value));
const result = await this.redis.srem(this.createKey(suffix), ...args);
return result > 0;
}
/**
* 获取集合中的所有元素
* @param suffix - 可选的键后缀
* @returns 集合中的所有元素
*/
async members(suffix: string = ""): Promise<string[]> {
return await this.redis.smembers(this.createKey(suffix));
}
/**
* 检查元素是否在集合中
* @param value - 要检查的元素
* @param suffix - 可选的键后缀
* @returns 元素是否存在
*/
async exists(value: any, suffix: string = ""): Promise<number> {
return await this.redis.sismember(
this.createKey(suffix),
this.dataToString(value)
);
}
/**
* 获取集合的大小
* @param suffix - 可选的键后缀
* @returns 集合的大小
*/
async size(suffix: string = ""): Promise<number> {
return await this.redis.scard(this.createKey(suffix));
}
/**
* 随机弹出一个元素
* @param suffix - 可选的键后缀
* @returns 弹出的元素
*/
async pop(suffix: string = ""): Promise<string | null> {
return await this.redis.spop(this.createKey(suffix));
}
/**
* 随机弹出多个元素
* @param count - 要弹出的元素数量
* @param suffix - 可选的键后缀
* @returns 弹出的元素数组
*/
async popMultiple(count: number, suffix: string = ""): Promise<string[]> {
return await this.redis.spop(this.createKey(suffix), count);
}
/**
* 随机获取一个元素
* @param suffix - 可选的键后缀
* @returns 随机获取的元素
*/
async randomMember(suffix: string = ""): Promise<string | null> {
return await this.redis.srandmember(this.createKey(suffix));
}
/**
* 随机获取多个元素
* @param count - 要获取的元素数量
* @param suffix - 可选的键后缀
* @returns 随机获取的元素数组
*/
async randomMembers(count: number, suffix: string = ""): Promise<string[]> {
return await this.redis.srandmember(this.createKey(suffix), count);
}
/**
* 获取多个集合的并集
* @param suffixes - 键后缀数组
* @returns 并集结果
*/
async union(...suffixes: string[]): Promise<string[]> {
const keys = suffixes.map((suffix) => this.createKey(suffix));
return await this.redis.sunion(...keys);
}
/**
* 获取多个集合的交集
* @param suffixes - 键后缀数组
* @returns 交集结果
*/
async intersect(...suffixes: string[]): Promise<string[]> {
const keys = suffixes.map((suffix) => this.createKey(suffix));
return await this.redis.sinter(...keys);
}
/**
* 获取多个集合的差集
* @param suffixes - 键后缀数组
* @returns 差集结果
*/
async difference(...suffixes: string[]): Promise<string[]> {
const keys = suffixes.map((suffix) => this.createKey(suffix));
return await this.redis.sdiff(...keys);
}
}
封装列表操作
接下来,我们创建一个 CacheList
类来封装 Redis 的列表操作:
import { BaseCache } from "./base";
export class CacheList extends BaseCache {
/**
* 从右侧推入元素到列表
* @param suffix - 可选的键后缀
* @param values - 要推入的元素
* @returns 列表的长度
*/
async push(suffix: string = "", ...values: any[]): Promise<number> {
const args = values.map((value) => this.dataToString(value));
console.log(args, values,this.createKey(suffix));
return await this.redis.rpush(this.createKey(suffix), ...args);
}
/**
* 从右侧弹出元素
* @param suffix - 可选的键后缀
* @returns 弹出的元素
*/
async pop(suffix: string = ""): Promise<string | null> {
return await this.redis.rpop(this.createKey(suffix));
}
/**
* 获取列表的长度
* @param suffix - 可选的键后缀
* @returns 列表的长度
*/
async length(suffix: string = ""): Promise<number> {
return await this.redis.llen(this.createKey(suffix));
}
/**
* 获取列表的指定范围内的元素
* @param start - 起始索引
* @param stop - 结束索引
* @param suffix - 可选的键后缀
* @returns 指定范围内的元素
*/
async range(
start: number,
stop: number,
suffix: string = ""
): Promise<string[]> {
return await this.redis.lrange(this.createKey(suffix), start, stop);
}
/**
* 从左侧推入元素到列表
* @param suffix - 可选的键后缀
* @param values - 要推入的元素
* @returns 列表的长度
*/
async lpush(suffix: string = "", ...values: any[]): Promise<number> {
const args = values.map((value) => this.dataToString(value));
return await this.redis.lpush(this.createKey(suffix), ...args);
}
/**
* 从左侧弹出元素
* @param suffix - 可选的键后缀
* @returns 弹出的元素
*/
async lpop(suffix: string = ""): Promise<string | null> {
return await this.redis.lpop(this.createKey(suffix));
}
/**
* 修剪列表,只保留指定范围内的元素
* @param start - 起始索引
* @param stop - 结束索引
* @param suffix - 可选的键后缀
* @returns 修剪结果
*/
async trim(
start: number,
stop: number,
suffix: string = ""
): Promise<string> {
return await this.redis.ltrim(this.createKey(suffix), start, stop);
}
/**
* 设置指定索引处的值
* @param index - 索引
* @param value - 新值
* @param suffix - 可选的键后缀
* @returns 设置结果
*/
async set(index: number, value: any, suffix: string = ""): Promise<string> {
return await this.redis.lset(
this.createKey(suffix),
index,
this.dataToString(value)
);
}
/**
* 根据值移除元素
* @param count - 移除的元素数量
* @param value - 要移除的元素
* @param suffix - 可选的键后缀
* @returns 移除的元素数量
*/
async remove(
count: number,
value: any,
suffix: string = ""
): Promise<number> {
return await this.redis.lrem(
this.createKey(suffix),
count,
this.dataToString(value)
);
}
/**
* 在指定元素前或后插入新元素
* @param pivot - 参考元素
* @param value - 新元素
* @param position - 插入位置("BEFORE" 或 "AFTER")
* @param suffix - 可选的键后缀
* @returns 插入后列表的长度
*/
async insert(
pivot: any,
value: any,
position: "BEFORE" | "AFTER",
suffix: string = ""
): Promise<number> {
const key = this.createKey(suffix);
const pivotStr = this.dataToString(pivot);
const valueStr = this.dataToString(value);
if (position === "BEFORE") {
return await this.redis.linsert(key, "BEFORE", pivotStr, valueStr);
} else if (position === "AFTER") {
return await this.redis.linsert(key, "AFTER", pivotStr, valueStr);
} else {
throw new Error(
"Invalid position argument. Must be 'BEFORE' or 'AFTER'."
);
}
}
}
封装哈希操作
然后,我们创建一个 CacheHash
类来封装 Redis 的哈希操作:
import { BaseCache } from "./base";
export class CacheHash extends BaseCache {
/**
* 设置哈希表中的字段值
* @param key - 字段名
* @param value - 字段值
* @param suffix - 可选的键后缀
* @returns 是否成功设置
*/
async set(
key: string,
value: Record<string, any>,
suffix: string = ""
): Promise<boolean> {
const result = await this.redis.hset(
this.createKey(suffix),
key,
JSON.stringify(value)
);
return result > 0;
}
/**
* 获取哈希表中的字段值
* @param key - 字段名
* @param suffix - 可选的键后缀
* @returns 字段值或 null
*/
async get(
key: string,
suffix: string = ""
): Promise<Record<string, any> | null> {
const result = await this.redis.hget(this.createKey(suffix), key);
return result ? JSON.parse(result) : null;
}
/**
* 获取哈希表中的所有字段和值
* @param suffix - 可选的键后缀
* @returns 哈希表中的所有字段和值
*/
async getAll(suffix: string = ""): Promise<Record<string, any>> {
const result = await this.redis.hgetall(this.createKey(suffix));
return Object.fromEntries(
Object.entries(result).map(([key, value]) => [key, JSON.parse(value)])
);
}
/**
* 检查哈希表中是否存在指定字段
* @param key - 字段名
* @param suffix - 可选的键后缀
* @returns 是否存在
*/
async exists(key: string, suffix: string = ""): Promise<number> {
return await this.redis.hexists(this.createKey(suffix), key);
}
/**
* 删除哈希表中的指定字段
* @param key - 字段名
* @param suffix - 可选的键后缀
* @returns 是否成功删除
*/
async delete(key: string, suffix: string = ""): Promise<boolean> {
const result = await this.redis.hdel(this.createKey(suffix), key);
return result > 0;
}
/**
* 获取哈希表中所有字段的数量
* @param suffix - 可选的键后缀
* @returns 字段数量
*/
async length(suffix: string = ""): Promise<number> {
return await this.redis.hlen(this.createKey(suffix));
}
/**
* 获取哈希表中的所有字段名
* @param suffix - 可选的键后缀
* @returns 所有字段名
*/
async keys(suffix: string = ""): Promise<string[]> {
return await this.redis.hkeys(this.createKey(suffix));
}
/**
* 获取哈希表中的所有字段值
* @param suffix - 可选的键后缀
* @returns 所有字段值
*/
async values(suffix: string = ""): Promise<any[]> {
const result = await this.redis.hvals(this.createKey(suffix));
return result.map((value) => JSON.parse(value));
}
/**
* 为哈希表中的字段值加上指定增量
* @param key - 字段名
* @param increment - 增量
* @param suffix - 可选的键后缀
* @returns 增加后的值
*/
async incrementBy(
key: string,
increment: number,
suffix: string = ""
): Promise<number> {
return await this.redis.hincrby(this.createKey(suffix), key, increment);
}
/**
* 为哈希表中的字段值加上指定浮点增量
* @param key - 字段名
* @param increment - 浮点增量
* @param suffix - 可选的键后缀
* @returns 增加后的值
*/
async incrementByFloat(
key: string,
increment: number,
suffix: string = ""
): Promise<string> {
return await this.redis.hincrbyfloat(
this.createKey(suffix),
key,
increment
);
}
}
封装有序集合操作
接下来,我们创建一个 CacheSortedSet
类来封装 Redis 的有序集合操作:
import { BaseCache } from "./base";
import { ZMember } from "./type";
export class CacheSortedSet extends BaseCache {
/**
* 添加一个成员到有序集合
* @param score - 成员的分数
* @param value - 成员的值
* @param suffix - 可选的键后缀
* @returns 是否成功添加
*/
async add(score: number, value: any, suffix: string = ""): Promise<boolean> {
const result = await this.redis.zadd(
this.createKey(suffix),
score,
this.dataToString(value)
);
return result > 0;
}
/**
* 添加多个成员到有序集合
* @param members - 成员数组
* @param suffix - 可选的键后缀
* @returns 是否成功添加
*/
async adds(members: ZMember[], suffix: string = ""): Promise<boolean> {
const args = members.flatMap((member) => [
member.score,
this.dataToString(member.value),
]);
const result = await this.redis.zadd(this.createKey(suffix), ...args);
return result > 0;
}
/**
* 移除有序集合中的一个成员
* @param value - 成员的值
* @param suffix - 可选的键后缀
* @returns 是否成功移除
*/
async remove(value: any, suffix: string = ""): Promise<boolean> {
const result = await this.redis.zrem(
this.createKey(suffix),
this.dataToString(value)
);
return result > 0;
}
/**
* 计算有序集合中指定分数范围的成员数量
* @param min - 最小分数
* @param max - 最大分数
* @param suffix - 可选的键后缀
* @returns 成员数量
*/
async count(min: number, max: number, suffix: string = ""): Promise<number> {
return await this.redis.zcount(this.createKey(suffix), min, max);
}
/**
* 获取有序集合中指定分数范围的成员
* @param min - 最小分数
* @param max - 最大分数
* @param suffix - 可选的键后缀
* @returns 成员数组
*/
async rangeByScore(
min: number,
max: number,
suffix: string = ""
): Promise<string[]> {
return await this.redis.zrangebyscore(this.createKey(suffix), min, max);
}
/**
* 获取有序集合中的元素数量
* @param suffix - 可选的键后缀
* @returns 元素数量
*/
async length(suffix: string = ""): Promise<number> {
return await this.redis.zcard(this.createKey(suffix));
}
/**
* 获取有序集合中指定范围内的元素
* @param start - 起始索引
* @param stop - 结束索引
* @param suffix - 可选的键后缀
* @returns 元素数组
*/
async range(
start: number,
stop: number,
suffix: string = ""
): Promise<string[]> {
return await this.redis.zrange(this.createKey(suffix), start, stop);
}
/**
* 获取有序集合中指定范围内的元素及其分数
* @param start - 起始索引
* @param stop - 结束索引
* @param suffix - 可选的键后缀
* @returns 元素及其分数数组
*/
async rangeWithScores(
start: number,
stop: number,
suffix: string = ""
): Promise<{ value: string; score: number }[]> {
const result = await this.redis.zrange(
this.createKey(suffix),
start,
stop,
"WITHSCORES"
);
const members: { value: string; score: number }[] = [];
for (let i = 0; i < result.length; i += 2) {
members.push({ value: result[i], score: parseFloat(result[i + 1]) });
}
return members;
}
/**
* 获取有序集合中指定成员的分数
* @param value - 成员的值
* @param suffix - 可选的键后缀
* @returns 成员的分数
*/
async score(value: any, suffix: string = ""): Promise<number | null> {
const result = await this.redis.zscore(
this.createKey(suffix),
this.dataToString(value)
);
return result ? parseFloat(result) : null;
}
/**
* 为有序集合中的成员的分数加上指定增量
* @param value - 成员的值
* @param increment - 增量
* @param suffix - 可选的键后缀
* @returns 增加后的分数
*/
async incrementBy(
value: any,
increment: number,
suffix: string = ""
): Promise<string> {
return await this.redis.zincrby(
this.createKey(suffix),
increment,
this.dataToString(value)
);
}
/**
* 删除有序集合中指定分数范围的成员
* @param min - 最小分数
* @param max - 最大分数
* @param suffix - 可选的键后缀
* @returns 删除的成员数量
*/
async removeRangeByScore(
min: number,
max: number,
suffix: string = ""
): Promise<number> {
return await this.redis.zremrangebyscore(this.createKey(suffix), min, max);
}
/**
* 删除有序集合中指定排名范围的成员
* @param start - 起始索引
* @param stop - 结束索引
* @param suffix - 可选的键后缀
* @returns 删除的成员数量
*/
async removeRangeByRank(
start: number,
stop: number,
suffix: string = ""
): Promise<number> {
return await this.redis.zremrangebyrank(
this.createKey(suffix),
start,
stop
);
}
}
封装位操作
然后,我们创建一个 CacheBitField
类来封装 Redis 的位操作:
import { BaseCache } from "./base";
import { BitCountRange } from "./type";
export class CacheBitField extends BaseCache {
/**
* 设置位域的某一位
* @param offset - 位的偏移量
* @param value - 位的值(true 或 false)
* @param suffix - 可选的键后缀
* @returns 之前的位值
*/
async setbit(
offset: number,
value: boolean,
suffix: string = ""
): Promise<boolean> {
const result = await this.redis.setbit(
this.createKey(suffix),
offset,
value ? 1 : 0
);
return result > 0;
}
/**
* 获取位域的某一位
* @param offset - 位的偏移量
* @param suffix - 可选的键后缀
* @returns 位值(0 或 1)
*/
async getbit(offset: number, suffix: string = ""): Promise<number> {
return await this.redis.getbit(this.createKey(suffix), offset);
}
/**
* 计算位域中指定范围内的位数
* @param option - 位数计算范围和模式
* @param suffix - 可选的键后缀
* @returns 位数
*/
async count(option: BitCountRange, suffix: string = ""): Promise<number> {
const key = this.createKey(suffix);
const { start, end, mode } = option;
if (mode === "BYTE") {
return await this.redis.bitcount(key, start, end, "BYTE");
} else if (mode === "BIT") {
return await this.redis.bitcount(key, start, end, "BIT");
} else {
return await this.redis.bitcount(key, start, end);
}
}
/**
* 对位域执行位操作
* @param operation - 位操作类型(AND, OR, XOR, NOT)
* @param destKey - 目标键
* @param keys - 源键数组
* @returns 结果位数
*/
async bitop(
operation: "AND" | "OR" | "XOR" | "NOT",
destKey: string,
keys: string[]
): Promise<number> {
return await this.redis.bitop(
operation,
destKey,
...keys.map(this.createKey.bind(this))
);
}
}
封装 HyperLogLog 操作
最后,我们创建一个 CacheHyperLogLog
类来封装 Redis 的 HyperLogLog 操作:
当然,我们继续封装 CacheHyperLogLog
类:
import { BaseCache } from "./base";
export class CacheHyperLogLog extends BaseCache {
/**
* 添加元素到 HyperLogLog
* @param suffix - 可选的键后缀
* @param values - 要添加的元素
* @returns 是否成功添加
*/
async add(suffix: string = "", ...values: any[]): Promise<boolean> {
const args = values.map((value) => this.dataToString(value));
const result = await this.redis.pfadd(this.createKey(suffix), ...args);
return result > 0;
}
/**
* 获取 HyperLogLog 中的基数估算值
* @param suffix - 可选的键后缀
* @returns 基数估算值
*/
async count(suffix: string = ""): Promise<number> {
return await this.redis.pfcount(this.createKey(suffix));
}
/**
* 合并多个 HyperLogLog
* @param suffix - 目标 HyperLogLog 的键后缀
* @param keys - 要合并的 HyperLogLog 的键
* @returns 是否成功合并
*/
async merge(suffix: string = "", ...keys: string[]): Promise<boolean> {
const result = await this.redis.pfmerge(
this.createKey(suffix),
...keys.map(this.createKey.bind(this))
);
return result === "OK";
}
/**
* 删除 HyperLogLog
* @param suffix - 可选的键后缀
* @returns 是否成功删除
*/
async delete(suffix: string = ""): Promise<boolean> {
const result = await this.redis.del(this.createKey(suffix));
return result > 0;
}
/**
* 重命名 HyperLogLog
* @param oldSuffix - 旧的键后缀
* @param newSuffix - 新的键后缀
* @returns 是否成功重命名
*/
async rename(oldSuffix: string, newSuffix: string): Promise<boolean> {
const result = await this.redis.rename(
this.createKey(oldSuffix),
this.createKey(newSuffix)
);
return result === "OK";
}
/**
* 获取 HyperLogLog 的内存使用情况
* @param suffix - 可选的键后缀
* @returns 内存使用情况(字节)
*/
async memoryUsage(suffix: string = ""): Promise<number | null> {
return await this.redis.memory("USAGE", this.createKey(suffix));
}
}
使用示例
现在我们已经完成了对 Redis 的封装,下面是如何使用这些封装类的示例:
import Redis from "ioredis";
import { CacheBitField } from "./TSRedisCacheKit/BitField";
import { CacheHash } from "./TSRedisCacheKit/Hash";
import { CacheHyperLogLog } from "./TSRedisCacheKit/HyperLogLog";
import { CacheList } from "./TSRedisCacheKit/List";
import { CacheSet } from "./TSRedisCacheKit/Set";
import { CacheSortedSet } from "./TSRedisCacheKit/SortedSet";
import { CacheString } from "./TSRedisCacheKit/String";
// 创建 Redis 客户端实例
const redisClient = new Redis();
// 创建缓存选项
const cacheOption = {
appName: "myApp",
funcName: "myFunc",
};
// 创建缓存实例
const cacheString = new CacheString("str", cacheOption, redisClient);
const cacheSet = new CacheSet("set", cacheOption, redisClient);
const cacheList = new CacheList("list", cacheOption, redisClient);
const cacheHash = new CacheHash("hash", cacheOption, redisClient);
const cacheSortedSet = new CacheSortedSet("ss", cacheOption, redisClient);
const cacheBitField = new CacheBitField("bf", cacheOption, redisClient);
const cacheHyperLogLog = new CacheHyperLogLog("hll", cacheOption, redisClient);
// 使用示例
export async function example() {
// 清理相关键
await redisClient.del("prefix-myApp-myFunc-key");
await redisClient.del("prefix-myApp-myFunc");
// 字符串操作
await cacheString.set("key", "value");
const value = await cacheString.get("key");
console.log("String value:", value);
// 清理相关键
await redisClient.del("prefix-myApp-myFunc");
// 集合操作
await cacheSet.add("member");
const members = await cacheSet.members();
console.log("Set members:", members);
// 清理相关键
await redisClient.del("prefix-myApp-myFunc");
// 列表操作
await cacheList.push("list", "item1", "item2");
const listItems = await cacheList.range(0, -1);
console.log("List items:", listItems);
// 清理相关键
await redisClient.del("prefix-myApp-myFunc");
// 哈希操作
await cacheHash.set("field", { foo: "bar" });
const hashValue = await cacheHash.get("field");
console.log("Hash value:", hashValue);
// 清理相关键
await redisClient.del("prefix-myApp-myFunc");
// 有序集合操作
await cacheSortedSet.add(1, "member1");
const sortedSetMembers = await cacheSortedSet.range(0, -1);
console.log("Sorted set members:", sortedSetMembers);
// 清理相关键
await redisClient.del("prefix-myApp-myFunc");
// 位操作
await cacheBitField.setbit(0, true);
const bitValue = await cacheBitField.getbit(0);
console.log("Bit value:", bitValue);
// 清理相关键
await redisClient.del("prefix-myApp-myFunc");
// HyperLogLog 操作
await cacheHyperLogLog.add("", "element1", "element2");
const count = await cacheHyperLogLog.count();
console.log("HyperLogLog count:", count);
}
总结
通过使用 Node.js、ioredis 和 TypeScript,我们可以方便地封装 Redis 的各种操作,使其在项目中更加易于使用和维护。本文介绍了如何封装字符串、集合、列表、哈希、有序集合、位操作以及 HyperLogLog 等操作的类。希望这些封装类能够在你的项目中派上用场。
转载自:https://juejin.cn/post/7394456880963534899