tapnow-nestjs-common v1.0.17
TapNow-NestJS-Common
TapNow-NestJS-Common 的主要目标是为了各个服务聚焦于业务需求,避免各种模块初始化(如Logger,DataBase等)、公用服务等代码重复出现在各个微服务中。TapNow-NestJS-Common 提供一个CommonModule
来初始化服务需要的模块,提供一些公用的装饰器和Service类来简化业务代码,以提高开发效率,减少维护成本。
CommonModule
是一个全局模块,默认会初始化ConfigModule
, LoggerModule
(nestjs-pino),HttpModule
, SequelizeModule
, 可根据微服务自身需要选择是否初始化RedisModule
, RabbitMQModule
。
安装
yarn add tapnow-nestjs-common
配置
在初始化Module时,需要定义每个服务自己的配置项,比如数据库等。配置定义如下:
export interface ConfigOptions {
namespaces: any[]
schema: any
}
namespaces
ConfigModule允许定义和加载多个自定义配置文件,使用registerAs()函数返回namespaces
配置对象,详细介绍可查看官方文档。配置主要分为TapNow-NestJS-Common
各模块所需要的配置项和微服务自身业务所需要的配置项。
TapNow-NestJS-Common 详细配置如下:
export default registerAs('common', () => ({
withCore: {
pino: {
// LoggerModule配置
},
},
withSequelize: {
// sequelize配置
},
withRedis: {
// redis 配置
},
withRabbitMQ: {
// rabbit MQ 配置
},
jwt: {
secret: {
client: 'XXXX', // for web client user, x-auth-schema === CLIENT will use this secret
merchant: 'XXXX', // for merchant user, x-auth-schema === MERCHANT will use this secret
admin: 'XXXX', // for admin user, x-auth-schema === ADMIN will use this secret
api: 'XXXX', // for partner backend, x-auth-schema === API will use this secret
internal: 'XXXX', // for internal service, x-auth-schema === INTERNAL will use this secret
},
},
}))
配置文件示例可查看 test/config/common.config.ts
schema
如果未提供所需的环境变量或它们不符合某些验证规则,则在应用程序启动期间抛出异常是标准做法。详细信息可查看官方文档
schema示例可查看test/config/schema.ts
初始化
定义好配置文件后,便可以开始初始化工作了。
@Module({
imports: [CommonModule.forRoot(config, { withRedis: true, withRabbitMQ: true })],
controllers: [],
providers: [],
})
export class AppModule {}
async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true })
await app.listen(3000)
}
bootstrap()
Module
LoggerModule
首先设置logger:
import { Logger } from 'nestjs-pino';
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(Logger));
推荐Logger使用方式
import { PinoLogger, InjectPinoLogger } from 'nestjs-pino';
export class MyService {
constructor(
private readonly logger: PinoLogger
) {
// Optionally you can set context for logger in constructor or ...
this.logger.setContext(MyService.name);
}
constructor(
// ... set context via special decorator
@InjectPinoLogger(MyService.name)
private readonly logger: PinoLogger
) {}
foo() {
// PinoLogger has same methods as pino instance
this.logger.trace({ foo: 'bar' }, 'baz %s', 'qux');
this.logger.debug('foo %s %o', 'bar', { baz: 'qux' });
this.logger.info('foo');
}
}
SequelizeModule
Sequelize的Model
已定好了createdAt
, updatedAt
, deletedAt
,不需要再额外定义了,直接继承Model
就好
createdAt?: Date | any;
updatedAt?: Date | any;
deletedAt?: Date | any;
TapNow-NestJS-Common 提供了ListingQueryBuilder
来处理分页查询,支持分页和游标两种方式。
const results = await ListingQueryBuilder.new<Picture>()
.model(Picture)
.include([User, Category])
.pageIndex(2)
.pageSize(2)
.order([
['id', 'ASC'],
['createdAt', 'ASC'],
])
.query()
const results = await ListingQueryBuilder.new<Picture>()
.model(Picture.scope('withAll'))
.include([User])
.pageSize(3)
.from(3)
.order([['id', 'ASC']])
.query()
RedisModule
RedisModule提供RedisService
, RedlockService
, SessionService
三个service 类。RedisService 提供一个redis实例来存储数据。RedlockService 提供分布式锁,获取单一资源锁用lock()
,lockWrapper
会在函数执行完成后,自动释放锁资源。
RabbitModule
RabbitMQTaskService
提供服务自己发布消息,自己处理消息的模式。
比如发送邮件,我们先发布一个发送邮件的任务
const EMAIL_TASK = 'email_task'
class EmailService {
constructor(private readonly rabbitMQTaskService: RabbitMQTaskService) {}
async sendEmail(payload) {
return rabbitMQTaskService.publishTask({
msgName: EMAIL_TASK,
payload,
})
}
}
然后监听EMAIL_TASK
event,发送邮件
@Injectable()
class EmailWork {
@OnEvent(EMAIL_TASK)
async charge(payload: PublishPayload) {
// sending email here
}
}
在业务里需要发送邮件时,调用EmailService
的sendEmail
即可。
RabbitTopicMQService
提供对其他服务发布消息或者订阅其他服务发布的消息功能。若消费服务失败,消息将被加入到死信队列。
例如:
支付服务需要将支付成功
告知Partner,此时支付服务需要发布一条支付成功的消息:
const PAYMENT_MESSAGE = 'payment_message'
const PAYMENT_SUCCESS = 'payment_success'
class NotificationService {
constructor(private readonly rabbitMQTopicService: RabbitTopicMQService) {}
async sendMessage(payload) {
return rabbitMQTopicService.publishMessage(PAYMENT_MESSAGE, {
msgName: PAYMENT_SUCCESS,
payload,
})
}
}
Partner订阅支付服务的支付消息:
const QUEUE_NAME = 'payment_queue'
const DEAD_QUEUE_NAME = 'payment_dead_queue'
@Injectable()
class PaymentMessage implements OnApplicationBootstrap {
constructor(private readonly rabbitMQTopicService: RabbitTopicMQService) {}
// subscribe payment message
async onApplicationBootstrap() {
await rabbitMQTopicService.subscribe(
QUEUE_NAME,
DEAD_QUEUE_NAME,
`#.${PAYMENT_MESSAGE}.#`,
{
durable: true,
autoDelete: false,
},
)
}
@OnEvent(PAYMENT_SUCCESS)
async onPaymentSuccess(payload: PublishPayload) {
// do something when received the payment successful message
}
}
Request
TapNow-NestJS-Common提供了一些公用的装饰器来简化API实现。
import { ClientAuth, CurrentUser, ApiPropertyOptional, ApiProperty, ApiBaseResult } from 'tapnow-nestjs-common'
class PartnerRequest {
@ApiPropertyOptional({
description: 'Partner名字',
example: 'Kitty',
})
@IsString()
name?: string
}
class PartnerResponse {
ApiProperty({
description: 'Partner的ID',
example: 'p_123e',
})
id: string,
ApiProperty({
description: 'Partner名字',
example: 'Kitty',
})
name: string,
}
@Controller()
class TestingController {
@ApiBaseResult(PartnerResponse, 200, '获取Partner信息')
@Get('fake/:id')
@AdminAuth()
getPartner(@CurrentUser() user, @Query() query: PartnerRequest)
: Promise<PartnerResponse> {
// some code here, and return partner finally, and TapNow-NestJS-Common will transform it to { code, message, data: partner }
}
}
如上代码实现了一个获取Partner信息的API,这个API是提供给管理后台的,所以需要进行AdminAuth权限校验。TapNow-NestJS-Common已设置全局的ValidationPipe,因此会对PartnerRequest请求参数进行校验。 PartnerRequest + ApiBaseResult定义了Swagger文档中该API的完整描述。
Response
TapNow-NestJS-Common 会捕捉请求范围内的异常并返回错误信息,对于正常的请求结果会转换成{ code, message, data }
的数据结构,其中data
是api返回的数据。
Auth
TapNow-NestJS-Common提供了PUBLIC
, CLIENT
, ADMIN
, MERCHANT
, API
, INTERNAL
, DEBUG
这几种授权方式,并提供了相应的装饰器。由于授权用到了session,所以必须加载RedisModule。
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago