XY.Core
0.1.0
dotnet add package XY.Core --version 0.1.0
NuGet\Install-Package XY.Core -Version 0.1.0
<PackageReference Include="XY.Core" Version="0.1.0" />
<PackageVersion Include="XY.Core" Version="0.1.0" />
<PackageReference Include="XY.Core" />
paket add XY.Core --version 0.1.0
#r "nuget: XY.Core, 0.1.0"
#:package XY.Core@0.1.0
#addin nuget:?package=XY.Core&version=0.1.0
#tool nuget:?package=XY.Core&version=0.1.0
XY.Core
XY.Core 是 XY 后端框架的ASP.NET Core Web 宿主基础设施包。
它适用于基于 ASP.NET Core Minimal API 的 API 宿主项目,提供统一的服务注册和中间件管道入口,帮助业务项目快速接入认证、鉴权、统一响应、OpenAPI、日志、上传、限流、缓存、健康检查等常用能力。
快速接入
运行环境:.NET 10 / ASP.NET Core 10。包名与命名空间均为 XY.Core,此版本为 0.1.0。
包已发布到所配置的 NuGet 源后,安装指定版本:
dotnet add package XY.Core --version 0.1.0
在 API 项目的 Program.cs 中注册并启用:
using XY.Core;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCore(builder.Configuration);
var app = builder.Build();
app.UseCore();
app.MapGet("/api/ping", () => new { ok = true }).AllowAnonymous();
app.Run();
也可以通过代码覆盖配置:
builder.Services.AddCore(builder.Configuration, options =>
{
options.OpenApi.Title = "Platform API";
options.Jwt.Issuer = "XY.Platform";
options.CorrelationId.HeaderName = "X-Correlation-Id";
});
默认读取配置节点:
{
"Core": {
}
}
以上空配置会采用各模块默认值;JWT 默认开启,未标记匿名的接口默认要求登录,权限模块默认关闭。默认 JWT 密钥仅供开发占位,部署时必须通过环境变量 Core__Jwt__Secret 或安全配置提供项目独立的随机密钥。默认启用的上传模块包含 avatar、image、document、video 策略;无需上传时设置 Core:Upload:Enable=false。
配置绑定后才执行 configure 回调,因此代码设置会覆盖配置文件中的同名选项。AddCore 在服务注册期间立即校验配置。
模块能力
| 模块 | 默认 | 说明 |
|---|---|---|
ForwardedHeaders |
启用 | 处理反向代理转发头 |
CorrelationId |
启用 | 请求链路 ID,写入响应头和日志上下文 |
RequestLogging |
启用 | 记录 HTTP 请求方法、路径、状态码、耗时和慢请求 |
SecurityHeaders |
启用 | 添加 HSTS、X-Frame-Options、Referrer-Policy 等基础安全头 |
Compression |
关闭 | gzip/br 响应压缩 |
Logging |
启用 | Serilog 控制台、文件、错误文件日志 |
Telemetry |
关闭 | OpenTelemetry Trace、Metrics、可选 Logs |
Upload |
启用 | 多策略文件上传和静态文件映射 |
RateLimiting |
启用 | 固定窗口、滑动窗口、令牌桶、并发限流 |
Json |
启用 | JSON 序列化配置和可空值转换器 |
Cors |
启用 | 多 CORS 策略 |
Response |
启用 | 统一响应包装和异常响应 |
Jwt |
启用 | JWT Bearer 认证 |
Permission |
关闭 | 权限鉴权扩展点,需显式开启并注册业务验证器 |
Idempotency |
启用 | 不安全 HTTP 方法的幂等保护 |
Cache |
启用 | 内存缓存或 Redis 缓存 |
Injection |
启用 | 按程序集扫描自动 DI |
Validation |
启用 | FluentValidation 参数验证 |
Mapper |
启用 | Mapster 对象映射 |
Http |
启用 | Flurl HTTP Client 配置 |
SignalR |
启用 | 自动扫描并映射 Hub |
Endpoint |
启用 | 自动扫描并映射 Minimal API Endpoint |
HealthChecks |
启用 | /health/live 和 /health/ready |
OpenApi |
启用 | OpenAPI JSON 和 Scalar UI |
完整配置参考
以下覆盖所有模块的可配置字段。除明确标为示例或占位的密钥、日志路径与连接串外,值来自当前版本实际默认选项。JSONC 注释用于中文说明;ASP.NET Core 的 JSON 配置提供程序支持注释,其他严格 JSON 工具需要先移除注释。不要直接复制整段作为生产配置。
<details> <summary>展开完整配置(逐字段中文说明)</summary>
{
// 宿主基础设施配置
"Core": {
// 是否在生产环境输出组件启动日志
"ShowStartupLogsInProduction": false,
// 统一响应与异常处理
"Response": {
// 是否启用本模块
"Enable": true,
// 响应包装路径前缀;空列表表示不限制前缀
"ResponseWrapApiPrefixes": ["/api"],
// 成功业务码
"SuccessCode": 0,
// 失败业务码
"ErrorCode": -1,
// 响应内容类型
"ContentType": "application/json;charset=utf-8",
// 生产环境系统异常的提示文本
"ErrorMsg": "系统出错啦,需要联系管理员",
// 请求成功提示文本
"SuccessMsg": "请求成功"
},
// Minimal API 端点扫描
"Endpoint": {
// 是否启用本模块
"Enable": true,
// 显式扫描程序集名称;优先于关键词
"AssemblyNames": [],
// 程序集名称关键词;仅自动扫描项目程序集
"ScanAssemblyKeywords": ["Application","Services"]
},
// 自动依赖注入
"Injection": {
// 是否启用本模块
"Enable": true,
// 显式扫描程序集名称;优先于关键词
"AssemblyNames": [],
// 程序集名称关键词;仅自动扫描项目程序集
"ScanAssemblyKeywords": ["Application","Service","Repository","Infrastructure","Core"],
// 自动注入的类名后缀;也支持 InjectionAttribute
"ScanClassSuffix": ["Service","Repository"]
},
// 日志输出
"Logging": {
// 是否启用本模块
"Enable": true,
// 最小日志级别
"MinimumLevel": "Information",
// 示例值 logs;默认是应用程序基目录下的 logs 绝对路径
"LogFilePath": "logs",
// 是否输出控制台日志
"EnableConsole": true,
// 是否输出文件日志
"EnableFile": true,
// 是否输出独立错误日志
"EnableErrorFile": true,
// 文件格式:Json 或 Text
"FileFormat": "Json",
// 错误日志文件名;null 表示自动选择
"ErrorLogFileName": null,
// 日志滚动周期:Day 或 Hour
"RollingInterval": "Day",
// 保留的日志文件数量
"RetainedFileCountLimit": 7,
// 错误日志保留数量;null 表示沿用主日志设置
"ErrorRetainedFileCountLimit": null,
// 控制台与文本日志输出模板
"OutputTemplate": "[{Timestamp:HH:mm:ss.fff} {Level:u3}][{CorrelationId}]{Message:lj} <{SourceContextShort}> {NewLine}{Exception}"
},
// 缓存
"Cache": {
// 是否启用本模块
"Enable": true,
// 缓存类型:Memory 或 Redis
"CacheType": "Memory",
// Redis 连接串示例;切换 Redis 时替换为实际配置
"RedisConnectionString": "127.0.0.1:6379,password=,defaultDatabase=0,connectTimeout=5000"
},
// JWT 认证
"Jwt": {
// 是否启用本模块
"Enable": true,
// 是否使用 Core 默认令牌校验参数
"UseDefaultTokenValidationParameters": true,
// 占位值留空;必须从安全配置提供独立随机签名密钥,不能直接使用本示例启动
"Secret": "",
// 令牌签发者,需与签发端一致
"Issuer": "SaaS.Tenant",
// 令牌接收者,需与签发端一致
"Audience": "SaaS.Client",
// 有效期,单位:分钟
"ExpireMinutes": 720
},
// 业务权限验证
"Permission": {
// 是否启用本模块
"Enable": false
},
// FluentValidation 参数验证
"Validation": {
// 是否启用本模块
"Enable": true,
// 显式扫描程序集名称;优先于关键词
"AssemblyNames": [],
// 程序集名称关键词;仅自动扫描项目程序集
"ScanAssemblyKeywords": ["Application"]
},
// JSON 序列化
"Json": {
// 是否启用本模块
"Enable": true,
// 是否将可空值类型的空字符串转换为 null
"EnableNullableValueConverters": true,
// 日期时间序列化规则
"DateTime": {
// 日期输出模式:Preserve 保留类型语义,Utc 转换为 UTC
"Mode": "Preserve",
// 如何解释未指定 Kind 的 DateTime:Local 或 Utc
"TreatUnspecifiedAs": "Local",
// 是否输出毫秒
"IncludeMilliseconds": true
}
},
// 跨域访问
"Cors": {
// 是否启用本模块
"Enable": true,
// 默认 CORS 策略名称;null 或空字符串不应用默认策略
"DefaultPolicy": "default",
// 命名策略字典
"Policies": {
// 内置默认跨域策略
"default": {
// 允许的来源;null 或 ["*"] 允许任意来源且不携带凭证,指定域名时允许凭证
"AllowedOrigins": null,
// 是否允许任意 HTTP 方法
"AllowAnyMethod": true,
// 允许的方法;AllowAnyMethod=false 时生效
"AllowedMethods": null,
// 是否允许任意请求头
"AllowAnyHeader": true,
// 允许的请求头;AllowAnyHeader=false 时生效
"AllowedHeaders": null,
// 预检请求缓存时间,单位:秒
"PreflightMaxAge": 3600,
// 浏览器可读取的额外响应头
"ExposedHeaders": null
}
}
},
// Mapster 对象映射
"Mapper": {
// 是否启用本模块
"Enable": true,
// 显式扫描程序集名称;优先于关键词
"AssemblyNames": [],
// 程序集名称关键词;仅自动扫描项目程序集
"ScanAssemblyKeywords": ["Application"]
},
// Flurl HTTP 客户端
"Http": {
// 是否启用本模块
"Enable": true,
// HTTP 请求超时,单位:秒;命名客户端 null 时沿用全局值
"TimeoutSeconds": 100,
// 全局默认请求头
"DefaultHeaders": {},
// 命名客户端字典;默认空,下方提供单个客户端配置
"NamedClients": {},
// 是否记录 HTTP 调用错误日志
"EnableErrorLogging": true
},
// 请求限流
"RateLimiting": {
// 是否启用本模块
"Enable": true,
// 不限流的 IP 列表
"IpWhitelist": [],
// 不限流的路径前缀
"PathWhitelist": ["/health","/openapi"],
// 全局限流策略名称;null 表示不启用全局限流
"GlobalPolicy": null,
// 是否允许应用全局限流策略
"EnableGlobalPolicy": true,
// 命名策略字典
"Policies": {}
},
// OpenAPI 文档
"OpenApi": {
// 是否启用本模块
"Enable": true,
// 文档标题
"Title": "XY API",
// OpenAPI 文档版本
"Version": "v1",
// 文档描述
"Description": "",
// Scalar UI 路径
"Path": "/scalar",
// Scalar 界面主题
"Theme": "Default",
// 是否允许生产环境开放 API 文档
"EnableInProduction": false
},
// 反向代理转发头
"ForwardedHeaders": {
// 是否启用本模块
"Enable": true,
// 可信代理 IP;与 KnownNetworks 都为空时信任所有代理,生产应显式配置
"KnownProxies": [],
// 可信代理网络,CIDR 格式
"KnownNetworks": []
},
// 实时通信
"SignalR": {
// 是否启用本模块
"Enable": true,
// 是否自动扫描并映射 Hub
"AutoMapHubs": true,
// 显式扫描程序集名称;优先于关键词
"AssemblyNames": [],
// 程序集名称关键词;仅自动扫描项目程序集
"ScanAssemblyKeywords": ["Application","Infrastructure"],
// 是否向 SignalR 客户端返回详细错误
"EnableDetailedErrors": false,
// SignalR 心跳间隔,单位:秒
"KeepAliveInterval": 15,
// SignalR 客户端超时,单位:秒
"ClientTimeoutInterval": 30,
// 最大接收消息字节数;null 表示沿用 ASP.NET Core SignalR 默认值
"MaximumReceiveMessageSize": null,
// 多实例 SignalR 广播共享配置
"Backplane": {
// 是否启用本模块
"Enable": false,
// Backplane 提供者,目前支持 Redis
"Provider": "Redis",
// Backplane 连接串;空值时尝试复用启用的 Redis 缓存配置
"ConnectionString": "",
// Redis 广播频道前缀;不同应用应区分
"ChannelPrefix": "xy"
},
// 是否允许指定路径从查询参数读取 JWT
"EnableAccessTokenQueryString": true,
// 查询参数令牌名称
"AccessTokenQueryStringKey": "access_token",
// 允许通过查询参数传递令牌的路径前缀
"AccessTokenPathPrefixes": ["/hubs","/api/hubs"]
},
// 文件上传
"Upload": {
// 是否启用本模块
"Enable": true,
// 命名策略字典
"Policies": {
// 内置头像上传策略
"avatar": {
// 文件存储根目录
"RootPath": "Uploads/Avatar",
// 上传端点路由
"Route": "/api/upload/avatar",
// 最大文件大小,单位:字节
"MaxFileSize": 2097152,
// 允许的文件扩展名
"AllowExtensions": [".jpg",".jpeg",".png"],
// 静态文件缓存时长,单位:小时;不会自动删除文件
"ExpireHours": 24,
// 返回 URL 使用的站点域名;空值取当前请求域名
"SiteDomain": "",
// 静态文件访问路径;大文件策略不自动映射
"RequestPath": "Uploads/Avatar",
// 文件名模板,支持 Guid、FileName、Extension、Time 和自定义变量
"FileNameTemplate": "avatar_{Time:yyyyMMdd}_{Guid}{Extension}",
// 目录模板;空值不增加动态目录
"DirectoryTemplate": "",
// 是否标记为大文件策略;断点续传由宿主实现,不自动映射普通上传端点
"EnableLargeFileUpload": false
},
// 内置图片上传策略
"image": {
// 文件存储根目录
"RootPath": "Uploads/Image",
// 上传端点路由
"Route": "/api/upload/image",
// 最大文件大小,单位:字节
"MaxFileSize": 6291456,
// 允许的文件扩展名
"AllowExtensions": [".jpg",".jpeg",".png",".webp"],
// 静态文件缓存时长,单位:小时;不会自动删除文件
"ExpireHours": 24,
// 返回 URL 使用的站点域名;空值取当前请求域名
"SiteDomain": "",
// 静态文件访问路径;大文件策略不自动映射
"RequestPath": "Uploads/Image",
// 文件名模板,支持 Guid、FileName、Extension、Time 和自定义变量
"FileNameTemplate": "{Time:yyyy/MM/dd}/{Guid}{Extension}",
// 目录模板;空值不增加动态目录
"DirectoryTemplate": "",
// 是否标记为大文件策略;断点续传由宿主实现,不自动映射普通上传端点
"EnableLargeFileUpload": false
},
// 内置文档上传策略
"document": {
// 文件存储根目录
"RootPath": "Uploads/Document",
// 上传端点路由
"Route": "/api/upload/document",
// 最大文件大小,单位:字节
"MaxFileSize": 10485760,
// 允许的文件扩展名
"AllowExtensions": [".pdf",".docx",".xlsx",".pptx",".zip",".rar",".txt"],
// 静态文件缓存时长,单位:小时;不会自动删除文件
"ExpireHours": 48,
// 返回 URL 使用的站点域名;空值取当前请求域名
"SiteDomain": "",
// 静态文件访问路径;大文件策略不自动映射
"RequestPath": "Uploads/Document",
// 文件名模板,支持 Guid、FileName、Extension、Time 和自定义变量
"FileNameTemplate": "{Time:yyyyMMddHHmmss}_{FileName}_{Guid}{Extension}",
// 目录模板;空值不增加动态目录
"DirectoryTemplate": "",
// 是否标记为大文件策略;断点续传由宿主实现,不自动映射普通上传端点
"EnableLargeFileUpload": false
},
// 内置视频策略;大文件传输由宿主实现
"video": {
// 文件存储根目录
"RootPath": "Uploads/Video",
// 上传端点路由
"Route": "/api/upload/video",
// 最大文件大小,单位:字节
"MaxFileSize": 10737418240,
// 允许的文件扩展名
"AllowExtensions": [".mp4",".mov",".avi",".mkv",".flv"],
// 静态文件缓存时长,单位:小时;不会自动删除文件
"ExpireHours": 168,
// 返回 URL 使用的站点域名;空值取当前请求域名
"SiteDomain": "",
// 静态文件访问路径;大文件策略不自动映射
"RequestPath": "Uploads/Video",
// 文件名模板,支持 Guid、FileName、Extension、Time 和自定义变量
"FileNameTemplate": "video_{Time:yyyyMMddHHmmss}_{Guid}{Extension}",
// 目录模板;空值不增加动态目录
"DirectoryTemplate": "",
// 是否标记为大文件策略;断点续传由宿主实现,不自动映射普通上传端点
"EnableLargeFileUpload": true
}
}
},
// 健康检查
"HealthChecks": {
// 是否启用本模块
"Enable": true,
// 存活检查路由
"LivePath": "/health/live",
// 就绪检查路由
"ReadyPath": "/health/ready"
},
// 请求链路 ID
"CorrelationId": {
// 是否启用本模块
"Enable": true,
// 请求头名称
"HeaderName": "X-Correlation-Id",
// 是否将链路 ID 写入响应头
"IncludeInResponse": true
},
// 安全响应头
"SecurityHeaders": {
// 是否启用本模块
"Enable": true,
// 是否启用 HSTS;非开发环境添加该响应头,浏览器仅在 HTTPS 下生效
"EnableHsts": true,
// HSTS 有效期,单位:天
"HstsMaxAgeDays": 365,
// 嵌入框架策略,默认 DENY
"FrameOptions": "DENY",
// 来源信息策略
"ReferrerPolicy": "no-referrer",
// 内容安全策略;空值不添加 CSP 头
"ContentSecurityPolicy": ""
},
// 响应压缩
"Compression": {
// 是否启用本模块
"Enable": false,
// 是否允许压缩 HTTPS 响应
"EnableForHttps": true,
// 压缩级别,例如 Fastest、Optimal
"Level": "Fastest"
},
// 请求日志
"RequestLogging": {
// 是否启用本模块
"Enable": true,
// 慢请求阈值,单位:毫秒
"SlowRequestThresholdMilliseconds": 1000,
// 跳过处理的路径前缀
"ExcludedPathPrefixes": ["/health","/favicon.ico","/scalar","/openapi"]
},
// OpenTelemetry 遥测
"Telemetry": {
// 是否启用本模块
"Enable": false,
// 遥测服务名称
"ServiceName": "XY.Api",
// 遥测服务版本
"ServiceVersion": "1.0.0",
// 遥测环境名称;空值时不添加 deployment.environment 属性
"Environment": "",
// 是否采集追踪
"EnableTracing": true,
// 是否采集指标
"EnableMetrics": true,
// 是否额外导出 ILogger 日志
"EnableLogs": false,
// 导出方式:None、Otlp、Console
"Exporter": "Otlp",
// OTLP 接收端地址
"OtlpEndpoint": "http://localhost:4317"
},
// 幂等保护
"Idempotency": {
// 是否启用本模块
"Enable": true,
// 请求头名称
"HeaderName": "Idempotency-Key",
// 是否强制要求幂等请求头
"RequireHeader": false,
// 有效期,单位:分钟
"ExpireMinutes": 10,
// 幂等存储:Auto、Memory、Redis
"Storage": "Auto",
// 需要幂等处理的 HTTP 方法
"Methods": ["POST","PUT","PATCH","DELETE"],
// 跳过处理的路径前缀
"ExcludedPathPrefixes": ["/api/upload","/health","/openapi","/scalar"],
// 可缓存 JSON 响应的最大字节数
"MaxResponseBodyBytes": 1048576
}
}
}
</details>
命名策略与客户端字段
RateLimiting.Policies 默认是空字典。添加命名策略时,每个策略可配置以下字段;设置 GlobalPolicy 为策略名称,或在端点调用 RequireRateLimiting 应用策略:
{
// 限流类型:FixedWindow、SlidingWindow、TokenBucket、Concurrency
"PolicyType": "SlidingWindow",
// 限流窗口,单位:秒
"WindowSeconds": 60,
// 窗口内请求上限或并发上限
"PermitLimit": 100,
// 排队请求上限,0 表示不排队
"QueueLimit": 0,
// 限流分区:ip 或 global
"PartitionBy": "ip"
}
Http.NamedClients 默认是空字典。每个命名客户端包含以下字段,BaseUrl 为示例,其他值为默认值:
{
// HTTP 客户端基础 URL 示例
"BaseUrl": "https://api.example.com",
// HTTP 请求超时,单位:秒;命名客户端 null 时沿用全局值
"TimeoutSeconds": null,
// 命名客户端的默认请求头
"Headers": {}
}
仅通过 C# 配置的扩展项
以下包含委托或框架对象,不能完整地通过 JSON 表达:
| 配置项 | 中文说明 |
|---|---|
| Response.SuccessFactory / FailFactory | 自定义成功或失败的响应结构,默认输出 code、message、data |
| Mapper.GlobalSettings | 配置 Mapster 映射规则,默认未设置 |
| Http.JsonSettings | 自定义 HTTP 客户端 JSON 序列化,默认未设置 |
| Http.ErrorFactory | 自定义调用失败处理,默认记录错误日志;EnableErrorLogging 开启时执行 |
| RateLimiting.Policies[名称].PartitionKeyResolver | 自定义分区键,优先于 PartitionBy;默认未设置 |
| Json.JsonSerializerOptions | 默认使用驼峰属性名、非缩进输出、保留 null 字段、UnsafeRelaxedJsonEscaping;转换器及命名策略等对象需通过 C# 设置 |
builder.Services.AddCore(builder.Configuration, options =>
{
// 自定义成功响应,失败响应使用 FailFactory 配套设置。
options.Response.SuccessFactory = context => new
{
code = context.Code,
message = context.Message,
result = context.Data
};
// 是否缩进输出 JSON。
options.Json.JsonSerializerOptions.WriteIndented = true;
// 设置全局 HTTP 客户端序列化选项。
options.Http.JsonSettings = json => json.WriteIndented = false;
});
权限验证器、JWT 会话验证器和 Endpoint/Hub 是服务或类型扩展,通过 DI 注册或程序集扫描接入,下文提供示例。
中间件顺序
UseCore() 内部已经按推荐顺序组织管道:
- 转发头
- CorrelationId
- 请求日志
- 安全响应头
- 响应压缩
- 上传静态文件、限流、JSON、CORS
- 统一响应和异常处理
- JWT 认证
- 权限鉴权
- 幂等保护
- Endpoint、SignalR、HealthChecks、OpenAPI 映射
应用通常不需要手动调整这些组件顺序。若需要额外中间件,建议放在 app.UseCore() 前后明确控制范围。
统一响应
本节的 ApiResults、SkipResponseWrap 和响应配置类型位于 XY.Core.Response;代码示例需引入 using XY.Core.Response;。BusinessException 位于 XY.Core。
默认情况下,命中 Response.ResponseWrapApiPrefixes 的接口会被包装为:
{
"code": 0,
"message": "请求成功",
"data": {}
}
业务失败推荐在应用服务或领域流程中抛出 BusinessException,统一异常处理中间件会将它转换为失败响应:
throw new BusinessException("业务校验失败");
throw new BusinessException("余额不足", 10001);
throw new BusinessException("提交失败", new { Field = "Name" });
throw new BusinessException("提交失败", 10002, new { Field = "Name" });
ApiResults.Fail(...) 适合在 Minimal API Endpoint 委托中直接返回失败结果。它返回的是 IResult,主要用于端点层,不建议在普通 Service 中使用:
app.MapPost("/api/orders", () =>
{
return ApiResults.Fail("业务校验失败");
});
某个 Endpoint 不需要统一包装时:
app.MapGet("/api/raw", () => new { ok = true })
.SkipResponseWrap();
如需自定义响应结构,可以通过 SuccessFactory 和 FailFactory 自定义统一响应格式:
builder.Services.AddCore(builder.Configuration, options =>
{
options.Response.SuccessFactory = context => new
{
success = true,
code = context.Code,
message = context.Message,
result = context.Data
};
options.Response.FailFactory = context => new
{
success = false,
code = context.Code,
message = context.Message,
error = context.Data
};
});
自定义响应格式后,运行时响应会按工厂输出。OpenAPI 响应包装会通过工厂推导 code、message 和数据字段名;如果使用非常规字段语义,建议同步检查 /openapi/v1.json 的展示结果。
OpenAPI 与 Bearer 认证
默认地址:
- Scalar UI:
/scalar - OpenAPI JSON:
/openapi/v1.json
当 Jwt.Enable = true 时,OpenAPI 会自动注册 Bearer/JWT 安全方案,并为非匿名接口添加 Bearer 安全要求。
生产环境默认不启用 OpenAPI。确需启用时:
{
"Core": {
"OpenApi": {
"EnableInProduction": true
}
}
}
Minimal API Endpoint
实现 IEndpoint 后,Endpoint 模块会按程序集扫描并自动映射。
using Microsoft.AspNetCore.Routing;
using XY.Core.Endpoint;
public sealed class UserEndpoint : IEndpoint
{
public int Order => 0;
public void MapEndpoints(IEndpointRouteBuilder app)
{
app.MapGet("/api/users", () => new[] { "admin" });
}
}
默认扫描关键字来自 Endpoint.ScanAssemblyKeywords,可通过配置或代码指定程序集。
JWT 与权限
权限扩展方法及接口位于 XY.Core.Permission,使用 SkipPermission 或注册验证器时需引入该命名空间。
Jwt 负责认证:校验 Bearer Token,解析当前用户。
Permission 负责授权:注册 ASP.NET Core Authorization 策略,并通过 IPermissionValidator 执行业务权限验证。它不是 JWT 的附属模块,也可以用于 API Key、网关鉴权、内部服务鉴权等非 JWT 场景。
两个模块可以独立开关,组合后决定未显式标记 Endpoint 的默认访问行为:
| Jwt | Permission | 默认行为 | 适用场景 |
|---|---|---|---|
| 开启 | 开启 | 需要登录 + 权限验证 | 后台管理、租户系统、企业内部系统 |
| 开启 | 关闭 | 只需要登录 | 只区分登录/未登录,不做菜单、按钮、资源权限 |
| 关闭 | 开启 | 不校验 JWT,但仍执行权限验证器 | 公开 API、API Key、网关鉴权、内部服务鉴权 |
| 关闭 | 关闭 | 完全公开 | Demo、健康探测、纯公开服务 |
当前实现规则:
Jwt.Enable = true时会注册 Bearer 认证,并启用UseAuthentication()。Permission.Enable = true时会设置全局授权策略,并执行IPermissionValidator.ValidateAsync()。Jwt.Enable = true且Permission.Enable = true时,默认策略为“需要登录 + 业务权限验证”。Jwt.Enable = true且Permission.Enable = false时,默认策略为“只要求登录”,框架仍会执行UseAuthorization(),但不会执行IPermissionValidator。Jwt.Enable = false且Permission.Enable = true时,权限验证器仍会执行,但不会检查 JWT 登录态;此时IPermissionValidator需要自行基于 API Key、网关 Header、内部签名、来源 IP、服务身份等信息判断是否允许访问。Jwt.Enable = false且Permission.Enable = false时,不启用认证授权中间件,接口默认公开。
单个 Endpoint 可以覆盖默认行为:
| 标记 | 行为 | 常见用途 |
|---|---|---|
.AllowAnonymous() |
跳过 ASP.NET Core 授权流程,通常等价于公开接口 | 登录、注册、公钥、健康检查 |
.SkipPermission() |
跳过 IPermissionValidator 权限验证 |
已登录即可访问的个人资料、退出登录 |
当 Jwt 和 Permission 都开启时,常见写法:
app.MapPost("/api/auth/login", Login)
.AllowAnonymous();
app.MapGet("/api/auth/profile", GetProfile)
.SkipPermission();
app.MapGet("/api/admin/menus", GetMenus);
含义分别是:
/api/auth/login:公开接口,不要求登录,不做权限验证。/api/auth/profile:要求登录,但不做业务权限验证。/api/admin/menus:要求登录,并执行业务权限验证。
典型权限验证流程:
请求进入
-> 如果 Jwt.Enable = true,JWT 认证中间件解析 Token
-> Authorization 执行全局策略
-> 如果 Endpoint 标记 AllowAnonymous,则跳过授权
-> 如果 Permission.Enable = true 且未 SkipPermission,则执行 IPermissionValidator
-> 根据验证结果返回 200 / 401 / 403
业务项目可以实现自己的权限校验器:
Permission.Enable 默认是 false。开启后,默认验证器 DefaultPermissionValidator 始终返回 true,不会校验业务权限。需要显式注册自己的实现;仅定义接口实现类不保证会被自动扫描。
using XY.Core.Permission;
public sealed class MyPermissionValidator : IPermissionValidator
{
public Task<bool> ValidateAsync()
{
return Task.FromResult(true);
}
}
在 AddCore 之前注册业务验证器,避免被默认实现占位:
builder.Services.AddScoped<IPermissionValidator, MyPermissionValidator>();
builder.Services.AddCore(builder.Configuration, options =>
{
options.Permission.Enable = true;
});
示例验证器仅演示接口结构,实际实现应基于当前用户和 Endpoint 权限元数据决定允许或拒绝访问。全局规则通过 FallbackPolicy 实现;显式使用 RequireAuthorization 的端点或自定义策略,应确认对应策略包含所需的权限要求。
需要跳过权限校验的 Endpoint 可以使用:
app.MapGet("/api/user/profile", () => "ok")
.SkipPermission();
完全公开的接口使用:
app.MapGet("/api/auth/publicKey", () => "public-key")
.AllowAnonymous();
日志与链路 ID
CorrelationId 会从请求头读取或自动生成链路 ID,并写入:
HttpContext.Items- 响应头,例如
X-Correlation-Id - Serilog
LogContext属性CorrelationId
因此请求链路内的框架日志、请求日志、业务日志会自动带上同一个 CorrelationId。
控制台日志默认面向人读,文件日志默认使用 JSON Lines,便于后续检索和日志平台采集。错误日志文件默认开启,只记录 Error/Fatal,主日志仍保留完整事件。
启用 Telemetry 后,日志会同时带上 TraceId 和 SpanId,便于从日志跳转到 OpenTelemetry Trace。
业务代码如需读取链路 ID:
using XY.Core.CorrelationId;
public sealed class OrderService(ICorrelationIdAccessor accessor)
{
public string? GetTraceId() => accessor.CorrelationId;
}
非 HTTP 场景可以手动建立日志范围:
using XY.Core.CorrelationId;
using var scope = CorrelationIdScope.BeginCorrelationScope("job-001");
OpenTelemetry
Telemetry 默认关闭。开启后会采集 ASP.NET Core 请求链路、HTTP Client 调用和 Runtime 指标,并可通过 OTLP 发往 OpenTelemetry Collector。
{
"Core": {
"Telemetry": {
"Enable": true,
"ServiceName": "XY.Platform.Api",
"ServiceVersion": "1.0.0",
"EnableTracing": true,
"EnableMetrics": true,
"EnableLogs": true,
"Exporter": "Otlp",
"OtlpEndpoint": "http://localhost:4317"
}
}
}
日志仍由 Serilog 负责本地控制台、文件和错误文件。EnableLogs=true 表示额外把 ILogger 日志导出到 OTLP,不替代现有文件日志。
本地想直接查看采集内容,可以使用 Console exporter,不需要 Collector:
{
"Core": {
"Telemetry": {
"Enable": true,
"EnableTracing": true,
"EnableMetrics": true,
"EnableLogs": false,
"Exporter": "Console"
}
}
}
生产环境建议使用 Exporter: "Otlp",将数据发送到 OpenTelemetry Collector、Grafana Alloy、Aspire Dashboard 或云平台 Agent。
幂等
端点扩展方法位于 XY.Core.Idempotency,使用 RequireIdempotency 或 SkipIdempotency 时需引入该命名空间。
Idempotency 用来防止同一个业务动作因为重复点击、客户端重试、网关重放而重复执行。
默认只处理:
POST / PUT / PATCH / DELETE
GET 查询默认不参与幂等。因此用户重复查询时仍会重新读取数据库,能看到其他用户新增的数据。不要把查询接口加入 Idempotency.Methods,除非你明确想缓存查询响应。
客户端传入:
Idempotency-Key: submit-order-001
前端接入时,只需要在关键写操作请求中增加这个请求头:
const idempotencyKey = crypto.randomUUID();
await fetch("/api/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey
},
body: JSON.stringify({
productId: "p001",
quantity: 1
})
});
前端使用规则:
GET查询不需要传Idempotency-Key。POST/PUT/PATCH/DELETE等写操作建议传。- 同一次业务动作的重试必须复用同一个 Key,例如提交订单超时后重试。
- 新的业务动作必须生成新的 Key,例如重新创建另一笔订单。
- 不要把用户 ID、Token、页面固定值当作 Key,否则会把不同业务动作误判为重复请求。
如果前端有自动重试,需要把 Key 保存在本次提交动作的上下文中:
const idempotencyKey = crypto.randomUUID();
async function submitOrder() {
return fetch("/api/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey
},
body: JSON.stringify(orderForm)
});
}
同一用户、同一方法、同一路径、同一个 Idempotency-Key、同一个请求体:
- 第一次请求会执行业务;仅缓存 HTTP 2xx、JSON 类型且不超过 MaxResponseBodyBytes 的响应,其他响应会移除幂等记录。
- 在幂等记录有效期内,满足缓存条件的后续重复请求不再执行业务,直接返回第一次缓存响应。
- 如果同一个 Key 被用于不同请求体,返回
409 Conflict。 - 如果第一次请求还在处理中,重复请求返回
409 Conflict和Retry-After: 1。
未传 Idempotency-Key 时默认跳过幂等,不影响现有接口。需要强制某个接口必须传幂等 Key:
app.MapPost("/api/orders", CreateOrder)
.RequireIdempotency();
需要跳过幂等:
app.MapPost("/api/callback", Callback)
.SkipIdempotency();
默认存储为 Auto:Redis 缓存启用时使用 Redis,否则使用内存。生产多实例建议使用 Redis。
上传
上传通过策略配置,每个策略对应一个上传接口、存储目录、大小限制和扩展名白名单。
{
"Core": {
"Upload": {
"Enable": true,
"Policies": {
"avatar": {
"RootPath": "Uploads/Avatar",
"Route": "/api/upload/avatar",
"MaxFileSize": 2097152,
"AllowExtensions": [ ".jpg", ".jpeg", ".png" ],
"RequestPath": "Uploads/Avatar",
"FileNameTemplate": "avatar_{Time:yyyyMMdd}_{Guid}{Extension}"
}
}
}
}
}
文件名模板支持常用变量:
{Guid}{FileName}{Extension}{Time:yyyyMMddHHmmss}- 调用方传入的自定义变量
限流
限流支持全局策略和命名策略。
{
"Core": {
"RateLimiting": {
"Enable": true,
"GlobalPolicy": "api",
"EnableGlobalPolicy": true,
"PathWhitelist": [ "/health", "/openapi", "/scalar" ],
"Policies": {
"api": {
"PolicyType": "SlidingWindow",
"WindowSeconds": 60,
"PermitLimit": 100,
"QueueLimit": 0,
"PartitionBy": "ip"
}
}
}
}
}
接口级命名策略可以配合 ASP.NET Core 原生 RequireRateLimiting("policy-name") 使用。
缓存
默认使用内存缓存:
{
"Core": {
"Cache": {
"Enable": true,
"CacheType": "Memory"
}
}
}
使用 Redis:
{
"Core": {
"Cache": {
"Enable": true,
"CacheType": "Redis",
"RedisConnectionString": "127.0.0.1:6379,password=,defaultDatabase=0,connectTimeout=5000"
}
}
}
启用 Redis 时必须配置连接串,否则应用启动会失败。
SignalR
Hub 增加 SignalRHubAttribute 后可自动映射:
using Microsoft.AspNetCore.SignalR;
using XY.Core.SignalR;
[SignalRHub("/hubs/notify")]
public sealed class NotifyHub : Hub
{
}
可通过 SignalR.AssemblyNames 或 SignalR.ScanAssemblyKeywords 控制扫描范围。
SignalR.Enable 和 AutoMapHubs 默认开启。JWT 默认允许 /hubs、/api/hubs 路径从 access_token 查询参数读取令牌,可通过 EnableAccessTokenQueryString 关闭。
多实例需要广播共享时,可启用 Redis Backplane:
{
"Core": {
"SignalR": {
"Backplane": {
"Enable": true,
"Provider": "Redis",
"ConnectionString": "127.0.0.1:6379",
"ChannelPrefix": "my-app"
}
}
}
}
Backplane 默认关闭。未单独提供连接串时可复用已启用的 Redis 缓存连接串,否则启用 Backplane 会启动失败。
程序集扫描
Endpoint、Injection、Validation、Mapper、SignalR 都支持 AssemblyNames 和 ScanAssemblyKeywords,前者优先。默认关键词为:
| 模块 | 关键词 |
|---|---|
| Endpoint | Application、Services |
| Injection | Application、Service、Repository、Infrastructure、Core |
| Validation / Mapper | Application |
| SignalR | Application、Infrastructure |
关键词自动扫描只匹配当前应用的项目程序集,不扫描 NuGet 包程序集。宿主或业务程序集名称不匹配时,应显式配置 AssemblyNames;业务能力也打成包时同样需要显式指定。Injection 默认扫描 Service、Repository 后缀的类,或带 InjectionAttribute 的类;默认生命周期为 Scoped。
JSON 日期时间
Core:Json:DateTime 默认 Mode=Preserve、TreatUnspecifiedAs=Local、IncludeMilliseconds=true。需要统一输出 UTC 时配置:
{
"Core": {
"Json": {
"DateTime": {
"Mode": "Utc",
"TreatUnspecifiedAs": "Utc",
"IncludeMilliseconds": true
}
}
}
}
健康检查
默认映射:
/health/live/health/ready
可通过 Core:HealthChecks 修改路径。路径必须以 / 开头,否则应用启动会失败。
启动校验
AddCore() 会在启动阶段校验关键配置,尽量提前暴露问题:
- 启用 Redis 缓存时,必须配置
Core:Cache:RedisConnectionString - 启用 JWT 且使用默认
TokenValidationParameters时,必须配置Core:Jwt:Secret - 启用上传时,至少需要一个上传策略
- 启用全局限流策略时,
GlobalPolicy必须存在于Policies - 健康检查路径必须以
/开头 - 启用 CorrelationId 时,必须配置 HeaderName
- 启用幂等时,HeaderName 不能为空,ExpireMinutes 必须大于 0
- 启用 OpenTelemetry OTLP 导出时,OtlpEndpoint 不能为空
JWT 验证扩展
默认情况下,Core 使用 Core:Jwt 中的 Secret、Issuer、Audience 配置 JWT 基础校验。宿主项目需要配置自己的密钥,并让签发方与验证方使用匹配的 Issuer、Audience 和密钥。
如需追加业务级会话校验,例如用户禁用、租户停用、密码版本变化等,实现 IJwtSessionValidator 即可。该校验发生在 JWT 基础校验成功之后;任一校验器调用 Fail() 都会让当前令牌认证失败。
接口位于 XY.Core.JwtIdentity,方法签名为 Task ValidateAsync(JwtSessionValidationContext context)。需将实现注册为 IJwtSessionValidator 服务,例如 builder.Services.AddScoped<IJwtSessionValidator, MySessionValidator>();可以注册多个验证器。
如需替换 JWT 基础校验参数,例如使用外部密钥源、非默认签名算法或自定义 Issuer/Audience 规则,设置:
{
"Core": {
"Jwt": {
"Enable": true,
"UseDefaultTokenValidationParameters": false
}
}
}
然后在宿主项目中接管 TokenValidationParameters:
services.PostConfigure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme,
options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
// 宿主项目自定义基础 JWT 校验参数
};
});
不建议外部直接整体覆盖 options.Events,否则会覆盖 Core 内置的 SignalR query token 读取和 IJwtSessionValidator 执行链。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- CSRedisCore (>= 3.8.807)
- FluentValidation (>= 12.1.1)
- FluentValidation.DependencyInjectionExtensions (>= 12.1.1)
- Flurl.Http (>= 4.0.2)
- Mapster (>= 10.0.7)
- Mapster.DependencyInjection (>= 10.0.7)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.8)
- Microsoft.AspNetCore.OpenApi (>= 10.0.8)
- Microsoft.AspNetCore.SignalR.StackExchangeRedis (>= 10.0.8)
- Microsoft.OpenApi (>= 2.3.9)
- OpenTelemetry.Exporter.Console (>= 1.15.3)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.15.3)
- OpenTelemetry.Extensions.Hosting (>= 1.15.3)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.15.2)
- OpenTelemetry.Instrumentation.Http (>= 1.15.1)
- OpenTelemetry.Instrumentation.Runtime (>= 1.15.1)
- Scalar.AspNetCore (>= 2.14.14)
- Scrutor (>= 7.0.0)
- Serilog (>= 4.3.1)
- Serilog.Enrichers.Environment (>= 3.0.1)
- Serilog.Enrichers.Process (>= 3.0.0)
- Serilog.Enrichers.Thread (>= 4.0.0)
- Serilog.Extensions.Logging (>= 10.0.0)
- Serilog.Sinks.Console (>= 6.1.1)
- Serilog.Sinks.File (>= 7.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 107 | 10/13/2026 |