# 前言

今天我们来尝试使用腾讯云 Serverless 的事件触发器函数,并且用 Nest 来做云函数执行体.

采用 Serverless 部署项目后,由于用户每次访问才会创建程序响应,响应结束之后又自动销毁

那么如果是像诸如异步队列这类需求,就可以使用事件触发函数.

# 创建腾讯云消息队列 TDMQ

腾讯云 Serverless 目前提供了几种事件触发方式

  • 定时触发
  • COS 触发
  • CMQ 主题订阅触发
  • Ckafkac 触发
  • API 网关触发
  • CLS 日志触发
  • MPS 触发
  • CLB 触发
  • TDMQ 消息队列触发

之前我们上传的项目则都是通过用户访问,即 API 网关触发的

今天我们来部署一个采用 TDMQ 消息队列触发的云函数

# 创建 Pulsar 版集群,命名空间,Topic, 订阅者

创建的东西有点多,在腾讯云后台点点鼠标就可以了,在这里可以看到价格,用量少的话基本上是免费的

  • 调用费: 0.02 元 / 万次 (按阶梯计费)

  • 提示:每月所有集群累计免费额度 1000 万 次

  • 存储费: 0.0025 元 / GB / 小时

  • 提示:每月所有集群累计免费额度 1024MB

注意的地方就是 Topic 需要创建一个 持久化 的主题

在 Topic 管理中再新增一个订阅,订阅名字可以自取

一切准备好之后还需要进入 角色管理 ,针对命名空间进行授权,目的是要获取 秘钥 ,将来在提交消息到队列需要用到.

# 创建新的云函数,并设置触发器为 TDMQ

# 创建云函数

我们创建一个简单的云函数,用以监听刚才创建的 Topic, 有消息来我们就发送一封邮件

首先写一个简单的,有消息过来我们就 console 打印出来

创建文件夹 sender , 里面只有 2 个文件, index.jsserverless.yml

h
$ mkdir sender
$ touch ./sender/index.js
$ touch ./sender/serverless.yml

index.js 内容很简单

t
'use strict';
exports.main_handler = async (event, context) => {
    console.log("Hello World")
    console.log(event)
    console.log(context)
    return event
};

serverless.yml

l
app: sender
component: scf
name: sender
stage: dev
inputs:
  description: 发送邮件
  eip: false
  ignoreTriggers: true
  handler: index.main_handler
  initTimeout: 65
  memorySize: 128
  msgTTL: 21600
  name: sender
  namespace: default
  publicAccess: true
  region: ap-shanghai
  retryNum: 2
  runtime: Nodejs16.13
  src:
    exclude:
      - .env
      - node_modules
    src: ./
  timeout: 3
  type: event
  vpcConfig:
    subnetId: subnet-asy3z93z
    vpcId: vpc-b5776770

这里先不创建触发器 ( ignoreTriggers: true ), 因为使用 Serverless.yml 创建不了 TDMQ 触发器

创建 package.json.env (之前的文章有所说明)

随后执行 npm run deploy

h
$ npm run deploy
> sender@1.0.0 deploy
> sls deploy
serverless ⚡tencent
Action: "deploy" - Stage: "dev" - App: "sender" - Name: "sender"
code:
  bucket: sls-cloudfunction-ap-shanghai-code
  object: /scf_component_i2594ce-1661844005.zip
description:           发送邮件
functionName:          sender
handler:               index.main_handler
lastVersion:           $LATEST
memorySize:            128
namespace:             default
runtime:               Nodejs16.13
sourceCodeDownloadUrl: https://sls-app-code-prod-1300862921.cos.ap-guangzhou.myqcloud.com/1252902543%2Fdev%2Fsender%2Fsender%2Fsource.zip?sign=q-sign-algorithm%3Dsha1%26q-ak%3DAKIDfBS1NUcuMcgVSTqzRhiX6lBUbY4KeLXW%26q-sign-time%3D1661844013%3B1661847613%26q-key-time%3D1661844013%3B1661847613%26q-header-list%3Dhost%26q-url-param-list%3D%26q-signature%3D1b65218fa54d616971a3ea4a1f13ce1971e6fc18
traffic:               1
triggers:              (empty array)
type:                  event
应用控制台: https://serverless.cloud.tencent.com/apps/sender/sender/dev
13s »sender» 执行成功

部署成功之后需要在后台创建触发器

# 绑定触发器

进入函数服务 - 选择 sender 云函数 - 触发管理 点击创建触发器

创建触发器

在这里选择刚才创建的 Topic 和订阅消费者

# 测试事件触发云函数

可以先来测试一下,进入到 Topic 管理界面,点击发送消息

发送消息

进入云函数日志管理可以查看相关日志

n
日志:
START RequestId: 32341b89-2836-11ed-a7ab-525400a75f9f
Hello World
{
  data: {
    msgBody: '{"user":"Mr.adam","email":"1015517471@qq.com"}',
    msgId: '\b���[\x10\x00\x18\x01 \x00',
    subscriptionName: 'sendToMy',
    tags: '',
    timestamp: 1661844875242,
    topic: 'persistent://pulsar-jm4bd43m2jab/sendmail/sendmail',
    topicType: 2
  },
  datacontenttype: 'application/json;charset=utf-8',
  id: '8efd64c4-09f5-4673-acfd-94224522638e',
  region: 'ap-shanghai',
  source: 'tdmq.cloud.tencent',
  specversion: '1.0',
  status: '',
  subject: 'qcs::tdmq:ap-shanghai:uin/1015517471:subscriptionName/pulsar-jm4bd43m2jab/sendmail/sendmail/sendToMy',
  tags: null,
  time: 1661844875277,
  type: 'connector:tdmq'
}
{
  succeed: [Function: succeed],
  fail: [Function: fail],
  callbackWaitsForEmptyEventLoop: [Getter/Setter],
  getRemainingTimeInMillis: [Function: getRemainingTimeInMillis],
  memory_limit_in_mb: 128,
  time_limit_in_ms: 3000,
  request_id: '32341b89-2836-11ed-a7ab-525400a75f9f',
  environment: '{"SCF_NAMESPACE":"default"}',
  environ: 'SCF_NAMESPACE=default;SCF_NAMESPACE=default',
  function_version: '$LATEST',
  function_name: 'sender',
  namespace: 'default',
  tencentcloud_region: 'ap-shanghai',
  tencentcloud_appid: '1252902543',
  tencentcloud_uin: '1015517471'
}

我们刚刚发送的内容正确地被我们接收到了!

# 部署 Nest 独立应用

如果只是单纯发送一封邮件,单个 js 文件或许可以满足需求,但是往往我们的任务会很复杂,比如连接数据库或消息队列,记录日志等操作,这样如果把所有东西都放到一个文件里执行就有点麻烦了,所以需要一个框架来帮我们处理这一类问题,该框架不用监听任何端口,只需要它执行便可,执行完自动销毁

刚好 Nest 框架可以满足这部分需求,接下来修改 sender 云函数为一个 Nest 框架,并且完成发送邮件的相关模块吧

# 安装 Nest 及相关模块

安装一个新的 Nest 项目

h
$ nest new sender

进入 sender 目录中,安装使用 Nest 发送邮件的库

h
$ npm install nestjs-mailer nodemailer -S
$ npm install @types/nodemailer -D

# 创建 mailer 模块

h
$ nest g mo mailer
$ nest g s mailer

# 修改 main.ts

目前项目是事件驱动,所以我们的项目不需要监听任何端口,执行其中的方法即可

t
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { MailerService } from "./mailer/mailer.service";
export default async function bootstrap(body:{user:string,email:string}) {
  // 这里使用 createApplicationContext 加载 AppModule
  const app = await NestFactory.createApplicationContext(AppModule);
  const mailerService = app.get(MailerService);
  await mailerService.sendMail(body);
  // 这里进行销毁实例操作
  await app.close();
}

# 集成 nestjs-mailer

MailerModule 中集成 nestjs-mailer 配置

这里使用我的 QQ 邮箱 SMTP 来发送邮件

t
import { Module } from '@nestjs/common';
import { MailerModule as Mailer } from 'nestjs-mailer';
import { MailerService } from './mailer.service';
@Module({
  imports:[
    Mailer.forRoot({
      config: {
        transport: {
          host: 'smtp.qq.com',
          port: 465,
          secure: true,
          auth: {
            user: '1015517471@qq.com',
            pass: 'xxxxxxx',
          }
        },
        defaults: {
          from: '"Mr.adam" <1015517471@qq.com>',
        },
      },
    }),
  ],
  providers: [MailerService]
})
export class MailerModule {}

mailer.service.ts

t
import { Injectable } from '@nestjs/common';
import { InjectMailer, Mailer, template } from 'nestjs-mailer';
@Injectable()
export class MailerService {
    constructor(
        @InjectMailer() private readonly mailer: Mailer,
    ) {}
    // 发送邮件
    async sendMail(body){
        this.mailer.sendMail({
            from: '"Mr.adam" <1015517471@qq.com>',
            to: body.email,
            subject: 'Hello ✔',
            text: `Hello ${body.user},This is a test mail!`
        }).catch(e => console.log(e));
        console.log("发送成功");
    }
}

index.js 文件是事件函数的入口文件,我们也要响应做出修改,让它初始化 nest 框架,并且把 event.data.msgBody 传输过去

t
'use strict';
const bootstrap = require("./dist/main.js");
exports.main_handler = async (event, context) => {
    await bootstrap.default(JSON.parse(event.data.msgBody));
    return event
};

现在编写一个 test.js 测试一下

首先执行 npm run build 打包 nest 项目

t
//test.js
const {main_handler} = require("./index.js");
const data = {
    data: {
        msgBody: '{"user":"Mr.adam","email":"1015517471@qq.com"}',
    }
};
main_handler(data,"context");

本地先执行一下测试脚本

h
$ node .\test.js
[Nest] 13404  - 2022/08/30 17:03:30     LOG [NestFactory] Starting Nest application...
[Nest] 13404  - 2022/08/30 17:03:30     LOG [InstanceLoader] MailerModule dependencies initialized +28ms
[Nest] 13404  - 2022/08/30 17:03:30     LOG [InstanceLoader] MailerCoreModule dependencies initialized +0ms
[Nest] 13404  - 2022/08/30 17:03:30     LOG [InstanceLoader] MailerModule dependencies initialized +0ms
[Nest] 13404  - 2022/08/30 17:03:30     LOG [InstanceLoader] AppModule dependencies initialized +1ms
object
发送成功

OK, 成功收到邮件!

邮件

# 部署云函数

# 部署 layer

之前的文章有详细讲过,这段就略过了...

### 修改 serverless.yml

l
app: sender
component: scf
name: sender
stage: dev
inputs:
  description: 发送邮件
  eip: false
  ignoreTriggers: true
  handler: index.main_handler
  initTimeout: 65
  memorySize: 128
  msgTTL: 21600
  name: sender
  namespace: default
  publicAccess: true
  region: ap-shanghai
  retryNum: 2
  runtime: Nodejs16.13
  layers:
    - name: sender-layer
      version: 1
  src:
    dist: ./
    exclude:
      - .env
      - node_modules
      - layer
    src: ./
  timeout: 3
  type: event
  vpcConfig:
    subnetId: subnet-asy3z93z
    vpcId: vpc-b5776770

# 执行部署

部署之前修改下 package.json

h
$ npm run deploy
serverless ⚡tencent
Action: "deploy" - Stage: "dev" - App: "sender" - Name: "sender"
code:
  bucket: sls-cloudfunction-ap-shanghai-code
  object: /scf_component_9syxhj-1661851211.zip
description:           发送邮件
functionName:          sender
handler:               index.main_handler
lastVersion:           $LATEST
layers:
  -
    name:    sender-layer
    version: 1
memorySize:            128
namespace:             default
runtime:               Nodejs16.13
sourceCodeDownloadUrl: https://sls-app-code-prod-1300862921.cos.ap-guangzhou.myqcloud.com/1252902543%2Fdev%2Fsender%2Fsender%2Fsource.zip?sign=q-sign-algorithm%3Dsha1%26q-ak%3DAKIDfBS1NUcuMcgVSTqzRhiX6lBUbY4KeLXW%26q-sign-time%3D1661851223%3B1661854823%26q-key-time%3D1661851223%3B1661854823%26q-header-list%3Dhost%26q-url-param-list%3D%26q-signature%3D1b65289dfdde661673d9a010e54b8e90b229657c
traffic:               1
triggers:              (empty array)
type:                  event
应用控制台: https://serverless.cloud.tencent.com/apps/sender/sender/dev
15s »sender» 执行成功

部署完成我们可以再到 Topic 管理发送一条消息测试一下,结果也是成功收到邮件!

至此,我们使用 Nest 独立应用接收到了来自 TDMQ 的通知消息

如何通过 TDMQ 发送消息则见下篇博文!

请我喝杯[咖啡]~( ̄▽ ̄)~*

一个放羊娃 微信支付

微信支付

一个放羊娃 支付宝

支付宝