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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RuoVea.ExFilter" Version="10.0.0.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RuoVea.ExFilter" Version="10.0.0.6" />
                    
Directory.Packages.props
<PackageReference Include="RuoVea.ExFilter" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RuoVea.ExFilter --version 10.0.0.6
                    
#r "nuget: RuoVea.ExFilter, 10.0.0.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RuoVea.ExFilter@10.0.0.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RuoVea.ExFilter&version=10.0.0.6
                    
Install as a Cake Addin
#tool nuget:?package=RuoVea.ExFilter&version=10.0.0.6
                    
Install as a Cake Tool

🛡️ 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)

自动将 ContentResultObjectResult 包装为统一的 RestfulResult 格式。

┌──────────────┐     原始响应                     ┌──────────────────────┐
│ ContentResult │ ────────────────────────────▶   │  ResultFilter         │
│ ObjectResult  │                                 │  ↓ 包装              │
└──────────────┘                                  │  RestfulResult {      │
                                                  │    Code: 200,         │
                                                  │    Data: {...},       │
                                                  │    Message: "请求成功" │
                                                  │  }                    │
                                                  └──────────────────────┘
注册
// <inheritdoc cref="ServicesExtensions.ResultSetup(IServiceCollection)"/>
services.ResultSetup();

不进行统一包装的结果类型(直接透传):ViewResultPartialViewResultFileResultChallengeResultSignInResultSignOutResultRedirectResult 系列、ForbidResultPageResultViewComponentResult


场景五:API 安全校验(ApISafeFilter)

验证请求头中的 appKeytimeStamp,防止未授权访问和重放攻击。

┌──────────────┐     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 等)完全无效,签名验证会因 SignKeynull 而在 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.0 TFM 上编译,所有公开 API 签名保持一致。
  • 依赖包版本从 8.0.* 同步升级至 10.0.*
    • RuoVea.ExConfig10.0.*
    • RuoVea.ExCrypt10.0.*
    • RuoVea.ExDto10.0.*
    • RuoVea.ExLog10.0.*
    • RuoVea.ExUtil10.0.*
    • System.Security.Cryptography.Pkcs10.0.8
    • System.Security.Cryptography.Xml10.0.8

注册方式升级建议

旧版手动注册 MvcBuilder:

// 旧版 (不再推荐)
services.AddMvcCore(options => {
    options.Filters.Add<ExceptionFilter>();
}).AddApiExplorer();

现有 直接使用扩展方法:

// 新版 (推荐)
services.ExceptionSetup();

⚠️ 已知问题与注意事项

🔴 严重(Critical)

编号 问题 影响范围 说明
BUG-01 ApISignSetup 缺少配置绑定 ✅ 已修复 现在 ApISignSetup 调用 services.Configure<AppSign>(...) 正确绑定配置到 IOptions<AppSign>

🟡 中等(Medium)

编号 问题 影响范围 说明
BUG-02 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 已注释 MiddlewareHelpersEncryptionRequestMiddleEncryptionResponseMiddle 的注册方法已被注释,相关中间件类存在但未暴露。
INFO-04 .NET 5.0 下 VirtualPathMiddle 不可用 VirtualPathMiddle 整个文件被 #if !NET5_0 预处理指令包裹,net5.0 项目不可见。

📄 License

MIT License (c) RuoVea


🔗 相关资源: NuGet Gallery · 问题反馈

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 162 6/24/2026
10.0.0.5 318 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 274 6/24/2026
8.0.1.11 569 5/28/2026
8.0.1.10 261 5/28/2026
8.0.1.9 1,045 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
Loading failed