feat: 初始化DevOps平台后端

This commit is contained in:
湛兮
2026-06-11 20:49:59 +08:00
commit caec5a618d
88 changed files with 11893 additions and 0 deletions
+15
View File
@@ -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();
}
}
+14
View File
@@ -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 {}
+50
View File
@@ -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');
});
});
+48
View File
@@ -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,
};
}
}