## 基础能力集成(Kafka / Redis / 队列 / 事务) ### 总览 - 目标: 将事件、任务、缓存、事务能力以统一规范接入 App/Core/Infrastructure 三层,替代“散落式调用”。 - 约束: 由 Application 发起流程;Core 编排业务规则且不直接依赖外设;Infrastructure 提供具体实现。 ### 1) 事务(TypeORM) - 发起层: Application(用例级事务边界) - 使用方式: ```ts // application/xxx.app.service.ts constructor(private readonly dataSource: DataSource, private readonly core: XxxCoreService) {} async runUseCase(dto: Dto) { return await this.dataSource.transaction(async (manager) => { // 将 manager 注入到仓储实现(通过请求域注入或方法透传) await this.core.handle(dto); // Core 内仅调用仓储接口 }); } ``` - 规范: - 事务只在 Application 层开启;Core 不直接操作事务对象 - 多仓储参与时基于同一 `EntityManager` ### 2) 队列(Bull/BullMQ 或 DB 队列) - 发起层: Application(用例结束后入队) - 接入点: `UnifiedQueueService` 或具体 Provider(如 `BullQueueProvider`/`DatabaseQueueProvider`) ```ts // application/xxx.app.service.ts constructor(private readonly queue: UnifiedQueueService) {} await this.queue.addJob('media', 'generateThumbnail', { attId }, { attempts: 3, delay: 0 }); ``` - 处理器建议放置: - `infrastructure/queues/xxx.processor.ts` 或独立消费模块 - 规范: - 入队数据为最小必要字段(ID/键);大对象存储DB再查 ### 3) 事件(Kafka / DB Outbox) - 发起层: Application(领域事件在用例完成后发布) - 接入点: `DomainEventService`(绑定 `IEventBus`,默认 DB Outbox,可切 Kafka) ```ts // application/xxx.app.service.ts constructor(private readonly events: DomainEventService) {} await this.events.publishEvent( 'system.settings.storage.updated', String(siteId), String(siteId), { storageType }, ); ``` - 配置切换: - 通过 `EventBusModule` 的 provider 切换 `DatabaseEventBusProvider` ⇄ `KafkaEventBusProvider` - 规范: - 事件名格式: `domain.aggregate.action` - 载荷仅含必要业务字段,带上 `tenantId/siteId` ### 4) Redis(缓存/限流/幂等) - 发起层: Application(流程性控制)或 Infrastructure(技术性实现) - 接入点: `RedisProvider`(`vendor/redis`) - 常见场景: - 读多写少配置缓存:`sys_config` 读取后短缓存 - 上传限流/防刷:基于 IP/UID 的计数器 - 幂等:`SETNX` + 过期控制 ### 5) 存储(本地/云) - 发起层: Application 调用 Core 规则后,委托 Infrastructure Provider 落地 - 接入点: `infrastructure/providers/*` 或 `vendor/storage/*`(如 `LocalStorageAdapter`) - 规范: - Provider 通过接口注入,便于切换 OSS/COS/Qiniu ### 6) 在三层中的放置原则 - Application: 事务、入队、发事件、协调多 Core 服务 - Core: 纯业务规则/策略与仓储接口;不直接依赖 Kafka/Redis/队列 - Infrastructure: 队列消费者、存储/HTTP/Redis 具体实现、TypeORM 仓储实现 ### 7) 示例:Upload 模块接入 - 用例: 上传完成 → 入库附件 → 入队生成缩略图 ```ts // application/upload.app.service.ts await this.dataSource.transaction(async (manager) => { const att = await this.core.validateAndPlan(fileMeta); await this.attachmentRepo.withManager(manager).save(att); }); await this.queue.addJob('media', 'generateThumbnail', { attId: att.id }); ``` ### 8) 示例:Settings.Storage 接入 - 用例: 切换默认存储 → 写 `sys_config` → 发布事件 → 入队校验可用性 ```ts // application/storage-settings.app.service.ts await this.dataSource.transaction(async (manager) => { await this.core.ensureExclusiveDefault(type); await this.sysConfigRepo.withManager(manager).setValue(key, value); }); await this.events.publishEvent('system.settings.storage.updated', String(siteId), String(siteId), { type }); await this.queue.addJob('ops', 'validateStorage', { type, siteId }); ``` ### 9) 配置与健康 - 配置在 `VendorModule`/`EventBusModule` 注入;通过 `ConfigService` 读取连接信息 - 健康检查:将 Redis/队列/事件写入健康聚合输出 ### 10) 方案B:vendor/storage 标准结构(平台外设) ``` vendor/storage/ ├── index.ts # 统一导出(接口、Token、模块、适配器) ├── storage.module.ts # 可配置模块(选择具体实现) ├── tokens.ts # 注入Token常量(如 STORAGE_ADAPTER) ├── interfaces/ │ ├── storage-adapter.ts # 适配器接口(平台标准) │ └── types.ts # 通用类型(上传结果、签名参数等) ├── adapters/ │ ├── local.adapter.ts # 本地实现 │ ├── aliyun-oss.adapter.ts # 阿里云实现 │ ├── qcloud-cos.adapter.ts # 腾讯云实现 │ └── qiniu.adapter.ts # 七牛云实现 ├── providers/ │ ├── storage.provider.ts # 工厂: 按配置/站点解析适配器 │ └── registry.ts # 多实例注册表 Map + TTL ├── health/storage.health.ts # 健康检查(各实现可选实现) ├── config/schema.ts # 配置Schema(必需项校验) └── __tests__/ ├── storage.contract.spec.ts # 契约测试(接口一致性) └── adapters/*.spec.ts # 各实现最小测试 ``` - Token ```ts export const STORAGE_ADAPTER = 'STORAGE_ADAPTER'; ``` - 接口 ```ts export interface StorageAdapter { upload(params: { key: string; content: Buffer | NodeJS.ReadableStream; mime?: string }): Promise<{ url: string; key: string }>; delete(key: string): Promise; signUpload?(params: { key: string; expiresSec?: number; mime?: string }): Promise<{ url: string; headers?: Record; fields?: Record }>; healthCheck?(): Promise; } ``` - 按 site_id 解析(站点启用 > 跟随系统 > local 兜底) ```ts function resolveAdapter(siteId: number): StorageAdapter { const enabled = readSiteEnabled(siteId); // storage_xxx with is_use const type = enabled?.type ?? readPlatformDefault(); const options = enabled?.options ?? readPlatformOptions(type) ?? {}; return registry.getOrCreate(siteId, type, options); } ``` - 本地隔离路径:`upload/site_{siteId}/...` - 健康检查:对活跃站点适配器定期 `healthCheck()` 并聚合到 Health > 说明:方案B 将“三方存储”视为外设,接口与实现均在 vendor;业务通过 Token 注入与按站点解析工厂使用,core 无需暴露存储端口。