【RuoYi-Eggjs】:缓存,让你的应用飞起来

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.7k人参与

【RuoYi-Eggjs】:缓存,让你的应用飞起来

前言

在 Web 应用开发中,缓存是提升性能的关键手段。无论是减少数据库查询、加快 API 响应,还是降低服务器负载,缓存都扮演着重要角色。[ruoyi-eggjs-cache](https://github.com/undsky/ruoyi-eggjs-cache) 就是一个为 Egg.js 量身定制的缓存插件,基于强大的 cache-manager 生态,提供了开箱即用的缓存能力。

核心特性

🎯 三种存储方式,按需选择

插件支持三种缓存存储方式,每种都有其独特的使用场景:

1. 内存缓存(Memory)

  • 优势:速度最快,毫秒级响应
  • 劣势:应用重启后丢失,占用内存
  • 适用场景:频繁访问的小数据、临时数据

2. 文件系统缓存(FS)

  • 优势:持久化存储,不占用应用内存
  • 劣势:读写速度相对较慢
  • 适用场景:大数据缓存、需要持久化的数据

3. Redis 缓存(推荐)

  • 优势:速度快、持久化、支持分布式
  • 劣势:需要额外的 Redis 服务
  • 适用场景:生产环境、集群部署、多实例共享数据

🔧 统一的 API,简单易用

插件提供了四个核心方法,覆盖所有缓存操作场景:

// 1. set - 设置缓存
await app.cache.default.set('key', 'value', 300);  // 缓存 5 分钟

// 2. get - 获取缓存
const value = await app.cache.default.get('key');

// 3. del - 删除缓存
await app.cache.default.del('key');

// 4. wrap - 缓存包装器(最常用)
const result = await app.cache.default.wrap('key', async () => {
  return await fetchData();  // 仅在缓存不存在时执行
});

其中 wrap 方法是最实用的——它会自动判断缓存是否存在,存在则直接返回,不存在则执行回调函数并缓存结果。

快速上手

安装

npm i ruoyi-eggjs-cache --save

配置

1. 启用插件

// config/plugin.js
exports.cache = {
  enable: true,
  package: "ruoyi-eggjs-cache",
};

2. 基础配置(内存缓存)

// config/config.default.js
const path = require('path');

config.cache = {
  default: 'memory',  // 默认使用内存缓存
  ttl: 600,           // 缓存 10 分钟
  fs: {
    path: path.join(appInfo.baseDir, 'cache'),
    subdirs: false,
    zip: false,
  },
  redis: null,
};

3. Redis 配置(推荐生产环境)

// config/config.prod.js
config.cache = {
  default: 'redis',
  ttl: 600,
  redis: {
    host: '127.0.0.1',
    port: 6379,
    password: '',
    db: 0,
  },
};

实战场景

场景 1:数据库查询缓存

数据库查询是最常见的性能瓶颈,使用缓存可以显著提升响应速度:

// app/service/user.js
class UserService extends Service {
  async getUserById(userId) {
    const { app } = this;
    
    // 使用 wrap 自动管理缓存
    return await app.cache.default.wrap(`user:${userId}`, async () => {
      return await app.mysql.select('SELECT * FROM users WHERE id = ?', [userId]);
    });
  }

  async updateUser(user) {
    const { app } = this;
    
    // 更新数据库
    await app.mysql.update('UPDATE users SET name = ? WHERE id = ?', [user.name, user.id]);
    
    // 删除缓存,确保下次获取最新数据
    await app.cache.default.del(`user:${user.id}`);
  }
}

关键点

  • 查询时使用 wrap 自动缓存
  • 更新/删除时手动清除缓存

场景 2:API 响应缓存

对于查询频繁但变化不大的列表接口,缓存可以大幅降低服务器负载:

// app/controller/article.js
class ArticleController extends Controller {
  async list() {
    const { ctx, app } = this;
    const params = ctx.request.body;
    
    // 根据查询参数生成唯一的缓存 key
    const cacheKey = `article:list:${JSON.stringify(params)}`;
    
    const articles = await app.cache.default.wrap(cacheKey, async () => {
      return await ctx.service.article.getList(params);
    }, { ttl: 60 });  // 列表缓存 1 分钟
    
    ctx.body = { code: 200, data: articles };
  }
}

场景 3:配置数据缓存

系统配置数据变化频率极低,非常适合长时间缓存:

// app/service/config.js
class ConfigService extends Service {
  async getConfigByKey(configKey) {
    const { app } = this;
    
    return await app.cache.default.wrap(`config:${configKey}`, async () => {
      return await app.mysql.select('SELECT * FROM sys_config WHERE config_key = ?', [configKey]);
    }, { ttl: 3600 });  // 缓存 1 小时
  }

  async updateConfig(config) {
    const { app } = this;
    
    await app.mysql.update('UPDATE sys_config SET config_value = ? WHERE config_key = ?', 
      [config.configValue, config.configKey]);
    
    // 清除配置缓存
    await app.cache.default.del(`config:${config.configKey}`);
  }
}

场景 4:不同存储方式的组合使用

针对不同的数据特点,选择最合适的缓存方式:

// app/service/data.js
class DataService extends Service {
  // 高频访问的小数据 → 内存缓存
  async getHotData() {
    return await this.app.cache.memory.wrap('hot:data', async () => {
      return await this.fetchHotData();
    });
  }

  // 体积大的数据 → 文件系统缓存
  async getBigData() {
    return await this.app.cache.fs.wrap('big:data', async () => {
      return await this.fetchBigData();
    }, { ttl: 1800 });
  }

  // 多实例共享数据 → Redis 缓存
  async getSharedData() {
    return await this.app.cache.redis.wrap('shared:data', async () => {
      return await this.fetchSharedData();
    }, { ttl: 600 });
  }
}

缓存策略最佳实践

1. 合理设计缓存 Key

好的 Key 命名能让缓存管理更清晰:

// ✅ 推荐:使用有意义的前缀和参数
`user:${userId}`
`article:list:${category}:${page}`
`config:${configKey}`

// ❌ 不推荐:无意义的 Key
`u123`
`data`

2. 根据数据特点设置 TTL

// 频繁变化的数据 - 短 TTL
await app.cache.default.wrap('stock:price', async () => {
  return await fetchStockPrice();
}, { ttl: 60 });  // 1 分钟

// 相对稳定的数据 - 中 TTL
await app.cache.default.wrap('user:info', async () => {
  return await fetchUserInfo();
}, { ttl: 600 });  // 10 分钟

// 几乎不变的数据 - 长 TTL
await app.cache.default.wrap('system:config', async () => {
  return await fetchSystemConfig();
}, { ttl: 3600 });  // 1 小时

3. 避免缓存雪崩

缓存雪崩是指大量缓存同时过期,导致瞬间的数据库压力激增。解决方法是给 TTL 加上随机值:

// 在基础 TTL 上增加随机时间
const ttl = 300 + Math.floor(Math.random() * 60);  // 300-360 秒
await app.cache.default.set('key', 'value', ttl);

4. 及时清除过期缓存

在数据变更(增删改)时,记得清除相关缓存:

// 更新后清除单条缓存
await app.cache.default.del(`user:${userId}`);

// 删除多条缓存
await app.cache.default.del([`user:${userId}`, `user:list`]);

// 清空所有缓存(慎用)
await app.cache.default.reset();

性能优化建议

  1. 选择合适的缓存方式

    • 开发/测试:Memory
    • 生产单机:FS 或 Redis
    • 生产集群:Redis
  2. 合理设置 TTL

    • 避免设置过长导致数据不一致
    • 避免设置过短失去缓存意义
  3. 缓存粒度控制

    • 细粒度:单条数据缓存(如 user:123
    • 粗粒度:列表缓存(如 user:list
    • 根据业务场景选择
  4. 监控缓存命中率

    • 定期检查缓存使用情况
    • 优化低命中率的缓存策略

总结

ruoyi-eggjs-cache 是一个轻量且强大的 Egg.js 缓存插件,它的优势在于:

  • 多种存储:Memory、FS、Redis 三种方式,适应不同场景
  • API 简洁:统一的接口,学习成本低
  • 开箱即用:默认配置即可使用,无需复杂配置
  • 智能降级:Redis 不可用时自动降级,保障稳定性
  • 开发友好:开发环境自动短 TTL,调试无烦恼

无论是初创项目还是大型应用,合理使用缓存都能带来显著的性能提升。如果你正在使用 Egg.js,不妨试试这个插件,让你的应用飞起来!


<think>我们面对的是一个Maven循环依赖问题。根据错误信息,ruoyi-system模块依赖于ruoyi-common模块,而ruoyi-common模块又反过来依赖于ruoyi-system模块,形成了循环:ruoyi-system->ruoyi-common->ruoyi-system。解决循环依赖的常见方法:1.重构代码:将两个模块中相互依赖的部分提取到一个新的模块中,或者将公共代码下沉到基础模块。2.检查依赖关系:分析为什么ruoyi-common会依赖ruoyi-system?通常,common模块应该是被其他模块依赖的基础模块,不应该反过来依赖上层模块。具体步骤:1.查看ruoyi-common模块的pom.xml,确认它是否直接或间接地依赖了ruoyi-system。2.查看ruoyi-system模块的pom.xml,确认它对ruoyi-common的依赖。根据错误信息,循环路径是:ruoyi-system->ruoyi-common->ruoyi-system。因此,我们需要在ruoyi-common模块中移除对ruoyi-system的依赖。可能的情况:-ruoyi-common模块的pom.xml中,显式地引入了ruoyi-system依赖。这种情况应该删除该依赖。-或者,ruoyi-common模块中的某些类引用了ruoyi-system模块中的类,导致编译时需要ruoyi-system。这种情况需要重构代码,将ruoyi-common中依赖ruoyi-system的类移到ruoyi-system模块中,或者将两个模块中相互依赖的代码提取到第三个模块。操作建议:1.检查代码:在ruoyi-common模块中搜索对ruoyi-system模块中类的引用。如果存在,考虑将这些引用移除或重构。2.如果ruoyi-common确实需要一些原本在ruoyi-system中的功能,可以将这些功能提取到一个新的模块,然后让两个模块都依赖这个新模块。3.如果循环依赖是由于某些工具类或通用类被放置在ruoyi-system模块中,而这些类又被ruoyi-common需要,那么应该将这些类移动到ruoyi-common模块。注意:在Maven项目中,循环依赖是不允许的,因此必须打破这个循环。根据RuoYi项目的常见结构,ruoyi-common模块通常是基础工具类、通用枚举等,而ruoyi-system是系统模块,包含系统相关的功能。所以,ruoyi-common不应该依赖ruoyi-system。实际解决步骤:1.打开ruoyi-common模块的pom.xml,查看<dependencies>部分,如果发现ruoyi-system依赖,则将其删除。2.如果pom中没有直接依赖,则可能是传递依赖导致?但根据错误信息,是直接形成了循环,所以重点检查直接依赖。3.使用Maven命令分析依赖树:在项目根目录下运行`mvndependency:tree`,查看依赖树,定位循环依赖的位置。如果代码中存在类之间的循环依赖,那么需要修改代码。例如:-如果ruoyi-common中的类A使用了ruoyi-system中的类B,那么可以考虑:a.将类B移动到ruoyi-common中(如果类B是通用的,则合理)b.或者,将类A中依赖类B的部分抽离出来,通过接口解耦(依赖倒置),将接口放在ruoyi-common,而实现放在ruoyi-system,这样ruoyi-common只依赖接口,而实现由ruoyi-system提供,但ruoyi-common不再直接依赖ruoyi-system。另一种方法是使用事件或消息机制来解耦两个模块之间的直接依赖。但是,由于这是一个常见问题,我们可以参考RuoYi项目的标准结构。实际上,在标准的RuoYi项目中,ruoyi-common模块是不应该依赖ruoyi-system模块的。因此,可能是开发过程中不小心引入了反向依赖。总结:先检查pom文件,再检查代码引用。优先确保ruoyi-common的pom中没有引入ruoyi-system依赖。然后检查代码,将ruoyi-common中引用ruoyi-system的类进行调整,要么移动类的位置,要么重构代码消除依赖。如果问题仍然存在,请提供更多信息,例如两个模块中相互引用的具体类名和代码片段,以便更精确地解决问题。</think>### 解决Maven循环依赖问题 您在构建Maven项目时遇到的错误信息表明存在**模块间循环依赖**。具体错误为: ``` Edge between 'Vertex{label='com.ruoyi:ruoyi-common:3.8.8'}' and 'Vertex{label='com.ruoyi:ruoyi-system:3.8.8'}' introduces a cycle in the graph com.ruoyi:ruoyi-system:3.8.8 --> com.ruoyi:ruoyi-common:3.8.8 --> com.ruoyi:ruoyi-system:3.8.8 ``` #### 问题本质 循环依赖发生在两个模块相互引用时: 1. `ruoyi-system` 模块依赖 `ruoyi-common` 2. `ruoyi-common` 模块又反向依赖 `ruoyi-system` 形成闭环:$$ \text{system} \rightarrow \text{common} \rightarrow \text{system} $$ #### 解决方案(逐步操作) ##### 1. **定位循环依赖点** - 检查 `ruoyi-common/pom.xml` 中是否显式依赖了 `ruoyi-system`: ```xml <!-- 错误示例 --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-system</artifactId> <version>3.8.8</version> </dependency> ``` - 若存在,**直接删除此依赖**(这是最快解决方案) ##### 2. **代码级依赖检查** 如果POM中无显式依赖,检查代码: - 在 `ruoyi-common` 模块中搜索 `ruoyi-system` 中的类引用 - 常见场景: - `ruoyi-common` 导入了 `ruoyi-system` 的Service/Entity类 - `ruoyi-common` 的工具类引用了系统模块的功能 ##### 3. **架构重构(核心方案)** ```mermaid graph LR A[原结构] --> B[ruoyi-system] --> C[ruoyi-common] C --> B %% 循环点 D[新结构] --> E[ruoyi-system] D --> F[ruoyi-common] D --> G[新建 ruoyi-common-core] F --> G %% 基础层 E --> G %% 共同依赖基础层 ``` - **步骤**: 1. 创建新模块 `ruoyi-common-core` 2. 将 `ruoyi-common` 中被 `ruoyi-system` 依赖的**基础代码**移至 `core` 模块 3. 调整依赖关系: - `ruoyi-common` 依赖 `ruoyi-common-core` - `ruoyi-system` 依赖 `ruoyi-common-core` - 确保 `ruoyi-common-core` **不依赖**其他业务模块 ##### 4. **依赖作用域优化** 若循环依赖无法完全消除,使用 `<scope>` 限制: ```xml <!--ruoyi-common 中 --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-system</artifactId> <version>3.8.8</version> <scope>provided</scope> <!-- 编译时不传递 --> </dependency> ``` #### 验证修复 执行以下命令验证: ```bash mvn clean install -DskipTests ``` 循环依赖错误应消失,构建成功[^1][^2]。 #### 预防措施 1. 遵循 **单向依赖原则**:基础模块(如common)永远不依赖上层模块 2. 定期运行依赖分析: ```bash mvn dependency:tree -Dincludes=com.ruoyi ``` 3. 使用架构守护工具(如ArchUnit)禁止跨模块反向引用 > **关键提示**:在RuoYi等标准框架中,`common` 模块通常是所有模块的基础层,若出现反向依赖,往往是代码结构问题,需优先检查模块职责划分[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值