You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:41:23