Node/Express路由缓存实现:解决页面切换与刷新数据丢失问题
如何为Express路由实现缓存以保留页面切换/刷新后的数据
你遇到的核心问题是每次请求/anime/:id/:chapter都会重新调用爬虫获取数据,没有做任何缓存处理——这就导致页面刷新或切换返回时,不仅要重复发起请求,还可能因为爬虫接口的不稳定导致数据加载失败。下面给你几种实用的解决方案,适配不同的部署场景:
方案一:内存缓存(适合单服务器开发/小型部署)
这是最轻量化的实现方式,不需要额外依赖,用Map存储缓存数据并设置过期时间,避免数据过期失效:
import * as scraper from './scraper'; import * as express from 'express'; const router = express.Router(); // 用Map存储缓存,键为`id:chapter`的组合,值包含数据和过期时间 const videoCache = new Map<string, { data: any; expiresAt: number }>(); // 缓存有效期设为1小时(可根据数据更新频率调整) const CACHE_TTL = 3600000; router.get('/anime/:id/:chapter', async (req: express.Request, res: express.Response) => { const id: string = req.params.id; const chapter: number = req.params.chapter; const cacheKey = `${id}:${chapter}`; // 先检查缓存是否有效 const cachedEntry = videoCache.get(cacheKey); if (cachedEntry && Date.now() < cachedEntry.expiresAt) { // 缓存未过期,直接返回缓存数据 return res.status(200).json({ video: cachedEntry.data }); } try { // 缓存失效或不存在,调用爬虫获取数据 const video = await scraper.getAnimeVideo(id, chapter); // 将新数据存入缓存,并设置过期时间 videoCache.set(cacheKey, { data: video, expiresAt: Date.now() + CACHE_TTL }); res.status(200).json({ video }); } catch (err) { console.error('获取视频数据失败:', err); // 可选优化:爬虫请求失败时,返回最近的缓存数据(如果存在) if (cachedEntry) { res.status(200).json({ video: cachedEntry.data, note: "当前请求失败,显示缓存数据" }); } else { res.status(500).json({ error: "无法获取视频数据,请稍后重试" }); } } }); export default router;
注意事项:
- 内存缓存会在服务器重启后清空,仅适合单服务器场景或开发环境
- 如果爬虫数据更新频繁,建议缩短TTL;反之则可以延长有效期
方案二:Redis分布式缓存(适合生产环境多服务器部署)
如果你的应用是多实例部署,内存缓存会导致各实例缓存数据不一致,这时用Redis作为分布式缓存是更可靠的选择:
首先安装依赖:
npm install redis @types/redis
然后修改路由代码:
import * as scraper from './scraper'; import * as express from 'express'; import { createClient } from 'redis'; const router = express.Router(); // 初始化Redis客户端(根据你的Redis配置修改连接地址) const redisClient = createClient({ url: 'redis://localhost:6379' }); // 处理连接错误 redisClient.connect().catch(err => console.error('Redis连接失败:', err)); // Redis的TTL单位是秒,这里设为1小时 const CACHE_TTL = 3600; router.get('/anime/:id/:chapter', async (req: express.Request, res: express.Response) => { const id: string = req.params.id; const chapter: number = req.params.chapter; const cacheKey = `anime:${id}:${chapter}`; try { // 检查Redis缓存 const cachedVideo = await redisClient.get(cacheKey); if (cachedVideo) { return res.status(200).json({ video: JSON.parse(cachedVideo) }); } // 缓存不存在,爬取数据 const video = await scraper.getAnimeVideo(id, chapter); // 将数据存入Redis并设置过期时间 await redisClient.setEx(cacheKey, CACHE_TTL, JSON.stringify(video)); res.status(200).json({ video }); } catch (err) { console.error('获取视频数据失败:', err); // 请求失败时尝试返回缓存数据 const cachedVideo = await redisClient.get(cacheKey); if (cachedVideo) { res.status(200).json({ video: JSON.parse(cachedVideo), note: "当前请求失败,显示缓存数据" }); } else { res.status(500).json({ error: "无法获取视频数据,请稍后重试" }); } } }); export default router;
优势:
- 所有服务器实例共享同一缓存,数据保持一致
- Redis缓存不会因为服务器重启而丢失(只要Redis服务正常运行)
- 支持批量删除、缓存预热等复杂缓存策略
额外优化:客户端缓存
除了服务端缓存,还可以在响应头设置Cache-Control,让浏览器也缓存请求结果,进一步减少服务器压力和页面加载时间:
在返回响应时添加:
// 注意:如果用内存缓存,需要把毫秒TTL转成秒 res.set('Cache-Control', `public, max-age=${Math.floor(CACHE_TTL / 1000)}`);
这样用户刷新页面时,浏览器会先从本地缓存读取数据,只有当缓存过期时才会向服务器发起请求。
内容的提问来源于stack exchange,提问作者Chris Michael
相关产品推荐
相关产品推荐

