using Dpz.Core.Backup;
using Dpz.Core.Infrastructure;
using Dpz.Core.MessageQueue.Extensions;
using Dpz.Core.Public.ViewModel.Messages;
using Dpz.Core.Service.Mcp;
using Dpz.Core.Service.Network;
using Dpz.Core.Service.ObjectStorage;
using Dpz.Core.Service.RepositoryService;
using Dpz.Core.Service.RepositoryServiceImpl;
using Dpz.Core.Web.Jobs.Hangfire;
using Dpz.Core.Web.Jobs.Hangfire.Build;
using Dpz.Core.Web.Jobs.MessageHandlers;
using Dpz.Core.Web.Jobs.Services;
using Dpz.Core.Web.Jobs.Services.FileTransfer;
using Dpz.Core.Web.Jobs.Services.Implements;

namespace Dpz.Core.Web.Jobs;

/// <summary>
/// Web.Jobs 宿主的服务注册扩展方法。
/// </summary>
public static class RegisterDependencyInjectionExtensions
{
    /// <summary>
    /// 注册 Web.Jobs 运行所需的默认业务服务。
    /// </summary>
    /// <param name="services">服务集合。</param>
    /// <returns>注册后的服务集合。</returns>
    public static IServiceCollection AddDefaultInjectServices(this IServiceCollection services)
    {
        services.AddSingleton<IPushMessage, PushMessage>();
        // 文件传输:房间存储(单例,内存存储)
        services.AddSingleton<IFileTransferRoomStore, FileTransferRoomStore>();
        services.AddSingleton<IFileTransferSessionService, FileTransferSessionService>();
        // 文件传输:过期房间后台清理
        services.AddHostedService<FileTransferRoomCleanupService>();
        services.AddScoped<IBackupRecordService, BackupRecordService>();

        services
            .AddHttpClient(
                "edge",
                (_, httpClient) =>
                {
                    httpClient.DefaultRequestHeaders.Add("User-Agent", EnvironmentInfo.UserAgent);
                    httpClient.Timeout = TimeSpan.FromMinutes(5);
                }
            )
            .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler
            {
                ServerCertificateCustomValidationCallback = (_, _, _, _) => true,
                MaxConnectionsPerServer = 10,
            });

        // 音乐同步下载的大文件耗时较长,使用独立的超时和连接策略
        services
            .AddHttpClient(
                MusicSyncActivator.HttpClientName,
                (_, httpClient) =>
                {
                    httpClient.DefaultRequestHeaders.Add("User-Agent", EnvironmentInfo.UserAgent);
                    httpClient.Timeout = TimeSpan.FromMinutes(30);
                }
            )
            .ConfigurePrimaryHttpMessageHandler(_ => new SocketsHttpHandler
            {
                ConnectTimeout = TimeSpan.FromSeconds(30),
                PooledConnectionLifetime = TimeSpan.FromMinutes(10),
                MaxConnectionsPerServer = 4,
            });

        services.AddNetworkServices();
        services.AddInProcessCodeMcp();
        services.AddS3ObjectStorage();
        services.AddBackupRestoreService();

        // 注册邮件发送辅助服务
        services.AddScoped<EmailSenderHelper>();

        // 获取ssh配置和校验服务
        services.AddScoped<SshConfigurationService>();
        services.AddHttpClient(
            PipelineVersionProbe.HttpClientName,
            (_, httpClient) =>
            {
                httpClient.Timeout = TimeSpan.FromSeconds(5);
                httpClient.DefaultRequestHeaders.Add("User-Agent", EnvironmentInfo.UserAgent);
            }
        );
        services.AddScoped<PipelineVersionProbe>();
        return services;
    }

    /// <summary>
    /// 注册 Web.Jobs 需要消费的消息队列处理器和 Outbox 后台补发服务
    /// </summary>
    /// <param name="services">服务集合。</param>
    /// <returns>注册后的服务集合。</returns>
    public static IServiceCollection AddMessageQueueServices(this IServiceCollection services)
    {
        // 注册消息消费者
        services.AddMessageConsumer<DeleteMarkdownMessage, DeleteMarkdownHandler>();
        services.AddMessageConsumer<RemoveImagesMessage, RemoveImagesHandler>();
        services.AddMessageConsumer<AddCommentCountMessage, AddCommentCountHandler>();
        services.AddMessageConsumer<AddDanmakuCountMessage, AddDanmakuCountHandler>();
        services.AddMessageConsumer<AnalyzeCodeMessage, AnalyzeCodeHandler>();
        services.AddMessageConsumer<SendMasterEmailMessage, SendMasterEmailHandler>();
        services.AddMessageConsumer<SendReplyEmailMessage, SendReplyEmailHandler>();
        services.AddMessageConsumer<SendEmailVerifyCodeMessage, SendEmailVerifyCodeHandler>();
        services.AddMessageConsumer<GetImageMetadataMessage, GetImageMetadataHandler>();
        services.AddMessageOutboxRetryWorker();

        return services;
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

RegisterDependencyInjectionExtensions.cs 说明

文件定位

该文件位于 Dpz.Core.Web.Jobs 宿主项目根目录,定义了一个静态扩展类 RegisterDependencyInjectionExtensions,为 IServiceCollection 提供两个扩展方法:AddDefaultInjectServices()(默认业务服务)与 AddMessageQueueServices()(消息队列消费服务)。它是整个 Jobs 宿主的 DI 装配中心——所有后台任务、消息消费、文件传输、推送等能力都在这里一次性登记。

该文件本身是纯声明式注册代码,没有复杂执行逻辑,但涉及大量跨项目/跨命名空间符号,下面按已验证的源码关系逐项说明。

宿主如何调用它

在入口 Program.cs 中有如下链式调用:

services.AddBusinessServices(configuration);
services.AddDefaultServices().AddDefaultInjectServices().AddMessageQueueServices();

其中 AddBusinessServices、AddDefaultServices 来自基础服务层(非本文件),本文件的两个方法是 Web.Jobs 自身特有依赖的补充,随后 Program.cs 还会注册 OpenIddict 客户端、Hangfire(AddAppHangfireService)、MVC 与 SignalR Hub 等。

方法一:AddDefaultInjectServices() — 默认业务服务

按生命周期分组,注册内容如下:

1. 单例业务服务

  • IPushMessage → PushMessage:推送服务。接口定义见 IPushMessage.cs,提供日志消息、CnBeta 消息、代码分析进度、应用通知四类推送(SignalR 对外通知)。
  • 文件传输(内存态):IFileTransferRoomStore → FileTransferRoomStore(房间存储)与 IFileTransferSessionService → FileTransferSessionService(会话服务),接口/实现均在 Services/FileTransfer 目录下。

2. 后台托管服务

  • FileTransferRoomCleanupService(HostedService):见 FileTransferRoomCleanupService.cs,每 30 秒用 PeriodicTimer 扫描过期未配对的传输房间,并通过 IHubContext<FileTransferHub> 向发起方推送 roomExpired 通知。

3. Scoped 服务

  • IBackupRecordService → BackupRecordService(备份记录,来自 Dpz.Core.Service.RepositoryService*)。
  • EmailSenderHelper(邮件发送辅助)、SshConfigurationService(读取/校验 SSH 配置,位于 Hangfire/Build)、PipelineVersionProbe(探测各发布目标版本,位于 PipelineVersionProbe.cs)。

4. 三组命名 HttpClient(通过 IHttpClientFactory 注册)

客户端名超时/策略用途
"edge"5 分钟超时、固定 User-Agent、跳过证书校验、单服务器最多 10 连接通用外站抓取,在本项目内被 TaskService.cs、ItHomeActivator.cs 通过 CreateClient("edge") 使用
MusicSyncActivator.HttpClientName("music-sync")30 分钟超时、SocketsHttpHandler 连接池 10 分钟、并发 4音乐同步大文件下载(常量定义见 MusicSyncActivator.cs)
PipelineVersionProbe.HttpClientName("pipeline-version")5 秒超时 + User-Agent发布流水线版本探测(常量定义见 PipelineVersionProbe.cs)

5. 子项目扩展方法聚合

  • AddNetworkServices()(来自 Dpz.Core.Service.Network)、AddInProcessCodeMcp()(Dpz.Core.Service.Mcp)、AddS3ObjectStorage()(Dpz.Core.Service.ObjectStorage)、AddBackupRestoreService()(Dpz.Core.Backup)。这些扩展的实际实现文件未逐一读取,仅通过文件顶部 using 与其所在的程序集命名空间对应确认。

方法二:AddMessageQueueServices() — 消息队列消费

逐一注册了 9 组「消息类型 → 处理器」,消息类型来自 Dpz.Core.Public.ViewModel.Messages,处理器全部位于 MessageHandlers 目录:

消息类型处理器(均已确认存在)
DeleteMarkdownMessageDeleteMarkdownHandler.cs
RemoveImagesMessageRemoveImagesHandler.cs
AddCommentCountMessageAddCommentCountHandler.cs
AddDanmakuCountMessageAddDanmakuCountHandler.cs
AnalyzeCodeMessageAnalyzeCodeHandler.cs
SendMasterEmailMessageSendMasterEmailHandler.cs
SendReplyEmailMessageSendReplyEmailHandler.cs
SendEmailVerifyCodeMessageSendEmailVerifyCodeHandler.cs
GetImageMetadataMessageGetImageMetadataHandler.cs

这些调用均落到 Dpz.Core.MessageQueue.Extensions 命名空间的基础设施扩展。已通过阅读 MessageQueueServiceExtensions.cs 确认其语义:

  • AddMessageConsumer<TMessage, THandler>():把处理器注册为 Scoped 的 IMessageHandler<TMessage>,同时注册一个 RabbitMQConsumerBackgroundService<TMessage> 托管后台服务来订阅并消费对应队列。
  • AddMessageOutboxRetryWorker():注册 Singleton 的 IMessageOutboxRetryService 与 MessageOutboxRetryBackgroundService 托管后台服务,用于把发布失败的 Outbox 记录定时补发。

说明:RabbitMQ 基础设施的注册(连接工厂、路由约定、发布者、配置节 AddRabbitMQ(...))不在本文件内,由宿主基础服务层完成;本文件只负责「消费端」装配。

整体协作流程

flowchart TD
    Start["Program.cs 宿主启动<br/>WebApplication.CreateBuilder"] --> Chain["AddDefaultServices()<br/>AddBusinessServices()(基础服务层,非本文件)"]

    Chain --> M1["AddDefaultInjectServices()<br/>(本文件 · 默认业务服务)"]
    M1 --> P1["Singleton: PushMessage<br/>对外推送日志/进度/通知"]
    M1 --> P2["Singleton: 文件传输房间/会话<br/>FileTransferRoomStore / FileTransferSessionService"]
    M1 --> P3["HostedService: FileTransferRoomCleanupService<br/>每30s清理过期房间并通知 FileTransferHub"]
    M1 --> P4["Scoped: 备份记录 / 邮件 / SSH配置 / 版本探测"]
    M1 --> P5["HttpClientFactory: edge / music-sync / pipeline-version<br/>(差异化超时与连接策略)"]
    M1 --> P6["子项目扩展: Network / InProcessCodeMcp / S3 / BackupRestore"]

    Chain --> M2["AddMessageQueueServices()<br/>(本文件 · 消息队列消费)"]
    M2 --> C1["9 × AddMessageConsumer<TMsg,THandler><br/>处理器注册为 Scoped + RabbitMQ 后台消费服务"]
    M2 --> C2["AddMessageOutboxRetryWorker<br/>Outbox 失败消息补发后台服务"]

    C1 --> RT["运行时: RabbitMQ 消息到达"]
    RT --> H["对应处理器执行(MessageHandlers 目录)"]
    H --> RES["处理成功 / 失败记录 Outbox 待补发"]
    C2 -.定时补发.-> RT

    P5 -. "CreateClient('edge')" .-> FE["TaskService / ItHomeActivator 外站抓取"]
    P3 -. SignalR .-> HUB["FileTransferHub 房间过期通知"]

小结

  • 本文件把 Jobs 宿主的依赖拆成两个语义清晰、可按需组合的扩展方法:AddDefaultInjectServices(业务服务装配)与 AddMessageQueueServices(消息消费装配),只做声明式注册,逻辑简单直接。
  • 覆盖了该宿主的三类支撑能力:后台定时任务(配合 Hangfire 的 Activator/Probe)、RabbitMQ 消息消费 + Outbox 补发、文件传输与实时推送(SignalR),并通过多个命名 HttpClient 工厂区分不同场景的网络策略(抓取、大文件下载、快速版本探测)。
  • 符号协作关系均已在源码中验证;NetworkServiceExtensions、McpServiceExtensions、ObjectStorageExtension、BackupExtensions 四个子项目扩展的具体实现文件未逐一读取,仅依据 using 与命名空间推定其来自相应程序集。
评论加载中...