using System.Globalization;
using Dpz.Core.Backup;
using Dpz.Core.MessageQueue.Extensions;
using Dpz.Core.Public.ViewModel.Messages;
using Dpz.Core.Service.Network;
using Dpz.Core.Shard.Implement.Service;
using Dpz.Core.Shard.Service;
using Dpz.Core.WebApi.Services.Handlers;
using Hangfire;
using Microsoft.AspNetCore.Localization;
#if DEBUG
using Dpz.Core.WebApi.MessageQueueIntegrationTests;
#endif

namespace Dpz.Core.WebApi;

/// <summary>
/// DI扩展
/// </summary>
public static class DependencyInjectionExtensions
{
    /// <param name="services"></param>
    extension(IServiceCollection services)
    {
        /// <summary>
        /// Ioc 注册
        /// </summary>
        /// <returns></returns>
        public IServiceCollection AddProjectServices()
        {
            services.AddHttpClient();

            services.AddHttpClient(
                "edge",
                httpClient =>
                {
                    httpClient.DefaultRequestHeaders.Add("User-Agent", EnvironmentInfo.UserAgent);
                }
            );

            //添加 腾讯云COS储存 S3兼容
            services.AddS3ObjectStorage();
            // 视频处理
            services.AddCosVideoSyncHandleService();
            services.AddBackupRestoreService();

            services.AddScoped<IVideoShardService, VideoShardService>();
            services.AddNetworkServices();
            services.AddDpzAspNetCoreServices();

            return services;
        }

        /// <summary>
        /// 添加消息队列服务 注册消费者
        /// </summary>
        /// <param name="configuration"></param>
        public void AddMessageQueueServices(IConfiguration configuration)
        {
            services.AddBatchTracking();

            services.AddMessageConsumer<NewsArticleMessage, NewsArticleMessageHandler>();

            services.AddMessageConsumer<BatchCompletionMessage, BatchCompletionMessageHandler>();

#if DEBUG
            services.AddMessageOutboxRetryWorker();
            services.AddSingleton<MessageQueueIntegrationTestState>();
            services.AddMessageConsumer<
                MqIntegrationCommandMessage,
                MqIntegrationCommandMessageHandler
            >();
            services.AddMessageConsumer<
                MqIntegrationResultMessage,
                MqIntegrationResultMessageHandler
            >();
            services.AddMessageConsumer<
                MqIntegrationResultHandlerMessage,
                MqIntegrationResultHandlerMessageHandler,
                MqIntegrationHandlerResult
            >();
#endif
        }
    }

    /// <summary>
    /// hangfire UI设置及周期性任务调度
    /// </summary>
    /// <param name="app"></param>
    /// <returns></returns>
    public static void HangfireSetting(this IApplicationBuilder app)
    {
        var supportedCultures = new[] { new CultureInfo("zh-CN") };
        app.UseRequestLocalization(
            new RequestLocalizationOptions
            {
                DefaultRequestCulture = new RequestCulture("zh-CN"),
                // Formatting numbers, dates, etc.
                SupportedCultures = supportedCultures,
                // UI strings that we have localized.
                SupportedUICultures = supportedCultures,
            }
        );

        app.UseHangfireDashboard(
            "/jobs",
            new DashboardOptions
            {
                AppPath = "/",
                StatsPollingInterval = 5000,
                //可编辑任务
                IsReadOnlyFunc = _ => false,
                DarkModeEnabled = true,
                DashboardTitle = "任务管理",
                FaviconPath = "/favicon.ico",
            }
        );
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

文件职责报告:src/Dpz.Core.WebApi/DependencyInjectionExtensions.cs

一、总体定位

这是 Dpz.Core.WebApi(对外 Web API 宿主)的依赖注入与服务装配集中入口,同时附带一处应用层中间件配置(本地化 + Hangfire 后台任务面板)。文件内部只有两个类型成员容器:静态类 DependencyInjectionExtensions,其中:

  • 用 C# 14 的 extension(IServiceCollection services) 扩展成员块声明了两个针对 IServiceCollection 的扩展方法(AddProjectServices、AddMessageQueueServices),调用形式仍是熟悉的 services.AddProjectServices()。
  • 用传统的 public static void HangfireSetting(this IApplicationBuilder app) 扩展方法声明了中间件配置。

调用方是 Program.cs,核心一行是:

services.AddDefaultServices().AddProjectServices().AddMessageQueueServices(configuration);

即:业务/仓储服务由 ServiceDependencyInjection.cs 的 AddDefaultServices() 注册,本文件负责补上 WebApi 宿主专属的横切依赖(HTTP 客户端、对象存储、视频、备份、网络、AspNetCore 适配)与 消息消费者。


二、AddProjectServices():宿主级基础设施注册

按文件中的顺序:

注册说明实现位置(已确认)
services.AddHttpClient()注册默认 IHttpClientFactory,供全应用按需构造 HttpClientBCL
services.AddHttpClient("edge", ...)注册名为 edge 的具名客户端,统一注入 EnvironmentInfo.UserAgent 作为默认 UAEnvironmentInfo.cs
services.AddS3ObjectStorage()腾讯云 COS(S3 兼容)对象存储服务ObjectStorageExtension.cs
services.AddCosVideoSyncHandleService()COS 视频处理(AddHttpClient<IVideoCl...> 形式,由同一扩展类提供)ObjectStorageExtension.cs
services.AddBackupRestoreService()备份/还原服务(IBackupService 等)BackupExtensions.cs
services.AddScoped<IVideoShardService, VideoShardService>()视频分片服务接口→实现绑定实现类见 VideoShardService.cs
services.AddNetworkServices()网络能力(如 IOpenAiService 等单例)NetworkServiceExtensions.cs
services.AddDpzAspNetCoreServices()IHttpContextAccessor + IHttpCurrentUserService(当前用户上下文)AspNetCoreServiceCollectionExtensions.cs

一个值得注意的点:这些扩展方法对应的 using(Dpz.Core.Backup、Dpz.Core.Service.Network、Dpz.Core.Service.ObjectStorage、Dpz.Core.AspNetCore 等)只有一部分在本文件顶部显式声明,其余依赖项目的 global using 清单 Imports.cs(例如 IServiceCollection、IConfiguration、IApplicationBuilder、Dpz.Core.Infrastructure、Dpz.Core.Service.ObjectStorage)。

该类整体不负责「业务服务」的批量注册(那是源生成器驱动的 AddGeneratedRepositoryServices(),见 ServiceDependencyInjection.cs),本文件只做「宿主装配」,所以它是薄而显式的一层,便于对照 Program.cs 阅读。


三、AddMessageQueueServices(IConfiguration configuration)

该方法把消息队列的消费者绑定到当前进程(发布者由 AddBusinessServices() 内部的 AddRabbitMQ(configuration) 提供,参见 MessageQueueServiceExtensions.cs 与 AGENTS.md 的说明):

  1. services.AddBatchTracking() → 注册单例 IBatchTracker 为 DistributedBatchTracker(同一文件已确认)。该实现基于分布式缓存做跨实例批次进度追踪,XML 注释提示多实例部署需配置分布式缓存(本文件不涉及缓存配置,配置来源未在本文件确认)。
  2. services.AddMessageConsumer<NewsArticleMessage, NewsArticleMessageHandler>() → 注册 Scoped 处理器 + RabbitMQConsumerBackgroundService<NewsArticleMessage> 后台消费者。处理器见 NewsArticleMessageHandler.cs,职责是把新闻文章消息落库,并在有 BatchId 时调用 IBatchTracker.RecordMessageConsumedAsync 推进批次进度。
  3. services.AddMessageConsumer<BatchCompletionMessage, BatchCompletionMessageHandler>() → 批次完成消息处理器见 BatchCompletionMessageHandler.cs,负责等待批次消费完成、发布 ClearCacheMessage 清理缓存、再清理批次记录。
  4. #if DEBUG 分支(仅调试构建启用):
    • services.AddMessageOutboxRetryWorker() → 注册 Outbox 补发服务与后台 Worker(IMessageOutboxRetryService 为单例);
    • services.AddSingleton<MessageQueueIntegrationTestState>() → 集成测试状态仓库,见 MessageQueueIntegrationTestState.cs;
    • 注册三组集成测试消费者:普通消息(MqIntegrationCommandMessageMessage→MqIntegrationCommandMessageHandler)、结果消息、以及带返回值的 IMessageHandler<TMessage, TResult> 形式(MqIntegrationResultHandlerMessageHandler + MqIntegrationHandlerResult)。契约定义在 MessageQueueIntegrationTestContracts.cs(整个文件被 #if DEBUG 包裹),处理器实现在 MessageQueueIntegrationTestHandlers.cs。带返回值的重载会改为注册 RabbitMQConsumerBackgroundServiceWithResult<TMessage, TResult>。

方法签名声明为 void,因此它在 Program.cs 的链式调用中必须位于最后。


四、HangfireSetting(this IApplicationBuilder app)

该方法主要做两件事,顺序不能颠倒:

  1. 请求本地化:UseRequestLocalization 固定为 zh-CN(DefaultRequestCulture / SupportedCultures / SupportedUICultures 均为 zh-CN),需要 System.Globalization 与 Microsoft.AspNetCore.Localization 两个 using。
  2. Hangfire Dashboard:把任务面板挂在 /jobs,配置项包括 AppPath = "/"、5s 统计轮询、IsReadOnlyFunc = _ => false(允许在面板上编辑/重试任务,属于开发调试便利设置)、深色模式、标题「任务管理」与 favicon。

需要注意的是:当前 WebApi 中该方法的调用点位于 Program.cs 的 #if DEBUG 块内(app.HangfireSetting();),即 Release 构建不会暴露 /jobs 面板。此外,同名扩展方法在仓库中并非唯一实现:AppExtensions.cs 与 HangfireConfiguration.cs 中存在结构相同的方法,说明这是各宿主的「本地化 + Dashboard」样板代码的第三次重复(是否应抽公共库属于设计问题,这里只做事实陈述)。


五、与 Program.cs 的协作时序

flowchart TD
    A["Program.cs: WebApplicationBuilder 建立"] --> A1["UseAgileConfig + 日志/配置"]
    A1 --> B["services.AddBusinessServices(configuration)<br/>内部 AddRabbitMQ(configuration) 提供发布者"]
    B --> C["services.AddIpRateLimit(configuration)"]
    C --> D["services.AddDefaultServices()"]
    D --> E["本文件 AddProjectServices()"]
    E --> E1["AddHttpClient() / AddHttpClient(&quot;edge&quot;)<br/>AddS3ObjectStorage() / AddCosVideoSyncHandleService()<br/>AddBackupRestoreService()"]
    E --> E2["AddScoped&lt;IVideoShardService, VideoShardService&gt;<br/>AddNetworkServices() / AddDpzAspNetCoreServices()"]
    E --> F["本文件 AddMessageQueueServices(configuration)"]
    F --> F1["AddBatchTracking()"]
    F --> F2["AddMessageConsumer&lt;NewsArticleMessage, ...&gt;<br/>AddMessageConsumer&lt;BatchCompletionMessage, ...&gt;"]
    F --> F3["#if DEBUG:<br/>AddMessageOutboxRetryWorker()<br/>+ 集成测试消费者/单例状态"]
    F --> G["AddCors / Authentication / PermissionAuthorization<br/>MCP / RazorPages / AddHangfireService(...)"]
    G --> H["builder.Build()"]
    H --> I["UseRouting / UseAuthentication / UseAuthorization<br/>UseOutputCache / UseRateLimiter / MapControllers"]
    I --> J["#if DEBUG: app.HangfireSetting()"]
    J --> J1["UseRequestLocalization(zh-CN)"]
    J --> J2["UseHangfireDashboard(&quot;/jobs&quot;)"]

六、阅读与维护要点小结

  1. 该文件是 WebApi 的「服务装配清单」:想知道某个能力(对象存储、视频、备份、网络、当前用户、视频分片)在 WebApi 里是否可用,先看 AddProjectServices();想知道 WebApi 消费了哪些 MQ 消息,看 AddMessageQueueServices()。
  2. 服务注册与中间件注册混在同一文件:AddProjectServices(注册阶段)与 HangfireSetting(管道阶段)职责不同,前者由 builder.Services 在构建前调用,后者必须 builder.Build() 之后调用。
  3. 条件编译有两处:AddMessageQueueServices 的 DEBUG 消费者/Outbox Worker,以及 HangfireSetting 的调用点(在 Program.cs 中;方法本身不限制环境)。发布环境的实际消费者只有 NewsArticleMessage、BatchCompletionMessage 两个。
  4. 依赖 global using:本文件的 IServiceCollection、IConfiguration、IApplicationBuilder、EnvironmentInfo 等符号来自 Imports.cs 的全局 using,单独移动该文件时需注意补齐。
  5. 未在本文件确认的部分:IBatchTracker→DistributedBatchTracker 所需的分布式缓存注册来源、AddBusinessServices/AddHangfireService/AddIpRateLimit 的具体实现(分别在业务服务层、Dpz.Core.Hangfire 与限流扩展中),本文件仅调用而不定义。
评论加载中...