GJJ.Keystone.ArchitectureGuard
4.2.2
dotnet add package GJJ.Keystone.ArchitectureGuard --version 4.2.2
NuGet\Install-Package GJJ.Keystone.ArchitectureGuard -Version 4.2.2
<PackageReference Include="GJJ.Keystone.ArchitectureGuard" Version="4.2.2" />
<PackageVersion Include="GJJ.Keystone.ArchitectureGuard" Version="4.2.2" />
<PackageReference Include="GJJ.Keystone.ArchitectureGuard" />
paket add GJJ.Keystone.ArchitectureGuard --version 4.2.2
#r "nuget: GJJ.Keystone.ArchitectureGuard, 4.2.2"
#:package GJJ.Keystone.ArchitectureGuard@4.2.2
#addin nuget:?package=GJJ.Keystone.ArchitectureGuard&version=4.2.2
#tool nuget:?package=GJJ.Keystone.ArchitectureGuard&version=4.2.2
GJJ.Keystone.ArchitectureGuard
面向 GJJ 微服务的架构守护测试库。在
*.Tests项目里跑,违规即让单元测试失败、CI 红灯; 报告带颜色、严重度分组、修复建议、文件超链接(IDE 可点击跳转)。核心承诺:零运行时依赖(不引 ASP.NET / Microsoft.Extensions.Logging),只在 CI / 本地测试期参与。
适用版本:3.0.7+
为什么独立成包
GJJ.Keystone.Abstractions应保持精简,只放契约GJJ.Keystone.Toolkit提供运行时无关的通用工具代码GJJ.Keystone.AspNetCore引入了 ASP.NET Core 概念,而本库的职责之一恰好是让非 Web 层免受这些概念污染
把架构规则放在独立程序集里,可避免把 Web 依赖拉入只需要在 CI / 测试期检查的项目。
速查
| 你想知道 | 看哪一节 |
|---|---|
| 有哪些规则 / 严重度 / 我违规会怎样 | 二、规则全集 |
| 怎么在我的 *.Tests 项目里接入 | 三、典型用法 |
| 失败时报告长什么样 / 怎么去掉颜色 | 四、输出格式与颜色 |
| 历史违规怎么豁免 | 五、基线管理(IgnoreFiles) |
| 怎么扩展自定义规则 | 六、扩展自定义规则 |
| 严重度怎么映射到 CI 行为 | 七、严重度模型 |
| 已知限制 / 误报特征 | 八、已知限制 |
一、模块全景
┌──────────────────────────────────────────────────────────────┐
│ ArchitectureGuardOptions │
│ - RootDirectory │
│ - IgnoredDirectoryNames / IgnoredRelativeFilePaths │
│ - HttpContextProtectedLayers │
│ - ControlledContextFieldNames │
│ - ViewModelDirectoryNames │
│ - AspNetCoreProtectedProjectSegments │
└──────────────────────────────────────────────────────────────┘
↓ ArchitectureGuard.Scan / ScanGjjDefaults
┌──────────────────────────────────────────────────────────────┐
│ IArchitectureRule × 18 │
│ - 文本扫描(SourceLineReader 剥离注释 / 字符串) │
│ - PathClassifier 判定层(Application/Infrastructure/...) │
│ - 每条规则带 DefaultSeverity + Suggestion │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ ArchitectureGuardResult │
│ - Violations: IReadOnlyList<ArchitectureViolation> │
│ - Format(): 带颜色 / 分组 / 修复建议的报告 │
│ - ThrowIfFailed(): xUnit 断言失败入口 │
└──────────────────────────────────────────────────────────────┘
↓ 进入 xUnit 测试输出
(终端: 渲染颜色 / IDE: 渲染加粗下划线斜体)
| 关键类型 | 文件 | 用途 |
|---|---|---|
ArchitectureGuard |
ArchitectureGuard.cs |
静态扫描入口(Scan / ScanGjjDefaults) |
ArchitectureGuardOptions |
ArchitectureGuardOptions.cs |
扫描根目录 + 规则可配置集合 |
ArchitectureGuardResult |
ArchitectureGuardResult.cs |
扫描结果 + Format() / ThrowIfFailed() |
ArchitectureViolation |
ArchitectureViolation.cs |
单条违规(RuleId / FilePath / Line / Message / Severity / Suggestion) |
ArchitectureSeverity |
ArchitectureSeverity.cs |
严重度枚举(Info / Warning / Error / Critical) |
ViolationFormatOptions |
ViolationFormatOptions.cs |
渲染选项(UseColor / ShowSuggestions / ShowPassingBanner) |
IArchitectureRule |
IArchitectureRule.cs |
规则接口(RuleId / Description / DefaultSeverity / Evaluate) |
GjjDefaultArchitectureRules |
GjjDefaultArchitectureRules.cs |
默认规则集装配(19 条) |
Internal/PathClassifier |
内部 | 层 / 文件归类(Controller / Application / ...) |
Internal/SourceLineReader |
内部 | 文本扫描器(剥离注释;可选保留字符串内容) |
Internal/ClassScanner |
内部 | 类级扫描器:聚合 partial class、检查类上 / 类体内 attribute |
Internal/ViolationReportRenderer |
内部 | ANSI 着色 / 分组 / 80 列换行 |
二、规则全集(19 条)
按主题分组。每条规则一旦命中:
- 严重度 ≥ Error 的违规默认让
ThrowIfFailed()抛InvalidOperationException - 严重度 = Warning / Info 不阻断(但会显示在报告里)
如何调整某条规则的严重度:在调用
Scan(...)时传入自定义IArchitectureRule替换默认实例,覆写DefaultSeverity。
分层 / DTO 边界(6 条)
| ID | 规则 | 默认严重度 | 检测点 |
|---|---|---|---|
| GJJ001 | ForbiddenHttpContext | Error | 非 Web 层(Application/Domain/Repository/Infrastructure/Model)不得依赖 HttpContext / IHttpContextAccessor / HttpRequest / HttpResponse / ISession / IFormCollection / IFormFile / IWebHostEnvironment / IHostingEnvironment / RouteData / RouteValueDictionary;也不得继承 Controller / ControllerBase |
| GJJ002 | ControllerDtoControlledFields | Error | Controller 入参 DTO(Controllers/**/DTOs/C_*.cs)与响应 ViewModel(Controllers/**/ViewModels/*VM.cs)不得暴露由服务端上下文控制的字段。默认字段:OperatorId、CreatedBy、ModifiedBy、UpdatedBy、CreateTime、CreatedTime、ModifiedTime、UpdateTime、UpdatedTime、IsDeleted、Deleted、IpAddress、ClientIp、UserAgent、RequestId、TraceId、CorrelationId、SessionId、TenantId |
| GJJ003 | CrossLayerDtoUsage | Warning | Controller 出参必须用 C_ DTO 或 *VM;Application 不得引用 C_ DTO / *VM / 任意 .Api.* 子命名空间;Infrastructure / Domain / Model 不得引用任何 A_ / C_ / *VM 类型 |
| GJJ004 | ProjectFileForbiddenReference | Error | 项目名含 .Application / .Domain / .Model / .Infrastructure 的 .csproj 禁止引用 Microsoft.AspNetCore.App / .Http / .Mvc / .Authentication / .Authorization / .Cors / .Routing / .HttpOverrides / Swashbuckle.AspNetCore / Serilog.AspNetCore |
| GJJ019 | KeystonePackageExclusivity | Error | 同一 .csproj 不得同时引用 GJJ.Keystone.AspNetCore 与 GJJ.Keystone.AspNetCore.Gateway(两包共享命名空间,同引必 CS0433;业务服务用主包、网关宿主用轻量包) |
| GJJ005 | SmEncryptionRuntimeChecks | Critical | *SMEncryptionMiddleware*.cs 必须同时引用 INonceStore / TryConsumeNonceAsync 与 IsWithinClockSkew / MaxClockSkewSeconds;且 MaxClockSkewSeconds = N 字面量须 ≤ 300 秒(M1 P0 防重放底线) |
异步 / 通用代码风险(2 条)
| ID | 规则 | 默认严重度 | 检测点 |
|---|---|---|---|
| GJJ006 | SyncOverAsync | Error | 在非测试 / 非 Program.cs / 非 Mappings-Profiles-Configurations 目录下出现 .Result、.Wait()、.GetAwaiter().GetResult()(线程池死锁源头) |
| GJJ010 | SwallowAllExceptions | Warning | catch (Exception ...) 块的整个 body 内既无 throw 也无 Log/logger 调用(典型"吞异常") |
安全 / 合规(7 条)
| ID | 规则 | 默认严重度 | 检测点 |
|---|---|---|---|
| GJJ007 | HardcodedSecrets | Critical | 源码字面量内出现 password=、apiKey=、secret=、连接串里 Password=...、"Bearer <20+ chars>" 等硬编码秘密 |
| GJJ008 | WeakCryptoApi | Critical | 使用 MD5 / SHA1 / DESCryptoServiceProvider / TripleDES / RijndaelManaged / CipherMode.ECB |
| GJJ009 | UnsafeDeserialization | Critical | BinaryFormatter / NetDataContractSerializer / TypeNameHandling.All-Auto-Objects(RCE 风险) |
| GJJ011 | RawSqlInterpolation | Critical | FromSqlRaw($"...") / ExecuteSqlRaw($"...") 等 EF Core *Raw 接口接收字符串插值(SQL 注入) |
| GJJ012 | InsecureCookie | Error | new CookieOptions { ... } 缺 Secure = true / HttpOnly = true / SameSite = 任一 |
| GJJ017 | CorsAllowAnyOrigin | Critical | .AllowAnyOrigin() / .WithOrigins("*") |
| GJJ018 | LoggingPii | Error | Logger 调用模板含 {password} / {token} / {idCard} / {mobile} / {bankCard} / {身份证} / {手机} / {密码} 等敏感占位符 |
架构纪律(4 条)
| ID | 规则 | 默认严重度 | 检测点 |
|---|---|---|---|
| GJJ013 | DomainEntityInController | Error | Controller 文件 using *.Domain* 或 public 方法签名含 *Entity / *Aggregate 后缀类型 |
| GJJ014 | RepositoryInController | Error | Controller 字段 / 构造参数直接持有 *Repository / I*Repository 类型 |
| GJJ015 | DbContextOutsideInfra | Error | DbContext 派生与字段必须位于 Infrastructure / Repository / *.JobService / *.Worker / *.HostedService / *.BackgroundService 项目 |
| GJJ016 | MissingAuthorize | Info / Error(自适应) | Controller 文件没有任何 [Authorize] 或 [AllowAnonymous] 标注。严重度按项目实际授权配置自动选择:扫描到 requireAuthenticatedByDefault: false 或 FallbackPolicy = null → Error(未标 = 真公开);否则 → Info(FallbackPolicy 已保护)。详见下方 GJJ016 特别说明。 |
GJJ016 特别说明
GJJ016 与 GJJ.Keystone.AspNetCore/Auth/JwtAuthExtensions.cs 的 AddJwtAuth 协同设计:
public static IServiceCollection AddJwtAuth(
this IServiceCollection services,
Action<JwtAuthOptions> configure,
bool requireAuthenticatedByDefault = true, // ★ 默认 true
ILogger? startupLogger = null)
{
// ...
services.AddAuthorization(options =>
{
if (requireAuthenticatedByDefault)
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
}
});
}
关键事实:当 requireAuthenticatedByDefault = true(默认),ASP.NET Core 授权管线注册 FallbackPolicy = RequireAuthenticatedUser()——这意味着所有未标注授权属性的端点都默认需要认证。
因此 Controller 三种状态的真实含义:
| Controller 标注 | 框架行为 | 安全风险 | reviewer 关注度 |
|---|---|---|---|
| 不标 | FallbackPolicy 强制认证 | 无(已认证) | 低 |
[Authorize] |
显式认证 | 无 | 低(语义同默认) |
[AllowAnonymous] |
显式破除默认认证 | 取决于业务 | 高(真正的公开端点) |
GJJ016 严重度自适应:规则会扫描全仓库源码自动决定严重度,无需手动配置:
| 检测到的信号 | 严重度 | 含义 |
|---|---|---|
requireAuthenticatedByDefault: false(在任何 .cs 文件里) |
Error | Keystone AddJwtAuth 关闭了 FallbackPolicy,未标 = 真公开 → 阻断 |
FallbackPolicy = null(手写关 fallback) |
Error | 直接关掉 ASP.NET Core 内置 fallback → 阻断 |
都没检测到(默认 / 显式 true / 仓库没用 AddJwtAuth) |
Info | 假设 FallbackPolicy 已保护 → 仅可读性提示 |
实现见 Rules/MissingAuthorizeRule.cs:DetectEffectiveSeverity。
强制升级到 Error(绕过自动检测)示例:
// 不论 FallbackPolicy 状态,强制所有 Controller 必须显式标授权
public sealed class StrictMissingAuthorizeRule : IArchitectureRule
{
private readonly MissingAuthorizeRule _inner = new();
public string RuleId => _inner.RuleId;
public string Description => _inner.Description;
public ArchitectureSeverity DefaultSeverity => ArchitectureSeverity.Error;
public IEnumerable<ArchitectureViolation> Evaluate(ArchitectureScanContext ctx)
=> _inner.Evaluate(ctx).Select(v => v with { Severity = ArchitectureSeverity.Error });
}
// 装配
var rules = GjjDefaultArchitectureRules.Create()
.Where(r => r.RuleId != "GJJ016")
.Concat(new[] { new StrictMissingAuthorizeRule() })
.ToArray();
ArchitectureGuard.Scan(options, rules).ThrowIfFailed();
三、典型用法
3.1 最简调用(推荐起点)
using GJJ.Keystone.ArchitectureGuard;
public sealed class ArchitectureGuardTests
{
[Fact]
public void GJJ_default_architecture_rules_should_pass()
{
var options = new ArchitectureGuardOptions
{
RootDirectory = LocateRepositoryRoot(),
};
ArchitectureGuard.ScanGjjDefaults(options).ThrowIfFailed();
}
private static string LocateRepositoryRoot()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null)
{
if (dir.EnumerateFiles("*.sln").Any())
return dir.FullName;
dir = dir.Parent;
}
throw new DirectoryNotFoundException("repo root (含 *.sln) 未找到");
}
}
ThrowIfFailed() 在违规时抛 InvalidOperationException,消息体就是带颜色 + 分组 + 修复建议的报告。
3.2 自定义规则集(跳过部分规则 / 改严重度)
var result = ArchitectureGuard.Scan(options,
new ForbiddenHttpContextDependencyRule(),
new SmEncryptionRuntimeChecksRule(),
// ...只跑你关心的规则
);
3.3 项目特有受控字段(在默认集合上扩展)
options.ControlledContextFieldNames.UnionWith(new[]
{
"DbPointCode", // 多数据库路由
"ChannelCode", // 渠道标识
"SubjectId", // 业务主体 ID
});
3.4 自定义忽略目录(如本项目特殊产物目录)
options.IgnoredDirectoryNames.Add(".claude");
options.IgnoredDirectoryNames.Add("artifacts");
四、输出格式与颜色
4.1 实际样例(带颜色版会在终端 / Rider / VS Code 渲染)
================================================================================
GJJ Architecture Guard — 23 violation(s) across 4 rule(s) ← 加粗
================================================================================
[CRITICAL] 1 violation(s) ← 亮红+粗
--------------------------------------------------------------------------------
GJJ005 SMEncryptionMiddleware 必须同时具备 nonce 去重 ... ← 亮红+粗
Demo/Security/SmEncryptionMiddleware.cs:1 ← 下划线
SMEncryptionMiddleware 缺少 nonce 一次性消费校验……
fix: 注入 INonceStore 并在解密前调用 TryConsumeNonceAsync... ← 斜体
[ERROR] 1 violation(s) ← 红+粗
--------------------------------------------------------------------------------
GJJ001 非 Web 层代码不得依赖 HttpContext 相关 API。
Demo.Application/News/NewsService.cs:7
非 Web 层禁止依赖 HttpContext 相关 API 'HttpContext'……
fix: 在 Controller 内提取所需值(OperatorId、ClientIp 等)……
[WARNING] 1 violation(s) ← 黄+粗
--------------------------------------------------------------------------------
GJJ003 DTO 不得跨层泄漏……
Demo.Infrastructure/News/NewsRepository.cs:14
...
fix: ...
================================================================================
Summary: Critical=1 Error=1 Warning=1 Info=0 ← 各色加粗
================================================================================
4.2 颜色控制
| 场景 | 做法 |
|---|---|
| 终端 dotnet test,想看颜色 | 默认即开 |
| CI 日志想抑制 ANSI | 设环境变量 NO_COLOR=1 或 TERM=dumb |
旧 IDE 测试面板把 \x1b[ 显示成乱码 |
shell 里 export NO_COLOR=1 |
| 测试代码里强制无色 | result.Format(new ViolationFormatOptions { UseColor = false }) |
4.3 ViolationFormatOptions
public sealed record ViolationFormatOptions
{
public bool? UseColor { get; init; } // null = 自动检测
public bool ShowSuggestions { get; init; } = true;
public bool ShowPassingBanner { get; init; } // 通过时也打印"PASSED"横幅
}
五、基线管理(IgnoreFiles)
历史违规一次性塞进 baseline 让 CI 绿;新写违规当场拦截。
var options = new ArchitectureGuardOptions { RootDirectory = root };
options.IgnoreFiles(
// GJJ001:历史代码非 Web 层依赖 HttpContext
"GJJ.UniServiceHub.Application/Admin/AdminService.cs",
"GJJ.UniServiceHub.Infrastructure/Admin/AdminCenterClient.cs",
// GJJ002:响应 ViewModel 含 createTime(出参视为合理)
"GJJ.UniServiceHub.Api/Controllers/.../SysUserInfoVM.cs"
);
ArchitectureGuard.ScanGjjDefaults(options).ThrowIfFailed();
5.1 何时加 baseline
- 历史代码违规,团队没排期治理
- 文本扫描误报(如
HttpRequestMessage字面量含 "HttpRequest" 子串) - 框架/平台规定的合法跨层(特殊代理转发)
5.2 何时不能加 baseline
- 新代码引入的违规 → 必须修代码
- Critical 级别(GJJ005/007/008/009/011/017)→ 改代码或评审豁免,不应静默
baseline 是技术债,应随 sprint 逐项清理。
六、扩展自定义规则
6.1 实现 IArchitectureRule
using GJJ.Keystone.ArchitectureGuard;
using GJJ.Keystone.ArchitectureGuard.Internal;
public sealed class MyProjectSpecificRule : IArchitectureRule
{
private const string FixSuggestion = "改用 IGjjCurrentUser 注入,不直接读 HttpContext.User。";
public string RuleId => "GJJ100";
public string Description => "禁止在 Application 层直接读 HttpContext.User.Claims。";
public ArchitectureSeverity DefaultSeverity => ArchitectureSeverity.Error;
public IEnumerable<ArchitectureViolation> Evaluate(ArchitectureScanContext context)
{
foreach (var file in context.SourceFiles.Where(PathClassifier.IsApplication))
{
foreach (var (lineNumber, text) in SourceLineReader.NonCommentLines(file))
{
if (!text.Contains("HttpContext.User", StringComparison.Ordinal)) continue;
yield return new ArchitectureViolation(
RuleId, file.RelativePath, lineNumber,
"Application 层禁止直接读 HttpContext.User.Claims。",
Severity: DefaultSeverity,
Suggestion: FixSuggestion);
}
}
}
}
6.2 接入
ArchitectureGuard.Scan(options,
new MyProjectSpecificRule(),
// 还可以叠加默认集
new ForbiddenHttpContextDependencyRule(),
// ...
).ThrowIfFailed();
6.3 助手 API(Internal)
测试项目通过 InternalsVisibleTo 可用,也是写自定义规则的好朋友:
| API | 用途 |
|---|---|
PathClassifier.IsApplication(file) |
文件是否在 Application 层 |
PathClassifier.IsInfrastructure(file) |
同上 Infrastructure |
PathClassifier.IsController(file) |
路径含 Controllers/ |
PathClassifier.IsControllerDto(file) |
入参 DTO(C_*.cs in DTOs/) |
PathClassifier.IsControllerViewModel(file, dirs) |
响应 VM(*VM.cs in ViewModels/) |
PathClassifier.IsInProtectedLayer(file, layers) |
是否在受保护层中 |
PathClassifier.IsTestProject(file) |
*.Tests / *.Test / tests/ |
SourceLineReader.NonCommentLines(file) |
逐行扫源码(剥离注释 + 字符串内容) |
SourceLineReader.NonCommentLines(file, keepStringContents: true) |
同上但保留字符串内容(适合检测硬编码字面量) |
ClassScanner.EnumerateClassDeclarations(files) |
遍历所有 class 声明(注释 / 字符串中的伪声明已剥) |
ClassScanner.BuildClassIndex(files) |
className → 所有声明位置(聚合 partial class 跨文件) |
ClassScanner.ClassHasAttribute(decl, regex) |
检查类声明上方连续 attribute + 类体内是否带某 attribute |
ClassScanner.AnyPartialHasAttribute(decls, regex) |
partial class 任一分片带该 attribute 即视为已声明 |
6.4 何时该用 ClassScanner
如果你的规则需要回答**"某个类是否带某 attribute"或"某个类是否包含某成员"**这种 类级问题——用 ClassScanner。
它已处理三类共性边界:
- 一文件多类(不同类各自独立判定)
- partial class 分散在多文件(attribute 标在任一分片都算)
- 注释 / 字符串中的伪 attribute(不误判)
不要在新规则里再写"扫文件全文找 attribute"——交给 ClassScanner。
七、严重度模型
| 级别 | 数值 | 默认 CI 行为 | 适用场景 |
|---|---|---|---|
Critical |
3 | ThrowIfFailed 抛异常 | 安全 / 合规 P0 红线(数据泄漏、RCE、SQL 注入、弱加密) |
Error |
2 | ThrowIfFailed 抛异常 | 架构 / 安全边界(必须修复) |
Warning |
1 | 不阻断 | 应当修复但允许逐步治理 |
Info |
0 | 不阻断 | 提示信息 |
ThrowIfFailed() 默认在任意违规时抛异常(包含 Warning)。如果只想 Error 及以上阻断 CI,自己控制:
var result = ArchitectureGuard.ScanGjjDefaults(options);
var blocking = result.Violations
.Where(v => v.Severity >= ArchitectureSeverity.Error)
.ToList();
if (blocking.Count > 0)
throw new InvalidOperationException(result.Format());
八、已知限制
8.1 文本扫描,非完整词法
- 不解析 C# 11 原始字符串字面量(
"""..."""),出现在受保护层会触发误报——避免使用或加IgnoreFiles - 病态代码模式(极端嵌套、宏拼接)可能误报
- M8 排期升级到 Roslyn Analyzer 后误报会大幅消失
8.2 规则范围限定
| 规则 | 必然误报场景 | 处理建议 |
|---|---|---|
| GJJ006 | AutoMapper / FluentValidation 配置类同步等待 | 默认已豁免 Mappings/Profiles/Configurations 目录 |
| GJJ010 | 业务有意 swallow(清理 / 重试外层) | 加 IgnoreFiles |
| GJJ013 | Controller 同名空间下"非真 Controller"类 | 规则只检查路径含 Controllers/,可调整目录结构 |
| GJJ015 | 独立 Job / Worker 项目 | 默认已识别 .JobService / .Worker / .HostedService / .BackgroundService 后缀 |
| GJJ016 | 文件只放数据契约不放真 Controller | 整 class 取名时去掉 Controller 后缀 |
8.3 不会做
- 不检测运行时行为(鉴权链是否真生效、注入是否真发生)——是静态文本扫描
- 不读取 / 修改源码(只读)
- 不联网
九、跑测试 / CI 集成
# 全 sln 测试
dotnet test GJJMicroserviceKit/GJJMicroserviceKit.sln
# 只跑架构守护
dotnet test --filter "FullyQualifiedName~ArchitectureGuardTests"
# 抑制 ANSI(CI 日志友好)
NO_COLOR=1 dotnet test ...
GitHub Actions 片段:
- name: Architecture Guard
env:
NO_COLOR: "1" # CI 不渲染 ANSI
run: |
dotnet test GJJ.UniServiceHub/GJJ.UniServiceHub.Tests \
--filter "FullyQualifiedName~ArchitectureGuardTests" \
--no-build
十、版本历史
| 版本 | 主要变更 |
|---|---|
| 3.0.8 | GJJ016 修正:严重度自适应——扫描 requireAuthenticatedByDefault: false / FallbackPolicy = null 时自动升 Error;其他情况维持 Info(FallbackPolicy 已保护)。无需用户手动配置。<br>新增 Internal/ClassScanner 共享类级扫描:处理一文件多类(#5)、partial class 跨文件(#6)、注释/字符串伪 attribute(#7)三类边界;GJJ016 改用之,判定精度提升。 |
| 3.0.7 | 新增 GJJ006-018 共 13 条规则;GJJ001-005 补强;GJJ006/015 默认豁免调参 |
| 3.0.6 | 输出报告重设计:严重度分组 + 颜色 / 加粗 / 斜体 / 下划线 + 修复建议;ViolationFormatOptions |
| 3.0.x | 新增 GJJ005(SM 中间件守护) |
| 3.x | 初版规则集(GJJ001-GJJ004) |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net8.0
- No dependencies.
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 |
|---|---|---|
| 4.2.2 | 143 | 7/3/2026 |
| 4.2.1 | 120 | 7/1/2026 |
| 4.2.0 | 131 | 6/5/2026 |
| 4.0.0 | 115 | 5/22/2026 |
| 3.0.10 | 112 | 5/13/2026 |
| 3.0.9 | 118 | 5/11/2026 |
| 3.0.8 | 112 | 5/11/2026 |
| 3.0.7 | 113 | 5/8/2026 |
| 3.0.6 | 107 | 5/6/2026 |
| 3.0.5 | 107 | 5/6/2026 |
| 3.0.4 | 104 | 5/6/2026 |
| 3.0.3 | 110 | 4/30/2026 |
| 3.0.2 | 115 | 4/30/2026 |
| 3.0.0 | 124 | 4/29/2026 |