using Dpz.Core.Entity.Base;
using Dpz.Core.EnumLibrary;
using Dpz.Core.Infrastructure;
using Dpz.Core.Service.Mediator.Features.Account.Queries;
using Dpz.Core.Service.Mediator.Features.Pipeline.Contracts;
using Dpz.Core.Service.Mediator.Features.Pipeline.Queries;
using Dpz.Core.Web.Jobs.Hangfire;
using Dpz.Core.Web.Jobs.Hangfire.Build;
using Dpz.Core.Web.Jobs.Security;
using Hangfire;
using Mediator;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using ZiggyCreatures.Caching.Fusion;
namespace Dpz.Core.Web.Jobs.Controllers;
[Authorize]
public class PipelineController(
IFusionCache fusionCache,
SshConfigurationService sshConfigurationService,
IMediator mediator
) : Controller
{
public IActionResult Index()
{
if (!HangfireDashboardPermission.HasPermission(User, Permissions.System))
{
return Forbid();
}
var sshConfiguration = sshConfigurationService.GetSshConfiguration();
var problems = SshConfigurationService.GetProblems(sshConfiguration);
ViewData["ConfigProblems"] = problems;
return View(sshConfiguration);
}
/// <summary>
/// 查询当前是否有构建正在运行
/// </summary>
public async Task<JsonResult> Status(CancellationToken cancellationToken)
{
var current = await fusionCache.TryGetAsync<string>(
BuildActivator.BuildLockKey,
token: cancellationToken
);
var running = current.HasValue && !string.IsNullOrWhiteSpace(current.Value);
var runId = running ? current.Value : null;
var pendingConfirmation = false;
List<PipelineVersionMatch>? matches = null;
if (runId != null)
{
var wait = await fusionCache.TryGetAsync<List<PipelineVersionMatch>>(
PipelineVersionProbe.WaitCacheKey(runId),
token: cancellationToken
);
if (wait.HasValue && wait.Value.Count > 0)
{
pendingConfirmation = true;
matches = wait.Value;
}
}
return Json(
new
{
running,
runId,
pendingConfirmation,
matches,
}
);
}
/// <summary>
/// 确认继续同版本发布
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public Task<ResponseResult> ConfirmVersion(string runId, CancellationToken cancellationToken)
{
return WriteVersionDecisionAsync(
runId,
PipelineVersionProbe.DecisionConfirm,
cancellationToken
);
}
/// <summary>
/// 取消同版本发布
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public Task<ResponseResult> CancelVersion(string runId, CancellationToken cancellationToken)
{
return WriteVersionDecisionAsync(
runId,
PipelineVersionProbe.DecisionCancel,
cancellationToken
);
}
private async Task<ResponseResult> WriteVersionDecisionAsync(
string runId,
string decision,
CancellationToken cancellationToken
)
{
if (!HangfireDashboardPermission.HasPermission(User, Permissions.System))
{
return ResponseResult.Fail("没有权限访问");
}
if (string.IsNullOrWhiteSpace(runId))
{
return ResponseResult.Fail("运行 ID 无效");
}
var current = await fusionCache.TryGetAsync<string>(
BuildActivator.BuildLockKey,
token: cancellationToken
);
if (!current.HasValue || current.Value != runId)
{
return ResponseResult.Fail("没有等待确认的发布任务");
}
var wait = await fusionCache.TryGetAsync<List<PipelineVersionMatch>>(
PipelineVersionProbe.WaitCacheKey(runId),
token: cancellationToken
);
if (!wait.HasValue || wait.Value.Count == 0)
{
return ResponseResult.Fail("没有等待确认的发布任务");
}
await fusionCache.SetAsync(
PipelineVersionProbe.DecisionCacheKey(runId),
decision,
options => options.SetDuration(TimeSpan.FromMinutes(5)),
token: cancellationToken
);
return ResponseResult.Ok();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ResponseResult> Run(BuildOption option, CancellationToken cancellationToken)
{
var result = new ResponseResult<object>();
if (!HangfireDashboardPermission.HasPermission(User, Permissions.System))
{
return result.WithFail("没有权限访问");
}
var current = await fusionCache.TryGetAsync<string>(
BuildActivator.BuildLockKey,
token: cancellationToken
);
if (current.HasValue && !string.IsNullOrWhiteSpace(current.Value))
{
return result.WithOk(new { runId = current.Value, alreadyRunning = true });
}
Version? version = null;
if (!string.IsNullOrWhiteSpace(option.Tag) && !Version.TryParse(option.Tag, out version))
{
return result.WithFail("版本号不是预期的");
}
option.Tag = version?.ToString();
var userInfo = await mediator.Send(
new GetUserInfoRequest { Account = User.NameIdentifier },
cancellationToken
);
var runId = ObjectId.GenerateNewId().ToString();
BackgroundJob.Enqueue<BuildActivator>(x =>
x.RunBuildAsync(option, runId, userInfo, CancellationToken.None)
);
return result.WithOk(new { runId, alreadyRunning = false });
}
/// <summary>
/// 获取最近的 CI/CD 历史记录
/// </summary>
[HttpGet]
public async Task<ResponseResult<List<PipelineHistoryLatestResponse>>> History(
CancellationToken cancellationToken
)
{
if (!HangfireDashboardPermission.HasPermission(User, Permissions.System))
{
return ResponseResult<List<PipelineHistoryLatestResponse>>.Fail("没有权限访问");
}
var list = await mediator.Send(new GetPipelineHistoryLatestRequest(), cancellationToken);
return ResponseResult<List<PipelineHistoryLatestResponse>>.Ok(list);
}
/// <summary>
/// 获取 CI/CD 历史记录详情
/// </summary>
[HttpGet]
public async Task<ResponseResult<PipelineHistoryResponse?>> HistoryDetail(
[FromQuery] GetPipelineHistoryDetailRequest request,
CancellationToken cancellationToken
)
{
if (!HangfireDashboardPermission.HasPermission(User, Permissions.System))
{
return ResponseResult<PipelineHistoryResponse?>.Fail("没有权限访问");
}
var detail = await mediator.Send(request, cancellationToken);
if (detail == null)
{
return ResponseResult<PipelineHistoryResponse?>.Fail("记录不存在");
}
return ResponseResult<PipelineHistoryResponse?>.Ok(detail);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个 ASP.NET Core MVC 控制器,用于管理 CI/CD 流水线(Pipeline)的构建和部署功能。以下是详细解释:
整体架构
控制器: PipelineController
- 使用依赖注入获取三个核心服务:
IFusionCache- 分布式缓存SshConfigurationService- SSH 配置服务IMediator- CQRS 中介者模式实现
核心功能模块
1. Index(首页)
public IActionResult Index()
- 权限检查:验证用户是否有系统权限
- 获取 SSH 配置并检测问题
- 返回配置视图
2. Status(状态查询)
public async Task<JsonResult> Status(CancellationToken cancellationToken)
- 查询当前是否有构建任务正在运行
- 检查构建锁(
BuildLockKey) - 如果有运行任务,检查是否有待确认的版本冲突(
PipelineVersionMatch) - 返回 JSON 格式的状态信息
3. 版本确认功能
ConfirmVersion(确认继续发布)
[HttpPost]
public Task<ResponseResult> ConfirmVersion(string runId, ...)
- 用户确认继续同版本发布
CancelVersion(取消发布)
[HttpPost]
public Task<ResponseResult> CancelVersion(string runId, ...)
- 用户取消同版本发布
两者都调用 WriteVersionDecisionAsync 核心方法:
- 验证权限和运行 ID
- 检查是否存在待确认任务
- 将决策写入缓存(5分钟过期)
4. Run(启动构建)
[HttpPost]
public async Task<ResponseResult> Run(BuildOption option, ...)
工作流程:
- 权限检查
- 检查是否已有构建在运行(防止并发)
- 验证版本号格式(
Version.TryParse) - 获取当前用户信息
- 生成唯一运行 ID(使用 MongoDB ObjectId)
- 使用 Hangfire 后台作业队列启动构建任务
- 返回运行 ID 和状态
5. History(历史记录)
[HttpGet]
public async Task<ResponseResult<List<...>>> History(...)
- 获取最近的 CI/CD 历史记录列表
- 使用 Mediator 模式发送查询请求
6. HistoryDetail(历史详情)
[HttpGet]
public async Task<ResponseResult<PipelineHistoryResponse?>> HistoryDetail(...)
- 根据请求参数获取特定历史记录详情
- 处理记录不存在的情况
技术特点
安全性
- 所有方法都需要
[Authorize]认证 - POST 方法使用
[ValidateAntiForgeryToken]防 CSRF 攻击 - 细粒度权限控制(
HangfireDashboardPermission.HasPermission)
- 所有方法都需要
分布式锁机制
- 使用 FusionCache 实现分布式锁(
BuildLockKey) - 防止构建任务并发执行
- 使用 FusionCache 实现分布式锁(
异步处理
- 所有 I/O 操作都使用
async/await - 支持
CancellationToken取消操作
- 所有 I/O 操作都使用
后台作业
- 使用 Hangfire 管理后台构建任务
- 解耦请求响应和实际构建过程
CQRS 模式
- 通过 Mediator 发送查询请求
- 分离命令和查询职责
这个控制器是一个典型的企业级 CI/CD 管理界面后端实现,提供了完整的构建流程控制、状态监控和历史记录功能。
AI 正在分析代码…
评论加载中...