RuoVea.ExFilter
10.0.0.6
dotnet add package RuoVea.ExFilter --version 10.0.0.6
NuGet\Install-Package RuoVea.ExFilter -Version 10.0.0.6
<PackageReference Include="RuoVea.ExFilter" Version="10.0.0.6" />
<PackageVersion Include="RuoVea.ExFilter" Version="10.0.0.6" />
<PackageReference Include="RuoVea.ExFilter" />
paket add RuoVea.ExFilter --version 10.0.0.6
#r "nuget: RuoVea.ExFilter, 10.0.0.6"
#:package RuoVea.ExFilter@10.0.0.6
#addin nuget:?package=RuoVea.ExFilter&version=10.0.0.6
#tool nuget:?package=RuoVea.ExFilter&version=10.0.0.6
🛡️ RuoVea.ExFilter
企业级 ASP.NET Core 过滤器与中间件库 — 集成全局异常处理、请求日志记录、资源防盗链、统一 RESTful 结果格式化、API 安全校验(AppKey + 时间戳 + 签名)、模型验证、匿名白名单、鉴权旁路、健康检查、虚拟路径、UI 资源解压等功能,并提供 7 种语言的国际化异常提示。
📖 目录
📋 概览
RuoVea.ExFilter 为 ASP.NET Core 开发者提供了一套开箱即用的过滤器(Filter)与中间件(Middleware)体系,涵盖从请求进入、安全校验、日志记录、异常处理到响应格式化的全生命周期管控。
┌──────────────────────────────────────────────────────────────┐
│ RuoVea.ExFilter │
├──────────────────────────────────────────────────────────────┤
│ 全局异常处理 请求日志拦截 资源安全 │
│ ├─ ExceptionFilter ├─ RequestAction ├─ ResourceFilter │
│ │ (ExceptionFilter │ Filter │ (Referer 防盗链) │
│ │ Attribute) │ (Order=8888) │ │
│ │ │ │ │
│ API 安全 结果标准化 模型验证 │
│ ├─ ApISafeFilter ├─ ResultFilter └─ ValidateModel │
│ │ (Order=6) │ (IResultFilter) ActionFilter │
│ └─ ApISignFilter └─ ContentResult / │
│ (Order=8) ObjectResult → │
│ RestfulResult │
├──────────────────────────────────────────────────────────────┤
│ 中间件 │
│ ├─ AnonymousMiddleware (匿名白名单) │
│ ├─ ByPassAuthMiddleware (开发/测试鉴权旁路) │
│ ├─ VirtualPathMiddle (反向代理虚拟路径) │
│ ├─ Health Check (/health 端点) │
│ └─ AllServices (/allservices 端点) │
├──────────────────────────────────────────────────────────────┤
│ 跳过标记 (Attribute): [NonException] [NonRequestAction] │
│ [NonResource] [NonRestfulResult] [NonAplSafe] [NonAplSign] │
├──────────────────────────────────────────────────────────────┤
│ 国际化: zh-CN | zh-TW | zh-HK | en-US | fr-FR | ja-JP | vi-VN│
└──────────────────────────────────────────────────────────────┘
设计原则
| 原则 | 说明 |
|---|---|
| 约定优于配置 | 提供零参数的 Setup 方法,自动从 appsettings.json 读取配置,默认可用 |
| 可插拔架构 | 每个 Filter / Middleware 独立注册,按需启用 |
| 跳过标记 | 6 种 [Non*] Attribute 精确控制单个 Action 是否参与特定过滤 |
| RESTful 统一输出 | 所有异常、验证错误、操作结果均转译为 RestfulResult 格式 |
| 国际化内置 | 所有提示消息自动根据 CultureInfo.CurrentUICulture 切换 7 种语言 |
📦 安装
.NET CLI
# .NET 8.0
dotnet add package RuoVea.ExFilter --version 8.0.1.11
# .NET 10.0
dotnet add package RuoVea.ExFilter --version 10.0.0.5
Package Manager
Install-Package RuoVea.ExFilter -Version 8.0.1.11
PackageReference
<PackageReference Include="RuoVea.ExFilter" Version="8.0.1.11" />
支持的 Target Framework
| TFM | 最低版本 |
|---|---|
net8.0 |
8.0.1.11 |
net10.0 |
10.0.0.5 |
NuGet 依赖项
❗ 必须了解: 安装 RuoVea.ExFilter 会自动引入以下依赖包,请确保项目不会与这些传递依赖产生版本冲突。
| 依赖包 | 8.0.x 版本 | 10.0.x 版本 | 说明 |
|---|---|---|---|
RuoVea.ExConfig |
8.0.* |
10.0.* |
配置读取(AppSettings) |
RuoVea.ExCrypt |
8.0.* |
10.0.* |
MD5 签名加密 |
RuoVea.ExDto |
8.0.* |
10.0.* |
基础 DTO 与枚举(CodeStatus, ClaimConst, RestfulResult 等) |
RuoVea.ExLog |
8.0.* |
10.0.* |
LogFactory 文件日志 |
RuoVea.ExUtil |
8.0.* |
10.0.* |
通用工具(UnixTime, Common 等) |
UAParser |
3.1.47 |
3.1.47 |
User-Agent 解析(浏览器/操作系统识别) |
System.Security.Cryptography.Pkcs |
8.0.1 |
10.0.8 |
加密原语 |
System.Security.Cryptography.Xml |
8.0.3 |
10.0.8 |
XML 安全操作 |
System.Text.RegularExpressions |
4.3.1 |
4.3.1 |
正则表达式 |
Microsoft.AspNetCore.App |
FrameworkReference | FrameworkReference | ASP.NET Core 框架引用 |
⚡ 30 秒快速开始
1. 导入命名空间
using RuoVea.ExFilter; // 所有 Filter、Middleware 扩展方法
using RuoVea.ExFilter.Middlewares; // 中间件类(如需手动实例化)
using RuoVea.ExFilter.Domain; // ExceptionVo、OperationVo
using RuoVea.ExFilter.OptionConfig; // 配置基类
2. 最简注册(Program.cs)
var builder = WebApplication.CreateBuilder(args);
// 注册所有核心过滤器
builder.Services.ExceptionSetup(); // 全局异常处理
builder.Services.RequestActionSetup(); // 请求日志
builder.Services.ResourceSetup(); // 资源防盗链
builder.Services.ResultSetup(); // 统一 RESTful 结果
var app = builder.Build();
// 注册中间件(按顺序)
app.UseHealthMiddle(); // /health 端点
app.UseByPassAuthMiddle(); // 开发鉴权旁路
app.UseAnonymousMiddle(); // 匿名白名单
// app.UseVirtualPathMiddle(); // 虚拟路径(如需)
app.Run();
3. 在 Action 上使用跳过标记
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
[HttpGet]
public IActionResult GetOrders() => Ok(new { count = 10 });
[NonRequestAction] // ← 跳过请求日志记录
[HttpGet("health")]
public IActionResult Health() => Ok("alive");
[NonException] // ← 跳过异常日志收集
[HttpGet("status")]
public IActionResult Status() => throw new Exception("silent error");
}
30 秒内你完成了: 异常处理注册 → 请求日志注册 → 防盗链注册 → 统一结果注册 → 健康检查端点 → 鉴权旁路启用 → 匿名白名单启用 → 在 Action 上精确控制过滤行为。
🧩 核心场景
场景一:全局异常处理(ExceptionFilter)
┌──────────────┐ Action 抛出异常 ┌─────────────────────────┐
│ Controller │ ──────────────────────▶ │ ExceptionFilter │
│ Action │ │ 1. 检查 [NonException] │
└──────────────┘ │ 2. 检查 Enabled 配置 │
│ 3. 分类异常类型 │
│ 4. LogFactory 写文件 │
│ 5. RestfulResult 输出 │
└─────────────┬───────────┘
│
▼
┌─────────────────────────┐
│ { │
│ Code: 500, │
│ Message: "异常详情", │
│ Data: null │
│ } │
└─────────────────────────┘
注册
// 方式一: 零配置 — 自动读取 appsettings.json 中的 "ExceptionLog" 节点
// <inheritdoc cref="ServicesExtensions.ExceptionSetup(IServiceCollection)"/>
services.ExceptionSetup();
// 方式二: Action 委托配置
// <inheritdoc cref="ServicesExtensions.ExceptionSetup(IServiceCollection, Action{ExceptionLog})"/>
services.ExceptionSetup(options =>
{
options.Enabled = true;
options.LogToFile = true;
options.LogMore = true; // 写入数据库(需实现 IRestfulFilterLog)
});
// 方式三: IConfiguration 节点配置
// <inheritdoc cref="ServicesExtensions.ExceptionSetup(IServiceCollection, IConfiguration)"/>
services.ExceptionSetup(config.GetSection("ExceptionLog"));
异常分类处理规则
| 异常类型 | 返回状态码 | 说明 |
|---|---|---|
ParamiterException |
InternalServerError (500) |
参数友好异常,返回自定义消息 |
ConnctionException |
RequestTimeout (408) |
连接异常 |
ArgumentOutOfRangeException |
BadRequest (400) |
参数超出范围 |
ArgumentNullException |
BadRequest (400) |
参数为空 |
ArgumentException |
BadRequest (400) |
参数不合法 |
| 其他所有异常 | InternalServerError (500) |
统一错误提示 |
appsettings.json 配置
{
"ExceptionLog": {
"Enabled": true,
"LogToFile": true,
"LogMore": false
}
}
场景二:请求操作日志(RequestActionFilter)
┌──────────────┐ OnActionExecutionAsync ┌────────────────────────┐
│ Controller │ ──────────────────────────────▶ │ RequestActionFilter │
│ Action │ │ 1. 检查 [NonRequest] │
└──────────────┘ │ 2. 执行 Action │
│ 3. 计时 │
│ 4. 提取参数/结果 │
│ 5. LogMore → OperationVo│
│ 6. 处理 BadRequest │
└────────────────────────┘
注册
// <inheritdoc cref="ServicesExtensions.RequestActionSetup(IServiceCollection)"/>
services.RequestActionSetup();
// <inheritdoc cref="ServicesExtensions.RequestActionSetup(IServiceCollection, Action{RequestLog})"/>
services.RequestActionSetup(options =>
{
options.Enabled = true;
options.LogToFile = true;
options.LogMore = true;
options.IgnoreApis = "/api/health,/api/metrics"; // 忽略指定路径
});
配置说明
{
"RequestLog": {
"Enabled": true,
"LogToFile": true,
"LogMore": false,
"IgnoreApis": "/api/health,/api/metrics"
}
}
❗ 性能陷阱:
LogMore = true时每次请求都会构造OperationVo对象并调用IRestfulFilterLog.OperationLog(),若实现类中执行数据库写入将直接影响请求耗时。建议:日志写入使用异步队列解耦,或仅在生产环境关键接口开启LogMore。
场景三:资源防盗链(ResourceFilter)
通过检查 HTTP Referer 请求头,防止外部站点直接引用本站资源(图片、文件等)。
// <inheritdoc cref="ServicesExtensions.ResourceSetup(IServiceCollection)"/>
services.ResourceSetup();
| 条件 | 结果 |
|---|---|
| 无 Referer(直接访问) | ForbidResult (403) |
Referer 不含 localhost |
ForbidResult (403) |
标记 [NonResource] 的 Action |
跳过检查 |
⚠️ 安全警告: 当前实现仅检查 Referer 是否包含
localhost,生产环境部署时请扩展为实际域名校验。Referer 请求头可被伪造,仅作为基础防护手段。
场景四:统一 RESTful 结果(ResultFilter)
自动将 ContentResult、ObjectResult 包装为统一的 RestfulResult 格式。
┌──────────────┐ 原始响应 ┌──────────────────────┐
│ ContentResult │ ────────────────────────────▶ │ ResultFilter │
│ ObjectResult │ │ ↓ 包装 │
└──────────────┘ │ RestfulResult { │
│ Code: 200, │
│ Data: {...}, │
│ Message: "请求成功" │
│ } │
└──────────────────────┘
注册
// <inheritdoc cref="ServicesExtensions.ResultSetup(IServiceCollection)"/>
services.ResultSetup();
不进行统一包装的结果类型(直接透传):ViewResult、PartialViewResult、FileResult、ChallengeResult、SignInResult、SignOutResult、RedirectResult 系列、ForbidResult、PageResult、ViewComponentResult。
场景五:API 安全校验(ApISafeFilter)
验证请求头中的 appKey 与 timeStamp,防止未授权访问和重放攻击。
┌──────────────┐ Order=6 ┌────────────────────────┐
│ Client │ ──────────────────────────────▶ │ ApISafeFilter │
│ (Header) │ appKey: "youraccount" │ 1. Validate() 配置补全 │
│ │ timeStamp: "1700000000000" │ 2. 检查 AppKeys 白名单 │
└──────────────┘ │ 3. 校验时间戳±2分钟 │
└────────────────────────┘
注册
// <inheritdoc cref="ServicesExtensions.ApISafeSetup(IServiceCollection, Action{ApISafe})"/>
services.ApISafeSetup(options =>
{
options.AppKeys = "youraccount1,youraccount2";
options.AppKeyName = "appKey"; // 请求头中 appKey 的键名
options.TimeStampName = "timeStamp"; // 请求头中时间戳的键名
options.ExpiresMinute = 2; // 超时时间(分钟),默认 2
});
客户端请求示例
curl -X GET "https://your-api.com/api/data" \
-H "appKey: youraccount1" \
-H "timeStamp: 1700000000000"
场景六:API 签名验证(ApISignFilter)
验证 MD5(appKey + signKey + timeStamp + data) 签名完整性,支持 GET/POST/PUT/DELETE。
┌──────────────┐ Order=8 ┌────────────────────────────┐
│ Client │ ──────────────────────────────▶ │ ApISignFilter │
│ (Header) │ appKey + timeStamp + signature │ 1. Validate() + ValidateSine()│
└──────────────┘ │ 2. 检查 IgnoreApis 白名单 │
│ 3. 提取请求参数 body/query │
│ 4. MD5 签名比对 │
└────────────────────────────┘
注册
// <inheritdoc cref="ServicesExtensions.ApISignSetup(IServiceCollection, Action{AppSign})"/>
services.ApISignSetup(options =>
{
options.AppKeys = "client1,client2";
options.SignKey = "your-secret-sign-key";
options.AppKeyName = "appKey";
options.TimeStampName = "timeStamp";
options.SignatureName = "signature";
options.ExpiresMinute = 5;
options.IgnoreApis = "/api/public,/api/health"; // 指定跳过签名的路径
});
客户端签名生成示例
// 客户端生成签名
public static string GenerateSignature(string appKey, string signKey, long timeStamp, string data)
{
var signStr = appKey + signKey + timeStamp + data;
return RuoVea.ExCrypt.Md5Crypt.Encrypt(signStr); // MD5 大写十六进制
}
请求示例
# POST 请求 — data 为请求体 JSON
curl -X POST "https://your-api.com/api/order" \
-H "appKey: client1" \
-H "timeStamp: 1700000000000" \
-H "signature: B1F2A3C4D5E6F7A8B9C0D1E2F3A4B5C6" \
-H "Content-Type: application/json" \
-d '{"productId": 123, "quantity": 1}'
⚠️ 已知 BUG — 配置绑定缺失 (🔴 Critical):
ApISignSetup的两个重载均 未调用services.Configure<AppSign>(config/actionOptions)(参见ServicesExtensions.cs第 274-311 行)。 但ApISignFilter构造函数依赖IOptions<AppSign>来获取配置:public ApISignFilter(IOptions<AppSign> options) { _options = options.Value; // ← 此处将得到未绑定的默认 AppSign 实例! }结果: 通过
ApISignSetup配置的所有参数(AppKeys、SignKey 等)完全无效,签名验证会因SignKey为null而在ValidateSine()中抛出ArgumentNullException。临时修复方案: 在调用
ApISignSetup之前手动添加:services.Configure<AppSign>(options => { options.AppKeys = "client1,client2"; options.SignKey = "your-secret-sign-key"; // ... 其余配置 });永久修复: 在
ApISignSetup方法体中加入services.Configure<AppSign>(actionOptions)/services.Configure<AppSign>(config)。
✅ 已修复 — SagnBase.Validate() 变量赋值: 默认值
"timeStamp"现在正确赋值到TimeStampName。
场景七:模型验证(ValidateModelActionFilter)
替代 ASP.NET Core 默认的模型验证,输出统一的 RESTful 格式。
注册
// 基础注册 — 注册自定义 ModelState 验证过滤器
// <inheritdoc cref="ServicesExtensions.AddValidateSetup(IServiceCollection, Action{MvcOptions})"/>
services.AddValidateSetup();
// 进阶注册 — 同时自定义 ApiBehaviorOptions(控制 InvalidModelStateResponseFactory)
// <inheritdoc cref="ServicesExtensions.AddRestfulModelsSetup(IServiceCollection, Action{ApiBehaviorOptions})"/>
services.AddRestfulModelsSetup(options =>
{
options.SuppressModelStateInvalidFilter = true; // 禁用默认模型验证
});
验证失败响应格式
{
"Code": 500,
"Message": "字段1不能为空|字段2格式不正确",
"Data": null,
"Extras": null
}
场景八:中间件(匿名访问 / 鉴权旁路 / 健康检查等)
匿名访问白名单中间件
对配置的白名单路径设置虚拟 ClaimsPrincipal,跳过后续身份认证。必须在 UseAuthentication / UseAuthorization 之前注册。
// <inheritdoc cref="MiddlewareHelpers.UseAnonymousMiddle(IApplicationBuilder)"/>
app.UseAnonymousMiddle();
{
"Anonymous": {
"Enabled": true,
"WhitelistPaths": "/api/login,/api/public/*,/health"
}
}
| 路径模式 | 匹配行为 |
|---|---|
/api/login |
精确匹配 |
/api/public/* |
前缀匹配(StartsWith) |
鉴权旁路中间件(开发/测试用)
通过 URL 参数模拟 JWT 鉴权,仅适用于开发/测试环境。
// <inheritdoc cref="MiddlewareHelpers.UseByPassAuthMiddle(IApplicationBuilder)"/>
app.UseByPassAuthMiddle();
| 端点 | 功能 |
|---|---|
GET /noauth?userid=8&rolename=AdminTest |
设置当前用户为 userid=8, role=AdminTest |
GET /noauth/reset |
重置为未登录状态 |
健康检查端点
// <inheritdoc cref="MiddlewareHelpers.UseHealthMiddle(IApplicationBuilder)"/>
app.UseHealthMiddle(); // 访问: GET /health → 返回 "ok" (200)
服务列表端点
// <inheritdoc cref="MiddlewareHelpers.UseAllServicesMiddle(IApplicationBuilder, IServiceCollection)"/>
app.UseAllServicesMiddle(builder.Services); // 访问: GET /allservices → 列出所有已注册服务
虚拟路径中间件
支持反向代理路径前缀,仅在 !NET5_0 条件下编译。
// 方式一: 自动从 appsettings.json VirtualPath 读取
// <inheritdoc cref="VirtualPathMiddle.UseVirtualPathMiddle(WebApplication)"/>
app.UseVirtualPathMiddle();
// 方式二: 显式指定虚拟路径
// <inheritdoc cref="VirtualPathMiddle.UseVirtualPathMiddle(WebApplication, string)"/>
app.UseVirtualPathMiddle("/api-gateway");
UI 资源解压
自动将 wwwroot/ui.zip 解压到 wwwroot/ 目录下(若 wwwroot/ui/index.html 不存在)。
// <inheritdoc cref="UiFilesZipSetup.AddUiFilesZipSetup(IServiceCollection, IWebHostEnvironment)"/>
services.AddUiFilesZipSetup(app.Environment);
场景九:HttpContext 扩展方法
RuoVea.ExFilter 提供了丰富的 HttpContext / HttpRequest 扩展方法:
IP 地址
| 方法 | 返回 | 说明 |
|---|---|---|
context.GetIp() |
string |
优先 X-Real-IP / X-Forwarded-For → RemoteIpAddress |
context.GetLanIp() |
string |
本机局域网 IPv4 地址 |
context.GetLocalIpAddressToIPv4() |
string |
连接本地 IPv4 |
context.GetLocalIpAddressToIPv6() |
string |
连接本地 IPv6 |
context.GetRemoteIpAddressToIPv6() |
string |
连接远程 IPv6 |
请求信息
| 方法 | 返回 | 说明 |
|---|---|---|
request.GetRequestUrlAddress() |
string |
完整请求 URL(Scheme://Host/PathBase/Path/Query) |
request.GetRequestHost() |
string |
(http\|s)://Host |
request.GetRequestHostPort() |
string |
(http\|s)://Host:端口 |
request.GetRefererUrl() |
string |
Referer 请求头 |
User-Agent 解析(基于 UAParser)
| 方法 | 返回 | 说明 |
|---|---|---|
context.GetBrowser() |
string |
浏览器识别(Chrome/Safari/Firefox/Edg/Opera/IE) |
context.UserAgent() |
string |
原始 User-Agent 字符串 |
context.GetOSVersion() |
string |
操作系统版本(Windows/Mac/Linux/Android 等) |
context.UserAgentInfo() |
(string PhoneModel, string OS, string Browser) |
UAParser 完整解析 |
context.GetDefault() |
ClientInfo |
UAParser Parser.GetDefault().Parse() 原始结果 |
其他
| 方法 | 返回 | 说明 |
|---|---|---|
context.SigninToSwagger(accessToken) |
void |
设置 Swagger 自动授权响应头 |
context.SignoutToSwagger() |
void |
取消 Swagger 自动授权 |
remotePath.EncodeRemotePath() |
string |
远程路径 URL Encode(保证首尾 /) |
⚙️ 配置选项详解
选项类层级
LogBase SagnBase
├─ Enabled: bool ├─ AppKeys: string
├─ LogToFile: bool ├─ AppKeyName: string = "appKey"
└─ LogMore: bool ├─ TimeStampName: string = "timeStamp"
└─ ExpiresMinute: int = 0
ExceptionLog : LogBase
ApISafe : SagnBase
RequestLog : LogBase
└─ IgnoreApis: string AppSign : SagnBase
├─ SignKey: string
Anonymous ├─ SignatureName: string = "signature"
├─ Enabled: bool = true └─ IgnoreApis: string
└─ WhitelistPaths: string
ExceptionLog(继承 LogBase)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Enabled |
bool |
false |
是否启用全局异常拦截 |
LogToFile |
bool |
false |
是否将异常写入日志文件(LogFactory) |
LogMore |
bool |
false |
是否记录更多内容(调用 IRestfulFilterLog.ExceptionLog 写入数据库) |
RequestLog(继承 LogBase)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Enabled |
bool |
false |
是否启用请求操作日志 |
LogToFile |
bool |
false |
是否写入调试日志文件 |
LogMore |
bool |
false |
是否记录更多内容(调用 IRestfulFilterLog.OperationLog 写入数据库) |
IgnoreApis |
string |
"" |
忽略的接口路径,多个以逗号分隔 |
ApISafe(继承 SagnBase)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
AppKeys |
string |
无(必传) | 合法 AppKey 列表,多个以逗号分隔 |
AppKeyName |
string |
"appKey" |
请求头中 AppKey 的键名 |
TimeStampName |
string |
"timeStamp" |
请求头中时间戳的键名 |
ExpiresMinute |
int |
0(Validate 后自动补为 2) |
时间戳有效时长(分钟) |
AppSign(继承 SagnBase,增加签名相关字段)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
AppKeys |
string |
无(必传) | 合法 AppKey 列表 |
AppKeyName |
string |
"appKey" |
请求头中 AppKey 的键名 |
TimeStampName |
string |
"timeStamp" |
请求头中时间戳的键名 |
ExpiresMinute |
int |
0(Validate 后自动补为 2) |
时间戳有效时长(分钟) |
SignKey |
string |
无(必传) | 签名密钥(双方约定) |
SignatureName |
string |
"signature" |
请求头中签名字段名 |
IgnoreApis |
string |
"" |
跳过签名验证的路径 |
Anonymous
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Enabled |
bool |
true |
是否启用匿名中间件 |
WhitelistPaths |
string |
"" |
白名单路径,多个以逗号分隔。支持 /* 前缀匹配 |
🛡️ 错误处理与日志
异常体系
ExceptionFilter 根据异常类型分类返回统一响应,并在 LogToFile=true 时通过 LogFactory.Error() 写入日志文件:
| 异常类型 | HTTP 状态码 | 返回消息 |
|---|---|---|
ParamiterException |
500 | paramiter.Message |
ConnctionException |
408 | connEx.Message |
ArgumentOutOfRangeException |
400 | argumentOutOfRangeEx.Message |
ArgumentNullException |
400 | argumentNullEx.Message |
ArgumentException |
400 | argumentEx.Message |
| 其他 | 500 | "内部服务器错误:{Message}"(i18n) |
日志写入链
Exception → ExceptionFilter → LogFactory.Error() [LogToFile=true]
→ IRestfulFilterLog.ExceptionLog(ExceptionVo) [LogMore=true]
Request → RequestActionFilter → LogFactory.Debug() [LogToFile=true]
→ IRestfulFilterLog.OperationLog(OperationVo) [LogMore=true]
自定义日志存储
实现 IRestfulFilterLog 接口即可接管异常日志和操作日志的持久化:
// <inheritdoc cref="IRestfulFilterLog"/>
public class MyRestfulFilterLog : RestfulFilterLog
{
private readonly MyDbContext _db;
public MyRestfulFilterLog(MyDbContext db) => _db = db;
public override void ExceptionLog(ExceptionVo exception)
{
_db.ExceptionLogs.Add(exception);
_db.SaveChanges();
}
public override void OperationLog(OperationVo operation)
{
_db.OperationLogs.Add(operation);
_db.SaveChanges();
}
}
// 注册
services.AddRestfulSetup<MyRestfulFilterLog>();
❗ 性能陷阱: 生产环境同步写入数据库会显著影响请求响应时间。建议使用
Channel<T>或IBackgroundTaskQueue进行异步批量写入。
🧵 线程安全
| 组件 | 线程安全 | 说明 |
|---|---|---|
ExceptionFilter |
✅ 是 | 通过 DI 注入,每次请求创建新实例(取决于注册生命周期) |
RequestActionFilter |
✅ 是 | 同上 |
ResourceFilter |
✅ 是 | 每次请求由 ASP.NET Core 框架实例化 |
ResultFilter |
✅ 是 | 同上 |
ApISafeFilter |
✅ 是 | 通过 DI 注入 IOptions<ApISafe>,线程安全 |
ApISignFilter |
✅ 是 | 同上 |
ValidateModelActionFilter |
✅ 是 | 同上 |
AnonymousMiddleware |
⚠️ 是 | 从 AppSettings 静态读取配置,多请求下读取同一配置无竞态 |
ByPassAuthMiddleware |
⚠️ 否 | _currentUserId / _currentRoleName 为实例字段,单例模式下所有请求共享状态! |
HttpContextExtensions |
✅ 是 | 纯扩展方法,无共享状态 |
SagnBase.Validate() |
✅ 是 | 配置对象在 DI 中为 Scoped/Transient,方法内无竞态 |
⚠️ 重要:
ByPassAuthMiddleware使用实例字段存储当前模拟的用户信息,在 Singleton 生命周期下多个请求会互相覆盖。请务必仅在开发环境使用,且不要在高并发测试中使用。
📊 过滤器速查表
| 过滤器 | 实现接口 | Order | 功能 | 跳过标记 |
|---|---|---|---|---|
ExceptionFilter |
ExceptionFilterAttribute |
— | 全局异常捕获,分类处理,统一 RESTful 输出 | [NonException] |
RequestActionFilter |
IAsyncActionFilter |
8888 |
请求日志记录(参数/结果/耗时),BadRequest 处理 | [NonRequestAction] |
ResourceFilter |
IResourceFilter |
— | Referer 防盗链检查 | [NonResource] |
ResultFilter |
IResultFilter |
— | ContentResult/ObjectResult 包装为 RestfulResult | [NonRestfulResult] |
ApISafeFilter |
IAsyncActionFilter |
6 |
AppKey + 时间戳校验 | [NonAplSafe] |
ApISignFilter |
IAsyncActionFilter |
8 |
MD5 签名验证(完整请求) | [NonAplSign] |
ValidateModelActionFilter |
ActionFilterAttribute |
— | 模型验证失败统一 RESTful 输出 | — |
跳过标记(Marker Attribute)
| Attribute | 作用域 | 说明 |
|---|---|---|
[NonException] |
Action / Controller | 跳过全局异常日志收集 |
[NonRequestAction] |
Action / Controller | 跳过请求日志记录 |
[NonResource] |
Action / Controller | 跳过资源防盗链检查 |
[NonRestfulResult] |
Action / Controller | 跳过 RESTful 结果统一格式化 |
[NonAplSafe] |
Action / Controller | 跳过 API 安全校验 |
[NonAplSign] |
Action / Controller | 跳过签名验证 |
中间件速查表
| 中间件 | 注册方法 | 端点 | 说明 |
|---|---|---|---|
AnonymousMiddleware |
UseAnonymousMiddle() |
— | 白名单路径设置虚拟 ClaimsPrincipal |
ByPassAuthMiddleware |
UseByPassAuthMiddle() |
/noauth, /noauth/reset |
URL 参数模拟鉴权 |
| Health Check | UseHealthMiddle() |
/health |
返回 "ok" (200) |
| All Services | UseAllServicesMiddle(services) |
/allservices |
HTML 表格列出所有 DI 服务 |
| Virtual Path | UseVirtualPathMiddle() |
— | 反向代理路径前缀(!NET5_0) |
🌍 国际化
所有错误消息和提示支持以下 7 种语言,根据 CultureInfo.CurrentUICulture 自动切换:
| 语言 | 区域代码 |
|---|---|
| 简体中文 | zh-CN |
| 繁体中文(台湾) | zh-TW |
| 粤语(香港) | zh-HK |
| 英语 | en-US |
| 法语 | fr-FR |
| 日语 | ja-JP |
| 越南语 | vi-VN |
国际化资源覆盖示例
| Key | zh-CN | en-US |
|---|---|---|
appkeysempty |
AppKeys为空,请设置 | AppKeys is empty, please set |
illegalrequest |
非法请求 | Illegal request |
interfacetimeout |
接口请求超时 | Interface request timeout |
requestillegal |
请求不合法 | Request illegal |
requestillegalk |
请求不合法-k | Request illegal-k |
requestillegalt |
请求不合法-t | Request illegal-t |
validationfailed |
Validation Failed:\r\n{0} | Validation Failed:\r\n{0} |
servererrorlogskip |
服务器发生严重错误导致日志记录被跳过,请检查全局错误日志处理,{0} | A serious server error caused log recording to be skipped, please check the global error log handler,{0} |
requestsuccess |
请求成功 | Request successful |
requestfailure |
请求失败 | Request failed |
🗺️ 版本迁移指南
v8.0.x → v10.0.x
- API 无变化。v10.0.x 仅在
net10.0TFM 上编译,所有公开 API 签名保持一致。 - 依赖包版本从
8.0.*同步升级至10.0.*:RuoVea.ExConfig→10.0.*RuoVea.ExCrypt→10.0.*RuoVea.ExDto→10.0.*RuoVea.ExLog→10.0.*RuoVea.ExUtil→10.0.*System.Security.Cryptography.Pkcs→10.0.8System.Security.Cryptography.Xml→10.0.8
注册方式升级建议
旧版手动注册 MvcBuilder:
// 旧版 (不再推荐)
services.AddMvcCore(options => {
options.Filters.Add<ExceptionFilter>();
}).AddApiExplorer();
现有 直接使用扩展方法:
// 新版 (推荐)
services.ExceptionSetup();
⚠️ 已知问题与注意事项
🔴 严重(Critical)
| 编号 | 问题 | 影响范围 | 说明 |
|---|---|---|---|
| ApISignSetup 缺少配置绑定 ✅ 已修复 | — | 现在 ApISignSetup 调用 services.Configure<AppSign>(...) 正确绑定配置到 IOptions<AppSign>。 |
🟡 中等(Medium)
| 编号 | 问题 | 影响范围 | 说明 |
|---|---|---|---|
| SagnBase.Validate() 赋值目标错误 ✅ 已修复 | — | 默认值现在正确赋值给 TimeStampName。 |
|
| BUG-03 | ByPassAuthMiddleware 非线程安全 | 并发测试场景 | 实例字段 _currentUserId / _currentRoleName 在 Singleton 模式下多请求互相覆盖。务必仅限开发环境单用户测试使用。 |
🔵 建议(Info)
| 编号 | 建议 | 说明 |
|---|---|---|
| INFO-01 | ResourceFilter 仅检查 localhost |
防盗链逻辑仅判断 Referer 是否包含 localhost,生产部署需扩展为实际域名校验。 |
| INFO-02 | LogMore 同步写入阻塞请求 | LogMore=true 时同步执行 IRestfulFilterLog 实现,建议生产环境用异步队列解耦。 |
| INFO-03 | EncryptionRequest/Response Middleware 已注释 | MiddlewareHelpers 中 EncryptionRequestMiddle 和 EncryptionResponseMiddle 的注册方法已被注释,相关中间件类存在但未暴露。 |
| INFO-04 | .NET 5.0 下 VirtualPathMiddle 不可用 | VirtualPathMiddle 整个文件被 #if !NET5_0 预处理指令包裹,net5.0 项目不可见。 |
📄 License
MIT License (c) RuoVea
🔗 相关资源: NuGet Gallery · 问题反馈
| 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
- RuoVea.ExConfig (>= 10.0.0.1)
- RuoVea.ExCrypt (>= 10.0.0.1)
- RuoVea.ExDto (>= 10.0.0.4)
- RuoVea.ExLog (>= 10.0.0.2)
- RuoVea.ExUtil (>= 10.0.0.4)
- System.Security.Cryptography.Pkcs (>= 10.0.8)
- UAParser (>= 3.1.47)
NuGet packages (12)
Showing the top 5 NuGet packages that depend on RuoVea.ExFilter:
| Package | Downloads |
|---|---|
|
RuoVea.OmiDict
字典管理 |
|
|
RuoVea.OmiApi.UserRoleMenu
用户角色菜单管理 |
|
|
RuoVea.OmiConfig
参数配置 |
|
|
RuoVea.OmiApi.SystemApp
系统应用管理 |
|
|
RuoVea.OmiLog
字典管理 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0.6 | 152 | 6/24/2026 |
| 10.0.0.5 | 317 | 5/28/2026 |
| 10.0.0.4 | 184 | 5/28/2026 |
| 10.0.0.3 | 362 | 1/27/2026 |
| 9.0.0.5 | 316 | 5/28/2026 |
| 9.0.0.4 | 591 | 1/27/2026 |
| 9.0.0.3 | 139 | 1/26/2026 |
| 8.0.1.12 | 260 | 6/24/2026 |
| 8.0.1.11 | 569 | 5/28/2026 |
| 8.0.1.10 | 261 | 5/28/2026 |
| 8.0.1.9 | 1,044 | 1/27/2026 |
| 7.0.1.11 | 433 | 5/28/2026 |
| 7.0.1.10 | 277 | 5/28/2026 |
| 7.0.1.9 | 1,144 | 1/27/2026 |
| 6.0.19.11 | 503 | 5/28/2026 |
| 6.0.19.10 | 327 | 5/28/2026 |
| 6.0.19.9 | 1,384 | 1/27/2026 |
| 5.0.0.10 | 92 | 5/28/2026 |
| 5.0.0.9 | 96 | 5/28/2026 |
| 5.0.0.8 | 138 | 1/27/2026 |