feat: 接入 Jenkins 同步 BullMQ 调度
- deploy-runs: Jenkins 自动同步优先使用 BullMQ 周期调度,并保留内存定时器降级\n- integrations/redis: 复用 BullMQ 连接工厂,避免业务模块散落 Redis 参数\n- README/RTK/settings: 同步 Jenkins 自动同步的 BullMQ 配置说明
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@ DATABASE_URL=mysql://devops_user:devops_password@127.0.0.1:3306/devops_platform
|
|||||||
USE_DATABASE_READS=false
|
USE_DATABASE_READS=false
|
||||||
PRISMA_CONNECT_ON_BOOT=false
|
PRISMA_CONNECT_ON_BOOT=false
|
||||||
|
|
||||||
# Redis and queues. Used by health checks and BullMQ-backed notification outbox retry.
|
# Redis and queues. Used by health checks, Jenkins auto sync, and notification outbox retry.
|
||||||
REDIS_URL=redis://127.0.0.1:6379/0
|
REDIS_URL=redis://127.0.0.1:6379/0
|
||||||
|
|
||||||
# Jenkins integration, server-side only.
|
# Jenkins integration, server-side only.
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ pnpm prisma:seed
|
|||||||
- `DATABASE_URL`:MySQL 连接地址。
|
- `DATABASE_URL`:MySQL 连接地址。
|
||||||
- `USE_DATABASE_READS`:是否启用 Prisma 数据读写。
|
- `USE_DATABASE_READS`:是否启用 Prisma 数据读写。
|
||||||
- `PRISMA_CONNECT_ON_BOOT`:启动时是否主动连接数据库。
|
- `PRISMA_CONNECT_ON_BOOT`:启动时是否主动连接数据库。
|
||||||
- `REDIS_URL`:Redis 连接地址,`/health` 会执行真实 PING,通知 outbox 重试会优先使用 BullMQ 调度。
|
- `REDIS_URL`:Redis 连接地址,`/health` 会执行真实 PING,Jenkins 自动同步和通知 outbox 重试会优先使用 BullMQ 调度。
|
||||||
- `JENKINS_BASE_URL`、`JENKINS_USERNAME`、`JENKINS_API_TOKEN`:Jenkins 集成配置。
|
- `JENKINS_BASE_URL`、`JENKINS_USERNAME`、`JENKINS_API_TOKEN`:Jenkins 集成配置。
|
||||||
- `GITEA_BASE_URL`、`GITEA_TOKEN`、`GITEA_WEBHOOK_SECRET`、`GITEA_WEBHOOK_RELAY_BASE_URL`:Gitea 集成与 Jenkins relay 诊断配置。
|
- `GITEA_BASE_URL`、`GITEA_TOKEN`、`GITEA_WEBHOOK_SECRET`、`GITEA_WEBHOOK_RELAY_BASE_URL`:Gitea 集成与 Jenkins relay 诊断配置。
|
||||||
- `NOTIFICATION_PROVIDER`、`WECOM_WEBHOOK_URL`、`FEISHU_WEBHOOK_URL`、`NOTIFICATION_WEBHOOK_URL`:通知集成配置。
|
- `NOTIFICATION_PROVIDER`、`WECOM_WEBHOOK_URL`、`FEISHU_WEBHOOK_URL`、`NOTIFICATION_WEBHOOK_URL`:通知集成配置。
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
- `deploy-runs`:发布记录 API、状态模型和 BPMN 步骤摘要。
|
- `deploy-runs`:发布记录 API、状态模型和 BPMN 步骤摘要。
|
||||||
- `integrations/jenkins`:Jenkins API 客户端边界。
|
- `integrations/jenkins`:Jenkins API 客户端边界。
|
||||||
- `integrations/gitea`:Gitea API 客户端边界。
|
- `integrations/gitea`:Gitea API 客户端边界。
|
||||||
- `integrations/redis`:Redis PING 健康检查和 BullMQ 连接参数边界,通知 outbox 调度复用该模块。
|
- `integrations/redis`:Redis PING 健康检查和 BullMQ 连接参数边界,Jenkins 自动同步与通知 outbox 调度复用该模块。
|
||||||
- `notifications/wecom`:企微、飞书和通用 webhook 通知边界。
|
- `notifications/wecom`:企微、飞书和通用 webhook 通知边界。
|
||||||
- `agent-config`:Agent 系统级配置、密钥加密和连接测试。
|
- `agent-config`:Agent 系统级配置、密钥加密和连接测试。
|
||||||
- `agent`:只面向运维工作流的 LLM 代理边界。
|
- `agent`:只面向运维工作流的 LLM 代理边界。
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 发布运行模块集中装配发布执行、Jenkins 同步、项目权限和通知依赖。
|
||||||
|
*/
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AuditModule } from '../audit/audit.module';
|
import { AuditModule } from '../audit/audit.module';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
import { JenkinsModule } from '../integrations/jenkins/jenkins.module';
|
import { JenkinsModule } from '../integrations/jenkins/jenkins.module';
|
||||||
|
import { RedisModule } from '../integrations/redis/redis.module';
|
||||||
import { WeComModule } from '../notifications/wecom/wecom.module';
|
import { WeComModule } from '../notifications/wecom/wecom.module';
|
||||||
import { ProjectsModule } from '../projects/projects.module';
|
import { ProjectsModule } from '../projects/projects.module';
|
||||||
import { DeployExecutionService } from './deploy-execution.service';
|
import { DeployExecutionService } from './deploy-execution.service';
|
||||||
@@ -11,7 +15,14 @@ import { DeployRunsService } from './deploy-runs.service';
|
|||||||
import { JenkinsSyncSchedulerService } from './jenkins-sync-scheduler.service';
|
import { JenkinsSyncSchedulerService } from './jenkins-sync-scheduler.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AuditModule, AuthModule, JenkinsModule, ProjectsModule, WeComModule],
|
imports: [
|
||||||
|
AuditModule,
|
||||||
|
AuthModule,
|
||||||
|
JenkinsModule,
|
||||||
|
ProjectsModule,
|
||||||
|
RedisModule,
|
||||||
|
WeComModule,
|
||||||
|
],
|
||||||
controllers: [DeployRunsController],
|
controllers: [DeployRunsController],
|
||||||
providers: [
|
providers: [
|
||||||
DeployExecutionService,
|
DeployExecutionService,
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Jenkins 自动同步调度器测试覆盖关闭、成功聚合和失败脱敏审计。
|
||||||
|
*/
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { AuditService } from '../audit/audit.service';
|
import { AuditService } from '../audit/audit.service';
|
||||||
import { EnvConfig } from '../config/env.schema';
|
import { EnvConfig } from '../config/env.schema';
|
||||||
@@ -23,7 +26,7 @@ describe('JenkinsSyncSchedulerService', () => {
|
|||||||
createAudit(),
|
createAudit(),
|
||||||
);
|
);
|
||||||
|
|
||||||
service.onModuleInit();
|
void service.onModuleInit();
|
||||||
jest.advanceTimersByTime(3000);
|
jest.advanceTimersByTime(3000);
|
||||||
|
|
||||||
expect(sync.syncJenkinsRuns).not.toHaveBeenCalled();
|
expect(sync.syncJenkinsRuns).not.toHaveBeenCalled();
|
||||||
@@ -89,7 +92,9 @@ describe('JenkinsSyncSchedulerService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function createConfig(
|
function createConfig(
|
||||||
values: Partial<Record<keyof EnvConfig, string | number | boolean | undefined>>,
|
values: Partial<
|
||||||
|
Record<keyof EnvConfig, string | number | boolean | undefined>
|
||||||
|
>,
|
||||||
): ConfigService<EnvConfig, true> {
|
): ConfigService<EnvConfig, true> {
|
||||||
return {
|
return {
|
||||||
get: jest.fn((key: keyof EnvConfig) => values[key]),
|
get: jest.fn((key: keyof EnvConfig) => values[key]),
|
||||||
|
|||||||
@@ -1,28 +1,55 @@
|
|||||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
/**
|
||||||
|
* Jenkins 自动同步调度服务封装 BullMQ 周期任务和进程内定时器降级路径。
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
OnModuleDestroy,
|
||||||
|
OnModuleInit,
|
||||||
|
Optional,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { Queue, Worker } from 'bullmq';
|
||||||
import { AuditService } from '../audit/audit.service';
|
import { AuditService } from '../audit/audit.service';
|
||||||
import { redactSensitive } from '../common/security/redact-sensitive';
|
import { redactSensitive } from '../common/security/redact-sensitive';
|
||||||
import { EnvConfig } from '../config/env.schema';
|
import { EnvConfig } from '../config/env.schema';
|
||||||
|
import { BullMqConnectionFactory } from '../integrations/redis/bullmq-connection.factory';
|
||||||
import { DeployRunsService } from './deploy-runs.service';
|
import { DeployRunsService } from './deploy-runs.service';
|
||||||
|
|
||||||
|
type JenkinsAutoSyncJobData = {
|
||||||
|
source: 'bullmq';
|
||||||
|
scheduledAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const jenkinsSyncQueueName = 'jenkins-auto-sync';
|
||||||
|
const jenkinsSyncJobName = 'sync-tick';
|
||||||
|
const jenkinsSyncSchedulerId = 'jenkins-auto-sync-scheduler';
|
||||||
|
const bullMqPrefix = 'devops-platform';
|
||||||
|
const bullMqStartupTimeoutMs = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 轻量自动同步器,先复用内存发布单和现有 Jenkins 同步逻辑。
|
* Jenkins 自动同步器优先用 BullMQ 周期调度,Redis 不可用时降级进程内定时器。
|
||||||
* 后续接 Redis/BullMQ 后,这里会迁移成队列 worker。
|
* 真正的发布单扫描和状态推进仍复用 DeployRunsService,避免队列层持有业务状态。
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JenkinsSyncSchedulerService
|
export class JenkinsSyncSchedulerService
|
||||||
implements OnModuleInit, OnModuleDestroy
|
implements OnModuleInit, OnModuleDestroy
|
||||||
{
|
{
|
||||||
private timer: ReturnType<typeof setInterval> | undefined;
|
private timer: ReturnType<typeof setInterval> | undefined;
|
||||||
|
private queue: Queue<JenkinsAutoSyncJobData, void, string> | undefined;
|
||||||
|
private worker: Worker<JenkinsAutoSyncJobData, void, string> | undefined;
|
||||||
private running = false;
|
private running = false;
|
||||||
|
private bullMqFallbackRecorded = false;
|
||||||
|
private bullMqRuntimeIssueRecorded = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly config: ConfigService<EnvConfig, true>,
|
private readonly config: ConfigService<EnvConfig, true>,
|
||||||
private readonly deployRunsService: DeployRunsService,
|
private readonly deployRunsService: DeployRunsService,
|
||||||
private readonly auditService: AuditService,
|
private readonly auditService: AuditService,
|
||||||
|
@Optional()
|
||||||
|
private readonly bullMqConnectionFactory?: BullMqConnectionFactory,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
onModuleInit(): void {
|
async onModuleInit(): Promise<void> {
|
||||||
if (!this.config.get('JENKINS_AUTO_SYNC_ENABLED', { infer: true })) {
|
if (!this.config.get('JENKINS_AUTO_SYNC_ENABLED', { infer: true })) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -31,19 +58,121 @@ export class JenkinsSyncSchedulerService
|
|||||||
infer: true,
|
infer: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.startBullMqScheduler(intervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onModuleDestroy(): Promise<void> {
|
||||||
|
this.stopMemoryScheduler();
|
||||||
|
await this.closeBullMqScheduler(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async startBullMqScheduler(intervalMs: number): Promise<void> {
|
||||||
|
const connection =
|
||||||
|
this.bullMqConnectionFactory?.createConnectionOptions(
|
||||||
|
'jenkins-auto-sync',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!connection) {
|
||||||
|
this.startMemoryScheduler(intervalMs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.queue = new Queue<JenkinsAutoSyncJobData, void, string>(
|
||||||
|
jenkinsSyncQueueName,
|
||||||
|
{
|
||||||
|
connection,
|
||||||
|
prefix: bullMqPrefix,
|
||||||
|
defaultJobOptions: {
|
||||||
|
removeOnComplete: { count: 20 },
|
||||||
|
removeOnFail: { count: 20 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.worker = new Worker<JenkinsAutoSyncJobData, void, string>(
|
||||||
|
jenkinsSyncQueueName,
|
||||||
|
async () => {
|
||||||
|
await this.runOnce();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connection,
|
||||||
|
prefix: bullMqPrefix,
|
||||||
|
concurrency: 1,
|
||||||
|
removeOnComplete: { count: 20 },
|
||||||
|
removeOnFail: { count: 20 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.queue.on('error', (error) => {
|
||||||
|
void this.recordBullMqIssue('queue', error, 'bullmq_recovering');
|
||||||
|
});
|
||||||
|
this.worker.on('error', (error) => {
|
||||||
|
void this.recordBullMqIssue('worker', error, 'bullmq_recovering');
|
||||||
|
});
|
||||||
|
this.worker.on('failed', (_job, error) => {
|
||||||
|
void this.recordBullMqIssue('job', error, 'bullmq_recovering');
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.withStartupTimeout(
|
||||||
|
Promise.all([
|
||||||
|
this.queue.waitUntilReady(),
|
||||||
|
this.worker.waitUntilReady(),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
await this.queue.upsertJobScheduler(
|
||||||
|
jenkinsSyncSchedulerId,
|
||||||
|
{ every: intervalMs },
|
||||||
|
{
|
||||||
|
name: jenkinsSyncJobName,
|
||||||
|
data: {
|
||||||
|
source: 'bullmq',
|
||||||
|
scheduledAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
opts: {
|
||||||
|
removeOnComplete: { count: 20 },
|
||||||
|
removeOnFail: { count: 20 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
void this.runOnce();
|
||||||
|
} catch (error) {
|
||||||
|
await this.closeBullMqScheduler(true);
|
||||||
|
await this.recordBullMqIssue('startup', error, 'memory_timer');
|
||||||
|
this.startMemoryScheduler(intervalMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private startMemoryScheduler(intervalMs: number): void {
|
||||||
|
if (this.timer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.timer = setInterval(() => {
|
this.timer = setInterval(() => {
|
||||||
void this.runOnce();
|
void this.runOnce();
|
||||||
}, intervalMs);
|
}, intervalMs);
|
||||||
void this.runOnce();
|
void this.runOnce();
|
||||||
}
|
}
|
||||||
|
|
||||||
onModuleDestroy(): void {
|
private stopMemoryScheduler(): void {
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
clearInterval(this.timer);
|
clearInterval(this.timer);
|
||||||
this.timer = undefined;
|
this.timer = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async closeBullMqScheduler(forceWorkerClose: boolean): Promise<void> {
|
||||||
|
const worker = this.worker;
|
||||||
|
const queue = this.queue;
|
||||||
|
|
||||||
|
this.worker = undefined;
|
||||||
|
this.queue = undefined;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
worker?.close(forceWorkerClose).catch(() => undefined),
|
||||||
|
queue?.close().catch(() => undefined),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
async runOnce(): Promise<void> {
|
async runOnce(): Promise<void> {
|
||||||
if (this.running) {
|
if (this.running) {
|
||||||
return;
|
return;
|
||||||
@@ -78,4 +207,57 @@ export class JenkinsSyncSchedulerService
|
|||||||
this.running = false;
|
this.running = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async withStartupTimeout<T>(promise: Promise<T>): Promise<T> {
|
||||||
|
return Promise.race([
|
||||||
|
promise,
|
||||||
|
new Promise<T>((_resolve, reject) => {
|
||||||
|
setTimeout(
|
||||||
|
() => reject(new Error('BullMQ Jenkins 同步调度器启动超时')),
|
||||||
|
bullMqStartupTimeoutMs,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async recordBullMqIssue(
|
||||||
|
stage: 'startup' | 'queue' | 'worker' | 'job',
|
||||||
|
error: unknown,
|
||||||
|
mode: 'memory_timer' | 'bullmq_recovering',
|
||||||
|
): Promise<void> {
|
||||||
|
const recorded =
|
||||||
|
mode === 'memory_timer'
|
||||||
|
? this.bullMqFallbackRecorded
|
||||||
|
: this.bullMqRuntimeIssueRecorded;
|
||||||
|
|
||||||
|
if (recorded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === 'memory_timer') {
|
||||||
|
this.bullMqFallbackRecorded = true;
|
||||||
|
} else {
|
||||||
|
this.bullMqRuntimeIssueRecorded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof Error ? error.message : 'BullMQ Jenkins 同步调度异常';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.auditService.record({
|
||||||
|
action:
|
||||||
|
mode === 'memory_timer'
|
||||||
|
? 'JENKINS_AUTO_SYNC_SCHEDULER_FALLBACK'
|
||||||
|
: 'JENKINS_AUTO_SYNC_SCHEDULER_DEGRADED',
|
||||||
|
resourceType: 'deploy_run',
|
||||||
|
after: {
|
||||||
|
stage,
|
||||||
|
mode,
|
||||||
|
message: redactSensitive({ message }).message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// 启动早期数据库可能尚不可用,调度降级不能被审计写入失败阻断。
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* 系统配置服务只返回集成变量名和配置状态,禁止暴露任何密钥原文。
|
||||||
|
*/
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { EnvConfig } from '../config/env.schema';
|
import { EnvConfig } from '../config/env.schema';
|
||||||
@@ -32,16 +35,20 @@ const INTEGRATION_DEFINITIONS: IntegrationDefinition[] = [
|
|||||||
required: ['JENKINS_BASE_URL', 'JENKINS_USERNAME', 'JENKINS_API_TOKEN'],
|
required: ['JENKINS_BASE_URL', 'JENKINS_USERNAME', 'JENKINS_API_TOKEN'],
|
||||||
optional: ['JENKINS_AUTO_SYNC_ENABLED', 'JENKINS_AUTO_SYNC_INTERVAL_MS'],
|
optional: ['JENKINS_AUTO_SYNC_ENABLED', 'JENKINS_AUTO_SYNC_INTERVAL_MS'],
|
||||||
variableHelp: {
|
variableHelp: {
|
||||||
JENKINS_BASE_URL:
|
JENKINS_BASE_URL: variableHelp(
|
||||||
variableHelp('Jenkins 服务端 API 地址,只能由后端服务访问。', {
|
'Jenkins 服务端 API 地址,只能由后端服务访问。',
|
||||||
|
{
|
||||||
docUrl: 'https://www.jenkins.io/doc/book/using/remote-access-api/',
|
docUrl: 'https://www.jenkins.io/doc/book/using/remote-access-api/',
|
||||||
}),
|
},
|
||||||
JENKINS_USERNAME: variableHelp('用于调用 Jenkins Remote API 的服务账号。'),
|
),
|
||||||
|
JENKINS_USERNAME: variableHelp(
|
||||||
|
'用于调用 Jenkins Remote API 的服务账号。',
|
||||||
|
),
|
||||||
JENKINS_API_TOKEN: variableHelp(
|
JENKINS_API_TOKEN: variableHelp(
|
||||||
'Jenkins 服务账号 API token,只能保存在服务端环境变量或后续加密密钥表。',
|
'Jenkins 服务账号 API token,只能保存在服务端环境变量或后续加密密钥表。',
|
||||||
),
|
),
|
||||||
JENKINS_AUTO_SYNC_ENABLED: variableHelp(
|
JENKINS_AUTO_SYNC_ENABLED: variableHelp(
|
||||||
'是否启用轻量自动同步器,自动扫描 queued/running 发布单。',
|
'是否启用 Jenkins 自动同步器;配置 REDIS_URL 时使用 BullMQ 调度,否则降级进程内定时器。',
|
||||||
{ example: 'true' },
|
{ example: 'true' },
|
||||||
),
|
),
|
||||||
JENKINS_AUTO_SYNC_INTERVAL_MS: variableHelp(
|
JENKINS_AUTO_SYNC_INTERVAL_MS: variableHelp(
|
||||||
@@ -57,7 +64,9 @@ const INTEGRATION_DEFINITIONS: IntegrationDefinition[] = [
|
|||||||
optional: ['GITEA_WEBHOOK_SECRET', 'GITEA_WEBHOOK_RELAY_BASE_URL'],
|
optional: ['GITEA_WEBHOOK_SECRET', 'GITEA_WEBHOOK_RELAY_BASE_URL'],
|
||||||
note: 'GITEA_WEBHOOK_SECRET 用于开启 webhook 签名校验;GITEA_WEBHOOK_RELAY_BASE_URL 用于校验仓库 push hook 是否已接入 Jenkins relay。',
|
note: 'GITEA_WEBHOOK_SECRET 用于开启 webhook 签名校验;GITEA_WEBHOOK_RELAY_BASE_URL 用于校验仓库 push hook 是否已接入 Jenkins relay。',
|
||||||
variableHelp: {
|
variableHelp: {
|
||||||
GITEA_BASE_URL: variableHelp('Gitea 服务端 API 地址,用于读取仓库、分支和 tag。'),
|
GITEA_BASE_URL: variableHelp(
|
||||||
|
'Gitea 服务端 API 地址,用于读取仓库、分支和 tag。',
|
||||||
|
),
|
||||||
GITEA_TOKEN: variableHelp(
|
GITEA_TOKEN: variableHelp(
|
||||||
'Gitea API token,只能由后端持有,用于读取 refs 和后续仓库元数据。',
|
'Gitea API token,只能由后端持有,用于读取 refs 和后续仓库元数据。',
|
||||||
{ docUrl: 'https://docs.gitea.com/development/api-usage' },
|
{ docUrl: 'https://docs.gitea.com/development/api-usage' },
|
||||||
@@ -108,9 +117,12 @@ const INTEGRATION_DEFINITIONS: IntegrationDefinition[] = [
|
|||||||
NOTIFICATION_WEBHOOK_URL: variableHelp(
|
NOTIFICATION_WEBHOOK_URL: variableHelp(
|
||||||
'内部通知网关或通用 webhook 的服务端地址,适合接自建消息中转服务。',
|
'内部通知网关或通用 webhook 的服务端地址,适合接自建消息中转服务。',
|
||||||
),
|
),
|
||||||
DEVOPS_PUBLIC_URL: variableHelp('通知消息中跳转回 DevOps 平台的公开访问地址。', {
|
DEVOPS_PUBLIC_URL: variableHelp(
|
||||||
|
'通知消息中跳转回 DevOps 平台的公开访问地址。',
|
||||||
|
{
|
||||||
example: 'https://devops.mrzhan.top',
|
example: 'https://devops.mrzhan.top',
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
NOTIFICATION_OUTBOX_RETRY_ENABLED: variableHelp(
|
NOTIFICATION_OUTBOX_RETRY_ENABLED: variableHelp(
|
||||||
'是否启用轻量通知 outbox 重试器,失败通知会按 nextAttemptAt 自动重投。',
|
'是否启用轻量通知 outbox 重试器,失败通知会按 nextAttemptAt 自动重投。',
|
||||||
{ example: 'true' },
|
{ example: 'true' },
|
||||||
@@ -236,7 +248,9 @@ export class SettingsService {
|
|||||||
private toNotificationIntegrationStatus(
|
private toNotificationIntegrationStatus(
|
||||||
definition: IntegrationDefinition,
|
definition: IntegrationDefinition,
|
||||||
): IntegrationConfigStatus {
|
): IntegrationConfigStatus {
|
||||||
const provider = this.getConfigValue('NOTIFICATION_PROVIDER')?.toLowerCase();
|
const provider = this.getConfigValue(
|
||||||
|
'NOTIFICATION_PROVIDER',
|
||||||
|
)?.toLowerCase();
|
||||||
const providerWebhook = this.notificationWebhookKey(provider);
|
const providerWebhook = this.notificationWebhookKey(provider);
|
||||||
const required: EnvStringKey[] = providerWebhook
|
const required: EnvStringKey[] = providerWebhook
|
||||||
? ['NOTIFICATION_PROVIDER', providerWebhook]
|
? ['NOTIFICATION_PROVIDER', providerWebhook]
|
||||||
|
|||||||
Reference in New Issue
Block a user