Skip to content
bitzorcas
中EN

Concept

质量门禁

verify-all 流程、CI 矩阵、测试分层、API 状态码契约、数据一致性测试矩阵、AOT/反射/T-SQL 门禁,以及门禁一次 BitzOrcas 合并的模板完成定义。

Last updated

经典项目的合并流程是一场信任游戏:提交信息写着”已自测”,然后一个被本机脏缓存喂绿的构建进入主干,晚上把所有人的流水线染红。BitzOrcas 不接受这种赌博——质量门禁在本地与 CI 中运行同一套脚本,因此本地合并通过即可预测 CI 构建通过。

唯一的合并入口是针对显式 BitzOrcas.Modern.slnx 运行的 scripts/build/verify-all.sh。对于阶段完成或 PR 报告,scripts/build/report-acceptance.sh 以关闭 fail-fast 的方式运行同样步骤,并生成 .verify-output/acceptance-report.md 作为评审材料。

失败失败失败

源码与清单

快速构建 / 单元 / 架构门禁

集成与 Docker 契约

Trim、包、模板与供应链

可审查发布证据

阻断合并

每次合并的门禁

十条命令无需 Docker 即可运行;一条 Docker 条件门禁覆盖 Testcontainers 集成测试。

#命令门禁
1dotnet restore BitzOrcas.Modern.slnxCentral Package Management 还原
2dotnet format BitzOrcas.Modern.slnx --verify-no-changes.editorconfig 格式一致性
3dotnet build BitzOrcas.Modern.slnx -c Release0 错误、0 警告(TreatWarningsAsErrors 全局开启)
4dotnet test BitzOrcas.Modern.slnx --filter "FullyQualifiedName!~Integration"单元 + 应用测试
5check-xml-files.shXML 良构性(csproj/props/targets/slnx/config/resx)
6check-xml-comments.shXML 文档规则(公开声明完整标注、无裸 inheritdoc)
7dotnet test BitzOrcas.Architecture.Tests依赖方向、模块边界、禁止引用
8dotnet test BitzOrcas.Integration.Tests --filter "Category!=Docker"DI 闭包冒烟
9dotnet publish src/Hosts/BitzOrcas.Api -c Release -p:PublishTrimmed=true硬门禁 裁剪发布与 AOT 兼容性
10dotnet test BitzOrcas.Framework.ConsumerContract.Tests目录驱动的本地 Feed、空缓存还原/构建/测试/发布

dotnet test tests/BitzOrcas.Integration.Tests --filter "Category=Docker"(Testcontainers / MsSqlBuilder / RabbitMqBuilder)是 Docker 条件门禁;任何使用这些 builder 的类都必须带 Docker 特征,否则会在无 Docker 的开发机上随机爆炸。

CI 矩阵

CI 定义在 .github/workflows/ci.yml,触发条件是 main 分支的 push/PR 加每日定时巡检;解决方案固定为 BitzOrcas.Modern.slnx,所有命令带 --disable-build-servers 保证可复现。五个任务各管一段风险:

任务职责
portability-gateubuntu/windows/macos 三 OS 矩阵还原 + Release 构建,随后跑架构门禁测试(前置 test-architecture-process-profiles.sh all),防止平台私有 API 渗入框架
fast-gateGitleaks 容器密钥扫描、dotnet format --verify-no-changes、IDE0005 未用 using 清零、OpenAPI 漂移检查(check-openapi-drift.sh)、分层测试过滤、XML 文件与 XML 注释检查
release-candidate商业候选产物的组装前置校验
publish-trimlinux-x64 / win-x64 / osx-arm64 三 RID 的 -p:PublishTrimmed=true 发布矩阵——任何被裁剪击穿的反射调用在此现形
integration-dockerTestcontainers 拉起真实 SQL Server / Redis / RabbitMQ 的契约测试

商业 GA 是独立保护工作流(commercial-ga.yml 等):对签名产物做只读验证,独立的 build-and-scan 与 sign-finalize-attest 作业钉到完整提交 SHA,签名环境中不执行任何仓库脚本。见商业 GA 门禁。

测试分层

仓库刻意没有引入数字型覆盖率门槛——coverlet 等采集器不在依赖账本中,CI 也没有覆盖率步骤。质量语义由脚本化硬门禁承载:各层级定向测试必须全绿,核心路径必须包含失败分支与负向用例。评估一次改动的验证充分性时,回答”这个风险被哪条门禁断言”,而不是报一个百分比。

层内容工具
Unit纯领域/规则xUnit + Shouldly + NSubstitute
ApplicationHandler/Pipeline/Authorization/Result/Port 契约xUnit + Shouldly + NSubstitute
IntegrationDB / CAP / RabbitMQ / API / Auth / OutboxTestcontainers(镜像按 digest 钉死)
Architecture依赖方向、模块边界、禁止引用ArchUnitNET

核心路径(事务/outbox/认证/数据权限)必须包含失败路径与负向测试。InMemoryDb 不能替代核心集成测试——它测不出真实的 SQL 方言差异、连接池耗尽和事务隔离行为。

数据/事务一致性矩阵

八种场景必须自动化:成功的 Command 提交业务数据;成功的 Command 写入 outbox;Result.Failure 回滚;系统异常回滚;RabbitMQ 不可用时 outbox 仍可恢复;租户 QueryFilter 生效;软删除 QueryFilter 生效;审计异步写入不阻塞业务事务。这八个场景是水平切片,任何一个新模块接入持久化后都要在同一套语义下复验。

API 状态码契约

每个错误都是一个符合 RFC 9457 的 application/problem+json:标准字段 type/title/status/detail/instance,外加可观测性扩展 errorCode/errorType/traceId/correlationId/requestId。映射入口集中在 Results/ResultExtensions.ToProblem,端点不允许自行分叉拼装错误体。

状态码含义
400请求结构/格式/基础校验
401未认证
403无权限或套餐不允许
404资源不存在
409并发/幂等/状态冲突
422业务规则未满足
429触发限流
500仅系统异常

真实的 409 响应形状如下(errorCode 取自强类型错误目录中的既有条目,前端按 code 精确分流,title/detail 走外部 i18n 本地化):

application/problem+json
{
"type": "https://docs.vnext.ailinkedlaw.com/errors/AI.Message.RequestConflict",
"title": "Request conflict",
"status": 409,
"detail": "同一会话已有进行中的请求,请等待其完成或先取消。",
"instance": "/api/ai/conversations/3fa1c2/messages",
"errorCode": "AI.Message.RequestConflict",
"errorType": "Conflict",
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"correlationId": "6f3a2c9e4d7b4f1a",
"requestId": "0HN7GJQKQV9PF0001"
}

errorCode 遵循 {Owner}.{Scenario}.{Reason} 三段形态,全部条目登记在 0008-error-catalog.json(当前 1,968 条);编造目录之外的 code 属于门禁违规。

REQ-GATE-001——反射零容忍

MakeGenericType(、GetMethod(、Activator.CreateInstance(、Assembly.GetTypes( 在运行时代码中是 P0 阻断;Assembly.GetType( 是 P1 警告。SqlSugar 适配器/Application/Domain 中任何未登记的 IL 事件自动升级为 P0 并阻断合并。扫描器是 scripts/build/check-reflection.sh,底层驱动 src/Tooling/BitzOrcas.ReflectionGuard.Cli;配套突变测试保证扫描器本身不腐化。

确有必要的反射逃生口需要三件套:// AOT-EXEMPT: 注解说明业务理由、覆盖裁剪场景的单元测试、以及登记到 docs/architecture/06-aot/0601-aot-exception-ledger.md 例外台账:

// 未经豁免的 Assembly.GetType 是 P1 警告;补充以下三行使其可通过门禁。
// AOT-EXEMPT: 仅启动期诊断探针使用,目标类型以 [DynamicallyAccessedMembers] 显式保留。
var probeType = assembly.GetType(probeTypeName);
// 台账条目还需注明 owner、删除条件与复审日期,季度复核未满足即删除豁免。

见 ADR 0103 了解源生成替代反射的整体策略。

REQ-GATE-002——禁止 T-SQL

check-no-tsql.sh 扫描 .cs 字符串字面量并硬阻断 "SELECT "、"INSERT "、"UPDATE "、"DELETE "、"EXEC "(除非在白名单内则为 P0)。白名单覆盖审计 Dapper 读侧例外与 Dapper Query Store 只读查询,每季度复核——每一次复核都必须回答同一个问题:这条 SQL 为什么还不能换成 ORM 表达式?替代方案:ORM Lambda/LINQ、Mapperly ProjectToDto()、[BitzTable] 驱动的 ORM 配置。

REQ-GATE-003——ArchUnitNET 断言与禁用包

架构测试工程 BitzOrcas.Architecture.Tests(TngTech.ArchUnitNET + Shouldly + xUnit,约百个测试类)持续断言:Domain 无上游依赖;Application 无 Infrastructure 依赖;Domain/Application 不引用任何 ORM 包;跨模块调用只能指向 {Module}.Contracts;Mapperly 仅出现在 Infrastructure/Workflow;IRepository<T> 签名不含 ORM 类型;任何地方都不引用 FluentValidation。

技术选型红线同样机器化。以下是 BannedPackagesTests 的核心逻辑摘录(注释为文档补充),这份前缀清单就是 ADR 层面”选型矩阵”的可执行形态:

tests/BitzOrcas.Architecture.Tests/BannedPackagesTests.cs
// 违禁前缀来自 ADR 0101/0002/0003 决议;调整此清单必须先修订对应 ADR。
private static readonly string[] BannedPrefixes =
[
"Autofac", // 容器统一内置 Microsoft.Extensions.DependencyInjection
"Newtonsoft.Json", // 序列化统一 System.Text.Json
"Swashbuckle", // OpenAPI 文档由内置管线与源生成产出
"AutoMapper", // 对象映射统一 Mapperly 编译期生成
"FluentValidation", // 校验统一 IRequestRule 纯函数规则管道
"Hangfire",
"Finbuckle",
"MediatR", // CQRS 消息面统一 Mediator 源生成方案
"Mapster",
];
[Fact]
public void Project_Assemblies_Should_Not_Reference_Banned_Libraries()
{
// 1. 枚举每个受控程序集的直接引用,比对违禁前缀。
var offenders = (
from assembly in ProjectAssemblies
from reference in assembly.GetReferencedAssemblies()
let name = reference.Name ?? string.Empty
from banned in BannedPrefixes
where name.StartsWith(banned, StringComparison.Ordinal)
select $"{assembly.GetName().Name} -> {name}").ToList();
// 2. 一条违规即可让 fast-gate 变红,并在断言消息里给出完整的依赖链便于定位。
offenders.ShouldBeEmpty($"禁止包被项目程序集引用:\n{string.Join("\n", offenders)}");
}

同类红线还包括:日志抽象隔离(Domain/Application 不得出现 Serilog 依赖)、时间读取一律经由 IAppClock(全域扫描禁止直读 DateTime.Now/UtcNow,保证利冲窗口、时效计算等场景可被确定性测试)。完整清单见测试工程的 CodingRedLineTests。

模板完成定义

十个条件定义一个完整的架构模板:文档含 ADR/context-map/selection-matrix/redlines;代码含 4 层项目 + AppHost + ServiceDefaults + 测试;一条真实业务切片跑通 API→DB→outbox;架构测试阻断错误依赖;集成测试拉起真实 SQL Server/Redis/RabbitMQ;CI 运行 build/unit/integration/architecture/trim;模板只生成 shell + 业务源码(绝不复制 Framework/Platform/Licensing 核心);每个 Profile 完成空缓存还原/构建/测试/发布;商业包通过签名/hash/SBOM/漏洞/许可证门禁;Package Entitlement、Runtime License、租户 Feature Entitlement 分层并含在线/离线/失败状态测试。

少一条,交付的就不是一个架构模板,而是一份需要收件人补课的源码压缩包。

另见

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%