当前位置: 首页 > news >正文

Nestjs框架: nestjs-schedule模块注册流程,源码解析与定时备份数据库

概述

  • 接触过Linux的小伙伴,应该知道Linux系统上有定时任务,而Windows系统上叫做计划任务
  • 不论哪种系统,大家或多或少都了解过定时任务,它可以通过系统脚本实现
  • 基于 NestJS定时任务,确实可以通过系统级脚本实现对MongoDB数据库的维护
  • 在管理的是日志部分的数据库,数据库需要进行滚动和备份操作, 就需要定时任务
  • NestJS的定时任务功能,有以下两个优点:
    • 不受平台限制
      • 它跟随业务系统运行,只要平台上有Node.js环境
      • 业务系统就能运行,定时任务也能生效
    • 支持多种任务类型
      • NestJS的定时任务既支持静态定时任务(即随着业务系统启动就注册好)
      • 也支持动态定时任务我们可以通过接口动态添加定时任务,这里有个定时任务示例叫addCronJob
        addCronJob(name: string, seconds: string) {const job = new CronJob(`${seconds} * * * * *`, () => {this.logger.warn(`time (${seconds}) for job ${name} to run!`);});this.schedulerRegistry.addCronJob(name, job);job.start();this.logger.warn(`job ${name} added for each minute at ${seconds} seconds!`,);
        }
        

场景

  • 定时任务有很多应用场景,比如计划类型的业务和通知类型的业务
  • 例如,我们每天要检查系统中的计划是否按照用户设定的节点推进,如果没有推进,就可以添加定时任务,每隔一段时间给用户发送通知
  • 当然,这里设置的定时任务通常是批量操作,而非为每个用户单独创建。添加定时任务后,其逻辑可能是根据用户数据库关联表格查询对应字段,然后给用户发送通知或消息

配置

下面来实操NestJS定时任务,并针对当前应用场景完成数据库部分数据的定时备份、滚动和清理

1 ) 配置NestJS服务端内容

  • 安装依赖:我们需要安装一个名为@nestjs/schedule的依赖

  • 配置定时任务相关内容

    • 我们需要设置一个环境变量 在 .env 类似相关文件中设置CRON_ONtrue
      • CRON_ON=true
    • 在app.module.js模块或其他拆分管理的module模块中注册:
      import { Module } from '@nestjs/common';
      import { ScheduleModule } from '@nestjs/schedule';@Module({imports: [ScheduleModule.forRoot()],
      })
      export class AppModule {}
      
    • 这样,是全局生效,我们也可以根据配置来根据配置进行条件生效,
    • 如, 新建 conditional.module.ts文件来做拆分管理
      import { Module } from '@nestjs/common';
      import * as dotenv from 'dotenv';
      import { toBoolean } from '../utils/format';
      import { MailModule } from './mail/mail.module';
      import { ScheduleModule } from '@nestjs/schedule';
      import { TasksService } from '@/common/cron/tasks.service';
      // import { StorageModule } from './storage/storage. module';const imports =[];
      const providers = [];const conditionalImports = () => {const envFilePaths = [`.env.${process.env.NODE_ENV |`development`}`,'.env',];const parsedConfig = dotenv.config({ path:'.env'}).parsed;envFilePaths.forEach((path) => {if (path ==',env') return;constconfig = dotenv.config({path});Object.assign(parsedConfig,config.parsed);});if(toBoolean(parsedConfig['MAIL_ON'])){imports.push(MailModule);}if(toBoolean(parsedConfig['CRON_ON']){imports.push(ScheduleModule.forRoot());providers.push(TasksService);}return imports;
      };@Module({imports: conditionalImports(),providers,
      });export class ConditionalModule {}
      
    • conditional.module.ts 加入 app.module.js
  • 创建任务服务类:新建 common/cron目录,在下面新建一个tasks.service.ts文件,导出TasksService

    import { Injectable } from '@nestjs/common';
    import { Cron } from '@nestjs/schedule';@Injectable()
    export class TasksService {@Cron('******')handleCron(){console. log('test');}
    }
    
    • 使用@Injectable()装饰器,可参考官方示例
    • 在类中调用console.log进行打印,其中cron是定时表达式,写六个星号代表每秒执行一次
  • 使用定时任务:在上面的 conditional.module,将TasksService添加到providers数组中,已处理

  • 这块,条件模块可以优化

    import { Module } from '@nestjs/common';
    import * as dotenv from 'dotenv';
    import { toBoolean } from '../utils/format';
    import { MailModule } from './mail/mail.module';
    import { ScheduleModule } from '@nestjs/schedule';
    import { TasksService } from '@/common/cron/tasks.service';
    // import { StorageModule } from './storage/storage. module';const imports =[];
    const providers = [];
    const exportsService = [];@Module({imports: conditionalImports(),providers,
    });export class ConditionalModule {static register(): DynamicModule {const envFilePaths = [`.env.${process.env.NODE_ENV |`development`}`,'.env',];const parsedConfig = dotenv.config({ path:'.env'}).parsed;envFilePaths.forEach((path) => {if (path ==',env') return;constconfig = dotenv.config({path});Object.assign(parsedConfig,config.parsed);});if(toBoolean(parsedConfig['MAIL_ON'])){imports.push(MailModule);}if(toBoolean(parsedConfig['CRON_ON']){imports.push(ScheduleModule.forRoot());providers.push(TasksService);exportsService.push(TasksService);}imports.push(conditionalImports());return {module: ConditionalModule,imports,providers,exports: exportsService}}
    }
    
  • 在 app.module.ts 中使用 ConditionalModule 的时候

    • 替换为: ConditionalModule.register()

2 )源码解析

  • NestJS的定时任务注册主要在onApplicationBootstrap这个生命周期钩子方法中进行
  • 我们可以查看@nestjs/schedule的官方源码,在相关文件中 schedule/lib/scheduler.orchestrator.ts
  • 可以找到onApplicationBootstrap方法, 定时任务就在这里注册
  • 当应用关闭时,会清除对应的定时器、计时器以及定时任务
    onApplicationBootstrap(){this.mountTimeouts();this.mountIntervals();this. mountCron();
    }onApplicationShutdown(){this. clearTimeouts();this.clearIntervals();this.closeCronJobs();
    }
    

3 ) 定时任务备份数据库

  • 参考文档: backup-and-restore-tools/#deployments
  • 定时任务主要有两个
    • 连接到 MongoDB 并导出 connections 数据
    • 删除已有的 connections 数据
  • 删除数据时,需要删除两部分内容
    • 一是当前 connections 中的已备份数据,避免重复备份导致日志文件越来越大
    • 二是对比备份时间,删除超过一定天数或规则的先前备份的 connections 数据,实现滚动记录,防止磁盘爆满
  • 注意,这里不考虑集群分片的备份,一般集群会用分布式日志系统,如: ELK
  • 更多参考上述文档或请教专业运维,下面仅演示单机

MongoDB 备份与恢复

  • 要实现备份,需要了解如何在 Docker 中执行 mongodump 命令

  • 在 Docker 中有多个 MongoDB 实例,加入要导出端口为 27017 的 MongoDB 中 connections 名为 log 的数据

  • 执行命令如下:

    docker exec -it <容器名称> mongodump --uri=<数据库连接> --collection=log --out=/tmp/<时间戳>-log 
    
  • 如:

    docker exec -it nestjs-starter-mongo-1 mongodump --uri=mongodb://root:exmaple@localhost:27017/nest-logs --collection=log --out=/tmp/2025-06-06-log
    
  • 上述命令将数据备份到 Docker 容器内的临时目录 /tmp

  • 可以使用以下命令验证备份是否成功:

    docker exec -it <容器名称> ls -la /tmp/<时间戳>-log 
    
  • 如:

    docker exec -it nestjs-starter-mongo-1 ls -la /tmp
    
  • 若要恢复数据,MongoDB 提供了 mongorestore 命令。执行命令如下:

    docker exec -it <容器名称> mongorestore --uri=<数据库连接> --nsInclude=<源数据库名称> <指定的备份目录路径>
    
  • 如:

    docker exec -it nestjs-starter-mongo-1 mongorestore -uri=mongob:/root:exmaple@localhost:27017/nest-logs --nsInclude="nest-logs.log" /tmp/2025-06-06-log/nest-logs
    
  • 为避免覆盖正在使用的 connection,恢复时应指定新的 connection 名称(文件路径), 以便不影响正在收集的 connection

  • 例如,从 netlogs 中读取数据并恢复到 netlogs.log-2025-06-06 这个新的 connection 上,若该 connection 不存在则会自动创建

    docker exec -it nestjs-starter-mongo-1 mongorestore -uri=mongob:/root:exmaple@localhost:27017/nest-logs --nsFrom="nest-logs.log" --nsTo="nest-logs.log-2025-06-06" /tmp/2025-06-06-log/nest-logs
    
  • 也就是,如果需要查看历史备份,通常会将其恢复到一个全新名称的 connection 上,而不是覆盖原有的 connection

  • 现在,我们使用命令来做,每次都是手动的,还是比较麻烦的,我们想要使用定时任务来做,就需要完善定时任务的脚本

  • 这里推荐一个 ssh 的工具: ssh2

    • 这里基于文档,可以封装一个 ssh 相关的模块
    • 来提供 sshService 服务
    • 这里细节不提供
  • 现在补充定时任务,编辑 common/cron/tasks.service.ts 示例如下:

    import { Injectable } from '@nestis/common';
    import { Cron } from '@nestjs/schedule';
    import { SshService } from '@/utils/ssh/ssh.service';@Injectable()
    export class TasksService {constructor(private sshService: SshService){}@Cron('* * ** * *')handleCron(){//备份:连接到MongoDB并导出对应的db中的collections的数据//滚动记录:删除已有的collections的数据//1.删除当前collections中的已备份数据//2.之前备份的collections->对比collection备份的时间,如果超过t天/hours的规则,则删除const containerName = 'mongo-mongo-1';const uri='mongodb: //root: example@localhost: 27017/nest-logs';const now = new Date();const collectionName = 'log';const outputPath =`/tmp/logs-${now.getTime()}`;const hostBackupPath = '/srv/logs';const cmd =`docker exec -i ${containerName} mongodump --uri=${uri} --collection=${collectionName} --out=${outputPath}`;const cpCmd =`docker cp ${containerName}:${outputPath} ${hostBackupPath}`;await this.sshService.exec(`${cmd} && ${cpCmd}`).catch((err) => err)await this.sshService.exec(`ls-la ${hostBackupPath}`);const delCmd = `find ${hostBackupPath} -type d -mtime +30 -exec rm-rf {}\\;`;await this.sshService.exec(delCmd).catch((err)=>err);const res = await this.sshService.exec(`ls-la ${hostBackupPath}`);console.log('~ TasksService ~ handleCron ~ res:', res);}
    }
    

相关文章:

  • Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
  • 飞云智能波段主图+多空短线决策副图指标,组合操盘技术图文解说
  • 预训练语言模型T5-11B的简要介绍
  • 【Dv3Admin】系统视图菜单字段管理API文件解析
  • 如何以 9 种方式将照片从手机传输到笔记本电脑
  • 智谱清言沉思智能体,天工智能体,agenticSeek等AI Agent测试记录
  • Linux缓冲区与glibc封装:入门指南
  • 2025年全国青少年信息素养大赛 scratch图形化编程挑战赛 小高组初赛 真题详细解析
  • 【更新至2024年】2000-2024年上市公司财务困境MertonDD模型数据(含原始数据+结果)
  • Shopify 主题开发:店铺品牌色在主题中的巧妙运用
  • Oracle 用户名大小写控制
  • 12.5Swing控件3Jpanel JOptionPane
  • 设计模式——模板方法
  • Qt生成日志与以及报错文件(mingw64位,winDbg)————附带详细解说
  • 《深度体验 Egg.js:打造企业级 Node.js 应用的全景指南》
  • AI生成的基于html+marked.js实现的Markdown转html工具,离线使用,可实时预览 [
  • 如何使用Webhook触发器,在 ONLYOFFICE 协作空间构建智能工作流
  • 自建 dnslog 回显平台:渗透测试场景下的隐蔽回显利器
  • stm32_DMA
  • 引领AI安全新时代 Accelerate 2025北亚巡展·北京站成功举办
  • dw做网站一般需要多大尺寸/巨量关键词搜索查询
  • 国家城乡建设网站/宁波seo推广公司排名
  • xml网站地图格式/搜狐财经峰会
  • mac系统 类似wordpress/平台seo
  • 施工企业市场调查目的与主题主要有()。/seo公司怎么推广宣传
  • 南阳专业做网站公司/seo相关岗位