feat: 初始化DevOps平台后端
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { HealthService } from './health.service';
|
||||
|
||||
@ApiTags('health')
|
||||
@Controller('health')
|
||||
export class HealthController {
|
||||
constructor(private readonly healthService: HealthService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOkResponse({ description: 'Runtime dependency health summary.' })
|
||||
async getHealth(): Promise<ReturnType<HealthService['getHealth']>> {
|
||||
return this.healthService.getHealth();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AgentModule } from '../agent/agent.module';
|
||||
import { GiteaModule } from '../integrations/gitea/gitea.module';
|
||||
import { JenkinsModule } from '../integrations/jenkins/jenkins.module';
|
||||
import { WeComModule } from '../notifications/wecom/wecom.module';
|
||||
import { HealthController } from './health.controller';
|
||||
import { HealthService } from './health.service';
|
||||
|
||||
@Module({
|
||||
imports: [AgentModule, GiteaModule, JenkinsModule, WeComModule],
|
||||
controllers: [HealthController],
|
||||
providers: [HealthService],
|
||||
})
|
||||
export class HealthModule {}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { LlmClient } from '../agent/llm.client';
|
||||
import { EnvConfig } from '../config/env.schema';
|
||||
import { GiteaClient } from '../integrations/gitea/gitea.client';
|
||||
import { JenkinsClient } from '../integrations/jenkins/jenkins.client';
|
||||
import { WeComClient } from '../notifications/wecom/wecom.client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { HealthService } from './health.service';
|
||||
|
||||
/**
|
||||
* 健康检查是前端总览和后续部署前置校验的统一数据入口。
|
||||
*/
|
||||
describe('HealthService', () => {
|
||||
it('returns ok when optional integrations are not configured but available services respond', async () => {
|
||||
const config = {
|
||||
get: jest.fn((key: keyof EnvConfig) => (key === 'REDIS_URL' ? undefined : undefined)),
|
||||
} satisfies Pick<ConfigService<EnvConfig, true>, 'get'>;
|
||||
const prisma = {
|
||||
databaseHealth: jest.fn(() =>
|
||||
Promise.resolve({ status: 'not_configured' as const }),
|
||||
),
|
||||
} satisfies Pick<PrismaService, 'databaseHealth'>;
|
||||
const jenkins = {
|
||||
healthSummary: jest.fn(() => ({ status: 'not_configured' as const })),
|
||||
} satisfies Pick<JenkinsClient, 'healthSummary'>;
|
||||
const gitea = {
|
||||
healthSummary: jest.fn(() => ({ status: 'not_configured' as const })),
|
||||
} satisfies Pick<GiteaClient, 'healthSummary'>;
|
||||
const weCom = {
|
||||
healthSummary: jest.fn(() => ({ status: 'not_configured' as const })),
|
||||
} satisfies Pick<WeComClient, 'healthSummary'>;
|
||||
const llm = {
|
||||
healthSummary: jest.fn(() => ({ status: 'not_configured' as const })),
|
||||
} satisfies Pick<LlmClient, 'healthSummary'>;
|
||||
const service = new HealthService(
|
||||
config as unknown as ConfigService<EnvConfig, true>,
|
||||
prisma as unknown as PrismaService,
|
||||
jenkins as unknown as JenkinsClient,
|
||||
gitea as unknown as GiteaClient,
|
||||
weCom as unknown as WeComClient,
|
||||
llm as unknown as LlmClient,
|
||||
);
|
||||
|
||||
const health = await service.getHealth();
|
||||
|
||||
expect(health.status).toBe('ok');
|
||||
expect(health.dependencies.database.status).toBe('not_configured');
|
||||
expect(health.dependencies.jenkins.status).toBe('not_configured');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { LlmClient } from '../agent/llm.client';
|
||||
import { EnvConfig } from '../config/env.schema';
|
||||
import { GiteaClient } from '../integrations/gitea/gitea.client';
|
||||
import { JenkinsClient } from '../integrations/jenkins/jenkins.client';
|
||||
import { WeComClient } from '../notifications/wecom/wecom.client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
type HealthStatus = 'ok' | 'not_configured' | 'unavailable';
|
||||
|
||||
@Injectable()
|
||||
export class HealthService {
|
||||
constructor(
|
||||
private readonly config: ConfigService<EnvConfig, true>,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly jenkins: JenkinsClient,
|
||||
private readonly gitea: GiteaClient,
|
||||
private readonly weCom: WeComClient,
|
||||
private readonly llm: LlmClient,
|
||||
) {}
|
||||
|
||||
async getHealth(): Promise<{
|
||||
status: HealthStatus;
|
||||
checkedAt: string;
|
||||
dependencies: Record<string, { status: HealthStatus; message?: string }>;
|
||||
}> {
|
||||
const dependencies = {
|
||||
database: await this.prisma.databaseHealth(),
|
||||
redis: this.config.get('REDIS_URL', { infer: true })
|
||||
? { status: 'ok' as const }
|
||||
: { status: 'not_configured' as const },
|
||||
jenkins: this.jenkins.healthSummary(),
|
||||
gitea: this.gitea.healthSummary(),
|
||||
wecom: this.weCom.healthSummary(),
|
||||
llm: this.llm.healthSummary(),
|
||||
};
|
||||
const hasUnavailable = Object.values(dependencies).some(
|
||||
(dependency) => dependency.status === 'unavailable',
|
||||
);
|
||||
|
||||
return {
|
||||
status: hasUnavailable ? 'unavailable' : 'ok',
|
||||
checkedAt: new Date().toISOString(),
|
||||
dependencies,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user