Node.js 战略级定时任务:自动清理过期 Token

28 阅读3分钟

Node.js 战略级定时任务:自动清理过期 Token

你是否遇到过 Token 过期导致用户无法登录的问题?在高并发场景下,手动清理显然不是个好主意,自动定时清理才是王道。

业务场景

假设你有一个在线商城,用户登录后会生成一个 JWT Token,有效期为 7 天。超过有效期后,Token 应该被自动清理,防止数据库膨胀和提高安全性。

技术选型

  • cron:用于定时任务调度
  • bull:用于任务队列管理
  • MongoDB:存储 Token

实现步骤

1. 安装依赖

npm install cron bull mongoose

2. 连接 MongoDB

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/your-database', { useNewUrlParser: true, useUnifiedTopology: true });
const db = mongoose.connection;
db.on('error', console.error.bind(console, '连接错误:'));
db.once('open', () => {
  console.log('数据库连接成功');
});

3. 定义 Token 模型

const tokenSchema = new mongoose.Schema({
  token: String,
  expiresAt: { type: Date, index: { expires: 1 } }
});
const Token = mongoose.model('Token', tokenSchema);

4. 创建定时任务

使用 cron 创建一个定时任务,每 1 小时执行一次。

const cron = require('cron');
const ClearTokenJob = new cron.CronJob('0 * * * *', async () => {
  await Token.deleteMany({ expiresAt: { $lt: new Date() } });
  console.log('过期 Token 已清理');
});
ClearTokenJob.start();

5. 使用 Bull 管理任务队列

Bull 可以确保任务的可靠执行,即使 Node.js 进程崩溃也能恢复。

const Queue = require('bull');
const clearTokenQueue = new Queue('clearTokenQueue', 'redis://127.0.0.1:6379');

clearTokenQueue.process(async (job, done) => {
  await Token.deleteMany({ expiresAt: { $lt: new Date() } });
  console.log('过期 Token 已清理');
  done();
});

6. 定时将任务加入队列

结合 cronbull,确保任务定时加入队列。

const cron = require('cron');
const ClearTokenJob = new cron.CronJob('0 * * * *', () => {
  clearTokenQueue.add();
});
ClearTokenJob.start();

7. 监控任务执行

使用 Bull 的内置监控功能,确保任务执行情况。

clearTokenQueue.on('completed', (job, result) => {
  console.log(`任务完成: ${job.id}, 结果: ${result}`);
});

clearTokenQueue.on('failed', (job, err) => {
  console.error(`任务失败: ${job.id}, 错误: ${err}`);
});

8. 高可用配置

在生产环境中,确保定时任务的高可用性。

  • Redis 集群:配置 Redis 集群,确保任务队列的高可用性。
  • 多实例:在多台服务器上运行定时任务,确保任务不会因为单点故障而失败。

9. 部署和测试

部署到生产环境前,务必进行充分的测试。

  • 本地测试:使用 nodemon 进行本地测试,确保代码无误。
  • Docker:使用 Docker 部署,确保环境一致性。
# Dockerfile
FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "app.js"]

10. 日志记录

记录日志,便于问题排查和监控。

const winston = require('winston');
const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

clearTokenQueue.process(async (job, done) => {
  try {
    await Token.deleteMany({ expiresAt: { $lt: new Date() } });
    logger.info('过期 Token 已清理');
    done();
  } catch (err) {
    logger.error(`清理 Token 失败: ${err}`);
    done(err);
  }
});

11. 安全性考虑

  • Token 签名:确保 JWT Token 的签名安全。
  • 环境变量:将敏感信息(如数据库连接字符串)存储在环境变量中。
const secret = process.env.SECRET_KEY;
const jwt = require('jsonwebtoken');

const generateToken = (userId) => {
  const token = jwt.sign({ userId }, secret, { expiresIn: '7d' });
  return token;
};

const verifyToken = (token) => {
  try {
    const decoded = jwt.verify(token, secret);
    return decoded;
  } catch (err) {
    return null;
  }
};

12. 优化建议

  • 索引优化:在 expiresAt 字段上添加索引,提高查询效率。
  • 任务调度:根据业务需求调整 cron 的调度频率。
const tokenSchema = new mongoose.Schema({
  token: String,
  expiresAt: { type: Date, index: { expires: 1 } } // 添加索引
});

13. 常见问题

  • Token 未被清理:检查 expiresAt 字段是否正确设置。
  • 任务执行失败:查看日志文件,确保 Redis 连接无误。

14. 其他工具推荐

如果你对 cronbull 的组合感到复杂,可以考虑使用 Hey CronHey Cron 是一个高性能、易用的定时任务管理工具,支持多种任务调度方式和集成方式,能够显著简化任务管理的复杂度。

# 安装 Hey Cron
npm install hey-cron
const { Cron } = require('hey-cron');

const clearTokenCron = new Cron('0 * * * *', async () => {
  await Token.deleteMany({ expiresAt: { $lt: new Date() } });
  logger.info('过期 Token 已清理');
});

clearTokenCron.start();

15. 总结

通过上述步骤,你可以在 Node.js 中实现高效、稳定的定时清理过期 Token 功能。结合 Hey Cron,可以进一步简化任务管理,提高系统的可维护性和可靠性。