前言
上一篇我们已经给 HQServer 的 RabbitMQ 封装补上了死信队列能力,到这里消息发布、消费、手动 Ack/Nack、消费失败处理和死信队列都已经有了统一入口。
不过基础能力越来越多以后,框架能跑起来只是第一步。真正进入项目长期使用阶段,还需要继续处理几个很实际的问题:接口成功和失败返回是否统一、异常能不能快速定位、服务是否具备存活和就绪检查、跨仓储写操作如何明确事务边界、HTTP 请求和文件存储是否有统一入口、RabbitMQ 消费者停止时能否正确释放资源,以及新同事拿到项目后能不能快速启动。
所以这一篇继续对 HQServer 做一次生产基线加固。目标依然保持不变:不引入 CQRS、MediatR 等重型架构,不把简单项目做复杂;只把企业项目中高频、通用、容易遗漏的基础能力继续沉淀到 HQ.Common,让业务层保持轻量。
一、这次优化内容
本次主要完成以下内容:
- 统一接口响应、业务错误码和 TraceId。
- 增加参数校验过滤器和更明确的全局异常映射。
- 补齐 HTTPS 重定向、CORS、存活检查和数据库就绪检查。
- 增加跨仓储事务入口
IHQUnitOfWork。 - 将
HttpClientHelper接入IHttpClientFactory。 - 增加轻量内存缓存和本地文件存储抽象。
- 补强 Quartz 错失触发策略和 RabbitMQ 消费生命周期。
- 增加基础测试、Docker Compose、环境变量示例和 README。
二、统一接口响应和错误码
前面已经有 ApiResult,但只靠 Success 和 Message,前端很难稳定区分参数错误、业务冲突、未授权、资源不存在等情况。生产环境出现问题时,前端报一个“接口失败”,后端也很难快速定位到对应日志。
这次给统一返回结构补上 Code 和 TraceId:
public sealed class ApiResult<T>
{
public bool Success { get; init; }
public int Code { get; init; }
public string? Message { get; init; }
public string? TraceId { get; init; }
public T? Data { get; init; }
public static ApiResult<T> Ok(T? data, string? message = null, string? traceId = null)
=> new()
{
Success = true,
Code = ApiResultCodes.Success,
Message = message,
TraceId = traceId,
Data = data
};
}
同时增加常用错误码和业务异常:
public static class ApiResultCodes
{
public const int Success = 0;
public const int ValidationError = 40001;
public const int Unauthorized = 40101;
public const int Forbidden = 40301;
public const int NotFound = 40401;
public const int BusinessError = 40002;
public const int InternalError = 50000;
}
public sealed class BusinessException : Exception
{
public int Code { get; }
public BusinessException(string message, int code = ApiResultCodes.BusinessError)
: base(message)
{
Code = code;
}
}
业务层遇到可预期的业务失败时,可以直接抛出 BusinessException。全局异常中间件会负责转成统一响应,不需要每个 Controller 都重复写 try-catch。
三、全局异常处理和参数校验
异常处理不应该只区分“成功”和“500”。这次在全局异常中间件中按异常类型映射 HTTP 状态码和业务码:
BusinessException:400,返回业务错误码。ArgumentException:400,返回参数错误码。UnauthorizedAccessException:403,返回无权限提示。- 未知异常:500,对外返回安全提示,详细异常仍保留在日志中。
另外,在注册 Controller 时加入全局参数校验过滤器:
builder.Services.AddControllers(options =>
options.Filters.Add<ApiValidationFilter>());
这样模型绑定失败时,接口直接返回统一的参数错误结构,而不是每个接口都手动判断 ModelState.IsValid。
四、TraceId 贯穿请求和日志定位
线上排查最怕“前端说刚才有一个请求失败了”,但没有时间、没有请求编号、没有复现条件。为此在 Web 默认管道里统一追加响应头:
app.Use(async (context, next) =>
{
context.Response.Headers.TryAdd("X-Trace-Id", context.TraceIdentifier);
await next();
});
现在接口响应体中的 traceId 和响应头 X-Trace-Id 都可以对应到服务端日志。出现异常时,前端只要把 TraceId 提供出来,就能更快找到同一条请求链路。
五、补齐 Web 生产基线
框架以前已经有认证、授权、Swagger、日志等能力,但生产服务还需要最基础的 HTTPS、跨域和健康检查能力。
1. HTTPS 和 CORS
app.UseHttpsRedirection();
app.UseCors(HQWebDefaults.DefaultCorsPolicyName);
CORS 从 Cors:AllowedOrigins 读取允许来源。未配置时保持默认宽松策略,实际生产环境建议明确填写前端域名,不要长期使用 *。
2. 存活和就绪检查
新增两个健康检查地址:
/health:存活检查,只确认应用本身能响应。/health/ready:就绪检查,额外执行SELECT 1检查数据库连接。
services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"])
.AddCheck<DatabaseHealthCheck>("database", tags: ["ready"]);
容器、负载均衡或部署脚本可以通过这两个地址判断应用是否应该接收流量。数据库不可用时,应用仍可能存活,但不应该被判定为就绪。
六、跨仓储事务使用 IHQUnitOfWork
仓储层的单次新增、修改、删除默认仍使用事务保护。但当一个业务同时操作多个仓储时,事务边界不能再靠每个仓储自己判断,否则无法保证整体一致性。
这次增加轻量的 IHQUnitOfWork:
public interface IHQUnitOfWork
{
Task ExecuteAsync(
Func<CancellationToken, Task> action,
IsolationLevel isolationLevel = IsolationLevel.ReadCommitted,
CancellationToken cancellationToken = default);
Task<TResult> ExecuteAsync<TResult>(
Func<CancellationToken, Task<TResult>> action,
IsolationLevel isolationLevel = IsolationLevel.ReadCommitted,
CancellationToken cancellationToken = default);
}
业务层使用方式如下:
public sealed class OrderService(IHQUnitOfWork unitOfWork)
{
public Task CreateAsync(CancellationToken cancellationToken)
{
return unitOfWork.ExecuteAsync(async ct =>
{
// 创建订单
// 扣减库存
// 写入业务日志
await Task.CompletedTask;
}, cancellationToken: cancellationToken);
}
}
这样单仓储写操作保持简单,真正需要跨仓储一致性的业务再明确使用工作单元,不需要在业务代码中手写 Begin、Commit、Rollback。
七、HttpClientHelper 接入 IHttpClientFactory
之前的 HttpClientHelper 已经封装了 GET、POST、JSON、表单、超时、日志和响应读取。实际项目中如果每次都自己 new HttpClient,容易出现连接复用不稳定和生命周期难管理的问题。
这次增加统一注册入口:
builder.Services.AddHQHttpClient(builder.Configuration);
注册后由 IHttpClientFactory 管理 HttpClient 生命周期,同时保留原有构造函数,避免影响已有调用代码。配置仍从 HttpClient 节点读取,例如超时时间、基础地址、连接数和 User-Agent。
八、缓存和文件存储基础抽象
缓存和文件上传几乎每个项目都会用到,但当前阶段没有必要一开始就强依赖 Redis、MinIO 或 OSS。因此先提供最小抽象和本地实现,后续需要接 Redis、MinIO、阿里云 OSS 时只需要新增 Provider。
1. IHQCache
public interface IHQCache
{
Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
Task SetAsync<T>(string key, T value, TimeSpan? absoluteExpiration = null,
CancellationToken cancellationToken = default);
Task RemoveAsync(string key, CancellationToken cancellationToken = default);
}
当前默认实现基于 IMemoryCache,适合单机项目或开发阶段使用。
2. IFileStorage
public interface IFileStorage
{
Task<string> SaveAsync(string relativePath, Stream content,
CancellationToken cancellationToken = default);
Task<Stream?> OpenReadAsync(string relativePath,
CancellationToken cancellationToken = default);
Task DeleteAsync(string relativePath, CancellationToken cancellationToken = default);
}
本地实现会将相对路径解析到配置的根目录,并拒绝越过根目录的路径,避免 ../ 目录穿越问题。
九、Quartz 和 RabbitMQ 继续补强
1. Quartz 错失触发策略
服务停机一段时间后恢复,如果定时任务把停机期间错过的任务全部补跑,可能瞬间触发大量报表、同步或清理任务。对于 HQServer 当前大多数固定周期任务,恢复后等待下一次正常触发更稳。
.WithCronSchedule(definition.Cron,
cron => cron.WithMisfireHandlingInstructionDoNothing());
后续如果某个任务确实需要补偿执行,可以再为它单独设计策略,而不是让所有任务默认集中补跑。
2. RabbitMQ 消费者生命周期
上一篇已经完成了手动 Ack/Nack 和死信队列。这次继续补上消费者生命周期管理:
- 订阅时按
ConsumerConcurrency设置 QoS 预取数量。 - 记录已创建的 Channel 和 ConsumerTag。
- 应用停止时取消消费者并释放 Channel。
- 记录消费者注册和取消日志,方便排查消费状态。
await channel.BasicQosAsync(
0,
(ushort)Math.Clamp(_options.ConsumerConcurrency, 1, ushort.MaxValue),
false,
cancellationToken);
这样消费者不再只负责“收到消息后处理”,也能在应用关闭时更平稳地停止,避免无序退出留下不清晰的消费状态。
十、补齐测试、Docker 和 README
框架不能只靠“看代码感觉没问题”。这次新增 HQ.Tests 测试项目,先覆盖两个基础点:
- 失败响应保留正确的
Code和TraceId。 - Quartz Cron 表达式有效和无效场景的验证。
同时增加:
docker-compose.yml:本地 SQL Server 和 RabbitMQ 依赖服务。.env.example:本地环境变量示例。README.md:架构、分层职责、快速启动、事务、Quartz、RabbitMQ 使用方式和设计约定。Directory.Packages.props:依赖版本目录入口,方便后续统一整理依赖版本。
验证命令:
dotnet restore
dotnet build WebApplication.sln -c Release
dotnet test WebApplication.sln -c Release
本次在 .NET SDK 9.0.316 环境验证结果:
Build succeeded.
0 Warning(s)
0 Error(s)
Passed! - Failed: 0, Passed: 3, Skipped: 0, Total: 3
十一、总结
这次不是新增一个单独的业务模块,而是继续把 HQServer 从“基础能力已经能用”往“默认行为更稳、部署和维护更省心”推进。
完成后,框架新增或补强了这些能力:
- 接口响应、错误码和 TraceId 统一。
- 参数校验和全局异常输出统一。
- HTTPS、CORS、存活检查、数据库就绪检查。
- 跨仓储事务入口。
- IHttpClientFactory、缓存和文件存储基础抽象。
- Quartz 错失触发保护。
- RabbitMQ 消费 QoS 和停止释放。
- 基础测试、本地依赖容器和项目 README。
HQServer 后续仍然会坚持轻量、稳定和高度封装的方向。Redis 缓存、MinIO/OSS 文件存储、RabbitMQ 重试策略、审计字段自动填充、软删除过滤等能力,都可以在当前 Common 层基础上继续扩展,不需要推翻现有结构。










暂无评论内容