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, ...)

工作流程:

  1. 权限检查
  2. 检查是否已有构建在运行(防止并发)
  3. 验证版本号格式(Version.TryParse
  4. 获取当前用户信息
  5. 生成唯一运行 ID(使用 MongoDB ObjectId)
  6. 使用 Hangfire 后台作业队列启动构建任务
  7. 返回运行 ID 和状态

5. History(历史记录)

[HttpGet]
public async Task<ResponseResult<List<...>>> History(...)
  • 获取最近的 CI/CD 历史记录列表
  • 使用 Mediator 模式发送查询请求

6. HistoryDetail(历史详情)

[HttpGet]
public async Task<ResponseResult<PipelineHistoryResponse?>> HistoryDetail(...)
  • 根据请求参数获取特定历史记录详情
  • 处理记录不存在的情况

技术特点

  1. 安全性

    • 所有方法都需要 [Authorize] 认证
    • POST 方法使用 [ValidateAntiForgeryToken] 防 CSRF 攻击
    • 细粒度权限控制(HangfireDashboardPermission.HasPermission
  2. 分布式锁机制

    • 使用 FusionCache 实现分布式锁(BuildLockKey
    • 防止构建任务并发执行
  3. 异步处理

    • 所有 I/O 操作都使用 async/await
    • 支持 CancellationToken 取消操作
  4. 后台作业

    • 使用 Hangfire 管理后台构建任务
    • 解耦请求响应和实际构建过程
  5. CQRS 模式

    • 通过 Mediator 发送查询请求
    • 分离命令和查询职责

这个控制器是一个典型的企业级 CI/CD 管理界面后端实现,提供了完整的构建流程控制、状态监控和历史记录功能。

评论加载中...