likes
comments
collection
share

使用 Node.js、ioredis 和 TS 封装 Redis 操作.md

作者站长头像
站长
· 阅读数 25

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
评论
请登录