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",
}
);
}
}
文件职责报告: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,供全应用按需构造 HttpClient | BCL |
services.AddHttpClient("edge", ...) | 注册名为 edge 的具名客户端,统一注入 EnvironmentInfo.UserAgent 作为默认 UA | EnvironmentInfo.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 的说明):
services.AddBatchTracking()→ 注册单例IBatchTracker为DistributedBatchTracker(同一文件已确认)。该实现基于分布式缓存做跨实例批次进度追踪,XML 注释提示多实例部署需配置分布式缓存(本文件不涉及缓存配置,配置来源未在本文件确认)。services.AddMessageConsumer<NewsArticleMessage, NewsArticleMessageHandler>()→ 注册 Scoped 处理器 +RabbitMQConsumerBackgroundService<NewsArticleMessage>后台消费者。处理器见 NewsArticleMessageHandler.cs,职责是把新闻文章消息落库,并在有BatchId时调用IBatchTracker.RecordMessageConsumedAsync推进批次进度。services.AddMessageConsumer<BatchCompletionMessage, BatchCompletionMessageHandler>()→ 批次完成消息处理器见 BatchCompletionMessageHandler.cs,负责等待批次消费完成、发布ClearCacheMessage清理缓存、再清理批次记录。#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)
该方法主要做两件事,顺序不能颠倒:
- 请求本地化:
UseRequestLocalization固定为zh-CN(DefaultRequestCulture/SupportedCultures/SupportedUICultures均为zh-CN),需要System.Globalization与Microsoft.AspNetCore.Localization两个 using。 - 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("edge")<br/>AddS3ObjectStorage() / AddCosVideoSyncHandleService()<br/>AddBackupRestoreService()"]
E --> E2["AddScoped<IVideoShardService, VideoShardService><br/>AddNetworkServices() / AddDpzAspNetCoreServices()"]
E --> F["本文件 AddMessageQueueServices(configuration)"]
F --> F1["AddBatchTracking()"]
F --> F2["AddMessageConsumer<NewsArticleMessage, ...><br/>AddMessageConsumer<BatchCompletionMessage, ...>"]
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("/jobs")"]
六、阅读与维护要点小结
- 该文件是 WebApi 的「服务装配清单」:想知道某个能力(对象存储、视频、备份、网络、当前用户、视频分片)在 WebApi 里是否可用,先看
AddProjectServices();想知道 WebApi 消费了哪些 MQ 消息,看AddMessageQueueServices()。 - 服务注册与中间件注册混在同一文件:
AddProjectServices(注册阶段)与HangfireSetting(管道阶段)职责不同,前者由builder.Services在构建前调用,后者必须builder.Build()之后调用。 - 条件编译有两处:
AddMessageQueueServices的 DEBUG 消费者/Outbox Worker,以及HangfireSetting的调用点(在 Program.cs 中;方法本身不限制环境)。发布环境的实际消费者只有NewsArticleMessage、BatchCompletionMessage两个。 - 依赖 global using:本文件的
IServiceCollection、IConfiguration、IApplicationBuilder、EnvironmentInfo等符号来自 Imports.cs 的全局 using,单独移动该文件时需注意补齐。 - 未在本文件确认的部分:
IBatchTracker→DistributedBatchTracker所需的分布式缓存注册来源、AddBusinessServices/AddHangfireService/AddIpRateLimit的具体实现(分别在业务服务层、Dpz.Core.Hangfire 与限流扩展中),本文件仅调用而不定义。