-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(核心模块): 新增mailer模块 并建立注册和找回密码邮件验证服务
- Loading branch information
1 parent
6a22fae
commit 8cce6fd
Showing
11 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './mailer.module'; | ||
export * from './mailer.decorators'; | ||
export * from './mailer-options.interface'; | ||
export * from './mailer.service'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { ModuleMetadata } from '@nestjs/common/interfaces'; | ||
import { Type } from '@nestjs/common'; | ||
export interface MailerModuleOptions{ | ||
transport: 'SMTPTransport' | 'SMTPPool' | 'SendmailTransport' | 'StreamTransport' | 'JSONTransport' | 'SESTransport' | 'Transport'; | ||
[key: string]: any; | ||
} | ||
|
||
import * as JSONTransport from 'nodemailer/lib/json-transport'; | ||
import * as SendmailTransport from 'nodemailer/lib/sendmail-transport'; | ||
import * as SESTransport from 'nodemailer/lib/ses-transport'; | ||
import * as SMTPPool from 'nodemailer/lib/smtp-pool'; | ||
import * as SMTPTransport from 'nodemailer/lib/smtp-transport'; | ||
import * as StreamTransport from 'nodemailer/lib/stream-transport'; | ||
|
||
export { TransportOptions } from 'nodemailer'; | ||
export interface SendmailTransportOptions extends SendmailTransport.Options {} | ||
export interface JSONTransportOptions extends JSONTransport.Options {} | ||
export interface SESTransportOptions extends SESTransport.Options {} | ||
export interface SMTPTransportOptions extends SMTPTransport.Options {} | ||
export interface SMTPPoolOptions extends SMTPPool.Options {} | ||
export interface StreamTransportOptions extends StreamTransport.Options {} | ||
|
||
export interface MailerOptionsFactory<T> { | ||
createMailerOptions(): Promise<T> | T; | ||
} | ||
|
||
export interface MailerModuleAsyncOptions<T> extends Pick<ModuleMetadata, 'imports'> { | ||
/** | ||
* 模块的名称 | ||
*/ | ||
name?: string; | ||
/** | ||
* 应该用于提供MailerOptions的类 | ||
*/ | ||
useClass?: Type<T>; | ||
/** | ||
* 工厂应该用来提供MailerOptions | ||
*/ | ||
useFactory?: (...args: any[]) => Promise<T> | T; | ||
/** | ||
* 应该注入的提供者 | ||
*/ | ||
inject?: any[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const MAILER_MODULE_OPTIONS = 'MAILER_MODULE_OPTIONS'; | ||
export const MAILER_TOKEN = 'MAILER_TOKEN'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Inject } from '@nestjs/common'; | ||
|
||
import { MAILER_TOKEN } from './mailer.constants'; | ||
|
||
export const InjectMailer = () => Inject(MAILER_TOKEN); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { DynamicModule, Module, Provider, Global } from '@nestjs/common'; | ||
import { MailerModuleAsyncOptions, MailerOptionsFactory } from './mailer-options.interface'; | ||
import { MailerService } from './mailer.service'; | ||
import { MAILER_MODULE_OPTIONS } from './mailer.constants'; | ||
import { createMailerClient } from './mailer.provider'; | ||
|
||
@Global() | ||
@Module({}) | ||
export class MailerModule { | ||
/** | ||
* 同步引导邮箱模块 | ||
* @param options 邮箱模块的选项 | ||
*/ | ||
static forRoot<T>(options: T): DynamicModule { | ||
return { | ||
module: MailerModule, | ||
providers: [ | ||
{ provide: MAILER_MODULE_OPTIONS, useValue: options }, | ||
createMailerClient<T>(), | ||
MailerService, | ||
], | ||
exports: [MailerService], | ||
}; | ||
} | ||
|
||
/** | ||
* 异步引导邮箱模块 | ||
* @param options 邮箱模块的选项 | ||
*/ | ||
static forRootAsync<T>(options: MailerModuleAsyncOptions<T>): DynamicModule { | ||
return { | ||
module: MailerModule, | ||
imports: options.imports || [], | ||
providers: [ | ||
...this.createAsyncProviders(options), | ||
createMailerClient<T>(), | ||
MailerService, | ||
], | ||
exports: [MailerService], | ||
}; | ||
} | ||
|
||
/** | ||
* 根据给定的模块选项返回异步提供程序 | ||
* @param options 邮箱模块的选项 | ||
*/ | ||
private static createAsyncProviders<T>( | ||
options: MailerModuleAsyncOptions<T>, | ||
): Provider[] { | ||
if (options.useFactory) { | ||
return [this.createAsyncOptionsProvider<T>(options)]; | ||
} | ||
return [ | ||
this.createAsyncOptionsProvider(options), | ||
{ | ||
provide: options.useClass, | ||
useClass: options.useClass, | ||
}, | ||
]; | ||
} | ||
|
||
/** | ||
* 根据给定的模块选项返回异步邮箱选项提供程序 | ||
* @param options 邮箱模块的选项 | ||
*/ | ||
private static createAsyncOptionsProvider<T>( | ||
options: MailerModuleAsyncOptions<T>, | ||
): Provider { | ||
if (options.useFactory) { | ||
return { | ||
provide: MAILER_MODULE_OPTIONS, | ||
useFactory: options.useFactory, | ||
inject: options.inject || [], | ||
}; | ||
} | ||
return { | ||
provide: MAILER_MODULE_OPTIONS, | ||
useFactory: async (optionsFactory: MailerOptionsFactory<T>) => await optionsFactory.createMailerOptions(), | ||
inject: [options.useClass], | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { MAILER_MODULE_OPTIONS, MAILER_TOKEN } from './mailer.constants'; | ||
import { createTransport } from 'nodemailer'; | ||
|
||
export const createMailerClient = <T>() => ({ | ||
provide: MAILER_TOKEN, | ||
useFactory: (options: T) => { | ||
return createTransport(options); | ||
}, | ||
inject: [MAILER_MODULE_OPTIONS], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { Inject, Injectable, Logger } from '@nestjs/common'; | ||
import { MAILER_TOKEN } from './mailer.constants'; | ||
import * as Mail from 'nodemailer/lib/mailer'; | ||
import { Options as MailMessageOptions } from 'nodemailer/lib/mailer'; | ||
|
||
import { from, Observable } from 'rxjs'; | ||
import { tap, retryWhen, scan, delay } from 'rxjs/operators'; | ||
|
||
const logger = new Logger('MailerModule'); | ||
|
||
@Injectable() | ||
export class MailerService { | ||
constructor( | ||
@Inject(MAILER_TOKEN) private readonly mailer: Mail, | ||
) { } | ||
// 注册插件 | ||
use(name: string, pluginFunc: (...args) => any): ThisType<MailerService> { | ||
this.mailer.use(name, pluginFunc); | ||
return this; | ||
} | ||
|
||
// 设置配置 | ||
set(key: string, handler: (...args) => any): ThisType<MailerService> { | ||
this.mailer.set(key, handler); | ||
return this; | ||
} | ||
|
||
// 发送邮件配置 | ||
async send(mailMessage: MailMessageOptions): Promise<any> { | ||
return await from(this.mailer.sendMail(mailMessage)) | ||
.pipe(handleRetry(), tap(() => { | ||
logger.log('send mail success'); | ||
this.mailer.close(); | ||
})) | ||
.toPromise(); | ||
} | ||
} | ||
|
||
export function handleRetry( | ||
retryAttempts = 5, | ||
retryDelay = 3000, | ||
): <T>(source: Observable<T>) => Observable<T> { | ||
return <T>(source: Observable<T>) => source.pipe( | ||
retryWhen(e => | ||
e.pipe( | ||
scan((errorCount, error) => { | ||
logger.error( | ||
`Unable to connect to the database. Retrying (${errorCount + 1})...`); | ||
if (errorCount + 1 >= retryAttempts) { | ||
logger.error('send mail finally error', JSON.stringify(error)); | ||
throw error; | ||
} | ||
return errorCount + 1; | ||
}, 0), | ||
delay(retryDelay), | ||
), | ||
), | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './services.module'; | ||
export * from './mail.services'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { InjectMailer, MailerService } from 'core/mailer'; | ||
import { ConfigService, EnvConfig } from 'config'; | ||
import { InjectConfig } from 'config/config.decorators'; | ||
|
||
@Injectable() | ||
export class MailService { | ||
private readonly from: string; | ||
private readonly name: string; | ||
private readonly host: string; | ||
constructor( | ||
@InjectMailer() private readonly mailer: MailerService, | ||
@InjectConfig() private readonly config: ConfigService<EnvConfig>, | ||
) { | ||
this.name = this.config.get('name'); | ||
this.host = `${this.config.get('HOST')}:${this.config.get('PORT')}`; | ||
this.from = `${this.name} <${this.config.get('MAIL_USER')}>`; | ||
} | ||
|
||
/** | ||
* 激活邮件 | ||
* @param to 激活人邮箱 | ||
* @param token token | ||
* @param username 名字 | ||
*/ | ||
sendActiveMail(to: string, token: string, username: string){ | ||
const name = this.name; | ||
const subject = `${name}社区帐号激活`; | ||
const html = `<p>您好:${username}</p> | ||
<p>我们收到您在${name}社区的注册信息,请点击下面的链接来激活帐户:</p> | ||
<a href="${this.host}/active_account?key=${token}&name=${username}">激活链接</a> | ||
<p>若您没有在${name}社区填写过注册信息,说明有人滥用了您的电子邮箱,请删除此邮件,我们对给您造成的打扰感到抱歉。</p> | ||
<p>${name}社区 谨上。</p>`; | ||
this.mailer.send({ | ||
from: this.from, | ||
to, | ||
subject, | ||
html, | ||
}); | ||
} | ||
|
||
/** | ||
* 重置密码邮件 | ||
* @param to 重置人邮箱 | ||
* @param token token | ||
* @param username 名字 | ||
*/ | ||
sendResetPassMail(to: string, token: string, username: string) { | ||
const name = this.name; | ||
const subject = `${name}社区密码重置`; | ||
const html = `<p>您好:${username}</p> | ||
<p>我们收到您在${name}社区重置密码的请求,请在24小时内单击下面的链接来重置密码:</p> | ||
<a href="${this.host}/reset_pass?key=${token}&name=${username}">重置密码链接</a> | ||
<p>若您没有在${name}社区填写过注册信息,说明有人滥用了您的电子邮箱,请删除此邮件,我们对给您造成的打扰感到抱歉。</p> | ||
<p>${name}社区 谨上。</p>`; | ||
this.mailer.send({ | ||
from: this.from, | ||
to, | ||
subject, | ||
html, | ||
}); | ||
this.mailer.send({ | ||
from: this.from, | ||
to, | ||
subject, | ||
html, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { MailService } from './mail.services'; | ||
|
||
@Module({ | ||
imports: [], | ||
providers: [MailService], | ||
exports: [MailService], | ||
}) | ||
export class ServicesModule { } |