# mm_cachebase

一个轻量级的本地缓存类库，提供了与Redis兼容的缓存操作接口，支持过期时间设置、哈希表操作、数值操作、字符串操作等功能，以及完整的事件通知系统。

## 项目信息

- **当前版本**: 1.8.8
- **更新日期**: 2024-12-23
- **Gitee地址**: [https://gitee.com/qiuwenwu91/mm_cachebase](https://gitee.com/qiuwenwu91/mm_cachebase)
- **Node.js 版本要求**: >= 14.0.0（推荐 16.0.0 及以上）

## 特点

- **Redis兼容API**：实现了与npm redis模块相似的接口，便于无缝切换
- **轻量级设计**：纯JavaScript实现，无外部依赖
- **完整的事件系统**：支持前置/后置/错误事件，便于日志记录和访问控制
- **过期时间管理**：支持设置和查询键的过期时间
- **丰富的数据类型支持**：支持字符串、哈希表等Redis常见数据类型
- **多文件持久化**：支持数据持久化到文件，通过scope参数实现多实例数据隔离

## 安装

```bash
npm install mm_cachebase
```

## 基本使用

```javascript
const CacheBase = require('mm_cachebase');

// 创建缓存实例
const cache = new CacheBase();

// 基本操作示例
async function example() {
    // 设置缓存，过期时间为10秒
    await cache.set("name", "张三", 10);
    
    // 获取缓存
    const value = await cache.get("name");
    $.log.debug(value); // 输出: 张三
    
    // 删除缓存
    await cache.del("name");
}
```

## API 说明

### 构造函数

```javascript
const cache = new CacheBase(scope, config);
```

创建一个新的缓存实例。缓存实例基于EventEmitter，支持完整的事件系统。

- `scope`: 缓存作用域名称（默认'sys'），用于多文件持久化时的数据隔离
- `config`: 配置选项对象，支持以下属性：
  - `dir`: 缓存文件存储目录（默认'./cache/'）
  - `expire`: 默认过期时间（秒）（默认0，表示永不过期）
  - `max`: 最大缓存键数量（默认0，表示无限制）
  - `persist`: 是否启用持久化（默认false）
  - `saveInterval`: 定时保存间隔（毫秒）（默认60000）

### 基本操作

#### 设置缓存
```javascript
await cache.set(key, value, maxAge)
```
- `key`: 缓存键名
- `value`: 缓存的值
- `maxAge`: 过期时间（秒），-1表示永不过期
- 返回: 'OK'

#### 获取缓存
```javascript
await cache.get(key)
```
- `key`: 缓存键名
- 返回: 缓存值，不存在则返回null

#### 删除缓存
```javascript
await cache.del(key)
```
- `key`: 缓存键名
- 返回: 成功返回1，失败返回0

#### 设置并获取旧值
```javascript
await cache.getset(key, value, maxAge)
```
- 先获取旧值，然后设置新值
- 返回: 旧值，不存在则返回null

#### 批量设置缓存
```javascript
await cache.mset(keys, values, maxAge)
```
- `keys`: 键名数组
- `values`: 值数组
- 返回: 'OK'

#### 批量获取缓存
```javascript
await cache.mget(...keys)
```
- `keys`: 一个或多个键名
- 返回: 值数组

#### 判断键是否存在
```javascript
await cache.exists(...keys)
```
- `keys`: 一个或多个键名
- 返回: 存在的键数量

### 过期时间操作

#### 设置过期时间
```javascript
await cache.expire(key, maxAge)
```
- `maxAge`: 过期时间（秒）
- 返回: 成功返回1，失败返回0

#### 查询剩余过期时间
```javascript
await cache.ttl(key)
```
- 返回: 剩余秒数，不存在或永不过期返回-1

#### 移除过期时间
```javascript
await cache.persist(key)
```
- 返回: 成功返回1，失败返回0

### 数值操作

#### 增加整数
```javascript
await cache.incr(key)
```
- 将键的值加1
- 返回: 增加后的值

#### 增加指定整数
```javascript
await cache.incrby(key, increment)
```
- `increment`: 要增加的整数
- 返回: 增加后的值

#### 减少整数
```javascript
await cache.decr(key)
```
- 将键的值减1
- 返回: 减少后的值

#### 减少指定整数
```javascript
await cache.decrby(key, decrement)
```
- `decrement`: 要减少的整数
- 返回: 减少后的值

#### 增加浮点数
```javascript
await cache.incrbyfloat(key, increment)
```
- `increment`: 要增加的浮点数
- 返回: 增加后的值

### 字符串操作

#### 追加字符串
```javascript
await cache.append(key, value)
```
- 在已有字符串后追加新字符串
- 返回: 追加后的字符串长度

#### 获取字符串长度
```javascript
await cache.strlen(key)
```
- 返回: 字符串长度

### 哈希表操作

#### 设置哈希表字段
```javascript
await cache.hset(key, field, value)
```
- `key`: 哈希表的键名
- `field`: 字段名
- `value`: 字段值
- 返回: 成功返回1，失败返回0

#### 获取哈希表字段
```javascript
await cache.hget(key, field)
```
- 返回: 字段值，不存在则返回null

#### 获取哈希表所有字段和值
```javascript
await cache.hgetall(key)
```
- 返回: 包含所有字段和值的对象

#### 删除哈希表字段
```javascript
await cache.hdel(key, field)
```
- 返回: 成功返回1，失败返回0

### 键操作

#### 获取所有键名
```javascript
await cache.keys(pattern)
```
- `pattern`: 支持*通配符的匹配模式
- 返回: 匹配的键名数组

#### 获取键数量
```javascript
await cache.dbsize()
```
- 返回: 缓存中的键数量

#### 清空缓存
```javascript
await cache.flushdb()
```
- 清空所有缓存
- 返回: 'OK'

#### 销毁缓存实例
```javascript
await cache.dispose()
```
- 清理所有资源，移除所有监听器

### 持久化操作

#### 连接并启动定时保存
```javascript
await cache.connect()
```
- 启用持久化时启动定时保存功能
- 返回: 连接成功返回true，失败返回false

#### 手动保存数据
```javascript
await cache.save()
```
- 手动将缓存数据保存到文件
- 返回: 保存成功返回true，失败返回false

### 兼容方法（保持向后兼容）

#### 添加缓存（仅当键不存在时）
```javascript
await cache.add(key, value, maxAge)
```
- 返回: 添加成功返回值，已存在返回0

#### 检查键是否存在
```javascript
await cache.has(key)
```
- 返回: 存在返回1，不存在返回0

#### 增加浮点数（兼容方法）
```javascript
await cache.addFloat(key, num, maxAge)
```
- `num`: 要增加的浮点数
- `maxAge`: 过期时间（秒），默认0表示永不过期
- 返回: 计算后的结果

#### 追加字符串（兼容方法）
```javascript
await cache.addStr(key, str, maxAge)
```
- `maxAge`: 过期时间（秒），默认0表示永不过期
- 返回: 追加后的完整字符串

#### 清空匹配缓存（兼容方法）
```javascript
await cache.clear(pattern)
```
- `pattern`: 可选，支持*通配符

## 事件系统

CacheBase 提供了完整的事件系统，允许你监听各种缓存操作的前后事件，以便进行日志记录、数据验证或操作取消等功能。

> **性能优化说明**：从版本 1.6.0 开始，查询类方法（如 get、hget、mget、exists、ttl 等）不再触发事件，以提高高频查询操作的性能。

### 事件监听方法

```javascript
// 监听事件
cache.on('event_name', callback);

// 监听一次性事件
cache.once('event_name', callback);

// 移除事件监听
cache.off('event_name', callback);
```

### 事件参数格式

所有事件的回调函数都会接收一个标准化的事件数据对象，包含以下属性：

- `key`: 操作的缓存键名（如果适用）
- `value`: 操作的值，可以是基本类型或包含多个属性的对象
- `result`: 操作的结果
- `error`: 错误信息（仅错误事件）
- `cancel`: 用于取消操作的标志（仅前置事件）
- `ttl`: 过期时间（秒）（仅过期时间相关操作）

### 支持的事件类型

每个缓存操作都有对应的前置事件（`_before`）、后置事件（`_after`）和错误事件（`_error`）。

#### 基本操作事件
- `set_before`, `set_after`, `set_error`
- `del_before`, `del_after`, `del_error`
- `mset_before`, `mset_after`, `mset_error`

#### 过期时间操作事件
- `expire_before`, `expire_after`, `expire_error`
- `persist_before`, `persist_after`, `persist_error`

#### 数值操作事件
- `incr_before`, `incr_after`, `incr_error`
- `decr_before`, `decr_after`, `decr_error`
- `incrby_before`, `incrby_after`, `incrby_error`
- `decrby_before`, `decrby_after`, `decrby_error`
- `incrbyfloat_before`, `incrbyfloat_after`, `incrbyfloat_error`

#### 字符串操作事件
- `append_before`, `append_after`, `append_error`

#### 哈希表操作事件
- `hset_before`, `hset_after`, `hset_error`
- `hdel_before`, `hdel_after`, `hdel_error`

#### 键操作事件
- `flushdb_before`, `flushdb_after`, `flushdb_error`
- `dispose_before`, `dispose_after`, `dispose_error`

### 事件使用示例

```javascript
const cache = new CacheBase();

// 监听设置操作的前置事件
cache.on('set_before', (data) => {
    $.log.debug('即将设置键:', data.key, '值:', data.value);
    
    // 数据验证示例
    if (typeof data.value === 'string' && data.value.length > 100) {
        $.log.debug('值长度过长，取消设置');
        data.cancel = true; // 取消操作
    }
});

// 错误事件监听
cache.on('mset_error', (data) => {
    $.log.error(`批量设置出错: ${data.error.message}`);
});
```

## 使用示例

### 基本缓存操作
```javascript
const cache = new CacheBase();

async function example() {
    // 设置缓存
    await cache.set("user", "张三", 60); // 60秒后过期
    
    // 获取缓存
    const user = await cache.get("user");
    $.log.debug(user); // 输出: 张三
    
    // 检查是否存在
    const exists = await cache.exists("user");
    $.log.debug(exists); // 输出: 1
    
    // 删除缓存
    await cache.del("user");
}
```

### 哈希表操作示例
```javascript
async function hashExample() {
    // 设置哈希表字段
    await cache.hset("user:123", "name", "张三");
    await cache.hset("user:123", "age", 30);
    await cache.hset("user:123", "email", "zhangsan@example.com");
    
    // 获取哈希表字段
    const name = await cache.hget("user:123", "name");
    $.log.debug(name); // 输出: 张三
    
    // 获取所有字段和值
    const user = await cache.hgetall("user:123");
    $.log.debug(user); // 输出: { name: "张三", age: 30, email: "zhangsan@example.com" }
    
    // 删除字段
    await cache.hdel("user:123", "email");
}
```

### 数值操作示例
```javascript
async function numberExample() {
    // 设置初始值
    await cache.set("counter", 10);
    
    // 增加1
    const incr = await cache.incr("counter");
    $.log.debug("增加后:", incr); // 输出: 11
    
    // 增加指定值
    const incrby = await cache.incrby("counter", 5);
    $.log.debug("增加5后:", incrby); // 输出: 16
    
    // 减少1
    const decr = await cache.decr("counter");
    $.log.debug("减少后:", decr); // 输出: 15
    
    // 浮点数操作
    await cache.set("price", 99.99);
    const incrFloat = await cache.incrbyfloat("price", 0.01);
    $.log.debug("浮点数增加后:", incrFloat); // 输出: 100
}
```

### 多文件持久化示例
```javascript
const CacheBase = require('mm_cachebase');

async function persistenceExample() {
    // 创建启用持久化的缓存实例，指定不同的scope
    const userCache = new CacheBase('users', { 
        persist: true, 
        saveInterval: 30000, // 30秒保存一次
        dir: './test_cache/' // 自定义缓存目录
    });
    
    const productCache = new CacheBase('products', { 
        persist: true, 
        saveInterval: 30000, 
        dir: './test_cache/' 
    });
    
    // 启动定时保存
    await userCache.connect();
    await productCache.connect();
    
    // 向不同scope添加数据
    await userCache.set('user1', { name: '张三', age: 30 }, 3600);
    await productCache.set('product1', { id: 1, name: '商品A', price: 99.99 }, 3600);
    
    // 数据会分别保存到 users.cache.json 和 products.cache.json
    $.log.debug('数据已保存，不同scope的数据完全隔离');
    
    // 手动保存（可选）
    await userCache.save();
    
    // 应用重启后，可通过相同的scope加载对应的数据
    const loadedUserCache = new CacheBase('users', { persist: true, dir: './test_cache/' });
    const loadedUser = await loadedUserCache.get('user1');
    $.log.debug('加载的用户数据:', loadedUser);
    
    // 销毁实例时，会自动保存数据
    await userCache.dispose();
    await productCache.dispose();
}
```

### 批量操作示例
```javascript
async function batchExample() {
    // 批量设置
    await cache.mset(["key1", "key2", "key3"], ["value1", "value2", "value3"]);
    
    // 批量获取
    const values = await cache.mget("key1", "key2", "key3");
    $.log.debug("批量获取:", values); // 输出: ["value1", "value2", "value3"]
    
    // 检查多个键是否存在
    const existsCount = await cache.exists("key1", "key2", "nonexistent");
    $.log.debug("存在的键数量:", existsCount); // 输出: 2
    
    // 获取键数量
    const count = await cache.dbsize();
    $.log.debug("键总数:", count);
    
    // 清空所有缓存
    await cache.flushdb();
}
```

## 注意事项

1. 所有操作方法都是异步的，需要使用async/await或Promise处理
2. 过期时间单位为秒，-1表示永不过期
3. 缓存数据默认存储在内存中，重启后数据会丢失
4. 启用持久化时，数据将保存到`{scope}.cache.json`文件，不同scope的数据存储在不同文件中，实现数据隔离
5. 缓存目录会在第一次使用时自动创建，无需手动创建
6. 事件回调函数中的 `cancel` 标志只在前置事件中有效，后置事件中修改不会产生效果
7. 所有事件参数都遵循统一的格式，包含key、value、result、error和cancel等字段

## 贡献指南

欢迎参与mm_cachebase项目的开发和改进！以下是贡献代码的基本步骤：

1. **Fork 仓库**：在Gitee上Fork项目到您的账户
2. **克隆仓库**：`git clone https://gitee.com/您的用户名/mm_cachebase.git`
3. **创建分支**：`git checkout -b feature/your-feature-name`
4. **提交更改**：`git commit -m "feat: 添加新功能描述"`
5. **推送分支**：`git push origin feature/your-feature-name`
6. **创建PR**：在Gitee上提交Pull Request

## 问题反馈

如果您在使用过程中遇到任何问题或有改进建议，请通过以下方式反馈：

- **Gitee Issues**：[https://gitee.com/qiuwenwu91/mm_cachebase/issues](https://gitee.com/qiuwenwu91/mm_cachebase/issues)
- **NPM 链接**：[https://www.npmjs.com/package/mm_cachebase](https://www.npmjs.com/package/mm_cachebase)

## 鸣谢

感谢所有为mm_cachebase项目做出贡献的开发者和用户！

## 相关链接

- **Node.js 官方文档**：[https://nodejs.org/docs/latest/api/](https://nodejs.org/docs/latest/api/)
- **Redis 命令参考**：[https://redis.io/commands/](https://redis.io/commands/)

## 许可证

MIT