前言
前面几篇文章已经把 JWT、Quartz.NET、RabbitMQ 等基础能力陆续接入到了 HQServer。功能能跑起来只是第一步,随着模块越来越多,如果继续把注册、管道、中间件、数据库初始化都堆在 Program.cs 里,后面维护会越来越吃力。
所以这一篇主要做一次工程化整理:不改变业务目标,不引入复杂框架,只把通用基础能力沉淀到公共层,让启动流程更清晰、异常输出更统一、RabbitMQ 消费更可靠,顺手把 Swagger 的 JWT 调试体验补齐。
一、这次优化解决什么问题
本次优化重点放在框架层和基础设施层,主要包括:
Program.cs瘦身,只保留应用启动的主流程。- 基础能力统一通过扩展方法注册,避免启动类里堆大量细节。
- 新增全局异常处理中间件,接口异常返回统一结构。
- 新增统一响应模型
ApiResult,方便后续接口输出规范化。 - 数据库启动预热增加重试机制,应用启动时更早暴露连接问题。
- RabbitMQ 发布、消费、队列声明和异常处理进一步封装。
- Swagger 增加 JWT Bearer 授权配置,调试受保护接口更方便。
- 修正项目默认命名空间残留,保持项目命名一致。
敏感配置、测试性质的用户相关业务代码本篇不处理,后面会单独清理。
二、Program.cs 瘦身
优化前,很多项目都会把日志、Swagger、数据库、认证、SignalR、Quartz、RabbitMQ、管道配置全部写在 Program.cs。短期看直观,长期看会变成一个“大杂烩”。
优化后的启动入口只保留核心编排:
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseCommonSerilog();
builder.Services.AddHQApplicationInfrastructure(builder.Configuration);
builder.Services.AddControllers();
builder.Services.AddHQSwagger();
var app = builder.Build();
await app.WarmupDatabaseAsync();
app.UseHQApplicationPipeline();
await app.RunAsync();
这样一眼就能看出应用启动顺序:创建宿主、注册基础设施、构建应用、预热数据库、挂载管道、运行服务。
三、统一基础设施注册
应用层新增 AddHQApplicationInfrastructure,把项目需要的基础能力集中注册:
public static IServiceCollection AddHQApplicationInfrastructure(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddAppSignalR();
services.AddHQSqlSugar(configuration);
services.AddHQServices(typeof(IServiceMarker).Assembly);
services.AddHQJwtAuthentication(configuration);
services.AddHQQuartz(configuration, typeof(Program).Assembly);
services.AddHQRabbitMQ(options => configuration.GetSection("RabbitMQ").Bind(options));
return services;
}
这种写法的好处是:以后新增底层能力时,只需要维护一个基础设施入口,不用反复污染启动类。
四、统一应用请求管道
中间件管道也被移动到了独立扩展方法中:
public static WebApplication UseHQApplicationPipeline(this WebApplication app)
{
app.UseHQGlobalExceptionHandler();
app.UseCommonSerilogRequestLogging();
if (SerilogExtensions.IsDevelopmentEnvironment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
else if (SerilogExtensions.IsProductionEnvironment())
{
app.UseHsts();
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapAppHub();
return app;
}
这里最重要的是顺序:全局异常处理放前面,认证授权放在控制器映射前面,开发环境才打开 Swagger。
五、全局异常处理和统一返回结构
框架层新增统一响应模型:
public sealed class ApiResult<T>
{
public bool Success { get; init; }
public string? Message { get; init; }
public T? Data { get; init; }
public static ApiResult<T> Ok(T? data, string? message = null)
=> new() { Success = true, Message = message, Data = data };
public static ApiResult<T> Fail(string message, T? data = default)
=> new() { Success = false, Message = message, Data = data };
}
同时新增全局异常处理中间件。当接口出现未捕获异常时,日志记录详细错误,对外只返回统一的安全提示:
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json; charset=utf-8";
var result = ApiResult.Fail("服务器内部错误,请稍后重试");
await context.Response.WriteAsync(JsonSerializer.Serialize(result, JsonOptions));
这样既避免把堆栈、路径、配置等内部信息暴露给前端,也方便前端按统一格式处理异常。
六、数据库启动预热
数据库连接问题如果等到第一个业务请求才暴露,排查体验并不好。这里新增启动预热:
public static async Task WarmupDatabaseAsync(this WebApplication app)
{
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
const int maxRetries = 3;
for (var retryCount = 0; retryCount < maxRetries; retryCount++)
{
try
{
await db.Ado.GetScalarAsync("SELECT 1");
return;
}
catch when (retryCount < maxRetries - 1)
{
await Task.Delay(TimeSpan.FromSeconds(retryCount + 1));
}
}
await db.Ado.GetScalarAsync("SELECT 1");
}
启动阶段会尝试执行一次轻量 SQL,并在短暂失败时重试。最终仍失败则让应用启动失败,方便尽早发现环境问题。
七、仓储事务策略微调
仓储层保留事务能力,但不再让所有查询默认都进入事务。常规查询默认直接执行,写操作仍使用事务保护:
private static readonly RepositoryExecutionOptions DefaultOptions =
new(false, IsolationLevel.ReadCommitted);
public IBaseRepository<T, TKey> WithTransaction()
{
return CreateExecutor(_options.EnableTransaction(IsolationLevel.ReadCommitted));
}
public IBaseRepository<T, TKey> WithoutTransaction()
{
return CreateExecutor(_options.DisableTransaction());
}
这样可以减少不必要的事务开销,同时保留显式事务能力。对于需要一致性保护的业务,可以通过 WithTransaction() 主动开启。
八、RabbitMQ 消费可靠性增强
前一篇我们已经接入了 RabbitMQ,这次继续补强消费端行为:
- 发布前统一创建通道并设置持久化消息属性。
- 消费端使用手动确认机制。
- 业务处理成功后执行
BasicAckAsync。 - 业务处理失败后执行
BasicNackAsync。 - 通过
RequeueOnConsumerError控制失败消息是否重新入队。 - 队列声明、交换机声明、绑定关系统一交给拓扑管理器处理。
try
{
var json = Encoding.UTF8.GetString(args.Body.Span);
var message = JsonSerializer.Deserialize<T>(json, JsonOptions);
if (message is null)
throw new InvalidOperationException("RabbitMQ 消息反序列化失败");
await handler(message, cancellationToken);
await channel.BasicAckAsync(args.DeliveryTag, false, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "RabbitMQ 消息消费失败");
await channel.BasicNackAsync(
args.DeliveryTag,
false,
_options.RequeueOnConsumerError,
cancellationToken);
}
生产环境里不建议失败消息无脑重新入队,否则可能因为一条坏消息造成无限消费失败。默认不重新入队会更稳,后续可以继续扩展死信队列。
九、Swagger 支持 JWT 调试
Swagger 增加 Bearer Token 配置后,可以直接在页面右上角授权,再调试需要认证的接口:
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "HQServer API",
Version = "v1",
Description = "HQServer 接口文档"
});
var securityScheme = new OpenApiSecurityScheme
{
Name = "Authorization",
Description = "输入 Bearer Token,例如:Bearer {token}",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = JwtBearerDefaults.AuthenticationScheme,
BearerFormat = "JWT"
};
options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, securityScheme);
});
这一步不影响接口权限逻辑,只是提升开发调试体验。
十、构建与安全检查
本次优化完成后,执行项目构建:
dotnet build HQ.Application/HQ.Application.csproj --nologo
构建结果为 0 个错误。当前仍有两个用户测试代码相关警告,本篇先不处理,后续清理测试代码时统一删除。
同时执行包漏洞检查:
dotnet list HQ.Application/HQ.Application.csproj package --vulnerable --include-transitive
检查结果:当前源下没有易受攻击的包。
十一、总结
这次优化没有追求“炫技式重构”,而是围绕企业项目最容易踩坑的几个点做了稳妥整理:
- 启动入口更短,项目结构更清晰。
- 基础设施注册集中,后续扩展更容易。
- 异常输出统一,避免泄露内部细节。
- 数据库启动预热,环境问题更早暴露。
- RabbitMQ 消费失败处理更可靠。
- Swagger 调试 JWT 接口更顺手。
到这里,HQServer 的基础框架已经从“功能可用”进一步走向“结构更稳、维护更舒服”。后续可以继续补充死信队列、统一参数校验、接口审计日志、权限细粒度策略等企业项目常见能力。










暂无评论内容