How To Develop Mog - 1

8 个月前
/ ,
395
1
这篇文章上次修改于 8 个月前,可能部分内容已经不适用,如有疑问可询问作者。

这是 《如何开发 Mog》 系列的第一篇章,不过对于这个项目的结构我可能就不会怎么讲了,可能就稍微略过了,整体下来可能这个系列比较混乱,是开发的过程中就捎带写下罢了

如何新增配置

在 Mog v1 的时候,ConfigService 已经被抽离为一个 Library,你可以前往 libs/config 里看到有关这个服务的所有信息

你可以进入 apps/configs-service 查看服务的文件

在 Mog Stage 5 之前,ConfigService 依旧是一个 Library,但是在 Stage 6 中它已经变成了一个独立的子服务统一管理配置。

在 ConfigService 里基本都是关键文件,除了那些启动用的 main.tsmodule 啥的, 其他新增配置都需要碰的,不过我大致列举一下吧:

  • config.dto.ts - 不同配置 Key 对应的 Dto
  • config.interface.ts - 配置 interface
  • config.default.ts - 默认配置

开发的顺序也是如列表那样的,需要先写好 Dto。

那么我现在以 计划任务 这个 feature 作为例子,介绍如何新增一个配置计划任务的配置项。

事实上,这个 PR 已经相对来说不能够来作为例子了,因为它是 Stage 5 实现的,在 Stage 6 我们将其全部迁移至了独立服务. 你只能够学习如何加配置,但是不能学习文件的储存位置,新的储存位置在 apps/configs-service 里面

计划任务配置项

在我设想的计划任务配置项中它需要有以下内容:

  • 任务名
  • Cron 表达式
  • 任务描述
  • 任务操作类型
  • 任务操作
  • 任务执行后对数据操作类型
  • 任务执行后对数据操作

因此让我们前往 config.dto.ts 定义 DTO,大致是这个样子的:

// config.dto.ts
export class ScheduleDto {
  @IsString()
  @IsNotEmpty()
  name: string;

  @IsString()
  @IsNotEmpty()
  cron: string;

  @IsString()
  description?: string;

  @IsString()
  @IsNotEmpty()
  @IsEnum(ScheduleType)
  type: ScheduleType;

  @IsNotEmpty()
  action: any; // 这随着 type 的不同而不同, 用于储存如:url,method,body的数据

  @IsString()
  @IsNotEmpty()
  @IsEnum(AfterSchedule)
  after: AfterSchedule;

  @IsNotEmpty()
  afterAction: string; // 这随着 after 的不同而不同

}

接着我们前往 config.interface.ts,在 ConfigsInterface 里新增一个 schedule:

export abstract class ConfigsInterface {
  // ...
  @Type(() => ScheduleDto)
  @ValidateNested()
  schedule: ScheduleDto[];
}

这样就可以辽~~在你更新此配置项的时候,ConfigService 便会自动检查你的数据格式是否合法了。让我们前进到获取数据库配置吧。

读取计划任务配置项

由于这个 feat 的特性,我需要在初始化的时候就读取数据库,但是一般情况下是不需要的。

在 constructor 里引入 ConfigService (记得 Module 里要 import )

Stage 6 后,子服务要使用配置服务不能直接导入 ConfigService 使用,这会导致多端数据不同步问题
import { Module } from '@nestjs/common';
import { ClientsModule } from '@nestjs/microservices';
import { ServicesEnum } from '~/shared/constants/services.constant';
import { REDIS_TRANSPORTER } from '~/shared/constants/transporter.constants';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: ServicesEnum.config,
        ...REDIS_TRANSPORTER,
      },
    ]),
  ],
  controllers: [
        // YOUR CONTROLLERS
    ],
})
export class YourModule {}

接着在 constructor 里引入 ClientProxy, 但请记住需要 Inject

export class NotificationService {
  constructor(
    @Inject(ServicesEnum.config) private readonly configService: ClientProxy,
  ) {}
}

这样便可得到配置

const config = transportReqToMicroservice(
  this.configService,
  ConfigEvents.ConfigGetByMaster,
  'schedule',
);

其他的 ConfigEvents 你可以在 shared/constants/event.constant.ts 里找到

transportReqToMicroservice 是转接请求到活动传播的一个小工具,它可以便捷地处理返回的 RpcException,并转化为 HttpException

如何新增活动

Mog 有一个文件储存了全部的 Events:shared/constants/event.constant.ts 里面是不同的 enum,它们遵循一个命名原则:<service_mininum_name> + Events 如:StoreService -> StoreEvents, ThemesService -> ThemesEvents

我以新增 StoreService 中创建文件活动为例子,为你讲述如何新增活动:

新增活动名

要确定是否需要鉴权,如果需要鉴权,在活动最后需要加上 auth

如:

export enum StoreEvents {
  //...
  StoreFileCreateByMaster = 'store.file.create.auth',
}

有关其他活动的具体要求,在后文有关新增服务时会着重讲述。

传播活动与监听活动

服务活动有两种触发方式,分别是Request-responseEvent-based,有关区别自行看文档。我在这个 feat 中需要使用 request-response 方式

前往对应 service 的 controller,撰写代码,如:(已假设你已写好 service 的相关逻辑)

@MessagePattern({ cmd: StoreEvents.StoreFileCreateByMaster })
async storeFileCreate(data: { name: string, content: string | Buffer }) {
  return await this.storeServiceService.create(data.name, data.content);
}

再前往 Gateway (core)中对应 module 的 controller,撰写代码,如:

@Post("/create")
@ApiOperation({ summary: '创建文件' })
@Auth()
create(@Body('name') name: string, @Body('content') content: Buffer) {
  return transportReqToMicroservice(
    this.store,
    StoreEvents.StoreFileCreateByMaster,
    { name, content },
  );
}
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...