diff --git a/src/config/index.ts b/src/config/index.ts index 720ea3e..491c8b6 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -5,3 +5,5 @@ export * from './meili.config'; export * from './api.config'; export * from './sms.config'; export * from './smtp.config'; +export * from './redis.config'; +export * from './queue.config'; diff --git a/src/config/queue.config.ts b/src/config/queue.config.ts new file mode 100644 index 0000000..b70ada9 --- /dev/null +++ b/src/config/queue.config.ts @@ -0,0 +1,5 @@ +import { QueueOptions } from '@/modules/core/types'; + +export const queue: () => QueueOptions = () => ({ + redis: 'default', +}); diff --git a/src/config/redis.config.ts b/src/config/redis.config.ts new file mode 100644 index 0000000..c8f8024 --- /dev/null +++ b/src/config/redis.config.ts @@ -0,0 +1,6 @@ +import { RedisOptions } from '@/modules/core/types'; + +export const redis: () => RedisOptions = () => ({ + host: '127.0.0.1', + port: 6379, +}); diff --git a/src/modules/core/core.module.ts b/src/modules/core/core.module.ts index b50b837..3ec2500 100644 --- a/src/modules/core/core.module.ts +++ b/src/modules/core/core.module.ts @@ -1,4 +1,12 @@ -import { DynamicModule, Module } from '@nestjs/common'; +import { BullModule } from '@nestjs/bullmq'; +import { DynamicModule, Module, ModuleMetadata } from '@nestjs/common'; + +import { isArray, isNil, omit } from 'lodash'; + +import { RedisService, SmsService, SmtpService } from '@/modules/core/services'; +import { QueueOptions, RedisOptions, SmsOptions, SmtpOptions } from '@/modules/core/types'; + +import { createQueueOptions, createRedisOptions } from '@/options'; import { Configure } from '../config/configure'; @@ -6,11 +14,54 @@ import { Configure } from '../config/configure'; export class CoreModule { static async forRoot(configure: Configure): Promise { await configure.store('app.name'); + const providers: ModuleMetadata['providers'] = []; + const exports: ModuleMetadata['exports'] = []; + let imports: ModuleMetadata['imports'] = []; + const redis = createRedisOptions(await configure.get('redis')); + if (!isNil(redis)) { + providers.push({ + provide: RedisService, + useFactory: () => { + const service = new RedisService(redis); + service.createClients(); + return service; + }, + }); + exports.push(RedisService); + + const queues = createQueueOptions(await configure.get('queue'), redis); + if (!isNil(queues)) { + if (isArray(queues)) { + imports = queues.map((v) => BullModule.forRoot(v.name, omit(v, 'name'))); + } else { + imports.push(BullModule.forRoot(queues)); + } + } + } + + const sms = await configure.get('sms'); + if (!isNil(sms)) { + providers.push({ + provide: SmsService, + useFactory: () => new SmsService(sms), + }); + exports.push(SmsService); + } + + const smtp = await configure.get('smtp'); + if (!isNil(smtp)) { + providers.push({ + provide: SmtpService, + useFactory: () => new SmtpService(smtp), + }); + exports.push(SmtpService); + } return { module: CoreModule, global: true, - providers: [], - exports: [], + providers, + exports, + imports, }; } } diff --git a/src/modules/core/services/index.ts b/src/modules/core/services/index.ts new file mode 100644 index 0000000..7267053 --- /dev/null +++ b/src/modules/core/services/index.ts @@ -0,0 +1,3 @@ +export * from './redis.service'; +export * from './sms.service'; +export * from './smtp.service'; diff --git a/src/modules/core/services/redis.service.ts b/src/modules/core/services/redis.service.ts new file mode 100644 index 0000000..dd7b829 --- /dev/null +++ b/src/modules/core/services/redis.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; + +import Redis from 'ioredis'; + +import { isNil } from 'lodash'; + +import { RedisOption } from '@/modules/core/types'; + +@Injectable() +export class RedisService { + protected options: RedisOption[]; + + protected clients: Record = {}; + + constructor(protected _options: RedisOption[]) { + this.options = _options; + } + + async createClients() { + this.options.map(async (option) => { + this.clients[option.name] = new Redis(option); + }); + } + + getClient(name?: string): Redis { + const key = isNil(name) ? 'default' : name; + if (this.clients[key]) { + return this.clients[key]; + } + throw new Error(`Unknown client ${key}`); + } + + getClients() { + return this.clients; + } +} diff --git a/src/options.ts b/src/options.ts index 6bb020a..730b1f4 100644 --- a/src/options.ts +++ b/src/options.ts @@ -5,7 +5,7 @@ import { NestFactory } from '@nestjs/core'; import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; import { existsSync } from 'fs-extra'; -import { isNil } from 'lodash'; +import { isArray, isNil, omit } from 'lodash'; import { RbacModule } from '@/modules/rbac/rbac.module'; import { UserModule } from '@/modules/user/user.module'; @@ -13,7 +13,13 @@ import { UserModule } from '@/modules/user/user.module'; import * as configs from './config'; import { ContentModule } from './modules/content/content.module'; import { GlobalExceptionFilter } from './modules/core/filters/global-exception.filter'; -import { CreateOptions, RedisOption, RedisOptions } from './modules/core/types'; +import { + BullOptions, + CreateOptions, + QueueOptions, + RedisOption, + RedisOptions, +} from './modules/core/types'; import * as dbCommands from './modules/database/commands'; import { DatabaseModule } from './modules/database/database.module'; import { MeiliModule } from './modules/meilisearch/meili.module'; @@ -64,6 +70,9 @@ export const createOptions: CreateOptions = { * @param options */ export const createRedisOptions = (options: RedisOptions) => { + if (isNil(options)) { + return undefined; + } const config: Array = Array.isArray(options) ? options : [{ ...options, name: 'default' }]; @@ -79,3 +88,31 @@ export const createRedisOptions = (options: RedisOptions) => { return names.includes(n.name) ? o : [...o, n]; }, []); }; + +/** + * 生成BullMQ模块的配置 + * @param options + * @param redis + */ +export const createQueueOptions = ( + options: QueueOptions, + redis: Array, +): BullOptions | undefined => { + if (isNil(options) || isNil(redis)) { + return undefined; + } + const names = redis.map(({ name }) => name); + if (names.length < 1 || !names.includes('default')) { + return undefined; + } + if (isArray(options)) { + return options.map((option) => ({ + ...omit(option, 'redis'), + connection: redis.find(({ name: c }) => c === (option.redis ?? 'default')), + })); + } + return { + ...omit(options, 'redis'), + connection: redis.find(({ name: c }) => c === (options.redis ?? 'default')), + }; +};