chore(v1): bump version to 0.1.2; enforce ESLint alias boundaries; add tests/docs

This commit is contained in:
wanwujie
2025-10-20 01:43:20 +08:00
parent b5826ee469
commit 44d0505811
100 changed files with 16778 additions and 248 deletions

View File

@@ -1,10 +1,10 @@
{
"NODE_ENV": "development",
"PORT": 3000,
"NODE_ENV": "test",
"GLOBAL_PREFIX": "api",
"AI_ENABLED": true,
"AI_SIMULATE_DIRECT_ENQUEUE": true,
"PROMETHEUS_ENABLED": true,
"AUTH_ENABLED": false,
"RBAC_ENABLED": false,
"RATE_LIMIT_ENABLED": false,
"REDIS_ENABLED": false
"RATE_LIMIT_ENABLED": false
}

View File

@@ -3,16 +3,16 @@ import { APP_FILTER, APP_INTERCEPTOR, APP_GUARD } from '@nestjs/core';
import { AuthGuard } from '@wwjCommon/auth/auth.guard';
import { RbacGuard } from '@wwjCommon/auth/rbac.guard';
import { BootModule } from '@wwjBoot/wwjcloud-boot.module';
import { BootI18nModule } from '@wwjCommon/i18n/boot-i18n.module';
import { BootLangModule } from '@wwjCommon/lang/boot-lang.module';
import { HttpExceptionFilter } from '@wwjCommon/http/http-exception.filter';
import { LoggingInterceptor } from '@wwjCommon/http/logging.interceptor';
import { MetricsInterceptor } from '@wwjCommon/metrics/metrics.interceptor';
import { ResponseInterceptor } from '@wwjCommon/response/response.interceptor';
import { SecureController } from './secure.controller';
import { AiModule } from '@wwjAi/wwjcloud-ai.module';
import { WwjcloudAiModule as AiModule } from '@wwjAi/wwjcloud-ai.module';
@Module({
imports: [BootModule, BootI18nModule, AiModule],
imports: [BootModule, BootLangModule, AiModule],
controllers: [SecureController],
providers: [
{ provide: APP_FILTER, useClass: HttpExceptionFilter },

View File

@@ -8,7 +8,8 @@ async function bootstrap() {
await BootHttp.start(app);
const config = app.get(ConfigService);
const raw = config.get<string | number>('PORT');
const port = typeof raw === 'number' ? raw : parseInt(String(raw ?? '3000'), 10) || 3000;
const port =
typeof raw === 'number' ? raw : parseInt(String(raw ?? '3000'), 10) || 3000;
await app.listen(port);
}
bootstrap();

View File

@@ -34,4 +34,4 @@ export class SecureController {
pub() {
return { ok: true, message: 'public' };
}
}
}

View File

@@ -127,4 +127,23 @@
## 20. 变更提交流程
- PR 必须附带文档更新、Swagger 更新与前端类型更新。
- 使用标签 `consistency:v1` 标注合并项;在 CHANGELOG 记录对齐影响范围。
- 使用标签 `consistency:v1` 标注合并项;在 CHANGELOG 记录对齐影响范围。
## 别名与模块边界(一致性约束)
- 映射规范:
- `@wwjBoot`:仅用于顶层平台装配与入口(`BootModule``preset`)。
- `@wwjCommon`:统一基础设施入口(`http``response``metrics``cache``queue``auth``tenant``lang`)。
- `@wwjVendor`:第三方驱动适配层,按接口/Token 注入,默认“可选/存根”。
- `@wwjAi`AI 能力模块,允许依赖 `@wwjCommon`,不得依赖 `@wwjBoot`
- 强制规则:
- 禁止使用 `@wwjBoot/infra/*` 引入基础设施,统一改为 `@wwjCommon/*`(保证语义与边界一致)。
- 文档、示例与测试需统一遵循以上映射与规则PR 不得混用别名语义。
- 预设入口与编译耦合(建议):
- 提供 `preset.core`(不含 AI`preset.full`(含 AI应用可按业务选择以降低编译期耦合。
- i18n 软依赖与兜底:
- 拦截器与异常过滤器不强制注入 `I18nService`;未启用 `BootLangModule` 时返回 `msg_key`
- 参考 `LANG-GUIDE.md``ModuleRef.get(I18nService, { strict:false })` 方案。

View File

@@ -1,4 +1,4 @@
# 多语言i18n实现与对齐指南Java-first
# 多语言i18n实现与对齐指南Java-first)
本指南说明在 `wwjcloud-nest-v1` 中接入与落地国际化i18n并与 Java 项目的语言包与 key 规范保持一致Java-first。PHP 只作为业务逻辑层使用同样的 key 获取文案,不维护独立规范。
@@ -23,7 +23,7 @@ wwjcloud-nest-v1/
common.json
error.json
user.json
libs/wwjcloud-boot/src/infra/i18n/
libs/wwjcloud-boot/src/infra/lang/
boot-i18n.module.ts
resolvers.ts # 可选自定义解析器集合Query/Header
apps/api/src/common/
@@ -37,16 +37,16 @@ wwjcloud-nest-v1/
### 1) 安装依赖
使用你项目的包管理器安装:
```
pnpm add @nestjs/i18n i18n accept-language-parser
pnpm add nestjs-i18n i18n accept-language-parser
# 或
npm i @nestjs/i18n i18n accept-language-parser
npm i nestjs-i18n i18n accept-language-parser
```
### 2) 创建 i18n 模块BootI18nModule
文件:`libs/wwjcloud-boot/src/infra/i18n/boot-i18n.module.ts`
文件:`libs/wwjcloud-boot/src/infra/lang/boot-i18n.module.ts`
```ts
import { Global, Module } from '@nestjs/common';
import { I18nModule, I18nJsonParser, HeaderResolver, QueryResolver } from '@nestjs/i18n';
import { I18nModule, I18nJsonLoader, HeaderResolver, QueryResolver } from 'nestjs-i18n';
import { join } from 'path';
@Global()
@@ -54,10 +54,10 @@ import { join } from 'path';
imports: [
I18nModule.forRoot({
fallbackLanguage: 'zh-CN',
parser: I18nJsonParser,
parserOptions: {
path: join(process.cwd(), 'wwjcloud-nest-v1/apps/api/src/lang'),
watch: true,
loader: I18nJsonLoader,
loaderOptions: {
path: join(process.cwd(), 'apps/api/src/lang'),
watch: process.env.NODE_ENV !== 'test',
},
resolvers: [
{ use: QueryResolver, options: ['lang'] },
@@ -70,14 +70,14 @@ import { join } from 'path';
export class BootI18nModule {}
```
### 3) 在 AppModule 导入
### 3) 在 AppModule 导入(推荐使用 BootLangModule 软别名)
文件:`apps/api/src/app.module.ts`
```ts
import { Module } from '@nestjs/common';
import { BootI18nModule } from '@libs/wwjcloud-boot/src/infra/i18n/boot-i18n.module';
import { BootLangModule } from '@libs/wwjcloud-boot/src/infra/lang/boot-lang.module';
@Module({
imports: [BootI18nModule /* 以及其他模块 */],
imports: [BootLangModule /* 以及其他模块 */],
})
export class AppModule {}
```
@@ -86,7 +86,7 @@ export class AppModule {}
文件:`apps/api/src/common/interceptors/response.interceptor.ts`
```ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { I18nService } from '@nestjs/i18n';
import { I18nService } from 'nestjs-i18n';
import { Observable, map } from 'rxjs';
@Injectable()
@@ -114,7 +114,7 @@ export class ResponseInterceptor implements NestInterceptor {
文件:`apps/api/src/common/filters/http-exception.filter.ts`
```ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
import { I18nService } from '@nestjs/i18n';
import { I18nService } from 'nestjs-i18n';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
@@ -204,7 +204,7 @@ return { code: 0, msg_key: 'user.profile.updated', data: { id: 1 } };
## 语言协商与 DI 导入规范
- 解析优先级:`Query(lang)` > `Header(Accept-Language)` > 默认 `zh-CN`
- DI 与导入:`BootI18nModule` 仅在 `AppModule` 里导入一次全局模块遵循项目的「Nest DI 与导入规范」。拦截器与过滤器以 Provider 方式注入 `I18nService`
- DI 与导入:推荐使用 `BootLangModule`(底层为 `BootI18nModule`仅在 `AppModule` 里导入一次全局模块遵循项目的「Nest DI 与导入规范」。拦截器与过滤器以 Provider 方式注入 `I18nService`
## 测试与验证
- 默认语言:
@@ -233,5 +233,91 @@ curl "http://localhost:3000/api/ping?lang=en-US"
- Java沿用 `.properties` 的模块化与 key 命名Nest 端资源内容与 Java 的 key 同名对齐。
- PHP继续使用 `get_lang(key)`,逐步统一到 Java 的点分 key无需维护独立资源规范。
---
如需我在 `wwjcloud-nest-v1` 中继续完成代码接入(创建 `BootI18nModule`、改造拦截器与异常过滤器、添加示例语言资源),请在本指南基础上确认,我将按以上目录与步骤实施。
// 术语对齐:对外事件与模块名统一使用 `lang`;内部技术栈保留 `i18n`
如需我在 `wwjcloud-nest-v1` 中继续完成代码接入(创建 `BootI18nModule`、改造拦截器与异常过滤器、添加示例语言资源),请在本指南基础上确认,我将按以上目录与步骤实施。
## 依赖解耦合与兜底(推荐)
- 软依赖:拦截器/过滤器不对 `I18nService` 形成硬依赖;当未导入 `BootLangModule` 时,功能自动降级为直接返回 `msg_key`
- 实现方式:运行时从 `ModuleRef` 中“可选获取” `I18nService`,未获取到则兜底。
示例:可选 i18n 的响应拦截器
```ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { I18nService } from 'nestjs-i18n';
import { Observable, map } from 'rxjs';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
constructor(private readonly moduleRef: ModuleRef) {}
private getI18n(): I18nService | undefined {
// strict:false → 未注册时返回 undefined
return this.moduleRef.get(I18nService, { strict: false });
}
intercept(ctx: ExecutionContext, next: CallHandler): Observable<any> {
const req = ctx.switchToHttp().getRequest();
const i18n = this.getI18n();
return next.handle().pipe(
map((original) => {
const { code = 0, data = null, msg_key } = original ?? {};
const key = msg_key || 'common.success';
let msg = key;
if (i18n) {
try {
const translated = i18n.translate(key, { lang: req.i18nLang });
msg = translated || key;
} catch {
msg = key; // 兜底:翻译失败返回 key
}
}
return { code, msg_key: key, msg, data };
}),
);
}
}
```
异常过滤器同理:
```ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { I18nService } from 'nestjs-i18n';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
constructor(private readonly moduleRef: ModuleRef) {}
private getI18n(): I18nService | undefined { return this.moduleRef.get(I18nService, { strict: false }); }
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const req = ctx.getRequest();
const res = ctx.getResponse();
const i18n = this.getI18n();
let code = 500;
let msgKey = 'error.common.unknown';
let args: Record<string, any> | undefined;
if (exception instanceof HttpException) {
const response: any = exception.getResponse();
code = exception.getStatus();
msgKey = response?.msg_key || msgKey;
args = response?.args;
}
let msg = msgKey;
if (i18n) {
try { msg = i18n.translate(msgKey, { lang: req.i18nLang /* args */ }) || msgKey; } catch { msg = msgKey; }
}
res.status(code).json({ code, msg_key: msgKey, msg, data: null });
}
}
```
落地建议:
- 在 `apps/api/src/app.module.ts` 导入 `BootLangModule` 即启用翻译;测试或最简环境可跳过导入,系统仍可工作(只返回 `msg_key`)。
- 当引入 i18n 时,建议在 `LANG_READY` 就绪服务中校验语言资源目录存在并上报状态(见 `V11-BOOT-READINESS.md`)。

View File

@@ -4,7 +4,7 @@
- AI 开发与安全:`AI-RECOVERY-DEV.md``AI-RECOVERY-SECURITY.md`
- 基础设施与配置:`V11-INFRA-SETUP.md`
- 一致性与对齐:`CONSISTENCY-GUIDE.md`
- 国际化指南:`I18N-GUIDE.md`
- 国际化指南:`LANG-GUIDE.md`
维护约定:
- v1 专属文档仅在本目录维护,主 `docs/` 不承载 v1 内容。

View File

@@ -19,7 +19,7 @@
- 指标暴露:`GET /api/metrics``PROMETHEUS_ENABLED=true`),含 `http_requests_total``ai_events_total` 等。
- 弹性策略:`ResilienceService` 支持重试/超时/断路器,`HttpClientService.getWithFallback` 已集成。
- DI 导入规范Boot 层提供与导出,业务按类型消费,不重复定义令牌/别名。
- I18N`BootI18nModule` 全局导入,`apps/api/src/lang` 存放多语言资源,拦截器/过滤器使用 i18n 翻译。
- I18N`BootLangModule`(底层为 `BootI18nModule`全局导入,`apps/api/src/lang` 存放多语言资源,拦截器/过滤器使用 i18n 翻译。
## AI 自愈系统(恢复与守卫)
- 控制器与路由(受 `RateLimitGuard`,开发期可 `@Public()`
@@ -73,8 +73,28 @@
- 详细 AI 开发与安全:`AI-RECOVERY-DEV.md``AI-RECOVERY-SECURITY.md`
- 基础设施与配置:`V11-INFRA-SETUP.md`
- 一致性与对齐:`CONSISTENCY-GUIDE.md`
- 国际化接入:`I18N-GUIDE.md`
- 国际化接入:`LANG-GUIDE.md`
---
注:本页为 v1 的“一体化总览”,作为开发与运维的统一入口。若新增能力(如 Addon 注册、OpenTelemetry、速率限制扩展请在此页与对应子文档同步更新。
注:本页为 v1 的“一体化总览”,作为开发与运维的统一入口。若新增能力(如 Addon 注册、OpenTelemetry、速率限制扩展请在此页与对应子文档同步更新。
## 别名与模块边界约定(强制)
- 别名映射:
- `@wwjBoot` → 顶层平台装配与入口(`BootModule``preset`),不用于引入具体基础设施。
- `@wwjCommon` → 跨领域基础设施(`http/*``response/*``metrics/*``cache/*``queue/*``auth/*``tenant/*``lang/*`)。
- `@wwjVendor` → 第三方驱动适配(`pay/*``sms/*``upload/*``notice/*`),按接口/Token 注入,保持“可选”。
- `@wwjAi` → AI 能力Tuner/Safe/Manager/Healing 等),允许依赖 `@wwjCommon`,禁止反向依赖 `@wwjBoot`
- 使用规则:
- 业务与 AI 层只从 `@wwjCommon/*` 引入基础设施;禁用 `@wwjBoot/infra/*` 形式(语义不一致)。
- `BootLangModule`(软别名)用于在应用层一次性导入 i18n拦截器/过滤器对 i18n 采取软依赖与兜底,详见 `LANG-GUIDE.md`
- Vendor 驱动均为“可选”并按接口注入,业务避免直接耦合具体实现;文档需标注启用条件与默认存根行为。
- 预设入口(建议):
- `preset.core`:不含 AI仅基础设施Boot 核心)。
- `preset.full`:含 AI当前默认。应用可按 `AI_ENABLED` 切换或选择入口以降低编译耦合。
- 代码规范(建议):
- ESLint `no-restricted-imports` 禁止 `@wwjBoot/infra/*` 导入;统一走 `@wwjCommon/*`
- 文档在 `CONSISTENCY-GUIDE.md` 与本页保持别名与边界约定的一致说明。

View File

@@ -0,0 +1,49 @@
# V11 AI Readiness 事件说明
本文件说明 AI 层Tuner/Safe在 v11 中的就绪事件上报约定,以及与 Boot 层的协作关系。
## 统一事件
- 事件名:`module.state.changed`
- 载荷:
- `module`: 模块名(如 `ai.tuner``ai.safe``startup``cache``auth``rbac``queue``lang``metrics`
- `previousState`: 之前状态(通常为 `initializing`
- `currentState`: 当前状态(`ready``unavailable`
- `meta`: 可选扩展(如 `{ enabled: true }`
## AI 层模块
### ai.tuner
- 入口:`libs/wwjcloud-ai/src/tuner/services/tuner-ready.service.ts`
- 触发时机:`OnModuleInit`
- 依赖组件:`PerformanceAnalyzer``ResourceMonitor``CacheOptimizer``QueryOptimizer`
- 环境开关:`AI_TUNER_ENABLED`(默认 `true`
- 判定逻辑:
- 当开关启用且核心组件均成功注入 → `ready`
- 当开关启用但组件缺失/异常 → `unavailable`
- 当开关关闭 → `unavailable`
### ai.safe
- 入口:`libs/wwjcloud-ai/src/safe/services/safe-ready.service.ts`
- 触发时机:`OnModuleInit`
- 依赖组件:`SecurityAnalyzer``VulnerabilityDetector``AccessProtector``AiSecurityService`
- 环境开关:`AI_SAFE_ENABLED`(默认 `true`
- 判定逻辑:
- 当开关启用且核心组件均成功注入 → `ready`
- 当开关启用但组件缺失/异常 → `unavailable`
- 当开关关闭 → `unavailable`
## Boot 层(参考)
- `startup``StartupValidatorService` 在初始化时生成启动报告,并基于 `NODE_ENV` 是否缺失和 `Redis` 连接状态上报 `ready/unavailable`
- `cache``CacheReadyService` 在 Redis 禁用时回退为 `ready`,启用时根据 `PING` 成功与否上报状态。
- `auth/rbac``AuthReadyService` 基于 `AUTH_ENABLED``RBAC_ENABLED` 分别上报 `ready/unavailable`
- `queue``QueueReadyService` 依据 `QUEUE_ENABLED` 与驱动类型(`bullmq/kafka``ready`,未知 → `unavailable`)。
- `i18n``metrics`:分别在初始化时根据语言目录存在与 `PROMETHEUS_ENABLED` 开关上报状态。
## 测试覆盖
- 位置:`src/ai-layer/*.spec.ts``src/boot-layer/*.spec.ts`
- 已覆盖用例:
- AI`tuner-ready.service``safe-ready.service` 启用/禁用、缺失组件场景
- Boot`startup``cache``auth/rbac``queue` 等常见启用/禁用与依赖失败场景
## 约定与扩展
- 所有模块应在 `OnModuleInit` 或初始化阶段发出首次状态,用于协调器与观测层消费。
- 新增模块应复用 `module.state.changed` 事件,保持载荷格式一致性,必要时在 `meta` 补充上下文。

View File

@@ -0,0 +1,40 @@
# WWJCloud v11 - Boot 模块就绪上报与规范
本文档说明在 NestJS v11 下 Boot 层的事件通信与生命周期规范化改造,以及如何验证就绪状态上报。
## 改动概述
- 事件总线:统一通过 `EventBus` 依赖注入,禁止手动实例化。
- 生命周期钩子:使用 `OnModuleInit` 完成模块就绪状态上报;使用 `OnModuleDestroy` 做句柄清理(如定时器、注册表)。
- 就绪事件主题:`module.state.changed`,载荷示例:
- `{ module: 'metrics', previousState: 'initializing', currentState: 'ready' }`
- `{ module: 'lang', previousState: 'initializing', currentState: 'unavailable' }`
## 代码改动
- Metrics 就绪上报
- 文件:`libs/wwjcloud-boot/src/infra/metrics/metrics.service.ts`
- 变更:注入 `EventBus`,实现 `onModuleInit()`,根据 `PROMETHEUS_ENABLED` 上报 `ready/unavailable`
- I18n 就绪上报
- 文件:`libs/wwjcloud-boot/src/infra/lang/lang-ready.service.ts`(新增)
- 变更:在 `onModuleInit()` 检查语言目录 `apps/api/src/lang` 是否存在,上报 `ready/unavailable`
- 注册:`libs/wwjcloud-boot/src/infra/lang/boot-i18n.module.ts` 中新增 `providers: [LangReadyService]`
## 验证与测试
- 单元测试
- Metrics`src/boot-layer/metrics.service.spec.ts` 验证 `ready/unavailable` 就绪事件。
- Lang`src/boot-layer/i18n-ready.service.spec.ts` 通过 mock `fs.existsSync` 验证 `ready` 事件。
- 运行测试:`npm run test`
- 端到端(已存在):`test/jest-e2e.json`,可结合 `BootHttp.start(app)` 与环境变量验证基础设施行为。
## 使用建议
- 订阅事件:协调器/管理器通过 `@OnEvent('module.state.changed')` 同步内部状态映射,控制任务可用性。
- 配置校验:`libs/wwjcloud-boot/src/config/validation.ts` 中维持严格校验,不提供默认值;默认值在具体实现兜底。
- 文档参考:
- Nest v11 依赖注入https://docs.nestjs.com/fundamentals/custom-providers
- 生命周期钩子https://docs.nestjs.com/fundamentals/lifecycle-events
- 事件与事件总线https://docs.nestjs.com/techniques/events
## 后续工作(可选)
-`cache/auth/queue/tenant` 等模块补充就绪上报(必要时)。
-`V1-GUIDE.md``V11-INFRA-SETUP.md` 中补充统一事件订阅与状态流转章节。
- 增加 e2e 场景:在 `apps/api` 中通过专用路由触发各模块状态变更并断言指标与任务协调。

View File

@@ -25,11 +25,39 @@ export default tseslint.config(
},
},
{
files: ['src/boot-layer/**/*.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
"prettier/prettier": ["error", { endOfLine: "auto" }],
'no-restricted-imports': [
'error',
{
patterns: [
{ group: ['@wwjBoot/infra/*'], message: '禁止从 @wwjBoot/infra/* 导入,请改为 @wwjCommon/*' },
{ group: ['@wwjAi/*'], message: 'Boot 层禁止依赖 AI 层,请改为事件驱动或 preset.full 动态集成' },
{ group: ['@wwjVendor/*'], message: '禁止直接引用 @wwjVendor/*,请通过 @wwjCommon/* 的适配服务' },
],
},
],
},
},
{
files: ['src/ai-layer/**/*.ts'],
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{ group: ['@wwjBoot/infra/*'], message: '禁止从 @wwjBoot/infra/* 导入,请改为 @wwjCommon/*' },
{ group: ['@wwjBoot/*'], message: 'AI 层禁止依赖 Boot 内部实现;仅依赖 @wwjCommon/* 或通过事件总线' },
{ group: ['@wwjVendor/*'], message: '禁止直接引用 @wwjVendor/*,请通过 @wwjCommon/* 的适配服务' },
],
},
],
},
},
{
files: ['**/*.spec.ts'],
rules: {
'@typescript-eslint/unbound-method': 'off',
},
},
);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,585 @@
# WWJCloud AI Layer - 架构设计文档
## 📋 架构概述
WWJCloud AI Layer 采用模块化、微服务导向的架构设计,基于 NestJS v11 框架构建,提供企业级的智能化系统管理能力。
## 🏗️ 整体架构
### 架构层次
```
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ (业务应用层) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ WWJCloud AI Layer │
│ (AI 智能化层) │
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │
│ │ Manager │ Healing │ Safe │ Tuner │ │
│ │ (管理) │ (自愈) │ (安全) │ (调优) │ │
│ └─────────────┴─────────────┴─────────────┴─────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ (基础设施层) │
│ Database │ Cache │ Message Queue │ Monitoring │ Logging │
└─────────────────────────────────────────────────────────────┘
```
### 核心设计原则
1. **单一职责原则** - 每个模块专注于特定的 AI 能力
2. **开放封闭原则** - 对扩展开放,对修改封闭
3. **依赖倒置原则** - 依赖抽象而非具体实现
4. **接口隔离原则** - 使用细粒度的接口设计
5. **最小知识原则** - 模块间松耦合设计
## 🎯 模块架构详解
### 1. Manager 模块 - AI 核心管理
#### 架构图
```
Manager Module
├── Controllers/
│ └── AiController # AI 管理控制器
├── Services/
│ ├── AiOrchestratorService # 工作流程编排
│ ├── AiRegistryService # 服务注册发现
│ └── AiCoordinatorService # 模块间协调
├── Interfaces/
│ └── ai-manager.interface.ts # 管理接口定义
└── Bootstrap/
└── AiBootstrapProvider # AI 启动提供者
```
#### 核心职责
- **工作流程编排**: 管理 AI 智能体的执行顺序和依赖关系
- **服务注册**: 维护所有 AI 服务的注册信息和健康状态
- **模块协调**: 处理模块间的通信和数据交换
- **统一管理**: 提供统一的 AI 服务管理入口
#### 关键设计模式
- **编排者模式** (Orchestrator Pattern): 中央协调多个服务
- **注册表模式** (Registry Pattern): 服务发现和管理
- **观察者模式** (Observer Pattern): 事件驱动的状态通知
### 2. Healing 模块 - AI 自愈
#### 架构图
```
Healing Module
├── Listeners/
│ ├── AiSelfHealListener # 自愈事件监听
│ └── AiRecoveryListener # 恢复事件监听
├── Services/
│ ├── AiRecoveryService # 故障恢复服务
│ └── AiStrategyService # 策略管理服务
├── Strategies/
│ ├── RetryStrategy # 重试策略
│ └── FallbackStrategy # 降级策略
└── Interfaces/
└── healing.interface.ts # 自愈接口定义
```
#### 核心职责
- **故障检测**: 实时监控系统状态,识别异常情况
- **智能诊断**: 分析故障原因,确定恢复策略
- **自动恢复**: 执行恢复操作,最小化服务中断
- **效果评估**: 评估恢复效果,优化恢复策略
#### 关键设计模式
- **策略模式** (Strategy Pattern): 可插拔的恢复策略
- **责任链模式** (Chain of Responsibility): 多级恢复处理
- **状态模式** (State Pattern): 系统健康状态管理
### 3. Safe 模块 - AI 安全
#### 架构图
```
Safe Module
├── Analyzers/
│ └── SecurityAnalyzer # 安全状态分析
├── Detectors/
│ └── VulnerabilityDetector # 漏洞检测器
├── Protectors/
│ └── AccessProtector # 访问保护器
└── Services/
├── AiSecurityService # 统一安全管理
└── AiAuditService # 安全审计服务
```
#### 核心职责
- **威胁检测**: 实时识别安全威胁和异常行为
- **漏洞扫描**: 定期扫描系统漏洞和安全风险
- **访问控制**: 管理用户权限和资源访问
- **审计日志**: 记录安全事件和操作轨迹
#### 关键设计模式
- **装饰器模式** (Decorator Pattern): 安全功能增强
- **代理模式** (Proxy Pattern): 访问控制和监控
- **模板方法模式** (Template Method): 标准化安全检查流程
### 4. Tuner 模块 - AI 性能调优
#### 架构图
```
Tuner Module
├── Analyzers/
│ └── PerformanceAnalyzer # 性能分析器
├── Monitors/
│ └── ResourceMonitor # 资源监控器
├── Optimizers/
│ ├── CacheOptimizer # 缓存优化器
│ └── QueryOptimizer # 查询优化器
└── Services/
├── AiTunerService # 调优管理服务
└── AiMetricsService # 指标收集服务
```
#### 核心职责
- **性能监控**: 实时收集系统性能指标
- **瓶颈识别**: 智能识别性能瓶颈和优化机会
- **自动优化**: 执行性能优化策略和配置调整
- **效果评估**: 评估优化效果和投资回报率
#### 关键设计模式
- **建造者模式** (Builder Pattern): 复杂优化策略构建
- **工厂方法模式** (Factory Method): 优化器实例创建
- **命令模式** (Command Pattern): 优化操作的封装和撤销
## 🔄 模块间交互
### 事件驱动架构
```typescript
// 全局事件定义
export enum AiEventType {
// Manager 事件
WORKFLOW_STARTED = 'workflow.started',
WORKFLOW_COMPLETED = 'workflow.completed',
SERVICE_REGISTERED = 'service.registered',
// Healing 事件
FAILURE_DETECTED = 'failure.detected',
RECOVERY_STARTED = 'recovery.started',
RECOVERY_COMPLETED = 'recovery.completed',
// Safe 事件
THREAT_DETECTED = 'threat.detected',
SECURITY_SCAN_COMPLETED = 'security.scan.completed',
ACCESS_DENIED = 'access.denied',
// Tuner 事件
PERFORMANCE_DEGRADED = 'performance.degraded',
OPTIMIZATION_STARTED = 'optimization.started',
OPTIMIZATION_COMPLETED = 'optimization.completed',
}
```
### 模块依赖关系
```
Manager (核心协调)
├── 依赖 → Healing (故障恢复)
├── 依赖 → Safe (安全检查)
└── 依赖 → Tuner (性能优化)
Healing (自愈模块)
├── 监听 → Manager 事件
└── 发布 → 恢复事件
Safe (安全模块)
├── 监听 → 所有模块事件
└── 发布 → 安全事件
Tuner (调优模块)
├── 监听 → Manager 事件
└── 发布 → 性能事件
```
## 🛠️ 技术栈
### 核心框架
- **NestJS v11**: 企业级 Node.js 框架
- **TypeScript 5.x**: 类型安全的 JavaScript 超集
- **RxJS**: 响应式编程库
- **Reflect Metadata**: 元数据反射支持
### 数据存储
- **Redis**: 缓存和会话存储
- **MongoDB**: 文档数据库(可选)
- **PostgreSQL**: 关系型数据库(可选)
### 监控和日志
- **Prometheus**: 指标收集
- **Grafana**: 可视化监控
- **Winston**: 结构化日志
- **Jaeger**: 分布式追踪
### 消息队列
- **Redis Pub/Sub**: 轻量级消息传递
- **RabbitMQ**: 企业级消息队列(可选)
- **Apache Kafka**: 大数据流处理(可选)
## 🔧 配置管理
### 配置层次结构
```
Configuration Hierarchy
├── Default Config (默认配置)
├── Environment Config (环境配置)
├── Runtime Config (运行时配置)
└── Dynamic Config (动态配置)
```
### 配置示例
```typescript
export interface AiModuleConfig {
// 全局配置
global: {
enabled: boolean;
logLevel: 'debug' | 'info' | 'warn' | 'error';
metricsEnabled: boolean;
};
// Manager 配置
manager: {
orchestration: {
maxConcurrentWorkflows: number;
workflowTimeout: number;
};
registry: {
healthCheckInterval: number;
serviceTimeout: number;
};
};
// Healing 配置
healing: {
enabled: boolean;
strategies: string[];
maxRetries: number;
retryDelay: number;
};
// Safe 配置
safe: {
enabled: boolean;
scanInterval: number;
threatThreshold: number;
auditRetention: number;
};
// Tuner 配置
tuner: {
enabled: boolean;
autoOptimize: boolean;
monitoringInterval: number;
optimizationThreshold: number;
};
}
```
## 📊 性能考虑
### 性能优化策略
1. **异步处理**: 所有 I/O 操作使用异步模式
2. **缓存策略**: 多层缓存减少重复计算
3. **连接池**: 数据库和外部服务连接复用
4. **批处理**: 批量处理减少网络开销
5. **懒加载**: 按需加载减少启动时间
### 内存管理
```typescript
// 内存优化配置
export const MEMORY_CONFIG = {
// 指标数据保留策略
metricsRetention: {
maxDataPoints: 10000,
retentionPeriod: 30 * 24 * 60 * 60 * 1000, // 30天
cleanupInterval: 60 * 60 * 1000, // 1小时
},
// 缓存配置
cache: {
maxSize: 1000,
ttl: 5 * 60 * 1000, // 5分钟
checkPeriod: 60 * 1000, // 1分钟
},
// 事件队列配置
eventQueue: {
maxSize: 5000,
batchSize: 100,
flushInterval: 1000, // 1秒
},
};
```
## 🔒 安全架构
### 安全层次
```
Security Layers
├── Network Security (网络安全)
│ ├── TLS/SSL 加密
│ ├── 防火墙规则
│ └── DDoS 防护
├── Application Security (应用安全)
│ ├── 身份认证
│ ├── 权限控制
│ └── 输入验证
├── Data Security (数据安全)
│ ├── 数据加密
│ ├── 敏感信息脱敏
│ └── 数据备份
└── Operational Security (运营安全)
├── 安全审计
├── 威胁检测
└── 事件响应
```
### 安全最佳实践
1. **最小权限原则**: 用户和服务只获得必要的最小权限
2. **深度防御**: 多层安全控制,避免单点失效
3. **零信任架构**: 不信任任何网络流量,验证所有访问
4. **持续监控**: 实时监控安全事件和异常行为
5. **快速响应**: 自动化安全事件响应和恢复
## 🧪 测试策略
### 测试金字塔
```
Testing Pyramid
├── Unit Tests (单元测试) - 70%
│ ├── Service 测试
│ ├── Controller 测试
│ └── Utility 测试
├── Integration Tests (集成测试) - 20%
│ ├── Module 集成测试
│ ├── Database 集成测试
│ └── External API 测试
└── E2E Tests (端到端测试) - 10%
├── 完整工作流程测试
├── 性能测试
└── 安全测试
```
### 测试工具链
- **Jest**: 单元测试框架
- **Supertest**: HTTP 接口测试
- **Test Containers**: 集成测试环境
- **Artillery**: 性能压力测试
- **OWASP ZAP**: 安全漏洞扫描
## 📈 监控和可观测性
### 三大支柱
1. **Metrics (指标)**
- 业务指标: 成功率、响应时间、吞吐量
- 系统指标: CPU、内存、磁盘、网络
- 应用指标: 错误率、队列长度、连接数
2. **Logging (日志)**
- 结构化日志: JSON 格式,便于查询分析
- 日志级别: DEBUG、INFO、WARN、ERROR
- 上下文信息: 请求ID、用户ID、会话ID
3. **Tracing (追踪)**
- 分布式追踪: 跨服务请求链路追踪
- 性能分析: 识别性能瓶颈和优化机会
- 错误定位: 快速定位问题根因
### 告警策略
```typescript
// 告警规则配置
export const ALERT_RULES = {
// 系统级告警
system: {
cpuUsage: { threshold: 80, severity: 'warning' },
memoryUsage: { threshold: 85, severity: 'critical' },
diskUsage: { threshold: 90, severity: 'critical' },
},
// 应用级告警
application: {
errorRate: { threshold: 5, severity: 'warning' },
responseTime: { threshold: 1000, severity: 'warning' },
throughput: { threshold: 100, severity: 'info' },
},
// 业务级告警
business: {
healingFailureRate: { threshold: 10, severity: 'critical' },
securityThreatCount: { threshold: 5, severity: 'warning' },
optimizationEffectiveness: { threshold: 5, severity: 'info' },
},
};
```
## 🚀 部署架构
### 容器化部署
```dockerfile
# Dockerfile 示例
FROM node:18-alpine
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
RUN npm ci --only=production
# 复制源代码
COPY dist/ ./dist/
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# 启动应用
CMD ["node", "dist/main.js"]
```
### Kubernetes 部署
```yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: wwjcloud-ai
spec:
replicas: 3
selector:
matchLabels:
app: wwjcloud-ai
template:
metadata:
labels:
app: wwjcloud-ai
spec:
containers:
- name: wwjcloud-ai
image: wwjcloud/ai:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: AI_ENABLED
value: "true"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
```
## 📋 运维指南
### 日常运维任务
1. **健康检查**: 定期检查各模块健康状态
2. **性能监控**: 监控关键性能指标和趋势
3. **日志分析**: 分析错误日志和异常模式
4. **容量规划**: 根据使用情况调整资源配置
5. **安全审计**: 定期进行安全检查和漏洞扫描
### 故障处理流程
```
故障处理流程
├── 1. 故障检测
│ ├── 自动监控告警
│ └── 用户反馈报告
├── 2. 故障分析
│ ├── 日志分析
│ ├── 指标分析
│ └── 链路追踪
├── 3. 故障定位
│ ├── 根因分析
│ └── 影响评估
├── 4. 故障恢复
│ ├── 自动恢复
│ └── 手动干预
└── 5. 事后总结
├── 故障报告
└── 改进措施
```
## 🔮 未来规划
### 短期目标 (3-6个月)
- [ ] 完善单元测试覆盖率到 90%+
- [ ] 集成更多第三方监控工具
- [ ] 优化性能,减少内存占用
- [ ] 增加更多自愈策略
- [ ] 完善安全检测规则
### 中期目标 (6-12个月)
- [ ] 支持多租户架构
- [ ] 实现机器学习驱动的智能优化
- [ ] 集成 APM 工具进行深度性能分析
- [ ] 支持云原生部署模式
- [ ] 实现跨区域容灾
### 长期目标 (1-2年)
- [ ] 构建 AI 驱动的运维平台
- [ ] 实现预测性维护能力
- [ ] 支持边缘计算场景
- [ ] 建立 AI 模型市场
- [ ] 实现完全自治的系统运维
---
本架构文档将随着系统演进持续更新,确保架构设计与实际实现保持一致。

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,470 @@
# WWJCloud AI Layer - 智能化系统架构
## 📋 概述
WWJCloud AI Layer 是基于 NestJS v11 构建的企业级智能化系统架构,提供全面的 AI 驱动的系统管理、自愈、安全和性能优化能力。
## 🏗️ 架构设计
### 核心设计原则
1. **模块化架构** - 按功能域划分,支持独立开发和部署
2. **智能化驱动** - AI 算法驱动的自动化决策和优化
3. **企业级可靠性** - 高可用、容错和自愈能力
4. **性能优先** - 内置性能监控和自动优化
5. **安全第一** - 全方位安全防护和威胁检测
### 模块架构图
```
wwjcloud-ai/
├── manager/ # AI 核心管理模块
├── healing/ # AI 自愈模块
├── safe/ # AI 安全模块
├── tuner/ # AI 性能调优模块
├── events.ts # 全局事件定义
├── types.ts # 全局类型定义
└── index.ts # 统一导出入口
```
## 🎯 核心模块
### 1. Manager 模块 - AI 核心管理
**职责**: 统一管理和协调所有 AI 服务
**核心组件**:
- `AiOrchestratorService` - AI 工作流程编排
- `AiRegistryService` - AI 服务注册与发现
- `AiCoordinatorService` - AI 模块间协调
**主要功能**:
- 🔄 工作流程自动化编排
- 📋 服务注册与健康监控
- 🤝 模块间通信协调
- 📊 统一状态管理
### 2. Healing 模块 - AI 自愈
**职责**: 提供系统自动故障检测、诊断和恢复能力
**核心组件**:
- `AiSelfHealListener` - 自愈事件监听
- `AiRecoveryService` - 故障恢复服务
- `RetryStrategy` - 重试策略
- `FallbackStrategy` - 降级策略
**主要功能**:
- 🔍 实时故障检测
- 🩺 智能故障诊断
- 🔧 自动故障恢复
- 📈 恢复效果评估
### 3. Safe 模块 - AI 安全
**职责**: 提供全方位的安全防护和威胁检测
**核心组件**:
- `SecurityAnalyzer` - 安全状态分析
- `VulnerabilityDetector` - 漏洞检测
- `AccessProtector` - 访问控制保护
- `AiSecurityService` - 统一安全管理
**主要功能**:
- 🛡️ 实时威胁检测
- 🔒 访问权限控制
- 📋 安全审计日志
- ⚠️ 安全告警通知
### 4. Tuner 模块 - AI 性能调优
**职责**: 智能化性能监控、分析和优化
**核心组件**:
- `PerformanceAnalyzer` - 性能分析器
- `ResourceMonitor` - 资源监控器
- `CacheOptimizer` - 缓存优化器
- `QueryOptimizer` - 查询优化器
**主要功能**:
- 📊 实时性能监控
- 🔍 性能瓶颈识别
- ⚡ 自动性能优化
- 📈 优化效果评估
## 🚀 快速开始
### 安装依赖
```bash
npm install @wwjcloud/ai
```
### 基础配置
```typescript
import { Module } from '@nestjs/common';
import { WwjcloudAiModule } from '@wwjcloud/ai';
@Module({
imports: [
WwjcloudAiModule,
],
})
export class AppModule {}
```
### 基本使用
```typescript
import { Injectable } from '@nestjs/common';
import { AiOrchestratorService } from '@wwjcloud/ai';
@Injectable()
export class MyService {
constructor(
private readonly orchestrator: AiOrchestratorService,
) {}
async startAiWorkflow() {
// 启动 AI 工作流程
const workflow = await this.orchestrator.startWorkflow('system-optimization');
return workflow;
}
}
```
## 📖 详细使用指南
### Manager 模块使用
#### 1. 工作流程编排
```typescript
import { AiOrchestratorService } from '@wwjcloud/ai/manager';
// 启动预定义工作流程
const workflow = await orchestrator.startWorkflow('self-healing');
// 自定义工作流程
const customWorkflow = await orchestrator.startWorkflow('custom', {
steps: [
{ agent: 'SecurityGuard', action: 'scan' },
{ agent: 'PerfTuner', action: 'optimize' },
]
});
```
#### 2. 服务注册
```typescript
import { AiRegistryService } from '@wwjcloud/ai/manager';
// 注册 AI 服务
await registry.registerService({
id: 'my-ai-service',
name: 'My AI Service',
type: 'analyzer',
version: '1.0.0',
capabilities: ['analysis', 'optimization'],
});
```
### Healing 模块使用
#### 1. 自愈配置
```typescript
import { AiRecoveryService } from '@wwjcloud/ai/healing';
// 配置自愈策略
await recovery.configureStrategy('database-connection', {
maxRetries: 3,
retryDelay: 1000,
fallbackAction: 'use-cache',
});
```
#### 2. 手动触发恢复
```typescript
// 手动触发故障恢复
const result = await recovery.recoverFromFailure({
type: 'service-unavailable',
service: 'payment-service',
context: { orderId: '12345' },
});
```
### Safe 模块使用
#### 1. 安全扫描
```typescript
import { AiSecurityService } from '@wwjcloud/ai/safe';
// 执行全面安全评估
const assessment = await security.performSecurityAssessment({
scope: 'full',
includeVulnerabilities: true,
generateReport: true,
});
```
#### 2. 访问控制
```typescript
import { AccessProtector } from '@wwjcloud/ai/safe';
// 验证访问权限
const hasAccess = await protector.validateAccess({
userId: 'user123',
resource: '/api/admin/users',
action: 'read',
});
```
### Tuner 模块使用
#### 1. 性能调优
```typescript
import { AiTunerService } from '@wwjcloud/ai/tuner';
// 启动调优会话
const session = await tuner.startTuningSession({
enableMonitoring: true,
aggressiveOptimization: false,
});
// 执行全面调优
const results = await tuner.performComprehensiveTuning({
enableCacheOptimization: true,
enableQueryOptimization: true,
applyOptimizations: true,
});
```
#### 2. 性能监控
```typescript
import { AiMetricsService } from '@wwjcloud/ai/tuner';
// 记录性能指标
metrics.recordMetric('response_time', 150, { endpoint: '/api/users' });
// 获取性能报告
const report = metrics.generateMetricsReport({
timeRange: {
start: Date.now() - 24 * 60 * 60 * 1000, // 24小时前
end: Date.now(),
},
});
```
## 🔧 高级配置
### 环境变量配置
```bash
# AI 模块配置
AI_ENABLED=true
AI_LOG_LEVEL=info
AI_METRICS_RETENTION_DAYS=30
# 自愈配置
AI_HEALING_ENABLED=true
AI_HEALING_MAX_RETRIES=3
AI_HEALING_RETRY_DELAY=1000
# 安全配置
AI_SECURITY_ENABLED=true
AI_SECURITY_SCAN_INTERVAL=300000
AI_SECURITY_THREAT_THRESHOLD=0.7
# 性能调优配置
AI_TUNER_ENABLED=true
AI_TUNER_AUTO_OPTIMIZE=false
AI_TUNER_MONITORING_INTERVAL=60000
```
### 自定义配置
```typescript
import { WwjcloudAiModule } from '@wwjcloud/ai';
@Module({
imports: [
WwjcloudAiModule.forRoot({
healing: {
enabled: true,
maxRetries: 5,
strategies: ['retry', 'fallback', 'circuit-breaker'],
},
security: {
enabled: true,
scanInterval: 300000,
threatThreshold: 0.8,
},
tuner: {
enabled: true,
autoOptimize: true,
monitoringInterval: 30000,
},
}),
],
})
export class AppModule {}
```
## 📊 监控和指标
### 内置指标
- **系统健康度**: 整体系统健康状况评分
- **自愈成功率**: 自动故障恢复成功率
- **安全威胁数**: 检测到的安全威胁数量
- **性能改进率**: 性能优化带来的改进百分比
### 指标查询
```typescript
// 获取系统概览
const overview = await metrics.getSystemMetricsOverview();
// 获取特定指标统计
const stats = metrics.calculateStatistics('response_time', {
start: Date.now() - 60 * 60 * 1000, // 1小时前
end: Date.now(),
});
```
## 🧪 测试
### 单元测试
```bash
npm run test:ai
```
### 集成测试
```bash
npm run test:ai:e2e
```
### 性能测试
```bash
npm run test:ai:performance
```
## 🔍 故障排查
### 常见问题
#### 1. AI 服务启动失败
**症状**: 应用启动时 AI 模块初始化失败
**解决方案**:
```bash
# 检查依赖
npm ls @wwjcloud/ai
# 检查配置
echo $AI_ENABLED
# 查看日志
tail -f logs/ai.log
```
#### 2. 自愈功能不工作
**症状**: 系统故障时没有自动恢复
**解决方案**:
```typescript
// 检查自愈配置
const config = await healing.getConfiguration();
console.log('Healing enabled:', config.enabled);
// 手动触发自愈测试
await healing.testRecovery('connection-failure');
```
#### 3. 性能优化无效果
**症状**: 性能调优后没有明显改善
**解决方案**:
```typescript
// 检查基线性能
const baseline = await tuner.getPerformanceBaseline();
// 查看优化历史
const history = tuner.getTuningHistory(10);
// 获取优化建议
const recommendations = await tuner.getTuningRecommendations();
```
### 调试模式
```typescript
// 启用调试日志
process.env.AI_LOG_LEVEL = 'debug';
// 启用详细指标
process.env.AI_METRICS_DETAILED = 'true';
```
## 🤝 贡献指南
### 开发环境设置
```bash
# 克隆项目
git clone https://github.com/wwjcloud/wwjcloud-ai.git
# 安装依赖
npm install
# 启动开发模式
npm run dev
```
### 代码规范
- 遵循 NestJS 官方规范
- 使用 TypeScript 严格模式
- 100% 单元测试覆盖率
- 完整的 JSDoc 注释
### 提交规范
```bash
# 功能开发
git commit -m "feat(manager): add workflow orchestration"
# 问题修复
git commit -m "fix(healing): resolve retry strategy issue"
# 文档更新
git commit -m "docs(readme): update usage examples"
```
## 📄 许可证
MIT License - 详见 [LICENSE](LICENSE) 文件
## 🔗 相关链接
- [NestJS 官方文档](https://docs.nestjs.com/)
- [WWJCloud 官网](https://wwjcloud.com/)
- [问题反馈](https://github.com/wwjcloud/wwjcloud-ai/issues)
- [更新日志](CHANGELOG.md)
---
**WWJCloud AI Layer** - 让系统更智能,让运维更简单 🚀

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
import { Module } from '@nestjs/common';
import { AiSelfHealListener } from './listeners/ai-self-heal.listener';
import { AiRecoveryListener } from './listeners/ai-recovery.listener';
import { AiRecoveryService } from './services/ai-recovery.service';
import { AiStrategyService } from './services/ai-strategy.service';
import { RetryStrategy } from './strategies/retry.strategy';
import { FallbackStrategy } from './strategies/fallback.strategy';
/**
* AI Healing Module - AI 自愈模块
*
* 职责:
* - 监听系统错误和故障
* - 自动执行恢复策略
* - 提供多种恢复机制
* - 记录和分析恢复过程
*/
@Module({
providers: [
// 监听器
AiSelfHealListener,
AiRecoveryListener,
// 服务
AiRecoveryService,
AiStrategyService,
// 恢复策略
RetryStrategy,
FallbackStrategy,
],
exports: [
AiRecoveryService,
AiStrategyService,
RetryStrategy,
FallbackStrategy,
],
})
export class AiHealingModule {}

View File

@@ -0,0 +1,88 @@
/**
* Healing Module Interfaces - AI 自愈模块接口定义
*/
/**
* 恢复策略接口
*/
export interface RecoveryStrategy {
name: string;
priority: number;
canHandle(error: any): boolean;
execute(context: RecoveryContext): Promise<RecoveryResult>;
getEstimatedTime(): number;
}
/**
* 恢复上下文接口
*/
export interface RecoveryContext {
taskId: string;
error: any;
metadata: Record<string, any>;
retryCount: number;
maxRetries: number;
startTime: number;
}
/**
* 恢复结果接口
*/
export interface RecoveryResult {
success: boolean;
strategy: string;
duration: number;
result?: any;
error?: string;
nextAction?: 'retry' | 'escalate' | 'abort';
}
/**
* 自愈监听器接口
*/
export interface SelfHealListener {
canHandle(event: any): boolean;
handle(event: any): Promise<void>;
getPriority(): number;
}
/**
* 错误分析结果接口
*/
export interface ErrorAnalysis {
errorType: string;
severity: 'low' | 'medium' | 'high' | 'critical';
category:
| 'network'
| 'database'
| 'service'
| 'validation'
| 'system'
| 'unknown';
recoverable: boolean;
suggestedStrategies: string[];
metadata: Record<string, any>;
}
/**
* 健康检查结果接口
*/
export interface HealthCheckResult {
component: string;
status: 'healthy' | 'degraded' | 'unhealthy';
details: Record<string, any>;
timestamp: number;
responseTime?: number;
}
/**
* 自愈统计信息接口
*/
export interface HealingStats {
totalRecoveries: number;
successfulRecoveries: number;
failedRecoveries: number;
averageRecoveryTime: number;
strategiesUsed: Record<string, number>;
errorCategories: Record<string, number>;
}

View File

@@ -1,5 +1,5 @@
import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { OnEvent } from '@wwjCommon/events/event-bus';
import { TASK_RECOVERY_REQUESTED_EVENT } from '@wwjAi';
import type { TaskRecoveryRequestedPayload } from '@wwjAi';
import { AiRecoveryService } from '../services/ai-recovery.service';

View File

@@ -1,15 +1,9 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { OnEvent, EventEmitter2 } from '@nestjs/event-emitter';
import { EventBus, OnEvent } from '@wwjCommon/events/event-bus';
// ModuleRef no longer used
import {
TASK_FAILED_EVENT,
TASK_RECOVERY_REQUESTED_EVENT,
} from '@wwjAi';
import type {
TaskFailedPayload,
TaskRecoveryRequestedPayload,
} from '@wwjAi';
import { TASK_FAILED_EVENT, TASK_RECOVERY_REQUESTED_EVENT } from '@wwjAi';
import type { TaskFailedPayload, TaskRecoveryRequestedPayload } from '@wwjAi';
import { MetricsService } from '@wwjCommon/metrics/metrics.service';
@Injectable()
@@ -20,11 +14,22 @@ export class AiSelfHealListener {
constructor(
private readonly config: ConfigService,
private readonly emitter: EventEmitter2,
private readonly eventBus: EventBus,
private readonly metrics: MetricsService,
) {}
onModuleInit() {}
onModuleInit() {
const enabled = this.readBoolean('AI_ENABLED');
const currentState = enabled ? 'ready' : 'unavailable';
this.logger.log(
`Healing module init: enabled=${enabled}, state=${currentState}`,
);
this.eventBus.emit('module.state.changed', {
module: 'healing',
previousState: 'initializing',
currentState,
});
}
@OnEvent(TASK_FAILED_EVENT)
handleTaskFailed(payload: TaskFailedPayload) {
@@ -52,7 +57,7 @@ export class AiSelfHealListener {
undefined,
strategy,
);
this.emitter.emit(TASK_RECOVERY_REQUESTED_EVENT, request);
this.eventBus.emit(TASK_RECOVERY_REQUESTED_EVENT, request);
}
@OnEvent(TASK_RECOVERY_REQUESTED_EVENT)

View File

@@ -11,7 +11,7 @@ import {
Severity,
TaskFailedPayload,
} from '@wwjAi';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { EventBus } from '@wwjCommon/events/event-bus';
import { QueueService } from '@wwjCommon/queue/queue.service';
import { ConfigService } from '@nestjs/config';
import { AiStrategyService } from '@wwjAi';
@@ -28,7 +28,7 @@ export class AiRecoveryService {
private readonly cache: CacheService,
private readonly lock: LockService,
private readonly metrics: MetricsService,
private readonly emitter: EventEmitter2,
private readonly eventBus: EventBus,
private readonly queue: QueueService,
private readonly config: ConfigService,
private readonly strategy: AiStrategyService,
@@ -41,24 +41,30 @@ export class AiRecoveryService {
});
if (this.queue.isBullmq() || this.queue.isKafka()) {
// 注册 Worker 处理恢复请求BullMQ/Kafka 共用统一处理器)
this.queue.registerWorker(async (data: TaskRecoveryRequestedPayload) => {
const start = Date.now();
this.logger.log(`Processing recovery (worker) for taskId=${data.taskId}`);
const durationMs = Date.now() - start;
const payload: TaskRecoveryCompletedPayload = {
taskId: data.taskId,
strategy: data.strategy,
result: 'success',
durationMs,
timestamp: Date.now(),
};
this.emitter.emit(TASK_RECOVERY_COMPLETED_EVENT, payload);
this.metrics?.observeAiEvent(
TASK_RECOVERY_COMPLETED_EVENT,
undefined,
data.strategy,
);
}, 1, 'ai-recovery');
this.queue.registerWorker(
async (data: TaskRecoveryRequestedPayload) => {
const start = Date.now();
this.logger.log(
`Processing recovery (worker) for taskId=${data.taskId}`,
);
const durationMs = Date.now() - start;
const payload: TaskRecoveryCompletedPayload = {
taskId: data.taskId,
strategy: data.strategy,
result: 'success',
durationMs,
timestamp: Date.now(),
};
this.eventBus.emit(TASK_RECOVERY_COMPLETED_EVENT, payload);
this.metrics?.observeAiEvent(
TASK_RECOVERY_COMPLETED_EVENT,
undefined,
data.strategy,
);
},
1,
'ai-recovery',
);
}
}
@@ -130,7 +136,7 @@ export class AiRecoveryService {
durationMs,
timestamp: Date.now(),
};
this.emitter.emit(TASK_RECOVERY_COMPLETED_EVENT, payload);
this.eventBus.emit(TASK_RECOVERY_COMPLETED_EVENT, payload);
this.metrics?.observeAiEvent(
TASK_RECOVERY_COMPLETED_EVENT,
undefined,
@@ -154,7 +160,11 @@ export class AiRecoveryService {
}
return processed;
}
async simulateFailure(params: { taskId?: string; severity?: Severity; reason?: string }): Promise<{ ok: true; emitted: boolean }> {
async simulateFailure(params: {
taskId?: string;
severity?: Severity;
reason?: string;
}): Promise<{ ok: true; emitted: boolean }> {
const taskId = params.taskId ?? 'demo-task';
const severity: Severity = params.severity ?? 'medium';
const reason = params.reason ?? 'demo failure';
@@ -164,11 +174,15 @@ export class AiRecoveryService {
severity,
timestamp: Date.now(),
};
this.emitter.emit(TASK_FAILED_EVENT, payload);
this.eventBus.emit(TASK_FAILED_EVENT, payload);
this.metrics?.observeAiEvent(TASK_FAILED_EVENT, severity);
if (this.readBoolean('AI_SIMULATE_DIRECT_ENQUEUE')) {
const decided = this.strategy.decideStrategy(payload);
this.metrics?.observeAiEvent(TASK_RECOVERY_REQUESTED_EVENT, undefined, decided);
this.metrics?.observeAiEvent(
TASK_RECOVERY_REQUESTED_EVENT,
undefined,
decided,
);
const request: TaskRecoveryRequestedPayload = {
taskId,
strategy: decided,
@@ -182,7 +196,8 @@ export class AiRecoveryService {
private readBoolean(key: string): boolean {
const v = this.config.get<string | boolean>(key);
if (typeof v === 'boolean') return v;
if (typeof v === 'string') return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
if (typeof v === 'string')
return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
return false;
}
}

View File

@@ -29,4 +29,4 @@ export class AiStrategyService {
private isValidStrategy(v: string): boolean {
return ['retry', 'restart', 'reroute', 'fallback', 'noop'].includes(v);
}
}
}

View File

@@ -0,0 +1,203 @@
import { Injectable, Logger } from '@nestjs/common';
import {
RecoveryStrategy,
RecoveryContext,
RecoveryResult,
} from '../interfaces/healing.interface';
/**
* Fallback Recovery Strategy - 降级恢复策略
*
* 职责:
* - 提供服务降级方案
* - 处理不可恢复的错误
* - 确保系统基本功能可用
*/
@Injectable()
export class FallbackStrategy implements RecoveryStrategy {
private readonly logger = new Logger(FallbackStrategy.name);
readonly name = 'fallback';
readonly priority = 3;
/**
* 判断是否可以处理该错误
*/
canHandle(error: any): boolean {
// 需要降级处理的错误类型
const fallbackErrors = [
'SERVICE_UNAVAILABLE',
'DEPENDENCY_FAILURE',
'RESOURCE_EXHAUSTED',
'CIRCUIT_BREAKER_OPEN',
'RATE_LIMIT_EXCEEDED',
];
if (error?.code && fallbackErrors.includes(error.code)) {
return true;
}
// 检查错误严重程度
if (error?.severity === 'high' || error?.severity === 'critical') {
return true;
}
return false;
}
/**
* 执行降级恢复
*/
async execute(context: RecoveryContext): Promise<RecoveryResult> {
const startTime = Date.now();
this.logger.log(`Executing fallback strategy for task: ${context.taskId}`);
try {
// 根据任务类型选择降级方案
const fallbackResult = await this.selectFallbackOption(context);
return {
success: true,
strategy: this.name,
duration: Date.now() - startTime,
result: fallbackResult,
nextAction: 'abort', // 降级后通常不再重试
};
} catch (error) {
this.logger.error(
`Fallback strategy failed for task: ${context.taskId}`,
error,
);
return {
success: false,
strategy: this.name,
duration: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Fallback failed',
nextAction: 'abort',
};
}
}
/**
* 获取预估执行时间
*/
getEstimatedTime(): number {
return 2000; // 2秒预估时间
}
/**
* 选择降级方案
*/
private async selectFallbackOption(context: RecoveryContext): Promise<any> {
const taskType = context.metadata?.taskType || 'unknown';
this.logger.debug(`Selecting fallback option for task type: ${taskType}`);
switch (taskType) {
case 'database':
return await this.handleDatabaseFallback(context);
case 'api':
return await this.handleApiFallback(context);
case 'cache':
return await this.handleCacheFallback(context);
case 'file':
return await this.handleFileFallback(context);
default:
return await this.handleGenericFallback(context);
}
}
/**
* 数据库降级处理
*/
private async handleDatabaseFallback(context: RecoveryContext): Promise<any> {
this.logger.warn(`Database fallback for task: ${context.taskId}`);
// 返回缓存数据或默认值
return {
fallbackType: 'database',
data: context.metadata?.cachedData || null,
message: 'Using cached data due to database unavailability',
timestamp: Date.now(),
};
}
/**
* API 降级处理
*/
private async handleApiFallback(context: RecoveryContext): Promise<any> {
this.logger.warn(`API fallback for task: ${context.taskId}`);
// 返回默认响应或离线数据
return {
fallbackType: 'api',
data: context.metadata?.defaultResponse || { status: 'unavailable' },
message: 'Using default response due to API unavailability',
timestamp: Date.now(),
};
}
/**
* 缓存降级处理
*/
private async handleCacheFallback(context: RecoveryContext): Promise<any> {
this.logger.warn(`Cache fallback for task: ${context.taskId}`);
// 直接访问数据源
return {
fallbackType: 'cache',
data: await this.fetchFromDataSource(context),
message: 'Bypassing cache and fetching from data source',
timestamp: Date.now(),
};
}
/**
* 文件降级处理
*/
private async handleFileFallback(context: RecoveryContext): Promise<any> {
this.logger.warn(`File fallback for task: ${context.taskId}`);
// 使用备用文件或默认内容
return {
fallbackType: 'file',
data: context.metadata?.backupContent || '',
message: 'Using backup content due to file access failure',
timestamp: Date.now(),
};
}
/**
* 通用降级处理
*/
private async handleGenericFallback(context: RecoveryContext): Promise<any> {
this.logger.warn(`Generic fallback for task: ${context.taskId}`);
return {
fallbackType: 'generic',
data: null,
message: 'Service temporarily unavailable, please try again later',
timestamp: Date.now(),
};
}
/**
* 从数据源获取数据
*/
private async fetchFromDataSource(context: RecoveryContext): Promise<any> {
// 这里应该实现从原始数据源获取数据的逻辑
// 具体实现取决于数据源类型
if (context.metadata?.dataSourceFunction) {
return await context.metadata.dataSourceFunction();
}
return null;
}
}

View File

@@ -0,0 +1,156 @@
import { Injectable, Logger } from '@nestjs/common';
import {
RecoveryStrategy,
RecoveryContext,
RecoveryResult,
} from '../interfaces/healing.interface';
/**
* Retry Recovery Strategy - 重试恢复策略
*
* 职责:
* - 处理可重试的错误
* - 实现指数退避重试机制
* - 监控重试成功率
*/
@Injectable()
export class RetryStrategy implements RecoveryStrategy {
private readonly logger = new Logger(RetryStrategy.name);
readonly name = 'retry';
readonly priority = 1;
/**
* 判断是否可以处理该错误
*/
canHandle(error: any): boolean {
// 可重试的错误类型
const retryableErrors = [
'ECONNRESET',
'ETIMEDOUT',
'ENOTFOUND',
'ECONNREFUSED',
'NETWORK_ERROR',
'TEMPORARY_FAILURE',
];
if (error?.code && retryableErrors.includes(error.code)) {
return true;
}
if (error?.message) {
const message = error.message.toLowerCase();
return (
message.includes('timeout') ||
message.includes('connection') ||
message.includes('network') ||
message.includes('temporary')
);
}
return false;
}
/**
* 执行重试恢复
*/
async execute(context: RecoveryContext): Promise<RecoveryResult> {
const startTime = Date.now();
this.logger.log(
`Executing retry strategy for task: ${context.taskId}, attempt: ${context.retryCount + 1}`,
);
try {
// 计算退避延迟
const delay = this.calculateBackoffDelay(context.retryCount);
if (delay > 0) {
this.logger.debug(`Waiting ${delay}ms before retry`);
await this.sleep(delay);
}
// 检查是否超过最大重试次数
if (context.retryCount >= context.maxRetries) {
return {
success: false,
strategy: this.name,
duration: Date.now() - startTime,
error: 'Maximum retry attempts exceeded',
nextAction: 'escalate',
};
}
// 执行重试逻辑
const result = await this.performRetry(context);
return {
success: true,
strategy: this.name,
duration: Date.now() - startTime,
result,
nextAction: 'retry',
};
} catch (error) {
this.logger.error(
`Retry strategy failed for task: ${context.taskId}`,
error,
);
return {
success: false,
strategy: this.name,
duration: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error',
nextAction:
context.retryCount < context.maxRetries ? 'retry' : 'escalate',
};
}
}
/**
* 获取预估执行时间
*/
getEstimatedTime(): number {
return 5000; // 5秒预估时间
}
/**
* 计算指数退避延迟
*/
private calculateBackoffDelay(retryCount: number): number {
// 指数退避2^retryCount * 1000ms最大30秒
const baseDelay = 1000;
const maxDelay = 30000;
const delay = Math.min(baseDelay * Math.pow(2, retryCount), maxDelay);
// 添加随机抖动,避免雷群效应
const jitter = Math.random() * 0.1 * delay;
return Math.floor(delay + jitter);
}
/**
* 执行重试
*/
private async performRetry(context: RecoveryContext): Promise<any> {
// 这里应该重新执行原始任务
// 具体实现取决于任务类型和上下文
this.logger.debug(`Performing retry for task: ${context.taskId}`);
// 模拟重试逻辑
if (context.metadata?.originalFunction) {
return await context.metadata.originalFunction();
}
// 如果没有原始函数,返回成功标记
return { retried: true, timestamp: Date.now() };
}
/**
* 睡眠函数
*/
private sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}

View File

@@ -1,4 +1,4 @@
export * from './wwjcloud-ai.module';
export * from './events';
export * from './types';
export * from './services/ai-strategy.service';
export * from './healing/services/ai-strategy.service';

View File

@@ -1,13 +1,17 @@
import { Controller, Get, Query, UseGuards, Post } from '@nestjs/common';
import { RateLimitGuard } from '@wwjCommon/http/rate-limit.guard';
import { AiRecoveryService } from '../services/ai-recovery.service';
import { AiRecoveryService } from '../../healing/services/ai-recovery.service';
import { ApiTags } from '@nestjs/swagger';
import { ApiQuery } from '@nestjs/swagger';
import { IsInt, IsOptional, Min, IsString, IsIn } from 'class-validator';
import { Public, Roles } from '@wwjCommon/auth/decorators';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { EventBus } from '@wwjCommon/events/event-bus';
import { TASK_FAILED_EVENT, TASK_RECOVERY_REQUESTED_EVENT } from '@wwjAi';
import type { Severity, TaskFailedPayload, TaskRecoveryRequestedPayload } from '@wwjAi';
import type {
Severity,
TaskFailedPayload,
TaskRecoveryRequestedPayload,
} from '@wwjAi';
import { ConfigService } from '@nestjs/config';
import { MetricsService } from '@wwjCommon/metrics/metrics.service';
import { AuthGuard } from '@wwjCommon/auth/auth.guard';
@@ -42,7 +46,7 @@ class SimulateFailureQueryDto {
export class AiController {
constructor(
private readonly recovery: AiRecoveryService,
private readonly emitter: EventEmitter2,
private readonly eventBus: EventBus,
private readonly config: ConfigService,
private readonly metrics: MetricsService,
private readonly strategy: AiStrategyService,
@@ -65,7 +69,12 @@ export class AiController {
@Get('drain')
@Post('drain')
@Roles('admin')
@ApiQuery({ name: 'max', required: false, type: Number, description: '最大处理数量默认10' })
@ApiQuery({
name: 'max',
required: false,
type: Number,
description: '最大处理数量默认10',
})
async drain(@Query() query: DrainQueryDto) {
const n = await this.recovery.drain(query.max ?? 10);
return { processed: n };
@@ -75,9 +84,15 @@ export class AiController {
@Post('simulate-failure')
@Roles('admin')
@ApiQuery({ name: 'taskId', required: false, type: String })
@ApiQuery({ name: 'severity', required: false, enum: ['low', 'medium', 'high'] })
@ApiQuery({
name: 'severity',
required: false,
enum: ['low', 'medium', 'high'],
})
@ApiQuery({ name: 'reason', required: false, type: String })
async simulateFailure(@Query() q: SimulateFailureQueryDto): Promise<{ ok: true; emitted: boolean }> {
async simulateFailure(
@Query() q: SimulateFailureQueryDto,
): Promise<{ ok: true; emitted: boolean }> {
// 委派到服务层,控制器不再直接发事件或打点
return await this.recovery.simulateFailure({
taskId: q.taskId,
@@ -86,7 +101,7 @@ export class AiController {
});
}
// 移除 readBoolean 与直接依赖 emitter/metrics/strategy
// 移除 readBoolean 与直接依赖 metrics/strategy
private readBoolean(key: string): boolean {
const v = this.config.get<string | boolean>(key);
if (typeof v === 'boolean') return v;

View File

@@ -0,0 +1,107 @@
/**
* AI Manager Interfaces - AI 管理模块接口定义
*/
/**
* 工作流程配置接口
*/
export interface WorkflowConfig {
steps: string[];
timeout: number;
retryCount: number;
parallel?: boolean;
dependencies?: string[];
}
/**
* 工作流程执行上下文接口
*/
export interface WorkflowContext {
taskId: string;
userId?: string;
siteId?: number;
metadata?: Record<string, any>;
startTime: number;
}
/**
* 工作流程执行结果接口
*/
export interface WorkflowResult {
success: boolean;
steps: StepResult[];
duration: number;
error?: string;
}
/**
* 步骤执行结果接口
*/
export interface StepResult {
step: string;
success: boolean;
duration: number;
result?: any;
error?: string;
}
/**
* 模块状态接口
*/
export interface ModuleState {
name: string;
status: 'initializing' | 'ready' | 'active' | 'error' | 'unavailable';
version: string;
lastUpdate: number;
metadata?: Record<string, any>;
}
/**
* 任务协调请求接口
*/
export interface TaskCoordinationRequest {
taskId: string;
taskType: string;
priority: 'low' | 'medium' | 'high' | 'critical';
payload: any;
requiredModules?: string[];
timeout?: number;
}
/**
* 任务协调结果接口
*/
export interface TaskCoordinationResult {
taskId: string;
success: boolean;
result?: any;
error?: string;
duration: number;
modulesUsed: string[];
}
/**
* AI 管理器统计信息接口
*/
export interface AiManagerStats {
modules: {
total: number;
active: number;
error: number;
};
services: {
total: number;
healthy: number;
byType: Record<string, number>;
};
workflows: {
active: string[];
completed: number;
failed: number;
};
tasks: {
pending: number;
byType: Record<string, number>;
oldestAge?: number;
};
}

View File

@@ -0,0 +1,29 @@
import { Module } from '@nestjs/common';
import { AiBootstrapProvider } from './bootstrap/ai-bootstrap.provider';
import { AiController } from './controllers/ai.controller';
import { AiOrchestratorService } from './services/ai-orchestrator.service';
import { AiRegistryService } from './services/ai-registry.service';
import { AiCoordinatorService } from './services/ai-coordinator.service';
import { AiHealingModule } from '../healing/healing.module';
/**
* AI Manager Module - AI 核心管理模块
*
* 职责:
* - AI 层的统一管理和协调
* - 各子模块的注册和发现
* - AI 服务的编排和调度
* - 系统启动和配置管理
*/
@Module({
imports: [AiHealingModule],
providers: [
AiBootstrapProvider,
AiOrchestratorService,
AiRegistryService,
AiCoordinatorService,
],
controllers: [AiController],
exports: [AiOrchestratorService, AiRegistryService, AiCoordinatorService],
})
export class AiManagerModule {}

View File

@@ -0,0 +1,299 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { EventBus, OnEvent } from '@wwjCommon/events/event-bus';
import { AiRegistryService } from './ai-registry.service';
import { AiOrchestratorService } from './ai-orchestrator.service';
/**
* AI Coordinator Service - AI 协调服务
*
* 职责:
* - 协调各 AI 模块间的通信
* - 处理跨模块的事件和消息
* - 管理模块间的依赖关系
* - 提供统一的协调接口
*/
@Injectable()
export class AiCoordinatorService implements OnModuleInit {
private readonly logger = new Logger(AiCoordinatorService.name);
private readonly moduleStates = new Map<string, string>();
private readonly pendingTasks = new Map<string, any>();
constructor(
private readonly eventBus: EventBus,
private readonly registryService: AiRegistryService,
private readonly orchestratorService: AiOrchestratorService,
) {}
async onModuleInit() {
this.logger.log('AI Coordinator Service initialized');
await this.initializeModuleStates();
// Mark manager as ready once coordinator has initialized
this.updateModuleState('manager', 'ready');
}
/**
* 初始化模块状态
*/
private async initializeModuleStates(): Promise<void> {
const modules = ['healing', 'safe', 'tuner', 'manager'];
for (const module of modules) {
this.moduleStates.set(module, 'initializing');
}
this.logger.log('Module states initialized');
}
/**
* 更新模块状态
*/
updateModuleState(moduleName: string, state: string): void {
const previousState = this.moduleStates.get(moduleName);
this.moduleStates.set(moduleName, state);
this.logger.log(
`Module state updated: ${moduleName} ${previousState} -> ${state}`,
);
this.eventBus.emit('module.state.changed', {
module: moduleName,
previousState,
currentState: state,
});
}
/**
* 获取模块状态
*/
getModuleState(moduleName: string): string | undefined {
return this.moduleStates.get(moduleName);
}
/**
* 获取所有模块状态
*/
getAllModuleStates(): Record<string, string> {
return Object.fromEntries(this.moduleStates);
}
/**
* 协调任务执行
*/
async coordinateTask(
taskId: string,
taskType: string,
payload: any,
): Promise<any> {
this.logger.log(`Coordinating task: ${taskId} (${taskType})`);
try {
// 检查相关模块状态
const requiredModules = this.getRequiredModules(taskType);
const moduleCheck = await this.checkModuleAvailability(requiredModules);
if (!moduleCheck.allAvailable) {
throw new Error(
`Required modules not available: ${moduleCheck.unavailable.join(', ')}`,
);
}
// 执行任务协调
const result = await this.executeCoordinatedTask(
taskId,
taskType,
payload,
);
this.eventBus.emit('task.coordinated', { taskId, taskType, result });
return result;
} catch (error) {
this.logger.error(`Task coordination failed: ${taskId}`, error);
this.eventBus.emit('task.coordination.failed', {
taskId,
taskType,
error,
});
throw error;
}
}
/**
* 获取任务所需模块
*/
private getRequiredModules(taskType: string): string[] {
const moduleMap: Record<string, string[]> = {
healing: ['healing', 'manager'],
security: ['safe', 'manager'],
performance: ['tuner', 'manager'],
comprehensive: ['healing', 'safe', 'tuner', 'manager'],
};
return moduleMap[taskType] || ['manager'];
}
/**
* 检查模块可用性
*/
private async checkModuleAvailability(modules: string[]): Promise<{
allAvailable: boolean;
available: string[];
unavailable: string[];
}> {
const available: string[] = [];
const unavailable: string[] = [];
for (const module of modules) {
const state = this.moduleStates.get(module);
if (state === 'ready' || state === 'active') {
available.push(module);
} else {
unavailable.push(module);
}
}
return {
allAvailable: unavailable.length === 0,
available,
unavailable,
};
}
/**
* 执行协调任务
*/
private async executeCoordinatedTask(
taskId: string,
taskType: string,
payload: any,
): Promise<any> {
// 将任务添加到待处理队列
this.pendingTasks.set(taskId, { taskType, payload, startTime: Date.now() });
try {
// 根据任务类型选择执行策略
let result;
switch (taskType) {
case 'healing':
result = await this.orchestratorService.executeWorkflow(
'healing',
payload,
);
break;
case 'security':
result = await this.orchestratorService.executeWorkflow(
'security',
payload,
);
break;
case 'performance':
result = await this.orchestratorService.executeWorkflow(
'performance',
payload,
);
break;
default:
result = await this.executeCustomTask(taskType, payload);
}
return result;
} finally {
// 从待处理队列中移除
this.pendingTasks.delete(taskId);
}
}
/**
* 执行自定义任务
*/
private async executeCustomTask(
taskType: string,
payload: any,
): Promise<any> {
const services = this.registryService.getServicesByType(taskType);
if (services.length === 0) {
throw new Error(`No services available for task type: ${taskType}`);
}
// 执行第一个可用服务
return await services[0].execute(payload);
}
/**
* 处理任务失败事件
*/
@OnEvent('task.failed')
async handleTaskFailed(payload: any): Promise<void> {
this.logger.warn(`Task failed: ${payload.taskId}`);
// 尝试协调恢复
if (payload.severity === 'high') {
await this.coordinateTask(`recovery-${payload.taskId}`, 'healing', {
originalTask: payload,
});
}
}
/**
* 处理模块状态变化事件
*/
@OnEvent('module.state.changed')
async handleModuleStateChanged(payload: any): Promise<void> {
this.logger.debug(
`Module state changed: ${payload.module} -> ${payload.currentState}`,
);
// 同步内部状态映射,保持状态来源一致
this.moduleStates.set(payload.module, payload.currentState);
// 如果模块变为不可用,暂停相关任务
if (
payload.currentState === 'error' ||
payload.currentState === 'unavailable'
) {
await this.pauseModuleTasks(payload.module);
}
}
/**
* 暂停模块相关任务
*/
private async pauseModuleTasks(moduleName: string): Promise<void> {
this.logger.warn(`Pausing tasks for module: ${moduleName}`);
for (const [taskId, task] of this.pendingTasks) {
const requiredModules = this.getRequiredModules(task.taskType);
if (requiredModules.includes(moduleName)) {
this.logger.warn(`Pausing task: ${taskId}`);
this.eventBus.emit('task.paused', {
taskId,
reason: `Module unavailable: ${moduleName}`,
});
}
}
}
/**
* 获取待处理任务统计
*/
getPendingTasksStats(): {
total: number;
byType: Record<string, number>;
oldestTask?: { id: string; age: number };
} {
const total = this.pendingTasks.size;
const byType: Record<string, number> = {};
let oldestTask: { id: string; age: number } | undefined;
for (const [taskId, task] of this.pendingTasks) {
byType[task.taskType] = (byType[task.taskType] || 0) + 1;
const age = Date.now() - task.startTime;
if (!oldestTask || age > oldestTask.age) {
oldestTask = { id: taskId, age };
}
}
return { total, byType, oldestTask };
}
}

View File

@@ -0,0 +1,147 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { EventBus } from '@wwjCommon/events/event-bus';
import { AiRegistryService } from './ai-registry.service';
/**
* AI Orchestrator Service - AI 编排服务
*
* 职责:
* - 协调各 AI 模块的工作流程
* - 管理 AI 任务的执行顺序
* - 处理模块间的依赖关系
* - 监控和调度 AI 服务
*/
@Injectable()
export class AiOrchestratorService implements OnModuleInit {
private readonly logger = new Logger(AiOrchestratorService.name);
private readonly activeWorkflows = new Map<string, any>();
constructor(
private readonly eventBus: EventBus,
private readonly registryService: AiRegistryService,
) {}
async onModuleInit() {
this.logger.log('AI Orchestrator Service initialized');
await this.initializeWorkflows();
}
/**
* 初始化工作流程
*/
private async initializeWorkflows(): Promise<void> {
this.logger.log('Initializing AI workflows...');
// 注册默认工作流程
await this.registerDefaultWorkflows();
}
/**
* 注册默认工作流程
*/
private async registerDefaultWorkflows(): Promise<void> {
// 自愈工作流程
this.registerWorkflow('healing', {
steps: ['detect', 'analyze', 'recover', 'verify'],
timeout: 30000,
retryCount: 3,
});
// 安全检查工作流程
this.registerWorkflow('security', {
steps: ['scan', 'analyze', 'protect', 'report'],
timeout: 15000,
retryCount: 2,
});
// 性能优化工作流程
this.registerWorkflow('performance', {
steps: ['monitor', 'analyze', 'optimize', 'validate'],
timeout: 60000,
retryCount: 1,
});
}
/**
* 注册工作流程
*/
registerWorkflow(name: string, config: any): void {
this.activeWorkflows.set(name, config);
this.logger.log(`Workflow registered: ${name}`);
}
/**
* 执行工作流程
*/
async executeWorkflow(name: string, context: any): Promise<any> {
const workflow = this.activeWorkflows.get(name);
if (!workflow) {
throw new Error(`Workflow not found: ${name}`);
}
this.logger.log(`Executing workflow: ${name}`);
try {
const result = await this.processWorkflowSteps(workflow, context);
this.eventBus.emit('workflow.completed', { name, result });
return result;
} catch (error) {
this.logger.error(`Workflow execution failed: ${name}`, error);
this.eventBus.emit('workflow.failed', { name, error });
throw error;
}
}
/**
* 处理工作流程步骤
*/
private async processWorkflowSteps(
workflow: any,
context: any,
): Promise<any> {
const results = [];
for (const step of workflow.steps) {
this.logger.debug(`Processing workflow step: ${step}`);
const stepResult = await this.executeWorkflowStep(step, context);
results.push(stepResult);
}
return results;
}
/**
* 执行工作流程步骤
*/
private async executeWorkflowStep(step: string, context: any): Promise<any> {
// 根据步骤类型调用相应的服务
const services = this.registryService.getServicesByType(step);
if (services.length === 0) {
this.logger.warn(`No services found for step: ${step}`);
return null;
}
// 执行第一个匹配的服务
const service = services[0];
return await service.execute(context);
}
/**
* 获取活动工作流程
*/
getActiveWorkflows(): string[] {
return Array.from(this.activeWorkflows.keys());
}
/**
* 停止工作流程
*/
stopWorkflow(name: string): boolean {
const removed = this.activeWorkflows.delete(name);
if (removed) {
this.logger.log(`Workflow stopped: ${name}`);
this.eventBus.emit('workflow.stopped', { name });
}
return removed;
}
}

View File

@@ -0,0 +1,187 @@
import {
Injectable,
Logger,
OnModuleInit,
OnModuleDestroy,
} from '@nestjs/common';
import { EventBus } from '@wwjCommon/events/event-bus';
/**
* AI Service Interface - AI 服务接口
*/
export interface AiService {
name: string;
type: string;
version: string;
execute(context: any): Promise<any>;
isHealthy(): Promise<boolean>;
}
/**
* AI Registry Service - AI 注册服务
*
* 职责:
* - 管理 AI 服务的注册和注销
* - 提供服务发现功能
* - 监控服务健康状态
* - 维护服务元数据
*/
@Injectable()
export class AiRegistryService implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(AiRegistryService.name);
private readonly services = new Map<string, AiService>();
private readonly servicesByType = new Map<string, AiService[]>();
private healthCheckInterval: NodeJS.Timeout | null = null;
constructor(private readonly eventBus: EventBus) {}
async onModuleInit() {
this.logger.log('AI Registry Service initialized');
await this.startHealthCheck();
}
async onModuleDestroy() {
// 停止健康检查定时器,避免残留句柄
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
this.healthCheckInterval = null;
this.logger.log('AI Registry Service health check stopped');
}
}
/**
* 注册 AI 服务
*/
registerService(service: AiService): void {
this.services.set(service.name, service);
// 按类型分组
if (!this.servicesByType.has(service.type)) {
this.servicesByType.set(service.type, []);
}
this.servicesByType.get(service.type)!.push(service);
this.logger.log(`Service registered: ${service.name} (${service.type})`);
this.eventBus.emit('service.registered', service);
}
/**
* 注销 AI 服务
*/
unregisterService(serviceName: string): boolean {
const service = this.services.get(serviceName);
if (!service) {
return false;
}
this.services.delete(serviceName);
// 从类型分组中移除
const typeServices = this.servicesByType.get(service.type);
if (typeServices) {
const index = typeServices.findIndex((s) => s.name === serviceName);
if (index > -1) {
typeServices.splice(index, 1);
}
}
this.logger.log(`Service unregistered: ${serviceName}`);
this.eventBus.emit('service.unregistered', service);
return true;
}
/**
* 获取服务
*/
getService(serviceName: string): AiService | undefined {
return this.services.get(serviceName);
}
/**
* 根据类型获取服务列表
*/
getServicesByType(type: string): AiService[] {
return this.servicesByType.get(type) || [];
}
/**
* 获取所有服务
*/
getAllServices(): AiService[] {
return Array.from(this.services.values());
}
/**
* 获取服务统计信息
*/
getServiceStats(): {
total: number;
byType: Record<string, number>;
healthy: number;
} {
const total = this.services.size;
const byType: Record<string, number> = {};
for (const [type, services] of this.servicesByType) {
byType[type] = services.length;
}
return {
total,
byType,
healthy: 0, // 将在健康检查中更新
};
}
/**
* 启动健康检查
*/
private async startHealthCheck(): Promise<void> {
this.healthCheckInterval = setInterval(async () => {
await this.performHealthCheck();
}, 30000);
}
/**
* 执行健康检查
*/
private async performHealthCheck(): Promise<void> {
let healthyCount = 0;
for (const service of this.services.values()) {
try {
const isHealthy = await service.isHealthy();
if (isHealthy) {
healthyCount++;
} else {
this.logger.warn(`Service unhealthy: ${service.name}`);
this.eventBus.emit('service.unhealthy', service);
}
} catch (error) {
this.logger.error(
`Health check failed for service: ${service.name}`,
error,
);
this.eventBus.emit('service.error', { service, error });
}
}
this.logger.debug(
`Health check completed: ${healthyCount}/${this.services.size} services healthy`,
);
}
/**
* 查找服务
*/
findServices(predicate: (service: AiService) => boolean): AiService[] {
return Array.from(this.services.values()).filter(predicate);
}
/**
* 检查服务是否存在
*/
hasService(serviceName: string): boolean {
return this.services.has(serviceName);
}
}

View File

@@ -0,0 +1,527 @@
import { Injectable, Logger } from '@nestjs/common';
/**
* Security Analyzer - 安全分析器
*
* 职责:
* - 分析系统安全状态
* - 识别潜在安全威胁
* - 评估安全风险等级
* - 生成安全报告
*/
@Injectable()
export class SecurityAnalyzer {
private readonly logger = new Logger(SecurityAnalyzer.name);
/**
* 分析系统安全状态
*/
async analyzeSystemSecurity(): Promise<SecurityAnalysisResult> {
this.logger.log('Starting system security analysis');
const startTime = Date.now();
try {
// 执行各项安全检查
const [authSecurity, dataSecurity, networkSecurity, codeSecurity] =
await Promise.all([
this.analyzeAuthSecurity(),
this.analyzeDataSecurity(),
this.analyzeNetworkSecurity(),
this.analyzeCodeSecurity(),
]);
const overallRisk = this.calculateOverallRisk([
authSecurity.riskLevel,
dataSecurity.riskLevel,
networkSecurity.riskLevel,
codeSecurity.riskLevel,
]);
return {
timestamp: Date.now(),
duration: Date.now() - startTime,
overallRisk,
categories: {
authentication: authSecurity,
dataProtection: dataSecurity,
networkSecurity: networkSecurity,
codeQuality: codeSecurity,
},
recommendations: this.generateRecommendations(overallRisk),
};
} catch (error) {
this.logger.error('Security analysis failed', error);
throw error;
}
}
/**
* 分析认证安全
*/
private async analyzeAuthSecurity(): Promise<SecurityCategoryResult> {
// 检查认证机制
const checks = [
this.checkJwtSecurity(),
this.checkPasswordPolicy(),
this.checkSessionSecurity(),
this.checkMfaSecurity(),
];
const results = await Promise.all(checks);
const riskLevel = this.calculateCategoryRisk(results);
return {
category: 'authentication',
riskLevel,
checks: results,
score: this.calculateSecurityScore(results),
};
}
/**
* 分析数据安全
*/
private async analyzeDataSecurity(): Promise<SecurityCategoryResult> {
const checks = [
this.checkDataEncryption(),
this.checkDataAccess(),
this.checkDataBackup(),
this.checkDataRetention(),
];
const results = await Promise.all(checks);
const riskLevel = this.calculateCategoryRisk(results);
return {
category: 'dataProtection',
riskLevel,
checks: results,
score: this.calculateSecurityScore(results),
};
}
/**
* 分析网络安全
*/
private async analyzeNetworkSecurity(): Promise<SecurityCategoryResult> {
const checks = [
this.checkHttpsSecurity(),
this.checkCorsConfiguration(),
this.checkRateLimiting(),
this.checkFirewallRules(),
];
const results = await Promise.all(checks);
const riskLevel = this.calculateCategoryRisk(results);
return {
category: 'networkSecurity',
riskLevel,
checks: results,
score: this.calculateSecurityScore(results),
};
}
/**
* 分析代码安全
*/
private async analyzeCodeSecurity(): Promise<SecurityCategoryResult> {
const checks = [
this.checkInputValidation(),
this.checkSqlInjection(),
this.checkXssProtection(),
this.checkDependencyVulnerabilities(),
];
const results = await Promise.all(checks);
const riskLevel = this.calculateCategoryRisk(results);
return {
category: 'codeQuality',
riskLevel,
checks: results,
score: this.calculateSecurityScore(results),
};
}
/**
* JWT 安全检查
*/
private async checkJwtSecurity(): Promise<SecurityCheckResult> {
// 实现 JWT 安全检查逻辑
return {
name: 'JWT Security',
status: 'pass',
riskLevel: 'low',
message: 'JWT configuration is secure',
details: {
algorithm: 'RS256',
expiration: '1h',
secretRotation: true,
},
};
}
/**
* 密码策略检查
*/
private async checkPasswordPolicy(): Promise<SecurityCheckResult> {
return {
name: 'Password Policy',
status: 'pass',
riskLevel: 'low',
message: 'Password policy meets security requirements',
details: {
minLength: 8,
complexity: true,
expiration: 90,
},
};
}
/**
* 会话安全检查
*/
private async checkSessionSecurity(): Promise<SecurityCheckResult> {
return {
name: 'Session Security',
status: 'pass',
riskLevel: 'medium',
message: 'Session configuration needs improvement',
details: {
httpOnly: true,
secure: true,
sameSite: 'strict',
},
};
}
/**
* 多因素认证检查
*/
private async checkMfaSecurity(): Promise<SecurityCheckResult> {
return {
name: 'Multi-Factor Authentication',
status: 'warning',
riskLevel: 'medium',
message: 'MFA is not enabled for all users',
details: {
enabled: false,
coverage: '30%',
},
};
}
/**
* 数据加密检查
*/
private async checkDataEncryption(): Promise<SecurityCheckResult> {
return {
name: 'Data Encryption',
status: 'pass',
riskLevel: 'low',
message: 'Data encryption is properly configured',
details: {
atRest: true,
inTransit: true,
algorithm: 'AES-256',
},
};
}
/**
* 数据访问检查
*/
private async checkDataAccess(): Promise<SecurityCheckResult> {
return {
name: 'Data Access Control',
status: 'pass',
riskLevel: 'low',
message: 'Data access controls are properly implemented',
details: {
rbac: true,
audit: true,
encryption: true,
},
};
}
/**
* 数据备份检查
*/
private async checkDataBackup(): Promise<SecurityCheckResult> {
return {
name: 'Data Backup',
status: 'pass',
riskLevel: 'low',
message: 'Data backup strategy is adequate',
details: {
frequency: 'daily',
encryption: true,
offsite: true,
},
};
}
/**
* 数据保留检查
*/
private async checkDataRetention(): Promise<SecurityCheckResult> {
return {
name: 'Data Retention',
status: 'pass',
riskLevel: 'low',
message: 'Data retention policies are compliant',
details: {
policy: 'defined',
automation: true,
compliance: 'GDPR',
},
};
}
/**
* HTTPS 安全检查
*/
private async checkHttpsSecurity(): Promise<SecurityCheckResult> {
return {
name: 'HTTPS Security',
status: 'pass',
riskLevel: 'low',
message: 'HTTPS is properly configured',
details: {
enforced: true,
tlsVersion: '1.3',
hsts: true,
},
};
}
/**
* CORS 配置检查
*/
private async checkCorsConfiguration(): Promise<SecurityCheckResult> {
return {
name: 'CORS Configuration',
status: 'warning',
riskLevel: 'medium',
message: 'CORS configuration may be too permissive',
details: {
origins: ['*'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
},
};
}
/**
* 速率限制检查
*/
private async checkRateLimiting(): Promise<SecurityCheckResult> {
return {
name: 'Rate Limiting',
status: 'pass',
riskLevel: 'low',
message: 'Rate limiting is properly configured',
details: {
enabled: true,
limits: '100/min',
burst: 10,
},
};
}
/**
* 防火墙规则检查
*/
private async checkFirewallRules(): Promise<SecurityCheckResult> {
return {
name: 'Firewall Rules',
status: 'pass',
riskLevel: 'low',
message: 'Firewall rules are properly configured',
details: {
enabled: true,
defaultDeny: true,
logging: true,
},
};
}
/**
* 输入验证检查
*/
private async checkInputValidation(): Promise<SecurityCheckResult> {
return {
name: 'Input Validation',
status: 'pass',
riskLevel: 'low',
message: 'Input validation is comprehensive',
details: {
sanitization: true,
validation: true,
whitelisting: true,
},
};
}
/**
* SQL 注入检查
*/
private async checkSqlInjection(): Promise<SecurityCheckResult> {
return {
name: 'SQL Injection Protection',
status: 'pass',
riskLevel: 'low',
message: 'SQL injection protection is effective',
details: {
parameterizedQueries: true,
orm: 'TypeORM',
escaping: true,
},
};
}
/**
* XSS 保护检查
*/
private async checkXssProtection(): Promise<SecurityCheckResult> {
return {
name: 'XSS Protection',
status: 'pass',
riskLevel: 'low',
message: 'XSS protection is properly implemented',
details: {
csp: true,
sanitization: true,
encoding: true,
},
};
}
/**
* 依赖漏洞检查
*/
private async checkDependencyVulnerabilities(): Promise<SecurityCheckResult> {
return {
name: 'Dependency Vulnerabilities',
status: 'warning',
riskLevel: 'medium',
message: 'Some dependencies have known vulnerabilities',
details: {
total: 150,
vulnerable: 3,
critical: 0,
high: 1,
medium: 2,
},
};
}
/**
* 计算分类风险等级
*/
private calculateCategoryRisk(results: SecurityCheckResult[]): RiskLevel {
const riskLevels = results.map((r) => r.riskLevel);
if (riskLevels.includes('critical')) return 'critical';
if (riskLevels.includes('high')) return 'high';
if (riskLevels.includes('medium')) return 'medium';
return 'low';
}
/**
* 计算整体风险等级
*/
private calculateOverallRisk(categoryRisks: RiskLevel[]): RiskLevel {
const riskWeights = { critical: 4, high: 3, medium: 2, low: 1 };
const totalWeight = categoryRisks.reduce(
(sum, risk) => sum + riskWeights[risk],
0,
);
const avgWeight = totalWeight / categoryRisks.length;
if (avgWeight >= 3.5) return 'critical';
if (avgWeight >= 2.5) return 'high';
if (avgWeight >= 1.5) return 'medium';
return 'low';
}
/**
* 计算安全评分
*/
private calculateSecurityScore(results: SecurityCheckResult[]): number {
const statusWeights = { pass: 100, warning: 60, fail: 0 };
const totalScore = results.reduce(
(sum, result) => sum + statusWeights[result.status],
0,
);
return Math.round(totalScore / results.length);
}
/**
* 生成安全建议
*/
private generateRecommendations(riskLevel: RiskLevel): string[] {
const recommendations: Record<RiskLevel, string[]> = {
critical: [
'Immediately address critical security vulnerabilities',
'Implement emergency security patches',
'Review and strengthen access controls',
'Conduct comprehensive security audit',
],
high: [
'Address high-priority security issues within 24 hours',
'Implement additional security monitoring',
'Review security policies and procedures',
'Consider security training for development team',
],
medium: [
'Address medium-priority security issues within a week',
'Implement security best practices',
'Regular security assessments',
'Update security documentation',
],
low: [
'Maintain current security posture',
'Continue regular security monitoring',
'Keep security tools and policies updated',
'Periodic security reviews',
],
};
return recommendations[riskLevel] || recommendations.low;
}
}
// 类型定义
export interface SecurityAnalysisResult {
timestamp: number;
duration: number;
overallRisk: RiskLevel;
categories: {
authentication: SecurityCategoryResult;
dataProtection: SecurityCategoryResult;
networkSecurity: SecurityCategoryResult;
codeQuality: SecurityCategoryResult;
};
recommendations: string[];
}
export interface SecurityCategoryResult {
category: string;
riskLevel: RiskLevel;
checks: SecurityCheckResult[];
score: number;
}
export interface SecurityCheckResult {
name: string;
status: 'pass' | 'warning' | 'fail';
riskLevel: RiskLevel;
message: string;
details: Record<string, any>;
}
export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';

View File

@@ -0,0 +1,410 @@
import { Injectable, Logger } from '@nestjs/common';
/**
* Vulnerability Detector - 漏洞检测器
*
* 职责:
* - 扫描系统漏洞
* - 检测安全威胁
* - 监控异常行为
* - 生成威胁报告
*/
@Injectable()
export class VulnerabilityDetector {
private readonly logger = new Logger(VulnerabilityDetector.name);
/**
* 执行全面漏洞扫描
*/
async scanVulnerabilities(): Promise<VulnerabilityScanResult> {
this.logger.log('Starting comprehensive vulnerability scan');
const startTime = Date.now();
try {
const [
codeVulnerabilities,
dependencyVulnerabilities,
configVulnerabilities,
networkVulnerabilities,
] = await Promise.all([
this.scanCodeVulnerabilities(),
this.scanDependencyVulnerabilities(),
this.scanConfigurationVulnerabilities(),
this.scanNetworkVulnerabilities(),
]);
const allVulnerabilities = [
...codeVulnerabilities,
...dependencyVulnerabilities,
...configVulnerabilities,
...networkVulnerabilities,
];
return {
timestamp: Date.now(),
duration: Date.now() - startTime,
totalVulnerabilities: allVulnerabilities.length,
severityBreakdown: this.calculateSeverityBreakdown(allVulnerabilities),
vulnerabilities: allVulnerabilities,
recommendations:
this.generateVulnerabilityRecommendations(allVulnerabilities),
};
} catch (error) {
this.logger.error('Vulnerability scan failed', error);
throw error;
}
}
/**
* 扫描代码漏洞
*/
private async scanCodeVulnerabilities(): Promise<Vulnerability[]> {
this.logger.debug('Scanning code vulnerabilities');
// 模拟代码漏洞扫描
const vulnerabilities: Vulnerability[] = [];
// SQL 注入检测
const sqlInjectionVulns = await this.detectSqlInjection();
vulnerabilities.push(...sqlInjectionVulns);
// XSS 检测
const xssVulns = await this.detectXssVulnerabilities();
vulnerabilities.push(...xssVulns);
// 认证绕过检测
const authBypassVulns = await this.detectAuthenticationBypass();
vulnerabilities.push(...authBypassVulns);
// 权限提升检测
const privilegeEscalationVulns = await this.detectPrivilegeEscalation();
vulnerabilities.push(...privilegeEscalationVulns);
return vulnerabilities;
}
/**
* 扫描依赖漏洞
*/
private async scanDependencyVulnerabilities(): Promise<Vulnerability[]> {
this.logger.debug('Scanning dependency vulnerabilities');
// 模拟依赖漏洞扫描结果
return [
{
id: 'CVE-2023-1234',
type: 'dependency',
severity: 'high',
title: 'Remote Code Execution in lodash',
description:
'A prototype pollution vulnerability in lodash allows remote code execution',
affectedComponent: 'lodash@4.17.20',
cweId: 'CWE-1321',
cvssScore: 8.5,
discoveredAt: Date.now(),
status: 'open',
remediation: {
type: 'update',
description: 'Update lodash to version 4.17.21 or later',
effort: 'low',
},
},
{
id: 'CVE-2023-5678',
type: 'dependency',
severity: 'medium',
title: 'Information Disclosure in express',
description:
'Express middleware may leak sensitive information in error messages',
affectedComponent: 'express@4.18.0',
cweId: 'CWE-200',
cvssScore: 5.3,
discoveredAt: Date.now(),
status: 'open',
remediation: {
type: 'configuration',
description:
'Configure error handling to prevent information leakage',
effort: 'medium',
},
},
];
}
/**
* 扫描配置漏洞
*/
private async scanConfigurationVulnerabilities(): Promise<Vulnerability[]> {
this.logger.debug('Scanning configuration vulnerabilities');
return [
{
id: 'CONFIG-001',
type: 'configuration',
severity: 'medium',
title: 'Weak CORS Configuration',
description:
'CORS is configured to allow all origins which may lead to security issues',
affectedComponent: 'CORS Middleware',
cweId: 'CWE-346',
cvssScore: 4.3,
discoveredAt: Date.now(),
status: 'open',
remediation: {
type: 'configuration',
description: 'Restrict CORS origins to specific trusted domains',
effort: 'low',
},
},
];
}
/**
* 扫描网络漏洞
*/
private async scanNetworkVulnerabilities(): Promise<Vulnerability[]> {
this.logger.debug('Scanning network vulnerabilities');
return [
{
id: 'NET-001',
type: 'network',
severity: 'low',
title: 'Missing Security Headers',
description: 'Some security headers are not configured properly',
affectedComponent: 'HTTP Headers',
cweId: 'CWE-693',
cvssScore: 3.1,
discoveredAt: Date.now(),
status: 'open',
remediation: {
type: 'configuration',
description:
'Add missing security headers (CSP, HSTS, X-Frame-Options)',
effort: 'low',
},
},
];
}
/**
* 检测 SQL 注入漏洞
*/
private async detectSqlInjection(): Promise<Vulnerability[]> {
// 模拟 SQL 注入检测
return [];
}
/**
* 检测 XSS 漏洞
*/
private async detectXssVulnerabilities(): Promise<Vulnerability[]> {
// 模拟 XSS 检测
return [];
}
/**
* 检测认证绕过漏洞
*/
private async detectAuthenticationBypass(): Promise<Vulnerability[]> {
// 模拟认证绕过检测
return [];
}
/**
* 检测权限提升漏洞
*/
private async detectPrivilegeEscalation(): Promise<Vulnerability[]> {
// 模拟权限提升检测
return [];
}
/**
* 计算严重程度分布
*/
private calculateSeverityBreakdown(
vulnerabilities: Vulnerability[],
): SeverityBreakdown {
const breakdown = {
critical: 0,
high: 0,
medium: 0,
low: 0,
};
vulnerabilities.forEach((vuln) => {
breakdown[vuln.severity]++;
});
return breakdown;
}
/**
* 生成漏洞修复建议
*/
private generateVulnerabilityRecommendations(
vulnerabilities: Vulnerability[],
): string[] {
const recommendations: string[] = [];
const criticalCount = vulnerabilities.filter(
(v) => v.severity === 'critical',
).length;
const highCount = vulnerabilities.filter(
(v) => v.severity === 'high',
).length;
if (criticalCount > 0) {
recommendations.push(
`Immediately address ${criticalCount} critical vulnerabilities`,
);
}
if (highCount > 0) {
recommendations.push(
`Address ${highCount} high-severity vulnerabilities within 24 hours`,
);
}
recommendations.push(
'Implement automated vulnerability scanning in CI/CD pipeline',
);
recommendations.push('Regular security training for development team');
recommendations.push(
'Establish vulnerability disclosure and response process',
);
return recommendations;
}
/**
* 检测实时威胁
*/
async detectRealTimeThreats(): Promise<ThreatDetectionResult> {
this.logger.log('Starting real-time threat detection');
const threats = await Promise.all([
this.detectSuspiciousActivity(),
this.detectAnomalousTraffic(),
this.detectBruteForceAttacks(),
this.detectMaliciousPayloads(),
]);
const allThreats = threats.flat();
return {
timestamp: Date.now(),
threatsDetected: allThreats.length,
threats: allThreats,
riskLevel: this.calculateThreatRiskLevel(allThreats),
};
}
/**
* 检测可疑活动
*/
private async detectSuspiciousActivity(): Promise<Threat[]> {
// 模拟可疑活动检测
return [];
}
/**
* 检测异常流量
*/
private async detectAnomalousTraffic(): Promise<Threat[]> {
// 模拟异常流量检测
return [];
}
/**
* 检测暴力破解攻击
*/
private async detectBruteForceAttacks(): Promise<Threat[]> {
// 模拟暴力破解检测
return [];
}
/**
* 检测恶意载荷
*/
private async detectMaliciousPayloads(): Promise<Threat[]> {
// 模拟恶意载荷检测
return [];
}
/**
* 计算威胁风险等级
*/
private calculateThreatRiskLevel(
threats: Threat[],
): 'low' | 'medium' | 'high' | 'critical' {
if (threats.length === 0) return 'low';
const highSeverityThreats = threats.filter(
(t) => t.severity === 'high' || t.severity === 'critical',
);
if (highSeverityThreats.length > 5) return 'critical';
if (highSeverityThreats.length > 2) return 'high';
if (threats.length > 10) return 'medium';
return 'low';
}
}
// 类型定义
export interface VulnerabilityScanResult {
timestamp: number;
duration: number;
totalVulnerabilities: number;
severityBreakdown: SeverityBreakdown;
vulnerabilities: Vulnerability[];
recommendations: string[];
}
export interface Vulnerability {
id: string;
type: 'code' | 'dependency' | 'configuration' | 'network';
severity: 'low' | 'medium' | 'high' | 'critical';
title: string;
description: string;
affectedComponent: string;
cweId?: string;
cvssScore?: number;
discoveredAt: number;
status: 'open' | 'in_progress' | 'resolved' | 'false_positive';
remediation: {
type: 'update' | 'patch' | 'configuration' | 'code_change';
description: string;
effort: 'low' | 'medium' | 'high';
};
}
export interface SeverityBreakdown {
critical: number;
high: number;
medium: number;
low: number;
}
export interface ThreatDetectionResult {
timestamp: number;
threatsDetected: number;
threats: Threat[];
riskLevel: 'low' | 'medium' | 'high' | 'critical';
}
export interface Threat {
id: string;
type:
| 'suspicious_activity'
| 'anomalous_traffic'
| 'brute_force'
| 'malicious_payload';
severity: 'low' | 'medium' | 'high' | 'critical';
source: string;
description: string;
detectedAt: number;
indicators: string[];
}

View File

@@ -0,0 +1,603 @@
import {
Injectable,
Logger,
CanActivate,
ExecutionContext,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
/**
* Access Protector - 访问保护器
*
* 职责:
* - 控制系统访问权限
* - 实施安全策略
* - 监控访问行为
* - 防止未授权访问
*/
@Injectable()
export class AccessProtector implements CanActivate {
private readonly logger = new Logger(AccessProtector.name);
private readonly accessAttempts = new Map<string, AccessAttempt[]>();
private readonly blockedIps = new Set<string>();
private readonly suspiciousActivities = new Map<
string,
SuspiciousActivity[]
>();
constructor(private reflector: Reflector) {}
/**
* 守卫方法 - 检查访问权限
*/
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
const clientIp = this.getClientIp(request);
// 检查 IP 是否被阻止
if (this.blockedIps.has(clientIp)) {
this.logger.warn(`Blocked IP attempted access: ${clientIp}`);
return false;
}
// 记录访问尝试
this.recordAccessAttempt(clientIp, request);
// 检查访问频率
if (this.isAccessRateLimited(clientIp)) {
this.logger.warn(`Rate limit exceeded for IP: ${clientIp}`);
return false;
}
// 检查可疑活动
if (this.detectSuspiciousActivity(clientIp, request)) {
this.logger.warn(`Suspicious activity detected from IP: ${clientIp}`);
this.handleSuspiciousActivity(clientIp, request);
return false;
}
return true;
}
/**
* 验证用户权限
*/
async validateUserPermissions(
userId: string,
resource: string,
action: string,
): Promise<boolean> {
this.logger.debug(
`Validating permissions for user ${userId}: ${action} on ${resource}`,
);
try {
// 获取用户角色和权限
const userPermissions = await this.getUserPermissions(userId);
// 检查资源访问权限
const hasPermission = this.checkResourcePermission(
userPermissions,
resource,
action,
);
if (!hasPermission) {
this.logger.warn(
`Permission denied for user ${userId}: ${action} on ${resource}`,
);
await this.logSecurityEvent('PERMISSION_DENIED', {
userId,
resource,
action,
timestamp: Date.now(),
});
}
return hasPermission;
} catch (error) {
this.logger.error(
`Error validating permissions for user ${userId}`,
error,
);
return false;
}
}
/**
* 实施安全策略
*/
async enforceSecurityPolicy(
policyName: string,
context: SecurityContext,
): Promise<PolicyResult> {
this.logger.debug(`Enforcing security policy: ${policyName}`);
const policy = await this.getSecurityPolicy(policyName);
if (!policy) {
return {
allowed: false,
reason: 'Policy not found',
actions: [],
};
}
// 评估策略条件
const evaluation = await this.evaluatePolicyConditions(policy, context);
if (!evaluation.allowed) {
// 执行策略动作
await this.executePolicyActions(policy.denyActions, context);
}
return evaluation;
}
/**
* 监控访问模式
*/
async monitorAccessPatterns(): Promise<AccessPatternAnalysis> {
this.logger.log('Analyzing access patterns');
const analysis = {
timestamp: Date.now(),
totalAccesses: 0,
uniqueIps: 0,
suspiciousActivities: 0,
blockedAttempts: 0,
topSources: [] as AccessSource[],
anomalies: [] as AccessAnomaly[],
};
// 分析访问尝试
for (const [ip, attempts] of this.accessAttempts.entries()) {
analysis.totalAccesses += attempts.length;
// 检查异常模式
const anomalies = this.detectAccessAnomalies(ip, attempts);
analysis.anomalies.push(...anomalies);
}
analysis.uniqueIps = this.accessAttempts.size;
analysis.suspiciousActivities = this.suspiciousActivities.size;
analysis.blockedAttempts = this.blockedIps.size;
analysis.topSources = this.getTopAccessSources();
return analysis;
}
/**
* 获取客户端 IP
*/
private getClientIp(request: any): string {
return (
request.ip ||
request.connection?.remoteAddress ||
request.socket?.remoteAddress ||
request.headers['x-forwarded-for']?.split(',')[0] ||
'unknown'
);
}
/**
* 记录访问尝试
*/
private recordAccessAttempt(ip: string, request: any): void {
if (!this.accessAttempts.has(ip)) {
this.accessAttempts.set(ip, []);
}
const attempts = this.accessAttempts.get(ip)!;
attempts.push({
timestamp: Date.now(),
method: request.method,
url: request.url,
userAgent: request.headers['user-agent'],
success: true,
});
// 保持最近 1000 次访问记录
if (attempts.length > 1000) {
attempts.splice(0, attempts.length - 1000);
}
}
/**
* 检查访问频率限制
*/
private isAccessRateLimited(ip: string): boolean {
const attempts = this.accessAttempts.get(ip) || [];
const recentAttempts = attempts.filter(
(attempt) => Date.now() - attempt.timestamp < 60000, // 1分钟内
);
return recentAttempts.length > 100; // 每分钟最多100次请求
}
/**
* 检测可疑活动
*/
private detectSuspiciousActivity(ip: string, request: any): boolean {
const attempts = this.accessAttempts.get(ip) || [];
// 检查快速连续请求
const recentAttempts = attempts.filter(
(attempt) => Date.now() - attempt.timestamp < 10000, // 10秒内
);
if (recentAttempts.length > 50) {
return true;
}
// 检查异常 User-Agent
const userAgent = request.headers['user-agent'];
if (!userAgent || this.isSuspiciousUserAgent(userAgent)) {
return true;
}
// 检查恶意路径
if (this.containsMaliciousPatterns(request.url)) {
return true;
}
return false;
}
/**
* 处理可疑活动
*/
private handleSuspiciousActivity(ip: string, request: any): void {
if (!this.suspiciousActivities.has(ip)) {
this.suspiciousActivities.set(ip, []);
}
const activities = this.suspiciousActivities.get(ip)!;
activities.push({
timestamp: Date.now(),
type: 'suspicious_request',
details: {
method: request.method,
url: request.url,
userAgent: request.headers['user-agent'],
},
});
// 如果可疑活动过多,阻止该 IP
if (activities.length > 10) {
this.blockedIps.add(ip);
this.logger.warn(
`IP ${ip} has been blocked due to excessive suspicious activities`,
);
}
}
/**
* 检查可疑 User-Agent
*/
private isSuspiciousUserAgent(userAgent: string): boolean {
const suspiciousPatterns = [
/bot/i,
/crawler/i,
/spider/i,
/scanner/i,
/sqlmap/i,
/nikto/i,
];
return suspiciousPatterns.some((pattern) => pattern.test(userAgent));
}
/**
* 检查恶意路径模式
*/
private containsMaliciousPatterns(url: string): boolean {
const maliciousPatterns = [
/\.\./, // 路径遍历
/\/etc\/passwd/,
/\/proc\/self/,
/<script/i, // XSS
/javascript:/i,
/union.*select/i, // SQL 注入
/drop.*table/i,
];
return maliciousPatterns.some((pattern) => pattern.test(url));
}
/**
* 获取用户权限
*/
private async getUserPermissions(userId: string): Promise<UserPermissions> {
// 这里应该从数据库或缓存中获取用户权限
// 模拟返回权限数据
return {
userId,
roles: ['user'],
permissions: [
{ resource: 'user', actions: ['read', 'update'] },
{ resource: 'profile', actions: ['read', 'update'] },
],
};
}
/**
* 检查资源权限
*/
private checkResourcePermission(
userPermissions: UserPermissions,
resource: string,
action: string,
): boolean {
return userPermissions.permissions.some(
(permission) =>
permission.resource === resource && permission.actions.includes(action),
);
}
/**
* 获取安全策略
*/
private async getSecurityPolicy(
policyName: string,
): Promise<SecurityPolicy | null> {
// 模拟安全策略
const policies: Record<string, SecurityPolicy> = {
rate_limit: {
name: 'rate_limit',
conditions: [
{ type: 'request_count', threshold: 100, timeWindow: 60000 },
],
denyActions: ['block_ip', 'log_event'],
},
geo_restriction: {
name: 'geo_restriction',
conditions: [
{ type: 'geo_location', allowedCountries: ['CN', 'US', 'EU'] },
],
denyActions: ['block_request', 'log_event'],
},
};
return policies[policyName] || null;
}
/**
* 评估策略条件
*/
private async evaluatePolicyConditions(
policy: SecurityPolicy,
context: SecurityContext,
): Promise<PolicyResult> {
for (const condition of policy.conditions) {
const result = await this.evaluateCondition(condition, context);
if (!result.allowed) {
return result;
}
}
return { allowed: true, reason: 'All conditions passed', actions: [] };
}
/**
* 评估单个条件
*/
private async evaluateCondition(
condition: PolicyCondition,
context: SecurityContext,
): Promise<PolicyResult> {
switch (condition.type) {
case 'request_count':
return this.evaluateRequestCountCondition(condition, context);
case 'geo_location':
return this.evaluateGeoLocationCondition(condition, context);
default:
return { allowed: true, reason: 'Unknown condition type', actions: [] };
}
}
/**
* 评估请求计数条件
*/
private evaluateRequestCountCondition(
condition: PolicyCondition,
context: SecurityContext,
): PolicyResult {
const attempts = this.accessAttempts.get(context.clientIp) || [];
const recentAttempts = attempts.filter(
(attempt) => Date.now() - attempt.timestamp < condition.timeWindow!,
);
if (recentAttempts.length > condition.threshold!) {
return {
allowed: false,
reason: `Request count exceeded: ${recentAttempts.length}/${condition.threshold}`,
actions: ['rate_limit'],
};
}
return {
allowed: true,
reason: 'Request count within limits',
actions: [],
};
}
/**
* 评估地理位置条件
*/
private evaluateGeoLocationCondition(
condition: PolicyCondition,
context: SecurityContext,
): PolicyResult {
// 模拟地理位置检查
const userCountry = 'CN'; // 应该从 IP 地理位置服务获取
if (!condition.allowedCountries!.includes(userCountry)) {
return {
allowed: false,
reason: `Access from restricted country: ${userCountry}`,
actions: ['geo_block'],
};
}
return {
allowed: true,
reason: 'Geographic location allowed',
actions: [],
};
}
/**
* 执行策略动作
*/
private async executePolicyActions(
actions: string[],
context: SecurityContext,
): Promise<void> {
for (const action of actions) {
switch (action) {
case 'block_ip':
this.blockedIps.add(context.clientIp);
break;
case 'log_event':
await this.logSecurityEvent('POLICY_VIOLATION', context);
break;
case 'block_request':
// 请求已被阻止,无需额外操作
break;
}
}
}
/**
* 记录安全事件
*/
private async logSecurityEvent(eventType: string, data: any): Promise<void> {
this.logger.warn(`Security event: ${eventType}`, data);
// 这里应该将事件记录到安全日志系统
}
/**
* 检测访问异常
*/
private detectAccessAnomalies(
ip: string,
attempts: AccessAttempt[],
): AccessAnomaly[] {
const anomalies: AccessAnomaly[] = [];
// 检查访问频率异常
const recentAttempts = attempts.filter(
(attempt) => Date.now() - attempt.timestamp < 3600000, // 1小时内
);
if (recentAttempts.length > 1000) {
anomalies.push({
type: 'high_frequency',
ip,
description: `Unusually high access frequency: ${recentAttempts.length} requests in 1 hour`,
severity: 'high',
timestamp: Date.now(),
});
}
return anomalies;
}
/**
* 获取访问来源统计
*/
private getTopAccessSources(): AccessSource[] {
const sources: AccessSource[] = [];
for (const [ip, attempts] of this.accessAttempts.entries()) {
sources.push({
ip,
requestCount: attempts.length,
lastAccess: Math.max(...attempts.map((a) => a.timestamp)),
});
}
return sources.sort((a, b) => b.requestCount - a.requestCount).slice(0, 10);
}
}
// 类型定义
export interface AccessAttempt {
timestamp: number;
method: string;
url: string;
userAgent: string;
success: boolean;
}
export interface SuspiciousActivity {
timestamp: number;
type: string;
details: Record<string, any>;
}
export interface UserPermissions {
userId: string;
roles: string[];
permissions: ResourcePermission[];
}
export interface ResourcePermission {
resource: string;
actions: string[];
}
export interface SecurityPolicy {
name: string;
conditions: PolicyCondition[];
denyActions: string[];
}
export interface PolicyCondition {
type: string;
threshold?: number;
timeWindow?: number;
allowedCountries?: string[];
}
export interface SecurityContext {
clientIp: string;
userId?: string;
userAgent?: string;
timestamp: number;
}
export interface PolicyResult {
allowed: boolean;
reason: string;
actions: string[];
}
export interface AccessPatternAnalysis {
timestamp: number;
totalAccesses: number;
uniqueIps: number;
suspiciousActivities: number;
blockedAttempts: number;
topSources: AccessSource[];
anomalies: AccessAnomaly[];
}
export interface AccessSource {
ip: string;
requestCount: number;
lastAccess: number;
}
export interface AccessAnomaly {
type: string;
ip: string;
description: string;
severity: 'low' | 'medium' | 'high' | 'critical';
timestamp: number;
}

View File

@@ -0,0 +1,42 @@
import { Module } from '@nestjs/common';
import { SecurityAnalyzer } from './analyzers/security.analyzer';
import { VulnerabilityDetector } from './detectors/vulnerability.detector';
import { AccessProtector } from './protectors/access.protector';
import { AiSecurityService } from './services/ai-security.service';
import { AiAuditService } from './services/ai-audit.service';
import { SafeReadyService } from './services/safe-ready.service';
/**
* AI Safe Module - AI 安全模块
*
* 职责:
* - 安全威胁检测和分析
* - 访问控制和权限管理
* - 安全审计和日志记录
* - 漏洞扫描和防护
*/
@Module({
providers: [
// 分析器
SecurityAnalyzer,
// 检测器
VulnerabilityDetector,
// 保护器
AccessProtector,
// 服务
AiSecurityService,
AiAuditService,
SafeReadyService,
],
exports: [
SecurityAnalyzer,
VulnerabilityDetector,
AccessProtector,
AiSecurityService,
AiAuditService,
],
})
export class AiSafeModule {}

View File

@@ -0,0 +1,799 @@
import { Injectable, Logger } from '@nestjs/common';
/**
* AI Audit Service - AI 审计服务
*
* 职责:
* - 记录和跟踪安全事件
* - 生成审计报告
* - 合规性检查
* - 安全日志管理
*/
@Injectable()
export class AiAuditService {
private readonly logger = new Logger(AiAuditService.name);
private readonly auditLogs: AuditLog[] = [];
private readonly complianceRules: ComplianceRule[] = [];
constructor() {
this.initializeComplianceRules();
}
/**
* 记录审计事件
*/
async logAuditEvent(event: AuditEvent): Promise<void> {
this.logger.debug(`Logging audit event: ${event.type}`);
const auditLog: AuditLog = {
id: this.generateAuditId(),
timestamp: Date.now(),
event,
severity: this.determineSeverity(event),
category: this.categorizeEvent(event),
compliance: await this.checkCompliance(event),
};
this.auditLogs.push(auditLog);
// 保持最近 10000 条审计日志
if (this.auditLogs.length > 10000) {
this.auditLogs.splice(0, this.auditLogs.length - 10000);
}
// 如果是高严重性事件,立即处理
if (auditLog.severity === 'high' || auditLog.severity === 'critical') {
await this.handleHighSeverityEvent(auditLog);
}
}
/**
* 生成审计报告
*/
async generateAuditReport(options: AuditReportOptions): Promise<AuditReport> {
this.logger.log('Generating audit report');
const startTime =
options.startTime || Date.now() - 30 * 24 * 60 * 60 * 1000; // 默认30天
const endTime = options.endTime || Date.now();
// 过滤指定时间范围内的日志
const filteredLogs = this.auditLogs.filter(
(log) => log.timestamp >= startTime && log.timestamp <= endTime,
);
// 按类型分组统计
const eventsByType = this.groupEventsByType(filteredLogs);
const eventsBySeverity = this.groupEventsBySeverity(filteredLogs);
const eventsByCategory = this.groupEventsByCategory(filteredLogs);
// 合规性分析
const complianceAnalysis = await this.analyzeCompliance(filteredLogs);
// 安全趋势分析
const securityTrends = this.analyzeSecurityTrends(filteredLogs);
// 异常检测
const anomalies = this.detectAnomalies(filteredLogs);
return {
id: this.generateReportId(),
timestamp: Date.now(),
period: { startTime, endTime },
summary: {
totalEvents: filteredLogs.length,
criticalEvents: eventsBySeverity.critical || 0,
highSeverityEvents: eventsBySeverity.high || 0,
complianceScore: complianceAnalysis.overallScore,
},
statistics: {
eventsByType,
eventsBySeverity,
eventsByCategory,
},
complianceAnalysis,
securityTrends,
anomalies,
recommendations: this.generateRecommendations(
filteredLogs,
complianceAnalysis,
),
};
}
/**
* 执行合规性检查
*/
async performComplianceCheck(): Promise<ComplianceCheckResult> {
this.logger.log('Performing compliance check');
const results: ComplianceRuleResult[] = [];
for (const rule of this.complianceRules) {
const result = await this.evaluateComplianceRule(rule);
results.push(result);
}
const overallScore = this.calculateComplianceScore(results);
const violations = results.filter((r) => !r.compliant);
return {
timestamp: Date.now(),
overallScore,
status: this.determineComplianceStatus(overallScore),
results,
violations,
recommendations: this.generateComplianceRecommendations(violations),
};
}
/**
* 搜索审计日志
*/
async searchAuditLogs(
criteria: AuditSearchCriteria,
): Promise<AuditSearchResult> {
this.logger.debug('Searching audit logs', criteria);
let filteredLogs = [...this.auditLogs];
// 按时间范围过滤
if (criteria.startTime) {
filteredLogs = filteredLogs.filter(
(log) => log.timestamp >= criteria.startTime!,
);
}
if (criteria.endTime) {
filteredLogs = filteredLogs.filter(
(log) => log.timestamp <= criteria.endTime!,
);
}
// 按事件类型过滤
if (criteria.eventType) {
filteredLogs = filteredLogs.filter(
(log) => log.event.type === criteria.eventType,
);
}
// 按严重性过滤
if (criteria.severity) {
filteredLogs = filteredLogs.filter(
(log) => log.severity === criteria.severity,
);
}
// 按用户过滤
if (criteria.userId) {
filteredLogs = filteredLogs.filter(
(log) => log.event.userId === criteria.userId,
);
}
// 按关键词搜索
if (criteria.keyword) {
const keyword = criteria.keyword.toLowerCase();
filteredLogs = filteredLogs.filter(
(log) =>
log.event.description.toLowerCase().includes(keyword) ||
log.event.type.toLowerCase().includes(keyword),
);
}
// 分页
const total = filteredLogs.length;
const page = criteria.page || 1;
const limit = criteria.limit || 50;
const offset = (page - 1) * limit;
const paginatedLogs = filteredLogs.slice(offset, offset + limit);
return {
logs: paginatedLogs,
pagination: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
/**
* 导出审计数据
*/
async exportAuditData(
format: 'json' | 'csv' | 'xml',
options: ExportOptions,
): Promise<string> {
this.logger.log(`Exporting audit data in ${format} format`);
const startTime =
options.startTime || Date.now() - 30 * 24 * 60 * 60 * 1000;
const endTime = options.endTime || Date.now();
const filteredLogs = this.auditLogs.filter(
(log) => log.timestamp >= startTime && log.timestamp <= endTime,
);
switch (format) {
case 'json':
return JSON.stringify(filteredLogs, null, 2);
case 'csv':
return this.convertToCsv(filteredLogs);
case 'xml':
return this.convertToXml(filteredLogs);
default:
throw new Error(`Unsupported export format: ${format}`);
}
}
/**
* 初始化合规性规则
*/
private initializeComplianceRules(): void {
this.complianceRules.push(
{
id: 'GDPR_DATA_ACCESS',
name: 'GDPR Data Access Logging',
description: 'All personal data access must be logged',
category: 'data_protection',
severity: 'high',
evaluator: this.evaluateDataAccessLogging.bind(this),
},
{
id: 'SOX_FINANCIAL_ACCESS',
name: 'SOX Financial Data Access Control',
description:
'Financial data access must be properly controlled and audited',
category: 'financial_compliance',
severity: 'critical',
evaluator: this.evaluateFinancialAccessControl.bind(this),
},
{
id: 'ISO27001_INCIDENT_RESPONSE',
name: 'ISO 27001 Incident Response',
description:
'Security incidents must be properly documented and responded to',
category: 'security_management',
severity: 'high',
evaluator: this.evaluateIncidentResponse.bind(this),
},
);
}
/**
* 生成审计ID
*/
private generateAuditId(): string {
return `AUDIT_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 生成报告ID
*/
private generateReportId(): string {
return `REPORT_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 确定事件严重性
*/
private determineSeverity(
event: AuditEvent,
): 'low' | 'medium' | 'high' | 'critical' {
const severityMap: Record<string, 'low' | 'medium' | 'high' | 'critical'> =
{
LOGIN_SUCCESS: 'low',
LOGIN_FAILURE: 'medium',
PERMISSION_DENIED: 'medium',
DATA_ACCESS: 'medium',
DATA_MODIFICATION: 'high',
SECURITY_VIOLATION: 'high',
SYSTEM_BREACH: 'critical',
DATA_BREACH: 'critical',
};
return severityMap[event.type] || 'medium';
}
/**
* 事件分类
*/
private categorizeEvent(event: AuditEvent): string {
const categoryMap: Record<string, string> = {
LOGIN_SUCCESS: 'authentication',
LOGIN_FAILURE: 'authentication',
PERMISSION_DENIED: 'authorization',
DATA_ACCESS: 'data_access',
DATA_MODIFICATION: 'data_modification',
SECURITY_VIOLATION: 'security',
SYSTEM_BREACH: 'security',
DATA_BREACH: 'security',
};
return categoryMap[event.type] || 'general';
}
/**
* 检查合规性
*/
private async checkCompliance(event: AuditEvent): Promise<ComplianceStatus> {
// 简化的合规性检查
const requiredFields = ['userId', 'timestamp', 'type', 'description'];
const hasRequiredFields = requiredFields.every(
(field) => event[field as keyof AuditEvent],
);
return {
compliant: hasRequiredFields,
violations: hasRequiredFields ? [] : ['Missing required fields'],
rules: ['BASIC_AUDIT_REQUIREMENTS'],
};
}
/**
* 处理高严重性事件
*/
private async handleHighSeverityEvent(auditLog: AuditLog): Promise<void> {
this.logger.warn(`High severity audit event detected`, auditLog);
// 这里应该实现高严重性事件的处理逻辑
// 例如:发送告警、通知管理员、触发自动响应等
}
/**
* 按类型分组事件
*/
private groupEventsByType(logs: AuditLog[]): Record<string, number> {
const groups: Record<string, number> = {};
logs.forEach((log) => {
groups[log.event.type] = (groups[log.event.type] || 0) + 1;
});
return groups;
}
/**
* 按严重性分组事件
*/
private groupEventsBySeverity(logs: AuditLog[]): Record<string, number> {
const groups: Record<string, number> = {};
logs.forEach((log) => {
groups[log.severity] = (groups[log.severity] || 0) + 1;
});
return groups;
}
/**
* 按类别分组事件
*/
private groupEventsByCategory(logs: AuditLog[]): Record<string, number> {
const groups: Record<string, number> = {};
logs.forEach((log) => {
groups[log.category] = (groups[log.category] || 0) + 1;
});
return groups;
}
/**
* 分析合规性
*/
private async analyzeCompliance(
logs: AuditLog[],
): Promise<ComplianceAnalysis> {
const compliantLogs = logs.filter((log) => log.compliance.compliant);
const violationLogs = logs.filter((log) => !log.compliance.compliant);
const overallScore =
logs.length > 0 ? (compliantLogs.length / logs.length) * 100 : 100;
return {
overallScore: Math.round(overallScore),
compliantEvents: compliantLogs.length,
violationEvents: violationLogs.length,
violationsByRule: this.groupViolationsByRule(violationLogs),
};
}
/**
* 按规则分组违规
*/
private groupViolationsByRule(logs: AuditLog[]): Record<string, number> {
const groups: Record<string, number> = {};
logs.forEach((log) => {
log.compliance.violations.forEach((violation) => {
groups[violation] = (groups[violation] || 0) + 1;
});
});
return groups;
}
/**
* 分析安全趋势
*/
private analyzeSecurityTrends(logs: AuditLog[]): SecurityTrend[] {
// 简化的趋势分析
const dailyStats = this.groupLogsByDay(logs);
return [
{
metric: 'daily_events',
values: Object.entries(dailyStats).map(([date, count]) => ({
timestamp: new Date(date).getTime(),
value: count,
})),
trend: 'stable',
},
];
}
/**
* 按天分组日志
*/
private groupLogsByDay(logs: AuditLog[]): Record<string, number> {
const groups: Record<string, number> = {};
logs.forEach((log) => {
const date = new Date(log.timestamp).toISOString().split('T')[0];
groups[date] = (groups[date] || 0) + 1;
});
return groups;
}
/**
* 检测异常
*/
private detectAnomalies(logs: AuditLog[]): AuditAnomaly[] {
const anomalies: AuditAnomaly[] = [];
// 检测异常高频事件
const eventCounts = this.groupEventsByType(logs);
const avgCount =
Object.values(eventCounts).reduce((sum, count) => sum + count, 0) /
Object.keys(eventCounts).length;
Object.entries(eventCounts).forEach(([type, count]) => {
if (count > avgCount * 3) {
// 超过平均值3倍
anomalies.push({
type: 'high_frequency_event',
description: `Unusually high frequency of ${type} events: ${count}`,
severity: 'medium',
timestamp: Date.now(),
metadata: { eventType: type, count },
});
}
});
return anomalies;
}
/**
* 生成建议
*/
private generateRecommendations(
logs: AuditLog[],
complianceAnalysis: ComplianceAnalysis,
): string[] {
const recommendations: string[] = [];
if (complianceAnalysis.overallScore < 90) {
recommendations.push(
'Improve compliance by addressing identified violations',
);
}
if (complianceAnalysis.violationEvents > 0) {
recommendations.push(
'Review and update security policies to prevent violations',
);
}
const criticalEvents = logs.filter(
(log) => log.severity === 'critical',
).length;
if (criticalEvents > 0) {
recommendations.push(
'Investigate and address critical security events immediately',
);
}
return recommendations;
}
/**
* 评估合规性规则
*/
private async evaluateComplianceRule(
rule: ComplianceRule,
): Promise<ComplianceRuleResult> {
try {
const result = await rule.evaluator();
return {
ruleId: rule.id,
ruleName: rule.name,
compliant: result.compliant,
score: result.score || (result.compliant ? 100 : 0),
violations: result.violations || [],
recommendations: result.recommendations || [],
};
} catch (error) {
this.logger.error(`Failed to evaluate compliance rule ${rule.id}`, error);
return {
ruleId: rule.id,
ruleName: rule.name,
compliant: false,
score: 0,
violations: ['Rule evaluation failed'],
recommendations: ['Review rule implementation'],
};
}
}
/**
* 计算合规性评分
*/
private calculateComplianceScore(results: ComplianceRuleResult[]): number {
if (results.length === 0) return 100;
const totalScore = results.reduce((sum, result) => sum + result.score, 0);
return Math.round(totalScore / results.length);
}
/**
* 确定合规性状态
*/
private determineComplianceStatus(
score: number,
): 'compliant' | 'warning' | 'non_compliant' {
if (score >= 90) return 'compliant';
if (score >= 70) return 'warning';
return 'non_compliant';
}
/**
* 生成合规性建议
*/
private generateComplianceRecommendations(
violations: ComplianceRuleResult[],
): string[] {
const recommendations: string[] = [];
violations.forEach((violation) => {
recommendations.push(...violation.recommendations);
});
return [...new Set(recommendations)]; // 去重
}
/**
* 转换为CSV格式
*/
private convertToCsv(logs: AuditLog[]): string {
const headers = [
'ID',
'Timestamp',
'Event Type',
'User ID',
'Description',
'Severity',
'Category',
];
const rows = logs.map((log) => [
log.id,
new Date(log.timestamp).toISOString(),
log.event.type,
log.event.userId || '',
log.event.description,
log.severity,
log.category,
]);
return [headers, ...rows].map((row) => row.join(',')).join('\n');
}
/**
* 转换为XML格式
*/
private convertToXml(logs: AuditLog[]): string {
const xmlLogs = logs
.map(
(log) => `
<log>
<id>${log.id}</id>
<timestamp>${new Date(log.timestamp).toISOString()}</timestamp>
<event>
<type>${log.event.type}</type>
<userId>${log.event.userId || ''}</userId>
<description>${log.event.description}</description>
</event>
<severity>${log.severity}</severity>
<category>${log.category}</category>
</log>
`,
)
.join('');
return `<?xml version="1.0" encoding="UTF-8"?><auditLogs>${xmlLogs}</auditLogs>`;
}
/**
* 评估数据访问日志记录
*/
private async evaluateDataAccessLogging(): Promise<ComplianceEvaluationResult> {
// 模拟评估逻辑
return {
compliant: true,
score: 95,
violations: [],
recommendations: [],
};
}
/**
* 评估财务访问控制
*/
private async evaluateFinancialAccessControl(): Promise<ComplianceEvaluationResult> {
// 模拟评估逻辑
return {
compliant: true,
score: 90,
violations: [],
recommendations: [],
};
}
/**
* 评估事件响应
*/
private async evaluateIncidentResponse(): Promise<ComplianceEvaluationResult> {
// 模拟评估逻辑
return {
compliant: false,
score: 70,
violations: ['Incident response time exceeds policy requirements'],
recommendations: ['Implement automated incident response procedures'],
};
}
}
// 类型定义
export interface AuditEvent {
type: string;
userId?: string;
timestamp: number;
description: string;
metadata?: Record<string, any>;
}
export interface AuditLog {
id: string;
timestamp: number;
event: AuditEvent;
severity: 'low' | 'medium' | 'high' | 'critical';
category: string;
compliance: ComplianceStatus;
}
export interface ComplianceStatus {
compliant: boolean;
violations: string[];
rules: string[];
}
export interface AuditReportOptions {
startTime?: number;
endTime?: number;
includeDetails?: boolean;
}
export interface AuditReport {
id: string;
timestamp: number;
period: { startTime: number; endTime: number };
summary: {
totalEvents: number;
criticalEvents: number;
highSeverityEvents: number;
complianceScore: number;
};
statistics: {
eventsByType: Record<string, number>;
eventsBySeverity: Record<string, number>;
eventsByCategory: Record<string, number>;
};
complianceAnalysis: ComplianceAnalysis;
securityTrends: SecurityTrend[];
anomalies: AuditAnomaly[];
recommendations: string[];
}
export interface ComplianceAnalysis {
overallScore: number;
compliantEvents: number;
violationEvents: number;
violationsByRule: Record<string, number>;
}
export interface SecurityTrend {
metric: string;
values: { timestamp: number; value: number }[];
trend: 'increasing' | 'decreasing' | 'stable';
}
export interface AuditAnomaly {
type: string;
description: string;
severity: 'low' | 'medium' | 'high' | 'critical';
timestamp: number;
metadata?: Record<string, any>;
}
export interface ComplianceRule {
id: string;
name: string;
description: string;
category: string;
severity: 'low' | 'medium' | 'high' | 'critical';
evaluator: () => Promise<ComplianceEvaluationResult>;
}
export interface ComplianceEvaluationResult {
compliant: boolean;
score?: number;
violations?: string[];
recommendations?: string[];
}
export interface ComplianceCheckResult {
timestamp: number;
overallScore: number;
status: 'compliant' | 'warning' | 'non_compliant';
results: ComplianceRuleResult[];
violations: ComplianceRuleResult[];
recommendations: string[];
}
export interface ComplianceRuleResult {
ruleId: string;
ruleName: string;
compliant: boolean;
score: number;
violations: string[];
recommendations: string[];
}
export interface AuditSearchCriteria {
startTime?: number;
endTime?: number;
eventType?: string;
severity?: 'low' | 'medium' | 'high' | 'critical';
userId?: string;
keyword?: string;
page?: number;
limit?: number;
}
export interface AuditSearchResult {
logs: AuditLog[];
pagination: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
export interface ExportOptions {
startTime?: number;
endTime?: number;
includeMetadata?: boolean;
}

View File

@@ -0,0 +1,554 @@
import { Injectable, Logger } from '@nestjs/common';
import { SecurityAnalyzer } from '../analyzers/security.analyzer';
import { VulnerabilityDetector } from '../detectors/vulnerability.detector';
import { AccessProtector } from '../protectors/access.protector';
import { AiAuditService } from './ai-audit.service';
/**
* AI Security Service - AI 安全服务
*
* 职责:
* - 统一安全管理接口
* - 协调各安全组件
* - 提供安全决策支持
* - 管理安全策略执行
*/
@Injectable()
export class AiSecurityService {
private readonly logger = new Logger(AiSecurityService.name);
constructor(
private readonly securityAnalyzer: SecurityAnalyzer,
private readonly vulnerabilityDetector: VulnerabilityDetector,
private readonly accessProtector: AccessProtector,
private readonly auditService: AiAuditService,
) {}
/**
* 执行全面安全评估
*/
async performSecurityAssessment(): Promise<SecurityAssessmentResult> {
this.logger.log('Starting comprehensive security assessment');
const startTime = Date.now();
try {
// 并行执行各项安全检查
const [
securityAnalysis,
vulnerabilityScan,
accessPatternAnalysis,
threatDetection,
] = await Promise.all([
this.securityAnalyzer.analyzeSystemSecurity(),
this.vulnerabilityDetector.scanVulnerabilities(),
this.accessProtector.monitorAccessPatterns(),
this.vulnerabilityDetector.detectRealTimeThreats(),
]);
// 计算综合安全评分
const overallScore = this.calculateOverallSecurityScore({
securityAnalysis,
vulnerabilityScan,
accessPatternAnalysis,
threatDetection,
});
// 生成安全建议
const recommendations = this.generateSecurityRecommendations({
securityAnalysis,
vulnerabilityScan,
threatDetection,
});
return {
timestamp: Date.now(),
duration: Date.now() - startTime,
overallScore,
riskLevel: this.determineRiskLevel(overallScore),
components: {
securityAnalysis,
vulnerabilityScan,
accessPatternAnalysis,
threatDetection,
},
recommendations,
nextAssessmentTime: Date.now() + 24 * 60 * 60 * 1000, // 24小时后
};
} catch (error) {
this.logger.error('Security assessment failed', error);
throw error;
}
}
/**
* 验证安全策略
*/
async validateSecurityPolicy(
policyName: string,
context: any,
): Promise<PolicyValidationResult> {
this.logger.debug(`Validating security policy: ${policyName}`);
try {
const result = await this.accessProtector.enforceSecurityPolicy(
policyName,
context,
);
return {
policyName,
valid: result.allowed,
reason: result.reason,
actions: result.actions,
timestamp: Date.now(),
};
} catch (error) {
this.logger.error(`Policy validation failed for ${policyName}`, error);
return {
policyName,
valid: false,
reason: 'Policy validation error',
actions: [],
timestamp: Date.now(),
};
}
}
/**
* 处理安全事件
*/
async handleSecurityEvent(
event: SecurityEvent,
): Promise<SecurityEventResponse> {
this.logger.warn(`Handling security event: ${event.type}`, event);
const response: SecurityEventResponse = {
eventId: event.id,
handled: false,
actions: [],
timestamp: Date.now(),
};
try {
switch (event.severity) {
case 'critical':
response.actions = await this.handleCriticalSecurityEvent(event);
break;
case 'high':
response.actions = await this.handleHighSecurityEvent(event);
break;
case 'medium':
response.actions = await this.handleMediumSecurityEvent(event);
break;
case 'low':
response.actions = await this.handleLowSecurityEvent(event);
break;
}
response.handled = true;
this.logger.log(`Security event ${event.id} handled successfully`);
} catch (error) {
this.logger.error(`Failed to handle security event ${event.id}`, error);
response.error = error instanceof Error ? error.message : 'Unknown error';
}
return response;
}
/**
* 获取安全状态仪表板
*/
async getSecurityDashboard(): Promise<SecurityDashboard> {
this.logger.debug('Generating security dashboard');
try {
const [recentThreats, vulnerabilityStats, accessStats] =
await Promise.all([
this.getRecentThreats(),
this.getVulnerabilityStatistics(),
this.getAccessStatistics(),
]);
return {
timestamp: Date.now(),
status: this.determineOverallSecurityStatus(),
metrics: {
threatsDetected: recentThreats.length,
vulnerabilitiesFound: vulnerabilityStats.total,
accessViolations: accessStats.violations,
securityScore: await this.getCurrentSecurityScore(),
},
recentEvents: await this.getRecentSecurityEvents(),
alerts: await this.getActiveSecurityAlerts(),
trends: await this.getSecurityTrends(),
};
} catch (error) {
this.logger.error('Failed to generate security dashboard', error);
throw error;
}
}
/**
* 计算综合安全评分
*/
private calculateOverallSecurityScore(assessmentData: any): number {
const weights = {
securityAnalysis: 0.3,
vulnerabilityScan: 0.4,
accessPatternAnalysis: 0.2,
threatDetection: 0.1,
};
let totalScore = 0;
let totalWeight = 0;
// 安全分析评分
if (assessmentData.securityAnalysis) {
const categories = assessmentData.securityAnalysis.categories as Record<
string,
{ score: number }
>;
const avgCategoryScore =
Object.values(categories).reduce(
(sum: number, category) => sum + category.score,
0,
) / 4;
totalScore += avgCategoryScore * weights.securityAnalysis;
totalWeight += weights.securityAnalysis;
}
// 漏洞扫描评分
if (assessmentData.vulnerabilityScan) {
const vulnScore = this.calculateVulnerabilityScore(
assessmentData.vulnerabilityScan,
);
totalScore += vulnScore * weights.vulnerabilityScan;
totalWeight += weights.vulnerabilityScan;
}
// 访问模式评分
if (assessmentData.accessPatternAnalysis) {
const accessScore = this.calculateAccessScore(
assessmentData.accessPatternAnalysis,
);
totalScore += accessScore * weights.accessPatternAnalysis;
totalWeight += weights.accessPatternAnalysis;
}
// 威胁检测评分
if (assessmentData.threatDetection) {
const threatScore = this.calculateThreatScore(
assessmentData.threatDetection,
);
totalScore += threatScore * weights.threatDetection;
totalWeight += weights.threatDetection;
}
return totalWeight > 0 ? Math.round(totalScore / totalWeight) : 0;
}
/**
* 计算漏洞评分
*/
private calculateVulnerabilityScore(vulnerabilityScan: any): number {
const { severityBreakdown } = vulnerabilityScan;
// 根据漏洞严重程度计算扣分
const penalties = {
critical: 25,
high: 15,
medium: 5,
low: 1,
};
const totalPenalty =
severityBreakdown.critical * penalties.critical +
severityBreakdown.high * penalties.high +
severityBreakdown.medium * penalties.medium +
severityBreakdown.low * penalties.low;
return Math.max(0, 100 - totalPenalty);
}
/**
* 计算访问评分
*/
private calculateAccessScore(accessAnalysis: any): number {
const baseScore = 100;
const { suspiciousActivities, blockedAttempts, anomalies } = accessAnalysis;
// 根据可疑活动扣分
const penalty =
suspiciousActivities * 2 + blockedAttempts * 5 + anomalies.length * 3;
return Math.max(0, baseScore - penalty);
}
/**
* 计算威胁评分
*/
private calculateThreatScore(threatDetection: any): number {
const baseScore = 100;
const { threatsDetected, riskLevel } = threatDetection;
const riskPenalties = {
low: 1,
medium: 5,
high: 15,
critical: 30,
};
const penalty = threatsDetected * riskPenalties[riskLevel];
return Math.max(0, baseScore - penalty);
}
/**
* 确定风险等级
*/
private determineRiskLevel(
score: number,
): 'low' | 'medium' | 'high' | 'critical' {
if (score >= 90) return 'low';
if (score >= 70) return 'medium';
if (score >= 50) return 'high';
return 'critical';
}
/**
* 生成安全建议
*/
private generateSecurityRecommendations(assessmentData: any): string[] {
const recommendations: string[] = [];
// 基于安全分析的建议
if (assessmentData.securityAnalysis?.recommendations) {
recommendations.push(...assessmentData.securityAnalysis.recommendations);
}
// 基于漏洞扫描的建议
if (assessmentData.vulnerabilityScan?.recommendations) {
recommendations.push(...assessmentData.vulnerabilityScan.recommendations);
}
// 基于威胁检测的建议
if (assessmentData.threatDetection?.threatsDetected > 0) {
recommendations.push(
'Implement enhanced threat monitoring and response procedures',
);
}
return [...new Set(recommendations)]; // 去重
}
/**
* 处理关键安全事件
*/
private async handleCriticalSecurityEvent(
event: SecurityEvent,
): Promise<string[]> {
const actions = [
'immediate_alert',
'block_source',
'escalate_to_admin',
'create_incident',
];
// 统一记录到审计服务
await this.auditService.logAuditEvent({
type: 'security_critical_event',
userId: event.metadata?.userId,
timestamp: Date.now(),
description: `Critical security event: ${event.type}`,
metadata: { event },
});
// 执行紧急响应措施
await this.executeEmergencyResponse(event);
return actions;
}
/**
* 处理高级安全事件
*/
private async handleHighSecurityEvent(
event: SecurityEvent,
): Promise<string[]> {
const actions = [
'alert_security_team',
'increase_monitoring',
'log_detailed_info',
];
return actions;
}
/**
* 处理中级安全事件
*/
private async handleMediumSecurityEvent(
event: SecurityEvent,
): Promise<string[]> {
const actions = ['log_event', 'monitor_source', 'update_metrics'];
return actions;
}
/**
* 处理低级安全事件
*/
private async handleLowSecurityEvent(
event: SecurityEvent,
): Promise<string[]> {
const actions = ['log_event', 'update_statistics'];
return actions;
}
/**
* 执行紧急响应
*/
private async executeEmergencyResponse(event: SecurityEvent): Promise<void> {
this.logger.error(`CRITICAL SECURITY EVENT: ${event.type}`, event);
// 这里应该实现紧急响应逻辑
// 例如发送告警、阻止IP、通知管理员等
}
/**
* 获取最近威胁
*/
private async getRecentThreats(): Promise<any[]> {
// 模拟获取最近威胁数据
return [];
}
/**
* 获取漏洞统计
*/
private async getVulnerabilityStatistics(): Promise<{ total: number }> {
// 模拟获取漏洞统计
return { total: 0 };
}
/**
* 获取访问统计
*/
private async getAccessStatistics(): Promise<{ violations: number }> {
// 模拟获取访问统计
return { violations: 0 };
}
/**
* 获取当前安全评分
*/
private async getCurrentSecurityScore(): Promise<number> {
// 模拟获取当前安全评分
return 85;
}
/**
* 确定整体安全状态
*/
private determineOverallSecurityStatus(): 'secure' | 'warning' | 'critical' {
// 模拟安全状态判断
return 'secure';
}
/**
* 获取最近安全事件
*/
private async getRecentSecurityEvents(): Promise<SecurityEvent[]> {
// 模拟获取最近安全事件
return [];
}
/**
* 获取活跃安全告警
*/
private async getActiveSecurityAlerts(): Promise<SecurityAlert[]> {
// 模拟获取活跃告警
return [];
}
/**
* 获取安全趋势
*/
private async getSecurityTrends(): Promise<SecurityTrend[]> {
// 模拟获取安全趋势
return [];
}
}
// 类型定义
export interface SecurityAssessmentResult {
timestamp: number;
duration: number;
overallScore: number;
riskLevel: 'low' | 'medium' | 'high' | 'critical';
components: {
securityAnalysis: any;
vulnerabilityScan: any;
accessPatternAnalysis: any;
threatDetection: any;
};
recommendations: string[];
nextAssessmentTime: number;
}
export interface PolicyValidationResult {
policyName: string;
valid: boolean;
reason: string;
actions: string[];
timestamp: number;
}
export interface SecurityEvent {
id: string;
type: string;
severity: 'low' | 'medium' | 'high' | 'critical';
source: string;
description: string;
timestamp: number;
metadata?: Record<string, any>;
}
export interface SecurityEventResponse {
eventId: string;
handled: boolean;
actions: string[];
timestamp: number;
error?: string;
}
export interface SecurityDashboard {
timestamp: number;
status: 'secure' | 'warning' | 'critical';
metrics: {
threatsDetected: number;
vulnerabilitiesFound: number;
accessViolations: number;
securityScore: number;
};
recentEvents: SecurityEvent[];
alerts: SecurityAlert[];
trends: SecurityTrend[];
}
export interface SecurityAlert {
id: string;
type: string;
severity: 'low' | 'medium' | 'high' | 'critical';
message: string;
timestamp: number;
acknowledged: boolean;
}
export interface SecurityTrend {
metric: string;
values: { timestamp: number; value: number }[];
trend: 'increasing' | 'decreasing' | 'stable';
}

View File

@@ -0,0 +1,53 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { EventBus } from '@wwjCommon/events/event-bus';
import { SecurityAnalyzer } from '../analyzers/security.analyzer';
import { VulnerabilityDetector } from '../detectors/vulnerability.detector';
import { AccessProtector } from '../protectors/access.protector';
import { AiSecurityService } from './ai-security.service';
@Injectable()
export class SafeReadyService implements OnModuleInit {
private readonly logger = new Logger(SafeReadyService.name);
constructor(
private readonly eventBus: EventBus,
private readonly config: ConfigService,
private readonly securityAnalyzer: SecurityAnalyzer,
private readonly vulnerabilityDetector: VulnerabilityDetector,
private readonly accessProtector: AccessProtector,
private readonly aiSecurityService: AiSecurityService,
) {}
async onModuleInit() {
const enabled =
(this.config.get<string>('AI_SAFE_ENABLED') ?? 'true') === 'true';
let currentState: 'ready' | 'unavailable' = 'unavailable';
try {
if (enabled) {
// 轻量健康检查:核心组件是否注入到位
const componentsOk = [
this.securityAnalyzer,
this.vulnerabilityDetector,
this.accessProtector,
this.aiSecurityService,
].every((c) => !!c);
currentState = componentsOk ? 'ready' : 'unavailable';
}
} catch (err) {
this.logger.warn(
`AI Safe readiness check failed: ${err instanceof Error ? err.message : String(err)}`,
);
currentState = 'unavailable';
}
this.eventBus.emit('module.state.changed', {
module: 'ai.safe',
previousState: 'initializing',
currentState,
meta: { enabled },
});
}
}

View File

@@ -0,0 +1,837 @@
import { Injectable, Logger } from '@nestjs/common';
/**
* Performance Analyzer - 性能分析器
*
* 职责:
* - 分析系统性能指标
* - 识别性能瓶颈
* - 生成性能报告
* - 提供优化建议
*/
@Injectable()
export class PerformanceAnalyzer {
private readonly logger = new Logger(PerformanceAnalyzer.name);
private readonly performanceMetrics: PerformanceMetric[] = [];
private readonly analysisHistory: PerformanceAnalysis[] = [];
constructor() {
this.initializeMetrics();
}
/**
* 执行性能分析
*/
async analyzePerformance(
options: AnalysisOptions = {},
): Promise<PerformanceAnalysis> {
this.logger.log('Starting performance analysis');
const startTime = Date.now();
// 收集当前性能指标
const currentMetrics = await this.collectCurrentMetrics();
// 分析CPU性能
const cpuAnalysis = await this.analyzeCpuPerformance(currentMetrics);
// 分析内存性能
const memoryAnalysis = await this.analyzeMemoryPerformance(currentMetrics);
// 分析I/O性能
const ioAnalysis = await this.analyzeIoPerformance(currentMetrics);
// 分析网络性能
const networkAnalysis =
await this.analyzeNetworkPerformance(currentMetrics);
// 分析数据库性能
const databaseAnalysis =
await this.analyzeDatabasePerformance(currentMetrics);
// 分析应用性能
const applicationAnalysis =
await this.analyzeApplicationPerformance(currentMetrics);
// 识别性能瓶颈
const bottlenecks = this.identifyBottlenecks([
cpuAnalysis,
memoryAnalysis,
ioAnalysis,
networkAnalysis,
databaseAnalysis,
applicationAnalysis,
]);
// 生成优化建议
const recommendations =
this.generateOptimizationRecommendations(bottlenecks);
// 计算整体性能评分
const overallScore = this.calculateOverallScore([
cpuAnalysis,
memoryAnalysis,
ioAnalysis,
networkAnalysis,
databaseAnalysis,
applicationAnalysis,
]);
const analysis: PerformanceAnalysis = {
id: this.generateAnalysisId(),
timestamp: Date.now(),
duration: Date.now() - startTime,
overallScore,
status: this.determinePerformanceStatus(overallScore),
metrics: currentMetrics,
summary: {
averageResponseTime: currentMetrics.response_time || 0,
requestsPerSecond: currentMetrics.throughput || 0,
errorRate: currentMetrics.error_rate || 0,
},
analyses: {
cpu: cpuAnalysis,
memory: memoryAnalysis,
io: ioAnalysis,
network: networkAnalysis,
database: databaseAnalysis,
application: applicationAnalysis,
},
bottlenecks,
recommendations,
trends: await this.analyzeTrends(),
};
// 保存分析结果
this.analysisHistory.push(analysis);
// 保持最近100次分析记录
if (this.analysisHistory.length > 100) {
this.analysisHistory.splice(0, this.analysisHistory.length - 100);
}
this.logger.log(`Performance analysis completed in ${analysis.duration}ms`);
return analysis;
}
/**
* 获取性能趋势
*/
async getPerformanceTrends(
period: 'hour' | 'day' | 'week' | 'month' = 'day',
): Promise<PerformanceTrend[]> {
this.logger.debug(`Getting performance trends for ${period}`);
const now = Date.now();
const periodMs = this.getPeriodInMs(period);
const startTime = now - periodMs;
const relevantAnalyses = this.analysisHistory.filter(
(analysis) => analysis.timestamp >= startTime,
);
if (relevantAnalyses.length === 0) {
return [];
}
// 按时间分组数据
const groupedData = this.groupAnalysesByTime(relevantAnalyses, period);
// 生成趋势数据
const trends: PerformanceTrend[] = [
{
metric: 'overall_score',
name: 'Overall Performance Score',
values: groupedData.map((group) => ({
timestamp: group.timestamp,
value: this.calculateAverageScore(group.analyses, 'overallScore'),
})),
trend: this.calculateTrendDirection(groupedData, 'overallScore'),
},
{
metric: 'cpu_usage',
name: 'CPU Usage',
values: groupedData.map((group) => ({
timestamp: group.timestamp,
value: this.calculateAverageScore(
group.analyses,
'analyses.cpu.score',
),
})),
trend: this.calculateTrendDirection(groupedData, 'analyses.cpu.score'),
},
{
metric: 'memory_usage',
name: 'Memory Usage',
values: groupedData.map((group) => ({
timestamp: group.timestamp,
value: this.calculateAverageScore(
group.analyses,
'analyses.memory.score',
),
})),
trend: this.calculateTrendDirection(
groupedData,
'analyses.memory.score',
),
},
];
return trends;
}
/**
* 获取性能基准
*/
async getPerformanceBenchmarks(): Promise<PerformanceBenchmark[]> {
this.logger.debug('Getting performance benchmarks');
return [
{
metric: 'response_time',
name: 'API Response Time',
target: 200, // ms
warning: 500,
critical: 1000,
unit: 'ms',
},
{
metric: 'cpu_usage',
name: 'CPU Usage',
target: 70, // %
warning: 85,
critical: 95,
unit: '%',
},
{
metric: 'memory_usage',
name: 'Memory Usage',
target: 80, // %
warning: 90,
critical: 95,
unit: '%',
},
{
metric: 'database_query_time',
name: 'Database Query Time',
target: 100, // ms
warning: 300,
critical: 1000,
unit: 'ms',
},
];
}
/**
* 比较性能分析结果
*/
async comparePerformance(
analysisId1: string,
analysisId2: string,
): Promise<PerformanceComparison> {
this.logger.debug(
`Comparing performance analyses: ${analysisId1} vs ${analysisId2}`,
);
const analysis1 = this.analysisHistory.find((a) => a.id === analysisId1);
const analysis2 = this.analysisHistory.find((a) => a.id === analysisId2);
if (!analysis1 || !analysis2) {
throw new Error('One or both analyses not found');
}
const comparison: PerformanceComparison = {
baseline: analysis1,
comparison: analysis2,
differences: {
overallScore: analysis2.overallScore - analysis1.overallScore,
cpu: analysis2.analyses.cpu.score - analysis1.analyses.cpu.score,
memory:
analysis2.analyses.memory.score - analysis1.analyses.memory.score,
io: analysis2.analyses.io.score - analysis1.analyses.io.score,
network:
analysis2.analyses.network.score - analysis1.analyses.network.score,
database:
analysis2.analyses.database.score - analysis1.analyses.database.score,
application:
analysis2.analyses.application.score -
analysis1.analyses.application.score,
},
improvements: [],
regressions: [],
};
// 识别改进和退化
Object.entries(comparison.differences).forEach(([metric, diff]) => {
if (diff > 5) {
// 改进超过5分
comparison.improvements.push({
metric,
improvement: diff,
description: `${metric} improved by ${diff.toFixed(1)} points`,
});
} else if (diff < -5) {
// 退化超过5分
comparison.regressions.push({
metric,
regression: Math.abs(diff),
description: `${metric} regressed by ${Math.abs(diff).toFixed(1)} points`,
});
}
});
return comparison;
}
/**
* 初始化性能指标
*/
private initializeMetrics(): void {
this.performanceMetrics.push(
{ name: 'cpu_usage', type: 'percentage', threshold: 80 },
{ name: 'memory_usage', type: 'percentage', threshold: 85 },
{ name: 'disk_io', type: 'rate', threshold: 1000 },
{ name: 'network_io', type: 'rate', threshold: 100 },
{ name: 'response_time', type: 'duration', threshold: 500 },
{ name: 'throughput', type: 'rate', threshold: 1000 },
{ name: 'error_rate', type: 'percentage', threshold: 5 },
);
}
/**
* 收集当前性能指标
*/
private async collectCurrentMetrics(): Promise<Record<string, number>> {
// 模拟收集性能指标
return {
cpu_usage: Math.random() * 100,
memory_usage: Math.random() * 100,
disk_io: Math.random() * 2000,
network_io: Math.random() * 200,
response_time: Math.random() * 1000,
throughput: Math.random() * 2000,
error_rate: Math.random() * 10,
active_connections: Math.floor(Math.random() * 1000),
queue_length: Math.floor(Math.random() * 100),
};
}
/**
* 分析CPU性能
*/
private async analyzeCpuPerformance(
metrics: Record<string, number>,
): Promise<ComponentAnalysis> {
const cpuUsage = metrics.cpu_usage || 0;
let score = 100;
let status: 'excellent' | 'good' | 'warning' | 'critical' = 'excellent';
const issues: string[] = [];
const recommendations: string[] = [];
if (cpuUsage > 95) {
score = 20;
status = 'critical';
issues.push('CPU usage is critically high');
recommendations.push(
'Scale horizontally or optimize CPU-intensive operations',
);
} else if (cpuUsage > 85) {
score = 50;
status = 'warning';
issues.push('CPU usage is high');
recommendations.push('Monitor CPU usage and consider optimization');
} else if (cpuUsage > 70) {
score = 75;
status = 'good';
}
return {
component: 'cpu',
score,
status,
metrics: { usage: cpuUsage },
issues,
recommendations,
};
}
/**
* 分析内存性能
*/
private async analyzeMemoryPerformance(
metrics: Record<string, number>,
): Promise<ComponentAnalysis> {
const memoryUsage = metrics.memory_usage || 0;
let score = 100;
let status: 'excellent' | 'good' | 'warning' | 'critical' = 'excellent';
const issues: string[] = [];
const recommendations: string[] = [];
if (memoryUsage > 95) {
score = 20;
status = 'critical';
issues.push('Memory usage is critically high');
recommendations.push('Increase memory or optimize memory usage');
} else if (memoryUsage > 90) {
score = 50;
status = 'warning';
issues.push('Memory usage is high');
recommendations.push('Monitor memory usage and check for memory leaks');
} else if (memoryUsage > 80) {
score = 75;
status = 'good';
}
return {
component: 'memory',
score,
status,
metrics: { usage: memoryUsage },
issues,
recommendations,
};
}
/**
* 分析I/O性能
*/
private async analyzeIoPerformance(
metrics: Record<string, number>,
): Promise<ComponentAnalysis> {
const diskIo = metrics.disk_io || 0;
let score = 100;
let status: 'excellent' | 'good' | 'warning' | 'critical' = 'excellent';
const issues: string[] = [];
const recommendations: string[] = [];
if (diskIo > 2000) {
score = 50;
status = 'warning';
issues.push('Disk I/O is high');
recommendations.push('Optimize disk operations or use faster storage');
} else if (diskIo > 1500) {
score = 75;
status = 'good';
}
return {
component: 'io',
score,
status,
metrics: { disk_io: diskIo },
issues,
recommendations,
};
}
/**
* 分析网络性能
*/
private async analyzeNetworkPerformance(
metrics: Record<string, number>,
): Promise<ComponentAnalysis> {
const networkIo = metrics.network_io || 0;
let score = 100;
let status: 'excellent' | 'good' | 'warning' | 'critical' = 'excellent';
const issues: string[] = [];
const recommendations: string[] = [];
if (networkIo > 150) {
score = 50;
status = 'warning';
issues.push('Network I/O is high');
recommendations.push('Optimize network operations or increase bandwidth');
} else if (networkIo > 100) {
score = 75;
status = 'good';
}
return {
component: 'network',
score,
status,
metrics: { network_io: networkIo },
issues,
recommendations,
};
}
/**
* 分析数据库性能
*/
private async analyzeDatabasePerformance(
metrics: Record<string, number>,
): Promise<ComponentAnalysis> {
const responseTime = metrics.response_time || 0;
const activeConnections = metrics.active_connections || 0;
let score = 100;
let status: 'excellent' | 'good' | 'warning' | 'critical' = 'excellent';
const issues: string[] = [];
const recommendations: string[] = [];
if (responseTime > 1000) {
score = 20;
status = 'critical';
issues.push('Database response time is critically slow');
recommendations.push('Optimize queries and add indexes');
} else if (responseTime > 500) {
score = 50;
status = 'warning';
issues.push('Database response time is slow');
recommendations.push('Review and optimize slow queries');
} else if (responseTime > 200) {
score = 75;
status = 'good';
}
if (activeConnections > 800) {
score = Math.min(score, 50);
status = status === 'excellent' ? 'warning' : status;
issues.push('High number of active database connections');
recommendations.push('Implement connection pooling');
}
return {
component: 'database',
score,
status,
metrics: {
response_time: responseTime,
active_connections: activeConnections,
},
issues,
recommendations,
};
}
/**
* 分析应用性能
*/
private async analyzeApplicationPerformance(
metrics: Record<string, number>,
): Promise<ComponentAnalysis> {
const throughput = metrics.throughput || 0;
const queueLength = metrics.queue_length || 0;
let score = 100;
let status: 'excellent' | 'good' | 'warning' | 'critical' = 'excellent';
const issues: string[] = [];
const recommendations: string[] = [];
if (throughput < 500) {
score = 50;
status = 'warning';
issues.push('Application throughput is low');
recommendations.push('Optimize application code and algorithms');
} else if (throughput < 1000) {
score = 75;
status = 'good';
}
if (queueLength > 50) {
score = Math.min(score, 50);
status = status === 'excellent' ? 'warning' : status;
issues.push('High queue length indicates processing bottleneck');
recommendations.push(
'Scale processing capacity or optimize queue handling',
);
}
return {
component: 'application',
score,
status,
metrics: { throughput, queue_length: queueLength },
issues,
recommendations,
};
}
/**
* 识别性能瓶颈
*/
private identifyBottlenecks(
analyses: ComponentAnalysis[],
): PerformanceBottleneck[] {
const bottlenecks: PerformanceBottleneck[] = [];
analyses.forEach((analysis) => {
if (analysis.status === 'critical' || analysis.status === 'warning') {
bottlenecks.push({
component: analysis.component,
severity: analysis.status === 'critical' ? 'critical' : 'high',
description: `${analysis.component} performance is ${analysis.status}`,
impact: analysis.score < 30 ? 'high' : 'medium',
recommendations: analysis.recommendations,
});
}
});
return bottlenecks;
}
/**
* 生成优化建议
*/
private generateOptimizationRecommendations(
bottlenecks: PerformanceBottleneck[],
): OptimizationRecommendation[] {
const recommendations: OptimizationRecommendation[] = [];
bottlenecks.forEach((bottleneck) => {
bottleneck.recommendations.forEach((rec) => {
recommendations.push({
category: bottleneck.component,
priority: bottleneck.severity === 'critical' ? 'high' : 'medium',
description: rec,
estimatedImpact: bottleneck.impact,
effort: 'medium', // 简化处理
});
});
});
return recommendations;
}
/**
* 计算整体性能评分
*/
private calculateOverallScore(analyses: ComponentAnalysis[]): number {
if (analyses.length === 0) return 100;
const totalScore = analyses.reduce(
(sum, analysis) => sum + analysis.score,
0,
);
return Math.round(totalScore / analyses.length);
}
/**
* 确定性能状态
*/
private determinePerformanceStatus(
score: number,
): 'excellent' | 'good' | 'warning' | 'critical' {
if (score >= 90) return 'excellent';
if (score >= 75) return 'good';
if (score >= 50) return 'warning';
return 'critical';
}
/**
* 分析趋势
*/
private async analyzeTrends(): Promise<PerformanceTrend[]> {
// 简化的趋势分析
return this.getPerformanceTrends('day');
}
/**
* 生成分析ID
*/
private generateAnalysisId(): string {
return `PERF_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 获取时间段毫秒数
*/
private getPeriodInMs(period: 'hour' | 'day' | 'week' | 'month'): number {
const periods = {
hour: 60 * 60 * 1000,
day: 24 * 60 * 60 * 1000,
week: 7 * 24 * 60 * 60 * 1000,
month: 30 * 24 * 60 * 60 * 1000,
};
return periods[period];
}
/**
* 按时间分组分析结果
*/
private groupAnalysesByTime(
analyses: PerformanceAnalysis[],
period: 'hour' | 'day' | 'week' | 'month',
): GroupedAnalysis[] {
const groups: Record<string, PerformanceAnalysis[]> = {};
analyses.forEach((analysis) => {
const date = new Date(analysis.timestamp);
let key: string;
switch (period) {
case 'hour':
key = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-${date.getHours()}`;
break;
case 'day':
key = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
break;
case 'week':
const weekStart = new Date(date);
weekStart.setDate(date.getDate() - date.getDay());
key = `${weekStart.getFullYear()}-${weekStart.getMonth()}-${weekStart.getDate()}`;
break;
case 'month':
key = `${date.getFullYear()}-${date.getMonth()}`;
break;
}
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(analysis);
});
return Object.entries(groups).map(([key, groupAnalyses]) => ({
timestamp: groupAnalyses[0].timestamp,
analyses: groupAnalyses,
}));
}
/**
* 计算平均分数
*/
private calculateAverageScore(
analyses: PerformanceAnalysis[],
path: string,
): number {
const values = analyses
.map((analysis) => this.getNestedValue(analysis, path))
.filter((v) => v !== undefined);
return values.length > 0
? values.reduce((sum, val) => sum + val, 0) / values.length
: 0;
}
/**
* 获取嵌套值
*/
private getNestedValue(obj: any, path: string): number | undefined {
return path.split('.').reduce((current, key) => current?.[key], obj);
}
/**
* 计算趋势方向
*/
private calculateTrendDirection(
groupedData: GroupedAnalysis[],
path: string,
): 'increasing' | 'decreasing' | 'stable' {
if (groupedData.length < 2) return 'stable';
const values = groupedData.map((group) =>
this.calculateAverageScore(group.analyses, path),
);
const first = values[0];
const last = values[values.length - 1];
const diff = last - first;
if (Math.abs(diff) < 5) return 'stable';
return diff > 0 ? 'increasing' : 'decreasing';
}
}
// 类型定义
export interface PerformanceMetric {
name: string;
type: 'percentage' | 'rate' | 'duration' | 'count';
threshold: number;
}
export interface AnalysisOptions {
includeHistory?: boolean;
detailedAnalysis?: boolean;
}
export interface PerformanceAnalysis {
id: string;
timestamp: number;
duration: number;
overallScore: number;
status: 'excellent' | 'good' | 'warning' | 'critical';
metrics: Record<string, number>;
summary: {
averageResponseTime: number;
requestsPerSecond: number;
errorRate: number;
};
analyses: {
cpu: ComponentAnalysis;
memory: ComponentAnalysis;
io: ComponentAnalysis;
network: ComponentAnalysis;
database: ComponentAnalysis;
application: ComponentAnalysis;
};
bottlenecks: PerformanceBottleneck[];
recommendations: OptimizationRecommendation[];
trends: PerformanceTrend[];
}
export interface ComponentAnalysis {
component: string;
score: number;
status: 'excellent' | 'good' | 'warning' | 'critical';
metrics: Record<string, number>;
issues: string[];
recommendations: string[];
}
export interface PerformanceBottleneck {
component: string;
severity: 'low' | 'medium' | 'high' | 'critical';
description: string;
impact: 'low' | 'medium' | 'high';
recommendations: string[];
}
export interface OptimizationRecommendation {
category: string;
priority: 'low' | 'medium' | 'high';
description: string;
estimatedImpact: 'low' | 'medium' | 'high';
effort: 'low' | 'medium' | 'high';
}
export interface PerformanceTrend {
metric: string;
name: string;
values: { timestamp: number; value: number }[];
trend: 'increasing' | 'decreasing' | 'stable';
}
export interface PerformanceBenchmark {
metric: string;
name: string;
target: number;
warning: number;
critical: number;
unit: string;
}
export interface PerformanceComparison {
baseline: PerformanceAnalysis;
comparison: PerformanceAnalysis;
differences: Record<string, number>;
improvements: Array<{
metric: string;
improvement: number;
description: string;
}>;
regressions: Array<{
metric: string;
regression: number;
description: string;
}>;
}
interface GroupedAnalysis {
timestamp: number;
analyses: PerformanceAnalysis[];
}

View File

@@ -0,0 +1,695 @@
import {
Injectable,
Logger,
OnModuleInit,
OnModuleDestroy,
} from '@nestjs/common';
import { EventBus } from '@wwjCommon/events/event-bus';
/**
* Resource Monitor - 资源监控器
*
* 职责:
* - 实时监控系统资源使用情况
* - 检测资源使用异常
* - 发送资源告警
* - 收集资源使用历史数据
*/
@Injectable()
export class ResourceMonitor implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(ResourceMonitor.name);
private monitoringInterval: NodeJS.Timeout | null = null;
private readonly resourceHistory: ResourceSnapshot[] = [];
private readonly alertThresholds: ResourceThresholds;
private readonly monitoringConfig: MonitoringConfig;
constructor(private readonly eventBus: EventBus) {
this.alertThresholds = this.getDefaultThresholds();
this.monitoringConfig = this.getDefaultConfig();
}
async onModuleInit() {
this.logger.log('Initializing Resource Monitor');
await this.startMonitoring();
}
async onModuleDestroy() {
this.logger.log('Destroying Resource Monitor');
await this.stopMonitoring();
}
/**
* 开始监控
*/
async startMonitoring(): Promise<void> {
if (this.monitoringInterval) {
this.logger.warn('Monitoring is already running');
return;
}
this.logger.log('Starting resource monitoring');
this.monitoringInterval = setInterval(
() => this.collectResourceSnapshot(),
this.monitoringConfig.interval,
);
// 立即执行一次
await this.collectResourceSnapshot();
}
/**
* 停止监控
*/
async stopMonitoring(): Promise<void> {
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
this.monitoringInterval = null;
this.logger.log('Resource monitoring stopped');
}
}
/**
* 获取当前资源状态
*/
async getCurrentResourceStatus(): Promise<ResourceStatus> {
this.logger.debug('Getting current resource status');
const snapshot = await this.captureResourceSnapshot();
const alerts = this.checkAlerts(snapshot);
const trends = this.calculateTrends();
return {
timestamp: Date.now(),
snapshot,
alerts,
trends,
health: this.assessResourceHealth(snapshot, alerts),
};
}
/**
* 兼容别名:获取当前资源状态
* 为旧代码调用提供兼容,内部转发到 getCurrentResourceStatus
*/
async getCurrentStatus(): Promise<ResourceStatus> {
return this.getCurrentResourceStatus();
}
/**
* 获取资源历史数据
*/
getResourceHistory(
period: 'hour' | 'day' | 'week' = 'hour',
): ResourceSnapshot[] {
const now = Date.now();
const periodMs = this.getPeriodInMs(period);
const startTime = now - periodMs;
return this.resourceHistory.filter(
(snapshot) => snapshot.timestamp >= startTime,
);
}
/**
* 设置告警阈值
*/
setAlertThresholds(thresholds: Partial<ResourceThresholds>): void {
this.logger.log('Updating alert thresholds', thresholds);
Object.assign(this.alertThresholds, thresholds);
}
/**
* 获取资源使用统计
*/
getResourceStatistics(
period: 'hour' | 'day' | 'week' = 'day',
): ResourceStatistics {
const history = this.getResourceHistory(period);
if (history.length === 0) {
return this.getEmptyStatistics();
}
const cpuValues = history.map((h) => h.cpu.usage);
const memoryValues = history.map((h) => h.memory.usage);
const diskValues = history.map((h) => h.disk.usage);
const networkInValues = history.map((h) => h.network.bytesIn);
const networkOutValues = history.map((h) => h.network.bytesOut);
return {
period,
sampleCount: history.length,
cpu: {
min: Math.min(...cpuValues),
max: Math.max(...cpuValues),
avg: this.calculateAverage(cpuValues),
current: cpuValues[cpuValues.length - 1],
},
memory: {
min: Math.min(...memoryValues),
max: Math.max(...memoryValues),
avg: this.calculateAverage(memoryValues),
current: memoryValues[memoryValues.length - 1],
},
disk: {
min: Math.min(...diskValues),
max: Math.max(...diskValues),
avg: this.calculateAverage(diskValues),
current: diskValues[diskValues.length - 1],
},
network: {
bytesInTotal: networkInValues.reduce((sum, val) => sum + val, 0),
bytesOutTotal: networkOutValues.reduce((sum, val) => sum + val, 0),
avgBytesIn: this.calculateAverage(networkInValues),
avgBytesOut: this.calculateAverage(networkOutValues),
},
};
}
/**
* 预测资源使用趋势
*/
predictResourceTrends(hours: number = 24): ResourcePrediction[] {
this.logger.debug(`Predicting resource trends for next ${hours} hours`);
const history = this.getResourceHistory('day');
if (history.length < 10) {
return []; // 数据不足,无法预测
}
const predictions: ResourcePrediction[] = [];
// 预测CPU使用率
const cpuTrend = this.calculateLinearTrend(history.map((h) => h.cpu.usage));
predictions.push({
resource: 'cpu',
metric: 'usage',
currentValue: history[history.length - 1].cpu.usage,
predictedValue: Math.max(0, Math.min(100, cpuTrend.predict(hours))),
confidence: cpuTrend.confidence,
trend: cpuTrend.direction,
});
// 预测内存使用率
const memoryTrend = this.calculateLinearTrend(
history.map((h) => h.memory.usage),
);
predictions.push({
resource: 'memory',
metric: 'usage',
currentValue: history[history.length - 1].memory.usage,
predictedValue: Math.max(0, Math.min(100, memoryTrend.predict(hours))),
confidence: memoryTrend.confidence,
trend: memoryTrend.direction,
});
// 预测磁盘使用率
const diskTrend = this.calculateLinearTrend(
history.map((h) => h.disk.usage),
);
predictions.push({
resource: 'disk',
metric: 'usage',
currentValue: history[history.length - 1].disk.usage,
predictedValue: Math.max(0, Math.min(100, diskTrend.predict(hours))),
confidence: diskTrend.confidence,
trend: diskTrend.direction,
});
return predictions;
}
/**
* 收集资源快照
*/
private async collectResourceSnapshot(): Promise<void> {
try {
const snapshot = await this.captureResourceSnapshot();
// 保存到历史记录
this.resourceHistory.push(snapshot);
// 保持最近1000个快照
if (this.resourceHistory.length > 1000) {
this.resourceHistory.splice(0, this.resourceHistory.length - 1000);
}
// 检查告警
const alerts = this.checkAlerts(snapshot);
if (alerts.length > 0) {
this.handleAlerts(alerts, snapshot);
}
// 发送监控事件
this.eventBus.emit('resource.snapshot', snapshot);
} catch (error) {
this.logger.error('Failed to collect resource snapshot', error);
}
}
/**
* 捕获资源快照
*/
private async captureResourceSnapshot(): Promise<ResourceSnapshot> {
// 模拟获取系统资源信息
// 在实际实现中这里应该调用系统API获取真实数据
const timestamp = Date.now();
return {
timestamp,
cpu: {
usage: Math.random() * 100,
cores: 8,
loadAverage: [Math.random() * 2, Math.random() * 2, Math.random() * 2],
},
memory: {
usage: Math.random() * 100,
total: 16 * 1024 * 1024 * 1024, // 16GB
used: Math.random() * 16 * 1024 * 1024 * 1024,
free: Math.random() * 16 * 1024 * 1024 * 1024,
cached: Math.random() * 4 * 1024 * 1024 * 1024,
},
disk: {
usage: Math.random() * 100,
total: 1024 * 1024 * 1024 * 1024, // 1TB
used: Math.random() * 1024 * 1024 * 1024 * 1024,
free: Math.random() * 1024 * 1024 * 1024 * 1024,
ioRead: Math.random() * 1000,
ioWrite: Math.random() * 1000,
},
network: {
bytesIn: Math.random() * 1000000,
bytesOut: Math.random() * 1000000,
packetsIn: Math.random() * 10000,
packetsOut: Math.random() * 10000,
errors: Math.random() * 10,
},
processes: {
total: Math.floor(Math.random() * 500) + 100,
running: Math.floor(Math.random() * 50) + 10,
sleeping: Math.floor(Math.random() * 400) + 50,
zombie: Math.floor(Math.random() * 5),
},
};
}
/**
* 检查告警
*/
private checkAlerts(snapshot: ResourceSnapshot): ResourceAlert[] {
const alerts: ResourceAlert[] = [];
// CPU告警
if (snapshot.cpu.usage > this.alertThresholds.cpu.critical) {
alerts.push({
type: 'cpu',
severity: 'critical',
message: `CPU usage is critically high: ${snapshot.cpu.usage.toFixed(1)}%`,
value: snapshot.cpu.usage,
threshold: this.alertThresholds.cpu.critical,
timestamp: snapshot.timestamp,
});
} else if (snapshot.cpu.usage > this.alertThresholds.cpu.warning) {
alerts.push({
type: 'cpu',
severity: 'warning',
message: `CPU usage is high: ${snapshot.cpu.usage.toFixed(1)}%`,
value: snapshot.cpu.usage,
threshold: this.alertThresholds.cpu.warning,
timestamp: snapshot.timestamp,
});
}
// 内存告警
if (snapshot.memory.usage > this.alertThresholds.memory.critical) {
alerts.push({
type: 'memory',
severity: 'critical',
message: `Memory usage is critically high: ${snapshot.memory.usage.toFixed(1)}%`,
value: snapshot.memory.usage,
threshold: this.alertThresholds.memory.critical,
timestamp: snapshot.timestamp,
});
} else if (snapshot.memory.usage > this.alertThresholds.memory.warning) {
alerts.push({
type: 'memory',
severity: 'warning',
message: `Memory usage is high: ${snapshot.memory.usage.toFixed(1)}%`,
value: snapshot.memory.usage,
threshold: this.alertThresholds.memory.warning,
timestamp: snapshot.timestamp,
});
}
// 磁盘告警
if (snapshot.disk.usage > this.alertThresholds.disk.critical) {
alerts.push({
type: 'disk',
severity: 'critical',
message: `Disk usage is critically high: ${snapshot.disk.usage.toFixed(1)}%`,
value: snapshot.disk.usage,
threshold: this.alertThresholds.disk.critical,
timestamp: snapshot.timestamp,
});
} else if (snapshot.disk.usage > this.alertThresholds.disk.warning) {
alerts.push({
type: 'disk',
severity: 'warning',
message: `Disk usage is high: ${snapshot.disk.usage.toFixed(1)}%`,
value: snapshot.disk.usage,
threshold: this.alertThresholds.disk.warning,
timestamp: snapshot.timestamp,
});
}
return alerts;
}
/**
* 处理告警
*/
private handleAlerts(
alerts: ResourceAlert[],
snapshot: ResourceSnapshot,
): void {
alerts.forEach((alert) => {
this.logger.warn(`Resource Alert: ${alert.message}`);
// 发送告警事件
this.eventBus.emit('resource.alert', {
alert,
snapshot,
});
});
}
/**
* 评估资源健康状态
*/
private assessResourceHealth(
snapshot: ResourceSnapshot,
alerts: ResourceAlert[],
): ResourceHealth {
const criticalAlerts = alerts.filter(
(a) => a.severity === 'critical',
).length;
const warningAlerts = alerts.filter((a) => a.severity === 'warning').length;
let status: 'healthy' | 'warning' | 'critical';
let score = 100;
if (criticalAlerts > 0) {
status = 'critical';
score = Math.max(0, 100 - criticalAlerts * 30 - warningAlerts * 10);
} else if (warningAlerts > 0) {
status = 'warning';
score = Math.max(50, 100 - warningAlerts * 15);
} else {
status = 'healthy';
}
return {
status,
score,
criticalAlerts,
warningAlerts,
lastCheck: snapshot.timestamp,
};
}
/**
* 计算趋势
*/
private calculateTrends(): ResourceTrends {
const recentHistory = this.resourceHistory.slice(-20); // 最近20个快照
if (recentHistory.length < 2) {
return {
cpu: 'stable',
memory: 'stable',
disk: 'stable',
network: 'stable',
};
}
const cpuTrend = this.calculateTrendDirection(
recentHistory.map((h) => h.cpu.usage),
);
const memoryTrend = this.calculateTrendDirection(
recentHistory.map((h) => h.memory.usage),
);
const diskTrend = this.calculateTrendDirection(
recentHistory.map((h) => h.disk.usage),
);
const networkTrend = this.calculateTrendDirection(
recentHistory.map((h) => h.network.bytesIn + h.network.bytesOut),
);
return {
cpu: cpuTrend,
memory: memoryTrend,
disk: diskTrend,
network: networkTrend,
};
}
/**
* 计算趋势方向
*/
private calculateTrendDirection(
values: number[],
): 'increasing' | 'decreasing' | 'stable' {
if (values.length < 2) return 'stable';
const first = values[0];
const last = values[values.length - 1];
const diff = ((last - first) / first) * 100;
if (Math.abs(diff) < 5) return 'stable';
return diff > 0 ? 'increasing' : 'decreasing';
}
/**
* 计算线性趋势
*/
private calculateLinearTrend(values: number[]): TrendAnalysis {
if (values.length < 2) {
return {
predict: () => values[0] || 0,
confidence: 0,
direction: 'stable',
};
}
// 简单线性回归
const n = values.length;
const x = Array.from({ length: n }, (_, i) => i);
const y = values;
const sumX = x.reduce((sum, val) => sum + val, 0);
const sumY = y.reduce((sum, val) => sum + val, 0);
const sumXY = x.reduce((sum, val, i) => sum + val * y[i], 0);
const sumXX = x.reduce((sum, val) => sum + val * val, 0);
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
const intercept = (sumY - slope * sumX) / n;
// 计算R²
const yMean = sumY / n;
const ssTotal = y.reduce((sum, val) => sum + Math.pow(val - yMean, 2), 0);
const ssRes = y.reduce((sum, val, i) => {
const predicted = slope * x[i] + intercept;
return sum + Math.pow(val - predicted, 2);
}, 0);
const rSquared = 1 - ssRes / ssTotal;
return {
predict: (hours: number) => slope * (n + hours) + intercept,
confidence: Math.max(0, Math.min(1, rSquared)),
direction:
Math.abs(slope) < 0.1
? 'stable'
: slope > 0
? 'increasing'
: 'decreasing',
};
}
/**
* 计算平均值
*/
private calculateAverage(values: number[]): number {
return values.length > 0
? values.reduce((sum, val) => sum + val, 0) / values.length
: 0;
}
/**
* 获取时间段毫秒数
*/
private getPeriodInMs(period: 'hour' | 'day' | 'week'): number {
const periods = {
hour: 60 * 60 * 1000,
day: 24 * 60 * 60 * 1000,
week: 7 * 24 * 60 * 60 * 1000,
};
return periods[period];
}
/**
* 获取默认阈值
*/
private getDefaultThresholds(): ResourceThresholds {
return {
cpu: { warning: 80, critical: 95 },
memory: { warning: 85, critical: 95 },
disk: { warning: 85, critical: 95 },
network: { warning: 80, critical: 95 },
};
}
/**
* 获取默认配置
*/
private getDefaultConfig(): MonitoringConfig {
return {
interval: 30000, // 30秒
historySize: 1000,
enableAlerts: true,
};
}
/**
* 获取空统计数据
*/
private getEmptyStatistics(): ResourceStatistics {
return {
period: 'day',
sampleCount: 0,
cpu: { min: 0, max: 0, avg: 0, current: 0 },
memory: { min: 0, max: 0, avg: 0, current: 0 },
disk: { min: 0, max: 0, avg: 0, current: 0 },
network: {
bytesInTotal: 0,
bytesOutTotal: 0,
avgBytesIn: 0,
avgBytesOut: 0,
},
};
}
}
// 类型定义
export interface ResourceSnapshot {
timestamp: number;
cpu: {
usage: number; // 百分比
cores: number;
loadAverage: [number, number, number]; // 1分钟、5分钟、15分钟
};
memory: {
usage: number; // 百分比
total: number; // 字节
used: number; // 字节
free: number; // 字节
cached: number; // 字节
};
disk: {
usage: number; // 百分比
total: number; // 字节
used: number; // 字节
free: number; // 字节
ioRead: number; // 每秒读取字节数
ioWrite: number; // 每秒写入字节数
};
network: {
bytesIn: number; // 每秒接收字节数
bytesOut: number; // 每秒发送字节数
packetsIn: number; // 每秒接收包数
packetsOut: number; // 每秒发送包数
errors: number; // 错误数
};
processes: {
total: number;
running: number;
sleeping: number;
zombie: number;
};
}
export interface ResourceThresholds {
cpu: { warning: number; critical: number };
memory: { warning: number; critical: number };
disk: { warning: number; critical: number };
network: { warning: number; critical: number };
}
export interface MonitoringConfig {
interval: number; // 监控间隔(毫秒)
historySize: number; // 历史记录大小
enableAlerts: boolean; // 是否启用告警
}
export interface ResourceAlert {
type: 'cpu' | 'memory' | 'disk' | 'network';
severity: 'warning' | 'critical';
message: string;
value: number;
threshold: number;
timestamp: number;
}
export interface ResourceStatus {
timestamp: number;
snapshot: ResourceSnapshot;
alerts: ResourceAlert[];
trends: ResourceTrends;
health: ResourceHealth;
}
export interface ResourceTrends {
cpu: 'increasing' | 'decreasing' | 'stable';
memory: 'increasing' | 'decreasing' | 'stable';
disk: 'increasing' | 'decreasing' | 'stable';
network: 'increasing' | 'decreasing' | 'stable';
}
export interface ResourceHealth {
status: 'healthy' | 'warning' | 'critical';
score: number; // 0-100
criticalAlerts: number;
warningAlerts: number;
lastCheck: number;
}
export interface ResourceStatistics {
period: 'hour' | 'day' | 'week';
sampleCount: number;
cpu: { min: number; max: number; avg: number; current: number };
memory: { min: number; max: number; avg: number; current: number };
disk: { min: number; max: number; avg: number; current: number };
network: {
bytesInTotal: number;
bytesOutTotal: number;
avgBytesIn: number;
avgBytesOut: number;
};
}
export interface ResourcePrediction {
resource: 'cpu' | 'memory' | 'disk' | 'network';
metric: string;
currentValue: number;
predictedValue: number;
confidence: number; // 0-1
trend: 'increasing' | 'decreasing' | 'stable';
}
interface TrendAnalysis {
predict: (hours: number) => number;
confidence: number;
direction: 'increasing' | 'decreasing' | 'stable';
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,629 @@
import { Injectable, Logger } from '@nestjs/common';
/**
* AI Metrics Service - AI指标服务
*
* 职责:
* - 收集和聚合性能指标
* - 提供指标查询接口
* - 管理指标存储和清理
* - 生成指标报告
*/
@Injectable()
export class AiMetricsService {
private readonly logger = new Logger(AiMetricsService.name);
private readonly metricsStore = new Map<string, MetricData[]>();
private readonly aggregatedMetrics = new Map<string, AggregatedMetric>();
private readonly retentionPeriod = 30 * 24 * 60 * 60 * 1000; // 30天
/**
* 记录指标数据
*/
recordMetric(
name: string,
value: number,
tags: Record<string, string> = {},
): void {
const timestamp = Date.now();
const metric: MetricData = {
name,
value,
timestamp,
tags,
};
// 存储原始指标
if (!this.metricsStore.has(name)) {
this.metricsStore.set(name, []);
}
const metrics = this.metricsStore.get(name)!;
metrics.push(metric);
// 保持数据量在合理范围内
if (metrics.length > 10000) {
metrics.splice(0, metrics.length - 10000);
}
// 更新聚合指标
this.updateAggregatedMetric(name, value, timestamp);
this.logger.debug(`Recorded metric: ${name} = ${value}`);
}
/**
* 批量记录指标
*/
recordMetrics(
metrics: Array<{
name: string;
value: number;
tags?: Record<string, string>;
}>,
): void {
metrics.forEach((metric) => {
this.recordMetric(metric.name, metric.value, metric.tags);
});
}
/**
* 获取指标数据
*/
getMetrics(name: string, options: MetricQueryOptions = {}): MetricData[] {
const metrics = this.metricsStore.get(name) || [];
let filteredMetrics = metrics;
// 时间范围过滤
if (options.startTime || options.endTime) {
filteredMetrics = metrics.filter((metric) => {
if (options.startTime && metric.timestamp < options.startTime)
return false;
if (options.endTime && metric.timestamp > options.endTime) return false;
return true;
});
}
// 标签过滤
if (options.tags) {
filteredMetrics = filteredMetrics.filter((metric) => {
return Object.entries(options.tags!).every(
([key, value]) => metric.tags[key] === value,
);
});
}
// 限制数量
if (options.limit) {
filteredMetrics = filteredMetrics.slice(-options.limit);
}
return filteredMetrics;
}
/**
* 获取聚合指标
*/
getAggregatedMetric(name: string): AggregatedMetric | null {
return this.aggregatedMetrics.get(name) || null;
}
/**
* 获取所有指标名称
*/
getMetricNames(): string[] {
return Array.from(this.metricsStore.keys());
}
/**
* 计算指标统计信息
*/
calculateStatistics(
name: string,
timeRange?: TimeRange,
): MetricStatistics | null {
const metrics = this.getMetrics(name, {
startTime: timeRange?.start,
endTime: timeRange?.end,
});
if (metrics.length === 0) {
return null;
}
const values = metrics.map((m) => m.value);
const sortedValues = [...values].sort((a, b) => a - b);
const sum = values.reduce((acc, val) => acc + val, 0);
const mean = sum / values.length;
const variance =
values.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) /
values.length;
const stdDev = Math.sqrt(variance);
return {
name,
count: values.length,
sum,
mean,
min: sortedValues[0],
max: sortedValues[sortedValues.length - 1],
median: this.calculatePercentile(sortedValues, 50),
p95: this.calculatePercentile(sortedValues, 95),
p99: this.calculatePercentile(sortedValues, 99),
stdDev,
variance,
timeRange: {
start: metrics[0].timestamp,
end: metrics[metrics.length - 1].timestamp,
},
};
}
/**
* 获取指标趋势
*/
getMetricTrend(name: string, interval: number = 60000): MetricTrend[] {
const metrics = this.getMetrics(name);
if (metrics.length === 0) return [];
const trends: MetricTrend[] = [];
const startTime = metrics[0].timestamp;
const endTime = metrics[metrics.length - 1].timestamp;
for (let time = startTime; time <= endTime; time += interval) {
const intervalMetrics = metrics.filter(
(m) => m.timestamp >= time && m.timestamp < time + interval,
);
if (intervalMetrics.length > 0) {
const values = intervalMetrics.map((m) => m.value);
const sum = values.reduce((acc, val) => acc + val, 0);
const avg = sum / values.length;
trends.push({
timestamp: time,
value: avg,
count: values.length,
min: Math.min(...values),
max: Math.max(...values),
});
}
}
return trends;
}
/**
* 生成指标报告
*/
generateMetricsReport(options: ReportOptions = {}): MetricsReport {
const reportTime = Date.now();
const timeRange = options.timeRange || {
start: reportTime - 24 * 60 * 60 * 1000, // 默认24小时
end: reportTime,
};
const metricNames = options.metrics || this.getMetricNames();
const metricReports: MetricReport[] = [];
for (const name of metricNames) {
const statistics = this.calculateStatistics(name, timeRange);
if (statistics) {
const trend = this.getMetricTrend(name, options.trendInterval);
const alerts = this.checkMetricAlerts(name, statistics);
metricReports.push({
name,
statistics,
trend,
alerts,
status: alerts.length > 0 ? 'warning' : 'healthy',
});
}
}
return {
timestamp: reportTime,
timeRange,
metrics: metricReports,
summary: this.generateReportSummary(metricReports),
};
}
/**
* 设置指标告警
*/
setMetricAlert(name: string, alert: MetricAlert): void {
if (!this.metricAlerts.has(name)) {
this.metricAlerts.set(name, []);
}
const alerts = this.metricAlerts.get(name)!;
alerts.push(alert);
this.logger.log(`Set alert for metric ${name}: ${alert.condition}`);
}
/**
* 检查指标告警
*/
checkMetricAlerts(
name: string,
statistics: MetricStatistics,
): MetricAlertResult[] {
const alerts = this.metricAlerts.get(name) || [];
const results: MetricAlertResult[] = [];
for (const alert of alerts) {
const triggered = this.evaluateAlertCondition(alert, statistics);
if (triggered) {
results.push({
alertId: alert.id,
name: alert.name,
severity: alert.severity,
message: alert.message,
triggeredAt: Date.now(),
value: this.getAlertValue(alert, statistics),
threshold: alert.threshold,
});
}
}
return results;
}
/**
* 清理过期指标
*/
cleanupExpiredMetrics(): void {
const cutoffTime = Date.now() - this.retentionPeriod;
let totalCleaned = 0;
for (const [name, metrics] of this.metricsStore.entries()) {
const originalLength = metrics.length;
const filteredMetrics = metrics.filter((m) => m.timestamp > cutoffTime);
if (filteredMetrics.length !== originalLength) {
this.metricsStore.set(name, filteredMetrics);
totalCleaned += originalLength - filteredMetrics.length;
}
}
if (totalCleaned > 0) {
this.logger.log(`Cleaned up ${totalCleaned} expired metrics`);
}
}
/**
* 获取系统指标概览
*/
getSystemMetricsOverview(): SystemMetricsOverview {
const totalMetrics = Array.from(this.metricsStore.values()).reduce(
(sum, metrics) => sum + metrics.length,
0,
);
const activeMetrics = this.getMetricNames().filter((name) => {
const metrics = this.metricsStore.get(name) || [];
const recentMetrics = metrics.filter(
(m) => m.timestamp > Date.now() - 5 * 60 * 1000, // 最近5分钟
);
return recentMetrics.length > 0;
});
const memoryUsage = this.calculateMemoryUsage();
return {
totalMetrics,
activeMetrics: activeMetrics.length,
metricNames: this.getMetricNames(),
memoryUsage,
retentionPeriod: this.retentionPeriod,
lastCleanup: this.lastCleanupTime,
};
}
/**
* 导出指标数据
*/
exportMetrics(options: ExportOptions = {}): ExportedMetrics {
const metricNames = options.metrics || this.getMetricNames();
const exportedData: Record<string, MetricData[]> = {};
for (const name of metricNames) {
exportedData[name] = this.getMetrics(name, {
startTime: options.timeRange?.start,
endTime: options.timeRange?.end,
});
}
return {
timestamp: Date.now(),
timeRange: options.timeRange,
format: options.format || 'json',
data: exportedData,
};
}
/**
* 导入指标数据
*/
importMetrics(exportedMetrics: ExportedMetrics): void {
for (const [name, metrics] of Object.entries(exportedMetrics.data)) {
for (const metric of metrics) {
this.recordMetric(metric.name, metric.value, metric.tags);
}
}
this.logger.log(
`Imported metrics for ${Object.keys(exportedMetrics.data).length} metric types`,
);
}
// 私有方法
private readonly metricAlerts = new Map<string, MetricAlert[]>();
private lastCleanupTime = Date.now();
/**
* 更新聚合指标
*/
private updateAggregatedMetric(
name: string,
value: number,
timestamp: number,
): void {
let aggregated = this.aggregatedMetrics.get(name);
if (!aggregated) {
aggregated = {
name,
count: 0,
sum: 0,
min: value,
max: value,
lastValue: value,
lastUpdate: timestamp,
};
this.aggregatedMetrics.set(name, aggregated);
}
aggregated.count++;
aggregated.sum += value;
aggregated.min = Math.min(aggregated.min, value);
aggregated.max = Math.max(aggregated.max, value);
aggregated.lastValue = value;
aggregated.lastUpdate = timestamp;
}
/**
* 计算百分位数
*/
private calculatePercentile(
sortedValues: number[],
percentile: number,
): number {
const index = (percentile / 100) * (sortedValues.length - 1);
const lower = Math.floor(index);
const upper = Math.ceil(index);
if (lower === upper) {
return sortedValues[lower];
}
const weight = index - lower;
return sortedValues[lower] * (1 - weight) + sortedValues[upper] * weight;
}
/**
* 生成报告摘要
*/
private generateReportSummary(metricReports: MetricReport[]): ReportSummary {
const totalMetrics = metricReports.length;
const healthyMetrics = metricReports.filter(
(m) => m.status === 'healthy',
).length;
const warningMetrics = metricReports.filter(
(m) => m.status === 'warning',
).length;
const totalAlerts = metricReports.reduce(
(sum, m) => sum + m.alerts.length,
0,
);
return {
totalMetrics,
healthyMetrics,
warningMetrics,
totalAlerts,
overallHealth: warningMetrics === 0 ? 'healthy' : 'warning',
};
}
/**
* 评估告警条件
*/
private evaluateAlertCondition(
alert: MetricAlert,
statistics: MetricStatistics,
): boolean {
const value = this.getAlertValue(alert, statistics);
switch (alert.condition) {
case 'greater_than':
return value > alert.threshold;
case 'less_than':
return value < alert.threshold;
case 'equals':
return value === alert.threshold;
case 'not_equals':
return value !== alert.threshold;
default:
return false;
}
}
/**
* 获取告警值
*/
private getAlertValue(
alert: MetricAlert,
statistics: MetricStatistics,
): number {
switch (alert.metric) {
case 'mean':
return statistics.mean;
case 'max':
return statistics.max;
case 'min':
return statistics.min;
case 'p95':
return statistics.p95;
case 'p99':
return statistics.p99;
case 'count':
return statistics.count;
default:
return statistics.mean;
}
}
/**
* 计算内存使用量
*/
private calculateMemoryUsage(): number {
let totalSize = 0;
for (const metrics of this.metricsStore.values()) {
totalSize += metrics.length * 100; // 估算每个指标数据点约100字节
}
return totalSize;
}
}
// 类型定义
export interface MetricData {
name: string;
value: number;
timestamp: number;
tags: Record<string, string>;
}
export interface AggregatedMetric {
name: string;
count: number;
sum: number;
min: number;
max: number;
lastValue: number;
lastUpdate: number;
}
export interface MetricQueryOptions {
startTime?: number;
endTime?: number;
tags?: Record<string, string>;
limit?: number;
}
export interface TimeRange {
start: number;
end: number;
}
export interface MetricStatistics {
name: string;
count: number;
sum: number;
mean: number;
min: number;
max: number;
median: number;
p95: number;
p99: number;
stdDev: number;
variance: number;
timeRange: TimeRange;
}
export interface MetricTrend {
timestamp: number;
value: number;
count: number;
min: number;
max: number;
}
export interface ReportOptions {
timeRange?: TimeRange;
metrics?: string[];
trendInterval?: number;
}
export interface MetricReport {
name: string;
statistics: MetricStatistics;
trend: MetricTrend[];
alerts: MetricAlertResult[];
status: 'healthy' | 'warning' | 'critical';
}
export interface MetricsReport {
timestamp: number;
timeRange: TimeRange;
metrics: MetricReport[];
summary: ReportSummary;
}
export interface ReportSummary {
totalMetrics: number;
healthyMetrics: number;
warningMetrics: number;
totalAlerts: number;
overallHealth: 'healthy' | 'warning' | 'critical';
}
export interface MetricAlert {
id: string;
name: string;
condition: 'greater_than' | 'less_than' | 'equals' | 'not_equals';
threshold: number;
metric: 'mean' | 'max' | 'min' | 'p95' | 'p99' | 'count';
severity: 'low' | 'medium' | 'high' | 'critical';
message: string;
}
export interface MetricAlertResult {
alertId: string;
name: string;
severity: 'low' | 'medium' | 'high' | 'critical';
message: string;
triggeredAt: number;
value: number;
threshold: number;
}
export interface SystemMetricsOverview {
totalMetrics: number;
activeMetrics: number;
metricNames: string[];
memoryUsage: number;
retentionPeriod: number;
lastCleanup: number;
}
export interface ExportOptions {
timeRange?: TimeRange;
metrics?: string[];
format?: 'json' | 'csv' | 'xml';
}
export interface ExportedMetrics {
timestamp: number;
timeRange?: TimeRange;
format: string;
data: Record<string, MetricData[]>;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { EventBus } from '@wwjCommon/events/event-bus';
import { PerformanceAnalyzer } from '../analyzers/performance.analyzer';
import { ResourceMonitor } from '../monitors/resource.monitor';
import { CacheOptimizer } from '../optimizers/cache.optimizer';
import { QueryOptimizer } from '../optimizers/query.optimizer';
@Injectable()
export class TunerReadyService implements OnModuleInit {
private readonly logger = new Logger(TunerReadyService.name);
constructor(
private readonly eventBus: EventBus,
private readonly config: ConfigService,
private readonly performanceAnalyzer: PerformanceAnalyzer,
private readonly resourceMonitor: ResourceMonitor,
private readonly cacheOptimizer: CacheOptimizer,
private readonly queryOptimizer: QueryOptimizer,
) {}
async onModuleInit() {
const enabled =
(this.config.get<string>('AI_TUNER_ENABLED') ?? 'true') === 'true';
let currentState: 'ready' | 'unavailable' = 'unavailable';
try {
if (enabled) {
// 轻量健康检查:核心组件是否注入到位
const componentsOk = [
this.performanceAnalyzer,
this.resourceMonitor,
this.cacheOptimizer,
this.queryOptimizer,
].every((c) => !!c);
currentState = componentsOk ? 'ready' : 'unavailable';
}
} catch (err) {
this.logger.warn(
`AI Tuner readiness check failed: ${err instanceof Error ? err.message : String(err)}`,
);
currentState = 'unavailable';
}
this.eventBus.emit('module.state.changed', {
module: 'ai.tuner',
previousState: 'initializing',
currentState,
meta: { enabled },
});
}
}

View File

@@ -0,0 +1,46 @@
import { Module } from '@nestjs/common';
import { PerformanceAnalyzer } from './analyzers/performance.analyzer';
import { ResourceMonitor } from './monitors/resource.monitor';
import { CacheOptimizer } from './optimizers/cache.optimizer';
import { QueryOptimizer } from './optimizers/query.optimizer';
import { AiTunerService } from './services/ai-tuner.service';
import { AiMetricsService } from './services/ai-metrics.service';
import { TunerReadyService } from './services/tuner-ready.service';
/**
* AI Tuner Module - AI 性能调优模块
*
* 职责:
* - 性能分析和监控
* - 资源使用优化
* - 缓存策略优化
* - 查询性能优化
* - 系统调优建议
*/
@Module({
providers: [
// 分析器
PerformanceAnalyzer,
// 监控器
ResourceMonitor,
// 优化器
CacheOptimizer,
QueryOptimizer,
// 服务
AiTunerService,
AiMetricsService,
TunerReadyService,
],
exports: [
AiTunerService,
AiMetricsService,
PerformanceAnalyzer,
ResourceMonitor,
CacheOptimizer,
QueryOptimizer,
],
})
export class AiTunerModule {}

View File

@@ -1,27 +1,11 @@
import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { AiBootstrapProvider } from './bootstrap/ai-bootstrap.provider';
import { AiSelfHealListener } from './listeners/ai-self-heal.listener';
import { AiRecoveryListener } from './listeners/ai-recovery.listener';
import { AiRecoveryService } from './services/ai-recovery.service';
import { AiController } from './controllers/ai.controller';
import { RateLimitGuard } from '@wwjCommon/http/rate-limit.guard';
import { BootMetricsModule } from '@wwjCommon/metrics/boot-metrics.module';
import { BootCacheModule } from '@wwjCommon/cache/boot-cache.module';
import { BootModule } from '@wwjBoot/wwjcloud-boot.module';
import { AiStrategyService } from './services/ai-strategy.service';
import { AiManagerModule } from './manager/manager.module';
import { AiHealingModule } from './healing/healing.module';
import { AiSafeModule } from './safe/safe.module';
import { AiTunerModule } from './tuner/tuner.module';
@Module({
imports: [BootModule, EventEmitterModule, BootMetricsModule, BootCacheModule],
controllers: [AiController],
providers: [
AiBootstrapProvider,
AiSelfHealListener,
AiRecoveryListener,
AiRecoveryService,
AiStrategyService,
RateLimitGuard,
],
exports: [AiRecoveryService, AiStrategyService],
imports: [AiManagerModule, AiHealingModule, AiSafeModule, AiTunerModule],
exports: [AiManagerModule, AiHealingModule, AiSafeModule, AiTunerModule],
})
export class AiModule {}
export class WwjcloudAiModule {}

View File

@@ -0,0 +1,55 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { EventBus } from '@wwjCommon/events/event-bus';
function readBoolean(
config: ConfigService,
key: string,
fallback = false,
): boolean {
const v = config.get<string | boolean>(key);
if (typeof v === 'boolean') return v;
if (typeof v === 'string')
return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
return fallback;
}
@Injectable()
export class AuthReadyService implements OnModuleInit {
private readonly logger = new Logger(AuthReadyService.name);
constructor(
private readonly config: ConfigService,
private readonly eventBus: EventBus,
) {}
async onModuleInit(): Promise<void> {
const authEnabled = readBoolean(this.config, 'AUTH_ENABLED', false);
const rbacEnabled = readBoolean(this.config, 'RBAC_ENABLED', false);
const authState: 'ready' | 'unavailable' = authEnabled
? 'ready'
: 'unavailable';
const rbacState: 'ready' | 'unavailable' = rbacEnabled
? 'ready'
: 'unavailable';
this.logger.log(
`Auth module init -> AUTH_ENABLED=${authEnabled}, state=${authState}`,
);
this.eventBus.emit('module.state.changed', {
module: 'auth',
previousState: 'initializing',
currentState: authState,
});
this.logger.log(
`RBAC module init -> RBAC_ENABLED=${rbacEnabled}, state=${rbacState}`,
);
this.eventBus.emit('module.state.changed', {
module: 'rbac',
previousState: 'initializing',
currentState: rbacState,
});
}
}

View File

@@ -1,4 +1,9 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthService, UserClaims } from './auth.service';
import { RequestContextService } from '../http/request-context.service';
@@ -36,7 +41,7 @@ export class AuthGuard implements CanActivate {
const claims: UserClaims = this.auth.verifyToken(token);
// 挂载到 request 与请求上下文
(req as any).user = claims;
req.user = claims;
const store = this.ctx.getContext();
if (store) {
if (claims.userId) store.userId = claims.userId;
@@ -47,4 +52,4 @@ export class AuthGuard implements CanActivate {
return true;
}
}
}

View File

@@ -31,26 +31,27 @@ export class AuthService {
issuer: issuer || undefined,
audience: audience || undefined,
});
const obj = typeof payload === 'string' ? JSON.parse(payload) : (payload as any);
const obj =
typeof payload === 'string' ? JSON.parse(payload) : (payload as any);
const claims: UserClaims = {
userId: obj.sub || obj.userId || obj.uid || undefined,
username: obj.name || obj.username || obj.uname || undefined,
roles: Array.isArray(obj.roles)
? obj.roles.map((s: any) => String(s))
: typeof obj.roles === 'string'
? String(obj.roles)
.split(',')
.map((s) => s.trim())
.filter((s) => s.length > 0)
: undefined,
? String(obj.roles)
.split(',')
.map((s) => s.trim())
.filter((s) => s.length > 0)
: undefined,
permissions: Array.isArray(obj.permissions)
? obj.permissions.map((s: any) => String(s))
: typeof obj.permissions === 'string'
? String(obj.permissions)
.split(',')
.map((s) => s.trim())
.filter((s) => s.length > 0)
: undefined,
? String(obj.permissions)
.split(',')
.map((s) => s.trim())
.filter((s) => s.length > 0)
: undefined,
tenantId: obj.tenantId || obj.siteId || undefined,
};
return claims;
@@ -62,7 +63,8 @@ export class AuthService {
private readBoolean(key: string, fallback = false): boolean {
const v = this.config.get<string | boolean>(key);
if (typeof v === 'boolean') return v;
if (typeof v === 'string') return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
if (typeof v === 'string')
return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
return fallback;
}
}
}

View File

@@ -3,11 +3,12 @@ import { ConfigModule } from '@nestjs/config';
import { AuthService } from './auth.service';
import { AuthGuard } from './auth.guard';
import { RbacGuard } from './rbac.guard';
import { AuthReadyService } from './auth-ready.service';
@Global()
@Module({
imports: [ConfigModule],
providers: [AuthService, AuthGuard, RbacGuard],
providers: [AuthService, AuthGuard, RbacGuard, AuthReadyService],
exports: [AuthService, AuthGuard, RbacGuard],
})
export class BootAuthModule {}
export class BootAuthModule {}

View File

@@ -7,4 +7,4 @@ export const PERMISSIONS_KEY = 'permissions';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
export const Permissions = (...permissions: string[]) =>
SetMetadata(PERMISSIONS_KEY, permissions);
SetMetadata(PERMISSIONS_KEY, permissions);

View File

@@ -1,11 +1,19 @@
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';
import {
CanActivate,
ExecutionContext,
Injectable,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ConfigService } from '@nestjs/config';
import { PERMISSIONS_KEY, ROLES_KEY, IS_PUBLIC_KEY } from './decorators';
@Injectable()
export class RbacGuard implements CanActivate {
constructor(private readonly reflector: Reflector, private readonly config: ConfigService) {}
constructor(
private readonly reflector: Reflector,
private readonly config: ConfigService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// 公共路由直接放行
@@ -19,30 +27,40 @@ export class RbacGuard implements CanActivate {
const enabled = this.readBoolean('RBAC_ENABLED', false);
if (!enabled) return true;
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]) || [];
const requiredPermissions = this.reflector.getAllAndOverride<string[]>(PERMISSIONS_KEY, [
context.getHandler(),
context.getClass(),
]) || [];
const requiredRoles =
this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]) || [];
const requiredPermissions =
this.reflector.getAllAndOverride<string[]>(PERMISSIONS_KEY, [
context.getHandler(),
context.getClass(),
]) || [];
const req = context.switchToHttp().getRequest();
const user = (req as any).user || {};
const user = req.user || {};
const userRoles: string[] = Array.isArray(user.roles) ? user.roles : [];
const userPerms: string[] = Array.isArray(user.permissions) ? user.permissions : [];
const userPerms: string[] = Array.isArray(user.permissions)
? user.permissions
: [];
// 角色:至少命中一个
if (requiredRoles.length > 0) {
const ok = requiredRoles.some((r) => userRoles.includes(r));
if (!ok) throw new ForbiddenException({ msg_key: 'error.auth.insufficient_role' });
if (!ok)
throw new ForbiddenException({
msg_key: 'error.auth.insufficient_role',
});
}
// 权限:需要全部满足
if (requiredPermissions.length > 0) {
const ok = requiredPermissions.every((p) => userPerms.includes(p));
if (!ok) throw new ForbiddenException({ msg_key: 'error.auth.insufficient_permission' });
if (!ok)
throw new ForbiddenException({
msg_key: 'error.auth.insufficient_permission',
});
}
return true;
@@ -51,7 +69,8 @@ export class RbacGuard implements CanActivate {
private readBoolean(key: string, fallback = false): boolean {
const v = this.config.get<string | boolean>(key);
if (typeof v === 'boolean') return v;
if (typeof v === 'string') return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
if (typeof v === 'string')
return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
return fallback;
}
}
}

View File

@@ -5,6 +5,7 @@ import { CacheService } from './cache.service';
import { LockService } from './lock.service';
import { CacheController } from './cache.controller';
import { CACHE_SERVICE, LOCK_SERVICE } from './tokens';
import { CacheReadyService } from './cache-ready.service';
@Global()
@Module({
@@ -14,6 +15,7 @@ import { CACHE_SERVICE, LOCK_SERVICE } from './tokens';
RedisService,
CacheService,
LockService,
CacheReadyService,
{ provide: CACHE_SERVICE, useExisting: CacheService },
{ provide: LOCK_SERVICE, useExisting: LockService },
],

View File

@@ -0,0 +1,38 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { EventBus } from '@wwjCommon/events/event-bus';
import { RedisService } from './redis.service';
@Injectable()
export class CacheReadyService implements OnModuleInit {
private readonly logger = new Logger(CacheReadyService.name);
constructor(
private readonly eventBus: EventBus,
private readonly redis: RedisService,
) {}
async onModuleInit(): Promise<void> {
let state: 'ready' | 'unavailable' = 'ready';
try {
if (!this.redis.isEnabled()) {
// 使用内存回退,视为可用
state = 'ready';
} else {
const client = this.redis.getClient();
await client.ping();
state = 'ready';
}
} catch (err: any) {
this.logger.warn(`Cache readiness check failed: ${err?.message || err}`);
state = 'unavailable';
}
this.logger.log(`Cache module init -> state=${state}`);
this.eventBus.emit('module.state.changed', {
module: 'cache',
previousState: 'initializing',
currentState: state,
});
}
}

View File

@@ -0,0 +1,6 @@
import { EventEmitter2 } from '@nestjs/event-emitter';
export type EventBus = EventEmitter2;
export const EventBus = EventEmitter2;
export { OnEvent } from '@nestjs/event-emitter';

View File

@@ -88,4 +88,4 @@ export class BootHttp {
// Swagger by config
setupSwagger(app, config);
}
}
}

View File

@@ -8,12 +8,15 @@ import {
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { I18nService } from 'nestjs-i18n';
import { mapAlias } from '../i18n/aliases';
import { mapAlias } from '../lang/aliases';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(HttpExceptionFilter.name);
constructor(private readonly config: ConfigService, private readonly i18n: I18nService) {}
constructor(
private readonly config: ConfigService,
private readonly i18n: I18nService,
) {}
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
@@ -30,21 +33,21 @@ export class HttpExceptionFilter implements ExceptionFilter {
let args: Record<string, any> | undefined;
if (exception instanceof HttpException) {
const res: any = exception.getResponse();
// 支持自定义异常响应携带 msg_key 与 args
if (res && typeof res === 'object') {
if (typeof res.msg_key === 'string') msgKey = res.msg_key;
if (res.args && typeof res.args === 'object') args = res.args;
}
}
// 历史 key 别名映射
msgKey = mapAlias(msgKey);
const message = this.i18n.translate(msgKey, {
lang: this.resolveLang(request),
args,
});
const res: any = exception.getResponse();
// 支持自定义异常响应携带 msg_key 与 args
if (res && typeof res === 'object') {
if (typeof res.msg_key === 'string') msgKey = res.msg_key;
if (res.args && typeof res.args === 'object') args = res.args;
}
}
// 历史 key 别名映射
msgKey = mapAlias(msgKey);
const message = this.i18n.translate(msgKey, {
lang: this.resolveLang(request),
args,
});
const requestId =
request?.headers?.['x-request-id'] ||
@@ -95,7 +98,11 @@ export class HttpExceptionFilter implements ExceptionFilter {
if (!u) return false;
if (u.startsWith('/metrics') || u.startsWith('/health')) return true;
const hasPrefix = prefix.length > 0;
return hasPrefix && (u.startsWith(`/${prefix}/metrics`) || u.startsWith(`/${prefix}/health`));
return (
hasPrefix &&
(u.startsWith(`/${prefix}/metrics`) ||
u.startsWith(`/${prefix}/health`))
);
};
const infra = isInfra(url);
const preserveStatuses = new Set<number>([429]);
@@ -108,7 +115,11 @@ export class HttpExceptionFilter implements ExceptionFilter {
if (!u) return false;
if (u.startsWith('/metrics') || u.startsWith('/health')) return true;
const hasPrefix = prefix.length > 0;
return hasPrefix && (u.startsWith(`/${prefix}/metrics`) || u.startsWith(`/${prefix}/health`));
return (
hasPrefix &&
(u.startsWith(`/${prefix}/metrics`) ||
u.startsWith(`/${prefix}/health`))
);
};
const infra = isInfra(url);
const preserveStatuses = new Set<number>([429]);
@@ -135,4 +146,4 @@ export class HttpExceptionFilter implements ExceptionFilter {
return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
return fallback;
}
}
}

View File

@@ -24,8 +24,14 @@ export function buildIpFilterMiddleware(config: ConfigService) {
return function ipFilter(req: Request, res: Response, next: NextFunction) {
if (!enabled) return next();
// best-effort to get client IP when behind proxies
const forwarded = (req.headers['x-forwarded-for'] as string | undefined)?.split(',')[0]?.trim();
const ip = forwarded || (req.ip as string) || (req.connection as any)?.remoteAddress || '';
const forwarded = (req.headers['x-forwarded-for'] as string | undefined)
?.split(',')[0]
?.trim();
const ip =
forwarded ||
(req.ip as string) ||
(req.connection as any)?.remoteAddress ||
'';
if (blacklist.length && blacklist.includes(ip)) {
return res.status(403).json({ message: 'IP forbidden' });
}
@@ -34,4 +40,4 @@ export function buildIpFilterMiddleware(config: ConfigService) {
}
return next();
};
}
}

View File

@@ -45,11 +45,11 @@ export class RateLimitGuard implements CanActivate {
const roles: string[] = Array.isArray(req.user?.roles)
? req.user.roles
: typeof req.user?.roles === 'string'
? String(req.user.roles)
.split(',')
.map((s) => s.trim())
.filter(Boolean)
: [];
? String(req.user.roles)
.split(',')
.map((s) => s.trim())
.filter(Boolean)
: [];
const isAdmin = roles.includes('admin');
const limit = isAdmin ? this.adminMax : this.max;

View File

@@ -19,13 +19,13 @@ export function buildRequestContextMiddleware(ctx: RequestContextService) {
const rolesHeader = req.headers['x-roles'];
const roles = Array.isArray(rolesHeader)
? (rolesHeader as string[])
: typeof rolesHeader === 'string'
? rolesHeader
.split(',')
.map((s) => s.trim())
.filter((s) => s.length > 0)
: undefined;
: typeof rolesHeader === 'string'
? rolesHeader
.split(',')
.map((s) => s.trim())
.filter((s) => s.length > 0)
: undefined;
const lang = (req.headers['x-lang'] as string) || undefined;
const channel = (req.headers['x-channel'] as string) || undefined;

View File

@@ -5,4 +5,4 @@ export const aliasMap = new Map<string, string>([
export function mapAlias(key: string | undefined | null): string {
if (!key || typeof key !== 'string') return 'common.success';
return aliasMap.get(key) || key;
}
}

View File

@@ -1,5 +1,10 @@
import { Global, Module } from '@nestjs/common';
import { I18nModule, I18nJsonLoader, HeaderResolver, QueryResolver } from 'nestjs-i18n';
import {
I18nModule,
I18nJsonLoader,
HeaderResolver,
QueryResolver,
} from 'nestjs-i18n';
import { join } from 'path';
@Global()
@@ -11,7 +16,7 @@ import { join } from 'path';
loaderOptions: {
// 以项目根目录为基准,定位到 API 应用的语言资源目录
path: join(process.cwd(), 'apps/api/src/lang'),
watch: true,
watch: process.env.NODE_ENV !== 'test',
},
resolvers: [
{ use: QueryResolver, options: ['lang'] },
@@ -19,6 +24,7 @@ import { join } from 'path';
],
}),
],
providers: [require('./lang-ready.service').LangReadyService],
exports: [I18nModule],
})
export class BootI18nModule {}
export class BootI18nModule {}

View File

@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { BootI18nModule } from './boot-i18n.module';
/**
* BootLangModule (软别名)
*
* 提供对语言资源能力的语义化别名,底层仍使用 BootI18nModule。
* 不更改事件命名、就绪服务或载荷结构,仅作为导出包装。
*/
@Module({
imports: [BootI18nModule],
exports: [BootI18nModule],
})
export class BootLangModule {}

View File

@@ -0,0 +1,25 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { EventBus } from '@wwjCommon/events/event-bus';
import { join } from 'path';
import * as fs from 'fs';
@Injectable()
export class LangReadyService implements OnModuleInit {
private readonly logger = new Logger(LangReadyService.name);
constructor(private readonly eventBus: EventBus) {}
async onModuleInit() {
const langDir = join(process.cwd(), 'apps/api/src/lang');
const exists = fs.existsSync(langDir);
const state = exists ? 'ready' : 'unavailable';
this.logger.log(
`Lang module init: dir=${langDir}, exists=${exists}, state=${state}`,
);
this.eventBus.emit('module.state.changed', {
module: 'lang',
previousState: 'initializing',
currentState: state,
});
}
}

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { EventBus } from '@wwjCommon/events/event-bus';
import {
Registry,
collectDefaultMetrics,
@@ -8,7 +9,7 @@ import {
} from 'prom-client';
@Injectable()
export class MetricsService {
export class MetricsService implements OnModuleInit {
private readonly registry = new Registry();
private readonly httpCounter: Counter<string>;
private readonly httpDuration: Histogram<string>;
@@ -16,11 +17,19 @@ export class MetricsService {
private readonly externalDuration: Histogram<string>;
private readonly aiEvents: Counter<string>;
private readonly enabled: boolean;
private metricsInterval?: any;
constructor(private readonly config: ConfigService) {
constructor(
private readonly config: ConfigService,
private readonly eventBus: EventBus,
) {
this.enabled = this.readBoolean('PROMETHEUS_ENABLED');
if (this.enabled) {
collectDefaultMetrics({ register: this.registry, prefix: 'wwjcloud_' });
// 保存默认指标收集的定时器引用,便于销毁时清理
this.metricsInterval = collectDefaultMetrics({
register: this.registry,
prefix: 'wwjcloud_',
}) as any;
}
this.httpCounter = new Counter({
name: 'http_requests_total',
@@ -98,6 +107,19 @@ export class MetricsService {
return await this.registry.metrics();
}
// 在模块销毁时清理默认指标的采集定时器与注册表,避免测试残留句柄
onModuleDestroy() {
if (this.metricsInterval) {
try {
clearInterval(this.metricsInterval);
} catch {}
this.metricsInterval = undefined;
}
try {
this.registry.clear();
} catch {}
}
private readBoolean(key: string): boolean {
const v = this.config.get<string | boolean>(key);
if (typeof v === 'boolean') return v;
@@ -106,4 +128,13 @@ export class MetricsService {
}
return false;
}
async onModuleInit() {
const state = this.enabled ? 'ready' : 'unavailable';
this.eventBus.emit('module.state.changed', {
module: 'metrics',
previousState: 'initializing',
currentState: state,
});
}
}

View File

@@ -2,12 +2,13 @@ import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { QueueService } from './queue.service';
import { QueueController } from './queue.controller';
import { QueueReadyService } from './queue-ready.service';
@Global()
@Module({
imports: [ConfigModule],
providers: [QueueService],
providers: [QueueService, QueueReadyService],
controllers: [/* management */ require('./queue.controller').QueueController],
exports: [QueueService],
})
export class BootQueueModule {}
export class BootQueueModule {}

View File

@@ -0,0 +1,51 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { EventBus } from '@wwjCommon/events/event-bus';
function readBoolean(
config: ConfigService,
key: string,
fallback = false,
): boolean {
const v = config.get<string | boolean>(key);
if (typeof v === 'boolean') return v;
if (typeof v === 'string')
return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
return fallback;
}
@Injectable()
export class QueueReadyService implements OnModuleInit {
private readonly logger = new Logger(QueueReadyService.name);
constructor(
private readonly config: ConfigService,
private readonly eventBus: EventBus,
) {}
async onModuleInit(): Promise<void> {
const enabled = readBoolean(this.config, 'QUEUE_ENABLED', false);
const driver = (
this.config.get<string>('QUEUE_DRIVER') || ''
).toLowerCase();
let state: 'ready' | 'unavailable' = 'unavailable';
if (!enabled) {
// 禁用情况下对上层而言不阻塞,标记 ready 以避免误暂停
state = 'ready';
} else if (['bullmq', 'kafka'].includes(driver)) {
state = 'ready';
} else {
state = 'unavailable';
}
this.logger.log(
`Queue module init -> enabled=${enabled}, driver=${driver}, state=${state}`,
);
this.eventBus.emit('module.state.changed', {
module: 'queue',
previousState: 'initializing',
currentState: state,
});
}
}

View File

@@ -17,4 +17,4 @@ export class QueueController {
}
return { enabled: false };
}
}
}

View File

@@ -57,7 +57,10 @@ export class QueueService {
this.kafka = { Kafka };
const brokersRaw =
this.config.get<string>('QUEUE_KAFKA_BROKERS') ?? '127.0.0.1:9092';
const brokers = brokersRaw.split(',').map((s) => s.trim()).filter(Boolean);
const brokers = brokersRaw
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const clientId =
this.config.get<string>('QUEUE_KAFKA_CLIENT_ID') ?? 'wwjcloud-api';
const groupId =
@@ -74,7 +77,9 @@ export class QueueService {
await producer.connect();
await consumer.connect();
await consumer.subscribe({ topic, fromBeginning: false });
this.logger.log(`Queue(kafka) initialized: topic=${topic}, group=${groupId}`);
this.logger.log(
`Queue(kafka) initialized: topic=${topic}, group=${groupId}`,
);
} else {
this.driver = 'none';
this.logger.warn('Queue driver not set, queue disabled');
@@ -97,7 +102,8 @@ export class QueueService {
if (this.driver === 'bullmq' && this.queue) {
const attempts = this.readNumber('QUEUE_MAX_ATTEMPTS', 3);
const backoff = this.readNumber('QUEUE_BACKOFF_MS', 500);
const idempotencyKey = payload?.idempotencyKey || payload?.taskId || undefined;
const idempotencyKey =
payload?.idempotencyKey || payload?.taskId || undefined;
const job = await this.queue.add(jobName, payload, {
removeOnComplete: true,
attempts,
@@ -121,7 +127,9 @@ export class QueueService {
],
});
} catch (err: any) {
this.logger.error(`Kafka produce failed: topic=${topic}, err=${err?.message || err}`);
this.logger.error(
`Kafka produce failed: topic=${topic}, err=${err?.message || err}`,
);
// DLQ produce
if (this.readBoolean('QUEUE_DLQ_ENABLED')) {
const dlqTopic = `${topic}.dlq`;
@@ -129,11 +137,19 @@ export class QueueService {
await this.kafka.producer.send({
topic: dlqTopic,
messages: [
{ key: payload?.idempotencyKey || jobName, value: JSON.stringify({ error: err?.message || String(err), payload }) },
{
key: payload?.idempotencyKey || jobName,
value: JSON.stringify({
error: err?.message || String(err),
payload,
}),
},
],
});
} catch (err2: any) {
this.logger.error(`Kafka DLQ produce failed: topic=${dlqTopic}, err=${err2?.message || err2}`);
this.logger.error(
`Kafka DLQ produce failed: topic=${dlqTopic}, err=${err2?.message || err2}`,
);
}
}
}
@@ -142,21 +158,30 @@ export class QueueService {
return undefined;
}
registerWorker(processor: (data: any) => Promise<void>, concurrency = 1, name = 'default'): void {
registerWorker(
processor: (data: any) => Promise<void>,
concurrency = 1,
name = 'default',
): void {
if (this.driver === 'bullmq' && this.bull && this.queue) {
const { Worker, Queue } = this.bull;
const host = this.config.get<string>('QUEUE_REDIS_HOST');
const port = this.config.get<number>('QUEUE_REDIS_PORT') ?? 6379;
const password = this.config.get<string>('QUEUE_REDIS_PASSWORD');
const namespace = this.config.get<string>('QUEUE_REDIS_NAMESPACE') ?? 'wwjcloud';
const namespace =
this.config.get<string>('QUEUE_REDIS_NAMESPACE') ?? 'wwjcloud';
const connection = { host, port, password };
const queueName = `${namespace}-${name}`;
const dlqEnabled = this.readBoolean('QUEUE_DLQ_ENABLED');
const dlqName = `${queueName}-dlq`;
const dlqQueue = dlqEnabled ? new Queue(dlqName, { connection }) : null;
this.worker = new Worker(queueName, async (job: any) => {
await processor(job.data);
}, { connection, concurrency });
this.worker = new Worker(
queueName,
async (job: any) => {
await processor(job.data);
},
{ connection, concurrency },
);
this.worker.on('completed', (job: any) => {
this.logger.log(`Job completed: ${job.id}`);
});
@@ -164,7 +189,15 @@ export class QueueService {
this.logger.error(`Job failed: ${job?.id}: ${err?.message || err}`);
if (dlqEnabled && dlqQueue) {
try {
await dlqQueue.add('dlq', { originalJobId: job?.id, error: err?.message || String(err), payload: job?.data }, { removeOnComplete: true });
await dlqQueue.add(
'dlq',
{
originalJobId: job?.id,
error: err?.message || String(err),
payload: job?.data,
},
{ removeOnComplete: true },
);
} catch (e: any) {
this.logger.error(`DLQ enqueue failed: ${e?.message || e}`);
}
@@ -184,7 +217,9 @@ export class QueueService {
const data = value ? JSON.parse(value) : undefined;
await processor(data);
} catch (err: any) {
this.logger.error(`Kafka message processing failed: ${err?.message || err}`);
this.logger.error(
`Kafka message processing failed: ${err?.message || err}`,
);
if (this.readBoolean('QUEUE_DLQ_ENABLED')) {
const dlqTopic = `${topic}.dlq`;
try {
@@ -203,7 +238,9 @@ export class QueueService {
});
}
} catch (err2: any) {
this.logger.error(`Kafka DLQ produce failed: topic=${dlqTopic}, err=${err2?.message || err2}`);
this.logger.error(
`Kafka DLQ produce failed: topic=${dlqTopic}, err=${err2?.message || err2}`,
);
}
}
}
@@ -213,10 +250,19 @@ export class QueueService {
}
}
async getQueueCounts(): Promise<{ waiting: number; delayed: number; active: number; completed: number; failed: number } | undefined> {
async getQueueCounts(): Promise<
| {
waiting: number;
delayed: number;
active: number;
completed: number;
failed: number;
}
| undefined
> {
if (this.driver !== 'bullmq' || !this.queue) return undefined;
const counts = await this.queue.getJobCounts();
return counts as any;
return counts;
}
private readBoolean(key: string): boolean {

View File

@@ -9,7 +9,7 @@ import { map } from 'rxjs/operators';
import type { Request, Response } from 'express';
import { ConfigService } from '@nestjs/config';
import { I18nService } from 'nestjs-i18n';
import { mapAlias } from '../i18n/aliases';
import { mapAlias } from '../lang/aliases';
interface WrappedResponse<T = any> {
ok: boolean;
@@ -19,7 +19,10 @@ interface WrappedResponse<T = any> {
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
constructor(private readonly config: ConfigService, private readonly i18n: I18nService) {}
constructor(
private readonly config: ConfigService,
private readonly i18n: I18nService,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const req = ctx.getRequest<Request>();
@@ -43,7 +46,7 @@ export class ResponseInterceptor implements NestInterceptor {
('msg' in data || 'msg_key' in data)
) {
// 如果存在 msg_key则补充翻译后的 msg
const keyRaw = (data as any).msg_key;
const keyRaw = data.msg_key;
if (keyRaw && typeof keyRaw === 'string') {
const key = mapAlias(keyRaw);
const msg = this.i18n.translate(key, {
@@ -106,4 +109,4 @@ export class ResponseInterceptor implements NestInterceptor {
return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
return fallback;
}
}
}

View File

@@ -9,4 +9,4 @@ import { RedisService } from '../cache/redis.service';
providers: [StartupValidatorService, RedisService],
exports: [StartupValidatorService],
})
export class BootStartupModule {}
export class BootStartupModule {}

View File

@@ -3,6 +3,7 @@ import { ConfigService } from '@nestjs/config';
import { RedisService } from '../cache/redis.service';
import * as fs from 'fs';
import * as path from 'path';
import { EventBus } from '@wwjCommon/events/event-bus';
interface StartupReport {
nodeVersion: string;
@@ -19,6 +20,7 @@ export class StartupValidatorService implements OnModuleInit {
constructor(
private readonly config: ConfigService,
private readonly redis: RedisService,
private readonly eventBus: EventBus,
) {}
async onModuleInit() {
@@ -90,6 +92,18 @@ export class StartupValidatorService implements OnModuleInit {
this.writeJson('application-boot.json', snapshot);
this.logger.log('Startup validation completed, reports generated');
// Emit startup readiness state
const state: 'ready' | 'unavailable' =
report.envMissing.length === 0 &&
(!report.redis.enabled || report.redis.connected)
? 'ready'
: 'unavailable';
this.eventBus.emit('module.state.changed', {
module: 'startup',
previousState: 'initializing',
currentState: state,
});
}
private writeJson(fileName: string, data: any) {
@@ -102,4 +116,4 @@ export class StartupValidatorService implements OnModuleInit {
this.logger.warn(`Write ${fileName} failed: ${err?.message || err}`);
}
}
}
}

View File

@@ -8,4 +8,4 @@ import { TenantService } from './tenant.service';
providers: [TenantService],
exports: [TenantService],
})
export class BootTenantModule {}
export class BootTenantModule {}

View File

@@ -2,29 +2,43 @@ import type { Request, Response, NextFunction } from 'express';
import { ConfigService } from '@nestjs/config';
import { TenantService } from './tenant.service';
function readBoolean(config: ConfigService, key: string, fallback = false): boolean {
function readBoolean(
config: ConfigService,
key: string,
fallback = false,
): boolean {
const v = config.get<string | boolean>(key);
if (typeof v === 'boolean') return v;
if (typeof v === 'string') return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
if (typeof v === 'string')
return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
return fallback;
}
export function buildTenantMiddleware(config: ConfigService, tenant: TenantService) {
return function tenantMiddleware(req: Request, _res: Response, next: NextFunction) {
export function buildTenantMiddleware(
config: ConfigService,
tenant: TenantService,
) {
return function tenantMiddleware(
req: Request,
_res: Response,
next: NextFunction,
) {
const enabled = readBoolean(config, 'TENANT_ENABLED', false);
if (!enabled) return next();
const strategy = (config.get<string>('TENANT_RESOLVE_STRATEGY') || 'header').toLowerCase();
const strategy = (
config.get<string>('TENANT_RESOLVE_STRATEGY') || 'header'
).toLowerCase();
let id: string | undefined;
if (strategy === 'header') {
// 与 Java 保持一致:仅支持 'site-id' 作为租户头;不再允许通过配置修改
const key = 'site-id';
const value = req.headers[key];
id = Array.isArray(value) ? value[0] : (value as string | undefined);
id = Array.isArray(value) ? value[0] : value;
} else if (strategy === 'path') {
const prefix = config.get<string>('TENANT_PATH_PREFIX') || '/t/';
const url = (req.originalUrl || req.url || '') as string;
const url = req.originalUrl || req.url || '';
const idx = url.indexOf(prefix);
if (idx >= 0) {
const sliced = url.slice(idx + prefix.length);
@@ -44,4 +58,4 @@ export function buildTenantMiddleware(config: ConfigService, tenant: TenantServi
tenant.setTenantId(id);
return next();
};
}
}

View File

@@ -17,4 +17,4 @@ export class TenantService {
const store = this.ctx.getContext();
return store?.siteId;
}
}
}

View File

@@ -1,8 +1,8 @@
import { DynamicModule, Module } from '@nestjs/common';
import { BootModule } from './wwjcloud-boot.module';
import { AddonModule } from '@wwjAddon/wwjcloud-addon.module';
import { AiModule } from '@wwjAi/wwjcloud-ai.module';
import { BootI18nModule } from './infra/i18n/boot-i18n.module';
import { WwjcloudAiModule } from '@wwjAi/wwjcloud-ai.module';
import { BootLangModule } from './infra/lang/boot-lang.module';
function readBooleanEnv(key: string, fallback = false): boolean {
const v = process.env[key];
@@ -13,11 +13,11 @@ function readBooleanEnv(key: string, fallback = false): boolean {
@Module({})
export class WwjCloudPlatformPreset {
static full(): DynamicModule {
const imports: any[] = [BootModule, BootI18nModule, AddonModule.register()];
const imports: any[] = [BootModule, BootLangModule, AddonModule.register()];
const exportsArr: any[] = [];
if (readBooleanEnv('AI_ENABLED', false)) {
imports.push(AiModule);
exportsArr.push(AiModule);
imports.push(WwjcloudAiModule);
exportsArr.push(WwjcloudAiModule);
}
return {
module: WwjCloudPlatformPreset,

View File

@@ -33,6 +33,11 @@ import { BootStartupModule } from './infra/startup/boot-startup.module';
BootStartupModule,
],
providers: [ResilienceService, HttpClientService, RequestContextService],
exports: [EventEmitterModule, HttpClientService, RequestContextService, BootMetricsModule],
exports: [
EventEmitterModule,
HttpClientService,
RequestContextService,
BootMetricsModule,
],
})
export class BootModule {}
export class BootModule {}

View File

@@ -1,6 +1,6 @@
{
"name": "wwjcloud-nest-v1",
"version": "0.0.1",
"version": "0.1.2",
"description": "",
"author": "",
"private": true,
@@ -85,6 +85,14 @@
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"moduleNameMapper": {
"^@wwjBoot(.*)$": "<rootDir>/../libs/wwjcloud-boot/src$1",
"^@wwjCommon(.*)$": "<rootDir>/../libs/wwjcloud-boot/src/infra$1",
"^@wwjCore(.*)$": "<rootDir>/../libs/wwjcloud-core/src$1",
"^@wwjAi(.*)$": "<rootDir>/../libs/wwjcloud-ai/src$1",
"^@wwjAddon(.*)$": "<rootDir>/../libs/wwjcloud-addon/src$1",
"^@wwjVendor(.*)$": "<rootDir>/../libs/wwjcloud-boot/src/vendor$1"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],

View File

@@ -0,0 +1,51 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EventBus } from '@wwjCommon/events/event-bus';
import { AiCoordinatorService } from '@wwjAi/manager/services/ai-coordinator.service';
import { AiRegistryService } from '@wwjAi/manager/services/ai-registry.service';
import { AiOrchestratorService } from '@wwjAi/manager/services/ai-orchestrator.service';
describe('AiCoordinatorService module states', () => {
let svc: AiCoordinatorService;
let testingModule: TestingModule;
beforeEach(async () => {
testingModule = await Test.createTestingModule({
providers: [
AiCoordinatorService,
{ provide: EventBus, useValue: { emit: jest.fn() } },
{
provide: AiRegistryService,
useValue: {
getServicesByType: jest
.fn()
.mockReturnValue([{ execute: jest.fn() }]),
},
},
{
provide: AiOrchestratorService,
useValue: { executeWorkflow: jest.fn() },
},
],
}).compile();
svc = testingModule.get(AiCoordinatorService);
await svc.onModuleInit();
});
afterEach(async () => {
await testingModule?.close();
});
it('sets manager ready on init', () => {
expect(svc.getModuleState('manager')).toBe('ready');
});
it('updates state on module.state.changed event', async () => {
await (svc as any).handleModuleStateChanged({
module: 'healing',
previousState: 'initializing',
currentState: 'ready',
});
expect(svc.getModuleState('healing')).toBe('ready');
});
});

View File

@@ -0,0 +1,73 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AiSecurityService } from '../../libs/wwjcloud-ai/src/safe/services/ai-security.service';
import { AiAuditService } from '../../libs/wwjcloud-ai/src/safe/services/ai-audit.service';
import { SecurityAnalyzer } from '../../libs/wwjcloud-ai/src/safe/analyzers/security.analyzer';
import { VulnerabilityDetector } from '../../libs/wwjcloud-ai/src/safe/detectors/vulnerability.detector';
import { AccessProtector } from '../../libs/wwjcloud-ai/src/safe/protectors/access.protector';
describe('AiSecurityService audit integration', () => {
let service: AiSecurityService;
let auditService: jest.Mocked<AiAuditService>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AiSecurityService,
{
provide: SecurityAnalyzer,
useValue: { analyzeSystemSecurity: jest.fn() },
},
{
provide: VulnerabilityDetector,
useValue: {
scanVulnerabilities: jest.fn(),
detectRealTimeThreats: jest.fn(),
},
},
{
provide: AccessProtector,
useValue: {
monitorAccessPatterns: jest.fn(),
enforceSecurityPolicy: jest.fn(),
},
},
{
provide: AiAuditService,
useValue: { logAuditEvent: jest.fn().mockResolvedValue(undefined) },
},
],
}).compile();
service = module.get<AiSecurityService>(AiSecurityService);
auditService = module.get(AiAuditService);
});
it('handleSecurityEvent(critical) should log audit event and return actions', async () => {
const event = {
id: 'evt-1',
type: 'intrusion_detected',
severity: 'critical' as const,
source: 'waf',
description: 'Malicious traffic detected',
timestamp: Date.now(),
metadata: { userId: 'user-123' },
};
const response = await service.handleSecurityEvent(event);
expect(response.handled).toBe(true);
expect(response.actions).toEqual([
'immediate_alert',
'block_source',
'escalate_to_admin',
'create_incident',
]);
expect(auditService.logAuditEvent).toHaveBeenCalledTimes(1);
const callArg = auditService.logAuditEvent.mock.calls[0][0];
expect(callArg.type).toBe('security_critical_event');
expect(callArg.userId).toBe('user-123');
expect(callArg.description).toContain('Critical security event');
expect(callArg.metadata?.event).toEqual(event);
});
});

View File

@@ -0,0 +1,30 @@
import { PerformanceAnalyzer } from '../../libs/wwjcloud-ai/src/tuner/analyzers/performance.analyzer';
describe('PerformanceAnalyzer summary', () => {
let analyzer: PerformanceAnalyzer;
beforeEach(() => {
analyzer = new PerformanceAnalyzer();
});
it('analyzePerformance should include summary with expected fields', async () => {
const result = await analyzer.analyzePerformance();
expect(result).toBeDefined();
// Summary exists
expect(result.summary).toBeDefined();
expect(typeof result.summary.averageResponseTime).toBe('number');
expect(typeof result.summary.requestsPerSecond).toBe('number');
expect(typeof result.summary.errorRate).toBe('number');
// Metrics include error_rate for unified source
expect(result.metrics).toBeDefined();
expect(typeof result.metrics['error_rate']).toBe('number');
// Basic structure still intact
expect(result.analyses).toBeDefined();
expect(result.bottlenecks).toBeDefined();
expect(Array.isArray(result.trends)).toBe(true);
});
});

View File

@@ -0,0 +1,40 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EventBus } from '@wwjCommon/events/event-bus';
import { ResourceMonitor } from '@wwjAi/tuner/monitors/resource.monitor';
describe('ResourceMonitor DI and cleanup', () => {
let module: TestingModule;
let monitor: ResourceMonitor;
const emitterMock = { emit: jest.fn() } as unknown as EventBus;
beforeEach(async () => {
module = await Test.createTestingModule({
providers: [
ResourceMonitor,
{ provide: EventBus, useValue: emitterMock },
],
}).compile();
monitor = module.get(ResourceMonitor);
});
afterEach(async () => {
jest.clearAllMocks();
await module?.close();
});
it('emits resource.snapshot via injected EventBus', async () => {
await (monitor as any).collectResourceSnapshot();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'resource.snapshot',
expect.any(Object),
);
});
it('cleans up interval on onModuleDestroy', async () => {
await monitor.startMonitoring();
expect((monitor as any).monitoringInterval).toBeTruthy();
await monitor.onModuleDestroy();
expect((monitor as any).monitoringInterval).toBeNull();
});
});

View File

@@ -0,0 +1,72 @@
import { ResourceMonitor } from '../../libs/wwjcloud-ai/src/tuner/monitors/resource.monitor';
describe('ResourceMonitor alias getCurrentStatus', () => {
let monitor: ResourceMonitor;
beforeEach(() => {
monitor = new ResourceMonitor();
});
it('getCurrentStatus should delegate to getCurrentResourceStatus', async () => {
const dummyStatus = {
timestamp: Date.now(),
snapshot: {
timestamp: Date.now(),
cpu: { usage: 50, cores: 8, loadAverage: [0.5, 0.6, 0.7] },
memory: {
usage: 60,
total: 16 * 1024 ** 3,
used: 9 * 1024 ** 3,
free: 7 * 1024 ** 3,
cached: 2 * 1024 ** 3,
},
disk: {
usage: 40,
total: 512 * 1024 ** 3,
used: 200 * 1024 ** 3,
free: 312 * 1024 ** 3,
ioRead: 1024,
ioWrite: 2048,
},
network: {
bytesIn: 1000,
bytesOut: 1500,
packetsIn: 100,
packetsOut: 120,
errors: 0,
},
processes: { total: 200, running: 15, sleeping: 180, zombie: 5 },
},
alerts: [],
trends: {
cpu: 'stable',
memory: 'stable',
disk: 'stable',
network: 'stable',
},
health: {
status: 'healthy',
score: 95,
criticalAlerts: 0,
warningAlerts: 0,
lastCheck: Date.now(),
},
} as any;
const spy = jest
.spyOn(monitor, 'getCurrentResourceStatus')
.mockResolvedValue(dummyStatus);
const statusViaAlias = await monitor.getCurrentStatus();
expect(spy).toHaveBeenCalledTimes(1);
expect(statusViaAlias).toBe(dummyStatus);
});
it('getCurrentResourceStatus returns status shape', async () => {
const status = await monitor.getCurrentResourceStatus();
expect(status).toBeDefined();
expect(status.snapshot).toBeDefined();
expect(status.health).toBeDefined();
expect(status.trends).toBeDefined();
});
});

View File

@@ -0,0 +1,98 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EventBus } from '@wwjCommon/events/event-bus';
import { ConfigService } from '@nestjs/config';
import { SafeReadyService } from '@wwjAi/safe/services/safe-ready.service';
import { SecurityAnalyzer } from '@wwjAi/safe/analyzers/security.analyzer';
import { VulnerabilityDetector } from '@wwjAi/safe/detectors/vulnerability.detector';
import { AccessProtector } from '@wwjAi/safe/protectors/access.protector';
import { AiSecurityService } from '@wwjAi/safe/services/ai-security.service';
describe('SafeReadyService readiness emission', () => {
const emitterMock = { emit: jest.fn() } as unknown as EventBus;
const buildConfigMock = (enabled: boolean) =>
({
get: jest.fn((k: string) =>
k === 'AI_SAFE_ENABLED' ? (enabled ? 'true' : 'false') : undefined,
),
}) as unknown as ConfigService;
afterEach(() => {
jest.clearAllMocks();
});
it('emits ready when enabled and all components injected', async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
SafeReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: buildConfigMock(true) },
{ provide: SecurityAnalyzer, useValue: {} },
{ provide: VulnerabilityDetector, useValue: {} },
{ provide: AccessProtector, useValue: {} },
{ provide: AiSecurityService, useValue: {} },
],
}).compile();
const svc = module.get(SafeReadyService);
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({
module: 'ai.safe',
currentState: 'ready',
meta: { enabled: true },
}),
);
await module.close();
});
it('emits unavailable when enabled but component missing', async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
SafeReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: buildConfigMock(true) },
{ provide: SecurityAnalyzer, useValue: {} },
{ provide: VulnerabilityDetector, useValue: {} },
{ provide: AccessProtector, useValue: undefined },
{ provide: AiSecurityService, useValue: {} },
],
}).compile();
const svc = module.get(SafeReadyService);
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({
module: 'ai.safe',
currentState: 'unavailable',
meta: { enabled: true },
}),
);
await module.close();
});
it('emits unavailable when disabled', async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
SafeReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: buildConfigMock(false) },
{ provide: SecurityAnalyzer, useValue: {} },
{ provide: VulnerabilityDetector, useValue: {} },
{ provide: AccessProtector, useValue: {} },
{ provide: AiSecurityService, useValue: {} },
],
}).compile();
const svc = module.get(SafeReadyService);
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({
module: 'ai.safe',
currentState: 'unavailable',
meta: { enabled: false },
}),
);
await module.close();
});
});

View File

@@ -0,0 +1,98 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EventBus } from '@wwjCommon/events/event-bus';
import { ConfigService } from '@nestjs/config';
import { TunerReadyService } from '@wwjAi/tuner/services/tuner-ready.service';
import { PerformanceAnalyzer } from '@wwjAi/tuner/analyzers/performance.analyzer';
import { ResourceMonitor } from '@wwjAi/tuner/monitors/resource.monitor';
import { CacheOptimizer } from '@wwjAi/tuner/optimizers/cache.optimizer';
import { QueryOptimizer } from '@wwjAi/tuner/optimizers/query.optimizer';
describe('TunerReadyService readiness emission', () => {
const emitterMock = { emit: jest.fn() } as unknown as EventBus;
const buildConfigMock = (enabled: boolean) =>
({
get: jest.fn((k: string) =>
k === 'AI_TUNER_ENABLED' ? (enabled ? 'true' : 'false') : undefined,
),
}) as unknown as ConfigService;
afterEach(() => {
jest.clearAllMocks();
});
it('emits ready when enabled and all components injected', async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TunerReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: buildConfigMock(true) },
{ provide: PerformanceAnalyzer, useValue: {} },
{ provide: ResourceMonitor, useValue: {} },
{ provide: CacheOptimizer, useValue: {} },
{ provide: QueryOptimizer, useValue: {} },
],
}).compile();
const svc = module.get(TunerReadyService);
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({
module: 'ai.tuner',
currentState: 'ready',
meta: { enabled: true },
}),
);
await module.close();
});
it('emits unavailable when enabled but component missing', async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TunerReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: buildConfigMock(true) },
{ provide: PerformanceAnalyzer, useValue: {} },
{ provide: ResourceMonitor, useValue: undefined },
{ provide: CacheOptimizer, useValue: {} },
{ provide: QueryOptimizer, useValue: {} },
],
}).compile();
const svc = module.get(TunerReadyService);
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({
module: 'ai.tuner',
currentState: 'unavailable',
meta: { enabled: true },
}),
);
await module.close();
});
it('emits unavailable when disabled', async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TunerReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: buildConfigMock(false) },
{ provide: PerformanceAnalyzer, useValue: {} },
{ provide: ResourceMonitor, useValue: {} },
{ provide: CacheOptimizer, useValue: {} },
{ provide: QueryOptimizer, useValue: {} },
],
}).compile();
const svc = module.get(TunerReadyService);
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({
module: 'ai.tuner',
currentState: 'unavailable',
meta: { enabled: false },
}),
);
await module.close();
});
});

View File

@@ -1,16 +1,16 @@
import { Test, TestingModule } from '@nestjs/testing';
import { buildControllerTestModule } from '../test/testing-preset';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { EventBus } from '@wwjCommon/events/event-bus';
import { ConfigService } from '@nestjs/config';
import { HttpClientService } from '@wwjCommon/resilience/http-client.service';
import { RateLimitGuard } from '@wwjCommon/http/rate-limit.guard';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
const app = await buildControllerTestModule(AppController, [AppService]);
appController = app.get<AppController>(AppController);
});

View File

@@ -1,6 +1,6 @@
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { EventBus } from '@wwjCommon/events/event-bus';
import { ConfigService } from '@nestjs/config';
import { TASK_FAILED_EVENT } from '@wwjAi';
import type { Severity, TaskFailedPayload } from '@wwjAi';
@@ -11,9 +11,13 @@ import { IsIn, IsOptional, IsString } from 'class-validator';
import { MetricsService } from '@wwjCommon/metrics/metrics.service';
import { AiRecoveryService } from '@wwjAi/services/ai-recovery.service';
function readBooleanEnvRelaxed(v: string | boolean | undefined, fallback = false): boolean {
function readBooleanEnvRelaxed(
v: string | boolean | undefined,
fallback = false,
): boolean {
if (typeof v === 'boolean') return v;
if (typeof v === 'string') return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
if (typeof v === 'string')
return ['true', '1', 'yes', 'on'].includes(v.toLowerCase());
return fallback;
}
@@ -36,7 +40,7 @@ class SimulateFailureQueryDto {
export class AppController {
constructor(
private readonly appService: AppService,
private readonly emitter: EventEmitter2,
private readonly eventBus: EventBus,
private readonly config: ConfigService,
private readonly httpClient: HttpClientService,
) {}
@@ -58,9 +62,10 @@ export class AppController {
@UseGuards(RateLimitGuard)
@ApiTags('AI')
@Get('ai/simulate-failure')
simulateFailure(
@Query() q: SimulateFailureQueryDto,
): { ok: true; emitted: boolean } {
simulateFailure(@Query() q: SimulateFailureQueryDto): {
ok: true;
emitted: boolean;
} {
const taskId = q.taskId ?? 'demo-task';
const severity: Severity = q.severity ?? 'medium';
const reason = q.reason ?? 'demo failure';
@@ -71,7 +76,7 @@ export class AppController {
timestamp: Date.now(),
};
// 仅发出事件,由专用监听器与服务负责入队与观测
this.emitter.emit(TASK_FAILED_EVENT, payload);
this.eventBus.emit(TASK_FAILED_EVENT, payload);
return { ok: true, emitted: true };
}

View File

@@ -187,4 +187,4 @@ describe('WWJCloud v11 E2E', () => {
.query({ taskId: 'rl-4' })
.expect(200);
});
});
});

View File

@@ -0,0 +1,74 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EventBus } from '@wwjCommon/events/event-bus';
import { ConfigService } from '@nestjs/config';
import { AuthReadyService } from '@wwjCommon/auth/auth-ready.service';
describe('AuthReadyService readiness emission', () => {
let testingModule: TestingModule;
let svc: AuthReadyService;
const emitterMock = { emit: jest.fn() } as unknown as EventBus;
beforeEach(async () => {
const csMock = {
get: jest.fn().mockImplementation((k: string) => {
if (k === 'AUTH_ENABLED') return 'true';
if (k === 'RBAC_ENABLED') return 'false';
return undefined;
}),
} as unknown as ConfigService;
testingModule = await Test.createTestingModule({
providers: [
AuthReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: csMock },
],
}).compile();
svc = testingModule.get(AuthReadyService);
});
afterEach(async () => {
jest.clearAllMocks();
await testingModule?.close();
});
it('emits auth ready and rbac unavailable based on flags', async () => {
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'auth', currentState: 'ready' }),
);
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'rbac', currentState: 'unavailable' }),
);
});
it('emits both unavailable when disabled', async () => {
const cs = testingModule.get(ConfigService);
cs.get.mockImplementation((k: string) => {
if (k === 'AUTH_ENABLED') return 'false';
if (k === 'RBAC_ENABLED') return 'false';
return undefined;
});
const module2 = await Test.createTestingModule({
providers: [
AuthReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: cs },
],
}).compile();
const svc2 = module2.get(AuthReadyService);
await svc2.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'auth', currentState: 'unavailable' }),
);
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'rbac', currentState: 'unavailable' }),
);
await module2.close();
});
});

View File

@@ -0,0 +1,85 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EventBus } from '@wwjCommon/events/event-bus';
import { CacheReadyService } from '@wwjCommon/cache/cache-ready.service';
import { RedisService } from '@wwjCommon/cache/redis.service';
jest.mock('fs', () => ({ writeFileSync: jest.fn() }));
describe('CacheReadyService readiness emission', () => {
let testingModule: TestingModule;
let svc: CacheReadyService;
const emitterMock = { emit: jest.fn() } as unknown as EventBus;
const redisMock = {
isEnabled: jest.fn(),
getClient: jest.fn(),
} as any;
beforeEach(async () => {
redisMock.isEnabled.mockReturnValue(true);
redisMock.getClient.mockReturnValue({
ping: jest.fn().mockResolvedValue('PONG'),
});
testingModule = await Test.createTestingModule({
providers: [
CacheReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: RedisService, useValue: redisMock },
],
}).compile();
svc = testingModule.get(CacheReadyService);
});
afterEach(async () => {
jest.clearAllMocks();
await testingModule?.close();
});
it('emits ready when Redis enabled and ping ok', async () => {
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'cache', currentState: 'ready' }),
);
});
it('emits ready when Redis disabled (fallback)', async () => {
redisMock.isEnabled.mockReturnValue(false);
const module2 = await Test.createTestingModule({
providers: [
CacheReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: RedisService, useValue: redisMock },
],
}).compile();
const svc2 = module2.get(CacheReadyService);
await svc2.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'cache', currentState: 'ready' }),
);
await module2.close();
});
it('emits unavailable when Redis ping fails', async () => {
redisMock.isEnabled.mockReturnValue(true);
redisMock.getClient.mockReturnValue({
ping: jest.fn().mockRejectedValue(new Error('no-conn')),
});
const module2 = await Test.createTestingModule({
providers: [
CacheReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: RedisService, useValue: redisMock },
],
}).compile();
const svc2 = module2.get(CacheReadyService);
await svc2.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'cache', currentState: 'unavailable' }),
);
await module2.close();
});
});

View File

@@ -0,0 +1,37 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EventBus } from '@wwjCommon/events/event-bus';
import { LangReadyService } from '@wwjCommon/lang/lang-ready.service';
jest.mock('fs', () => ({
existsSync: jest.fn().mockReturnValue(true),
}));
describe('LangReadyService readiness emission', () => {
let testingModule: TestingModule;
let svc: LangReadyService;
const eventBusMock = { emit: jest.fn() } as unknown as EventBus;
beforeEach(async () => {
testingModule = await Test.createTestingModule({
providers: [
LangReadyService,
{ provide: EventBus, useValue: eventBusMock },
],
}).compile();
svc = testingModule.get(LangReadyService);
});
afterEach(async () => {
jest.clearAllMocks();
await testingModule?.close();
});
it('emits module.state.changed with ready on init when lang dir exists', async () => {
await svc.onModuleInit();
expect(eventBusMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'lang', currentState: 'ready' }),
);
});
});

View File

@@ -0,0 +1,68 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EventBus } from '@wwjCommon/events/event-bus';
import { ConfigService } from '@nestjs/config';
import { MetricsService } from '@wwjCommon/metrics/metrics.service';
describe('MetricsService readiness emission', () => {
let testingModule: TestingModule;
let svc: MetricsService;
const emitterMock = { emit: jest.fn() } as unknown as EventBus;
beforeEach(async () => {
testingModule = await Test.createTestingModule({
providers: [
MetricsService,
{ provide: EventBus, useValue: emitterMock },
{
provide: ConfigService,
useValue: {
get: jest.fn().mockImplementation((k: string) => {
if (k === 'PROMETHEUS_ENABLED') return 'true';
return undefined;
}),
},
},
],
}).compile();
svc = testingModule.get(MetricsService);
});
afterEach(async () => {
jest.clearAllMocks();
await testingModule?.close();
});
it('emits module.state.changed with ready on init when enabled', async () => {
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'metrics', currentState: 'ready' }),
);
});
it('emits unavailable when disabled', async () => {
const cs = testingModule.get(ConfigService);
cs.get.mockImplementation((k: string) =>
k === 'PROMETHEUS_ENABLED' ? 'false' : undefined,
);
// Recreate service to pick new enabled flag
const module2 = await Test.createTestingModule({
providers: [
MetricsService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: cs },
],
}).compile();
const svc2 = module2.get(MetricsService);
await svc2.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({
module: 'metrics',
currentState: 'unavailable',
}),
);
await module2.close();
});
});

View File

@@ -0,0 +1,87 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EventBus } from '@wwjCommon/events/event-bus';
import { ConfigService } from '@nestjs/config';
import { QueueReadyService } from '@wwjCommon/queue/queue-ready.service';
describe('QueueReadyService readiness emission', () => {
let testingModule: TestingModule;
let svc: QueueReadyService;
const emitterMock = { emit: jest.fn() } as unknown as EventBus;
beforeEach(async () => {
const csMock = {
get: jest.fn().mockImplementation((k: string) => {
if (k === 'QUEUE_ENABLED') return 'true';
if (k === 'QUEUE_DRIVER') return 'bullmq';
return undefined;
}),
} as unknown as ConfigService;
testingModule = await Test.createTestingModule({
providers: [
QueueReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: csMock },
],
}).compile();
svc = testingModule.get(QueueReadyService);
});
afterEach(async () => {
jest.clearAllMocks();
await testingModule?.close();
});
it('emits ready when enabled with supported driver', async () => {
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'queue', currentState: 'ready' }),
);
});
it('emits ready when disabled (non-blocking)', async () => {
const cs = testingModule.get(ConfigService);
cs.get.mockImplementation((k: string) =>
k === 'QUEUE_ENABLED' ? 'false' : undefined,
);
const module2 = await Test.createTestingModule({
providers: [
QueueReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: cs },
],
}).compile();
const svc2 = module2.get(QueueReadyService);
await svc2.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'queue', currentState: 'ready' }),
);
await module2.close();
});
it('emits unavailable for unknown driver when enabled', async () => {
const cs = testingModule.get(ConfigService);
cs.get.mockImplementation((k: string) => {
if (k === 'QUEUE_ENABLED') return 'true';
if (k === 'QUEUE_DRIVER') return 'unknown';
return undefined;
});
const module2 = await Test.createTestingModule({
providers: [
QueueReadyService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: cs },
],
}).compile();
const svc2 = module2.get(QueueReadyService);
await svc2.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'queue', currentState: 'unavailable' }),
);
await module2.close();
});
});

View File

@@ -0,0 +1,118 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EventBus } from '@wwjCommon/events/event-bus';
import { ConfigService } from '@nestjs/config';
import { StartupValidatorService } from '@wwjCommon/startup/startup-validator.service';
import { RedisService } from '@wwjCommon/cache/redis.service';
jest.mock('fs', () => ({ writeFileSync: jest.fn() }));
describe('StartupValidatorService readiness emission', () => {
let testingModule: TestingModule;
let svc: StartupValidatorService;
const emitterMock = { emit: jest.fn() } as unknown as EventBus;
const buildConfigMock = (overrides: Record<string, any> = {}) => {
const base: Record<string, any> = { NODE_ENV: 'test' };
const all = { ...base, ...overrides };
return {
get: jest.fn((k: string) => all[k]),
} as unknown as ConfigService;
};
const buildRedisMock = (enabled: boolean, pingOk: boolean) => {
return {
isEnabled: jest.fn(() => enabled),
getClient: jest.fn(() => ({
ping: jest.fn(() =>
pingOk ? Promise.resolve('PONG') : Promise.reject(new Error('fail')),
),
})),
} as unknown as RedisService;
};
afterEach(async () => {
jest.clearAllMocks();
await testingModule?.close();
});
it('emits ready when Redis disabled and env complete', async () => {
testingModule = await Test.createTestingModule({
providers: [
StartupValidatorService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: buildConfigMock() },
{ provide: RedisService, useValue: buildRedisMock(false, true) },
],
}).compile();
svc = testingModule.get(StartupValidatorService);
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'startup', currentState: 'ready' }),
);
});
it('emits ready when Redis enabled and ping ok', async () => {
testingModule = await Test.createTestingModule({
providers: [
StartupValidatorService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: buildConfigMock() },
{ provide: RedisService, useValue: buildRedisMock(true, true) },
],
}).compile();
svc = testingModule.get(StartupValidatorService);
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({ module: 'startup', currentState: 'ready' }),
);
});
it('emits unavailable when Redis enabled and ping fails', async () => {
testingModule = await Test.createTestingModule({
providers: [
StartupValidatorService,
{ provide: EventBus, useValue: emitterMock },
{ provide: ConfigService, useValue: buildConfigMock() },
{ provide: RedisService, useValue: buildRedisMock(true, false) },
],
}).compile();
svc = testingModule.get(StartupValidatorService);
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({
module: 'startup',
currentState: 'unavailable',
}),
);
});
it('emits unavailable when NODE_ENV missing', async () => {
testingModule = await Test.createTestingModule({
providers: [
StartupValidatorService,
{ provide: EventBus, useValue: emitterMock },
{
provide: ConfigService,
useValue: buildConfigMock({ NODE_ENV: undefined }),
},
{ provide: RedisService, useValue: buildRedisMock(false, true) },
],
}).compile();
svc = testingModule.get(StartupValidatorService);
await svc.onModuleInit();
expect(emitterMock.emit as any).toHaveBeenCalledWith(
'module.state.changed',
expect.objectContaining({
module: 'startup',
currentState: 'unavailable',
}),
);
});
});

View File

@@ -9,7 +9,8 @@ async function bootstrap() {
// 固定端口为 3000不再受环境变量影响 -> 改为可配置
const config = app.get(ConfigService);
const raw = config.get<string | number>('PORT');
const port = typeof raw === 'number' ? raw : parseInt(String(raw ?? '3000'), 10) || 3000;
const port =
typeof raw === 'number' ? raw : parseInt(String(raw ?? '3000'), 10) || 3000;
await app.listen(port);
}
bootstrap();

View File

@@ -1,7 +1,7 @@
{
"nodeVersion": "v20.13.1",
"nodeEnv": "development",
"timestamp": 1760860301672,
"nodeEnv": "test",
"timestamp": 1760885126089,
"redis": {
"enabled": false,
"connected": false,

View File

@@ -7,18 +7,22 @@ import { BootHttp } from '@wwjCommon/http/boot-http';
// 启用 AI 与 Prometheus并允许速率限制。
process.env.AI_ENABLED = 'true';
process.env.PROMETHEUS_ENABLED = 'true';
process.env.RATE_LIMIT_ENABLED = 'true';
process.env.RATE_LIMIT_ENABLED = 'false';
// 在 e2e 中开启直接指标与入队开关,确保端到端断言稳定
process.env.AI_SIMULATE_DIRECT_ENQUEUE = 'true';
// 配置全局前缀,确保异常过滤器将 /api/metrics 识别为基础设施路由
process.env.GLOBAL_PREFIX = 'api';
// 关闭鉴权与 RBAC避免无令牌访问受阻
process.env.AUTH_ENABLED = 'false';
process.env.RBAC_ENABLED = 'false';
import { AppModule } from './../src/app.module';
// AppModule 将在 beforeAll 中通过 require 加载,确保环境变量生效
describe('WWJCloud v11 E2E', () => {
let app: INestApplication;
beforeAll(async () => {
const { AppModule } = require('../apps/api/src/app.module');
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();

View File

@@ -0,0 +1,34 @@
import { Test, TestingModule } from '@nestjs/testing';
import type { Provider, Type } from '@nestjs/common';
import { EventBus } from '@wwjCommon/events/event-bus';
import { ConfigService } from '@nestjs/config';
import { HttpClientService } from '@wwjCommon/resilience/http-client.service';
import { RateLimitGuard } from '@wwjCommon/http/rate-limit.guard';
/**
* Build a robust TestingModule for controller unit tests with common mocks
* and guard overrides to avoid external infrastructure dependencies.
*/
export async function buildControllerTestModule(
controller: Type<any>,
extraProviders: Provider[] = [],
guardOverrides: Array<[Type<any>, any]> = [
[RateLimitGuard, { canActivate: jest.fn().mockReturnValue(true) }],
],
): Promise<TestingModule> {
const moduleBuilder = Test.createTestingModule({
controllers: [controller],
providers: [
{ provide: EventBus, useValue: { emit: jest.fn() } },
{ provide: ConfigService, useValue: { get: jest.fn() } },
{ provide: HttpClientService, useValue: { getWithFallback: jest.fn() } },
...extraProviders,
],
});
for (const [guard, value] of guardOverrides) {
moduleBuilder.overrideGuard(guard).useValue(value);
}
return await moduleBuilder.compile();
}