NestJS实战06-定时任务

by 雪隐 from juejin.cn/user/143341...

本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权

概要

前面几章完成了,当日任务和长期目标的基础模块,现在我将要完成定时任务模块。就像我一开始介绍的那样,我要对我每天没有完成的任务,或者长期目标没有达成的情况下,发送电子邮件来提醒我。

如果大家时间充裕的话,可以看下相关的文章使用Cron Jobs和NestJS实现任务自动化通过工作队列发送邮件。重点要看下Cron Jobs,里面有对时间设置的具体说明。

由于个人管理项目,没有什么特别需要处理高并发的需求,所以我只写了普通的邮件发送就足够了,不需要通过工作队列来处理。

定时任务介绍

NestJS 提供了一种非常方便的方式来创建定时任务,通常用于执行周期性的后台任务,例如数据同步、数据清理、报告生成等等。

以下是如何在 NestJS 中创建和使用定时任务的步骤:

  1. 安装相关依赖:
shall 复制代码
$ pnpm install --save @nestjs/schedule
  1. app.module.ts中注册定时任务:
ts 复制代码
// app.module.ts
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';

@Module({
  imports: [
    ScheduleModule.forRoot()
  ],
})
export class AppModule {}
  1. 创建定时任务服务
ts 复制代码
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';

@Injectable()
export class TasksService {
  private readonly logger = new Logger(TasksService.name);

  @Cron('45 * * * * *')
  handleCron() {
    this.logger.debug('Called when the current second is 45');
  }
}

详细内容请参照官方文档

邮件发送

在 NestJS 中发送邮件通常涉及使用邮件发送库,如 Nodemailer。下面是如何在 NestJS 中发送邮件的一般步骤:

  1. 安装 Nodemailer:

首先,你需要在你的 NestJS 项目中安装 Nodemailer。可以使用以下命令安装:

ts 复制代码
$ pnpm install nodemailer
  1. 创建一个邮件服务:

在你的 NestJS 应用程序中,创建一个专门的邮件服务,该服务负责配置和发送邮件。你可以创建一个自定义的邮件服务类,也可以将邮件配置添加到现有的服务中。

ts 复制代码
import { Injectable } from '@nestjs/common';
import * as nodemailer from 'nodemailer';
import { ConfigService } from '@nestjs/config';
import { ConfigEnum } from './enum/config.enum';

@Injectable()
export class EmailService {
  private transporter;

  constructor(private readonly configService: ConfigService) {
    const mailConfig = this.configService.get(ConfigEnum.MAIL_CONFIG);

    this.transporter = nodemailer.createTransport({
      host: mailConfig.host,
      port: mailConfig.port,
      secure: true, // 如果是 SMTPS 连接,设置为 true
      auth: {
        user: mailConfig.authUser,
        pass: mailConfig.authPass,
      },
    });
  }

  async sendMail(to: string, subject: string, text: string): Promise<void> {
    const mailOptions = {
      from: this.configService.get(ConfigEnum.MAIL_CONFIG).authUser,
      to,
      subject,
      text,
    };

    await this.transporter.sendMail(mailOptions);
  }
}

在这个示例中,我们创建了一个 EmailService,它接收邮件配置信息并初始化 Nodemailer 的传输器。然后,它提供了一个 sendMail 方法,用于发送邮件。

  1. 注册邮件服务:

EmailService 添加到你的 NestJS 模块的 providers 数组中,以便在整个应用程序中使用它。

ts 复制代码
import { Module } from '@nestjs/common';
import { EmailService } from './email.service';

@Module({
  providers: [EmailService],
  // ...
})
export class AppModule {}
  1. 使用邮件服务:

现在,你可以在你的控制器或其他服务中使用 EmailService 来发送邮件。例如,在你的控制器中:

ts 复制代码
import { Controller, Get } from '@nestjs/common';
import { EmailService } from './email.service';

@Controller('email')
export class EmailController {
  constructor(private readonly emailService: EmailService) {}

  @Get('send')
  async sendEmail() {
    try {
      await this.emailService.sendMail('recipient@example.com', 'Hello', 'This is the email body.');
      return 'Email sent successfully!';
    } catch (error) {
      return 'Email sending failed: ' + error;
    }
  }
}

这是一个简单的示例,说明如何在 NestJS 中发送邮件。你可以根据你的需求扩展和自定义邮件服务,以适应不同的邮件发送场景。确保你的配置信息正确,以及你的邮件服务提供了适当的错误处理来处理可能的发送失败情况。

定时任务做成

  • 首先,因为需要用到当日任务的长期目标模块里面的方法。先导入这2个模块
ts 复制代码
import { Module } from '@nestjs/common';
import { TasksCronService } from './tasks-cron.service';
import { TasksModule } from '../tasks/tasks.module';
import { LongTeamGoalsModule } from '../long-team-goals/long-team-goals.module';

@Module({
  imports: [TasksModule, LongTeamGoalsModule],
  providers: [TasksCronService],
})
export class TasksCronModule {}
  • 然后都要用到邮件发送,所以要在构造函数中初期化transporter。并且发送成功或者失败,希望能在log日志中能看到,所以loggerErrorloggerNomal2个函数。
ts 复制代码
import * as nodemailer from 'nodemailer';
import { TasksService } from '../tasks/tasks.service';
import { LongTeamGoalsService } from '../long-team-goals/long-team-goals.service';
import { ConfigService } from '@nestjs/config';
import { ConfigEnum } from '../enum/config.enum';
import { Injectable, Logger } from '@nestjs/common';

@Injectable()
export class TasksCronService {
  private transporter;
  private readonly logger = new Logger(TasksCronService.name);

  constructor(
    private readonly configService: ConfigService,
    private readonly tasksService: TasksService,
    private readonly longsService: LongTeamGoalsService,
  ) {
    const MAIL_CONFIG = this.configService.get(ConfigEnum.MAIL_CONFIG);

    this.transporter = nodemailer.createTransport({
      host: MAIL_CONFIG.host,
      port: MAIL_CONFIG.port,
      secure: true,
      auth: {
        user: MAIL_CONFIG.authUser,
        pass: MAIL_CONFIG.authPass,
      },
    });
  }
  
  loggerError(message: string, error?: any) {
    this.logger.error(message, error);
  }

  loggerNomal(message: string, info?: any) {
    this.logger.log(message, info);
  }
  
  // 发送邮件
  async sendReminderEmail(taskTitles: string) {
    const MAIL_CONFIG = this.configService.get(ConfigEnum.MAIL_CONFIG);

    const mailOptions = {
      from: MAIL_CONFIG.authUser,
      to: MAIL_CONFIG.destUser,
      subject: '您有未完成的任务',
      text: `以下是您今天尚未完成的任务:\n\n${taskTitles}`,
    };

    this.transporter.sendMail(mailOptions, (error, info) => {
      if (error) {
        this.loggerError(`邮件发送失败: ${error}`);
      } else {
        this.loggerNomal(`邮件已发送: ${info.response}`);
      }
    });
  }
}
  • 简单的介绍下,项目中定时任务的内容。
  1. 每天下午17:30点,检查当日任务里面有没有没完成的任务。如果有就发送电子邮件
ts 复制代码
  @Cron('30 17 * * *') // 每天下午7点30分执行
  async handleCron() {
    this.loggerNomal('开始检查未完成的任务');

    const currentDate = dayjs().format('YYYY-MM-DD');
    const startDate = currentDate;
    const endDate = currentDate;

    const tasks = await this.tasksService.search({
      startDate,
      endDate,
    });

    // 过滤当日任务中已完成的任务。
    const incompleteTasks = tasks.data.filter((task) => !task.isCompleted);

    if (incompleteTasks.length) {
      const titles = incompleteTasks.map((task) => task.title).join('\n');
      await this.sendReminderEmail(titles);
    }
  }
  1. 每天晚上22:00点,检查长期目标里面有没有临近的任务还没有完成。如果有就发送电子邮件
ts 复制代码
  @Cron('0 22 * * *')
  async handleLongCron() {
    const nearlyExpiredGoals = await this.longsService.findNearlyExpiredGoals(
      3,
    );

    if (nearlyExpiredGoals.length) {
      const titles = nearlyExpiredGoals.map((long) => long.title).join('\n');
      await this.sendReminderEmail(titles);
      await this.longsService.setWarned(nearlyExpiredGoals);
    }
  }
  • 长期目标模块中的方法
ts 复制代码
  // 在你的目标服务中
  async findNearlyExpiredGoals(days: number): Promise<any[]> {
    const deadlineDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000);
    return this.longTermGoalModel
      .find({
        deadline: { $lte: deadlineDate },
        isWarned: false,
      })
      .exec();
  }

总结

这章介绍了下定时任务和邮件发送相关的内容,顺便完成,当日任务和长期目标的提醒功能。 如果大家觉得这篇文章对您们有帮助,请记得点赞评论🙏!

本章代码

代码

相关推荐
AlienZHOU6 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc9 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师11 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen11 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒12 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow12 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林81812 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang8007812 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明12 小时前
第 6 章 组件进阶
前端·vue.js
江华森12 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端