测试与质量
使用分层自动测试和部署门禁,让可配置平台能够安全演进。
FormPlatform 测试项目指南
English: TESTING_GUIDE.md
1. 当前测试体系
FormPlatform 使用分层验证,而不是让一种测试承担全部责任:
| 层 | 位置 | 目的 | 外部依赖 |
|---|---|---|---|
| 前端单元/契约 | `src/FormPlatform.Host/ClientApp/src/**/*.test.js` | 纯运行时逻辑、组件公共契约和回归规则 | Node.js、Vitest |
| 静态检查 | 各项目/脚本 | JSON/XML/JS/C# 结构、链接、格式和 manifest | 无或本机 SDK |
| PostgreSQL 集成测试 | `tests/FormPlatform.PostgreSql.Tests` | 真实 ORM、事务、Reference、migration/checksum | Docker |
| SQL Server 集成测试 | `tests/FormPlatform.SqlServer.Tests` | 真实 SQL Server ORM、生成主键/分页、migration/checksum | Docker |
| MySQL 集成测试 | `tests/FormPlatform.MySql.Tests` | 真实 MySQL `binary(16)` UUID v7 CRUD/filter | Docker |
| Playwright E2E | `tests/e2e` | 浏览器、认证、路由、表单、模块和错误 UX | 已启动 Host、Chromium、测试账号 |
| 全模块构建 | `eng/build-all.ps1` | Host/SDK/Sample/Todo/ResourceBooking 二进制兼容 | Node、.NET、同级模块仓库 |
| 人工验收 | 浏览器/数据库/日志 | Designer、打印、支付/OIDC 等环境相关流程 | 对应环境 |
当前 GitHub CI 已形成八道强制门禁:前端 Vitest、全模块构建并组装可部署 runtime、SDK/module 兼容性验证、PostgreSQL Testcontainers 集成测试、SQL Server Testcontainers 集成测试、MySQL Testcontainers 集成测试、使用独立 PostgreSQL 和一次性管理员账号启动真实 Host 的 Playwright 核心流程,以及最终的发布 runtime 完整性门禁。最终门禁依赖此前全部成功。
2. 测试前准备
基础版本:.NET 10 SDK、Node.js 22。PostgreSQL、SQL Server 和 MySQL 集成测试都需要 Docker Desktop/Engine。E2E 需要目标 FormPlatform 已启动,且 ResourceBooking 用例需要模块已部署和客户端扩展已启用。
不要让自动测试连接开发或生产数据库。Testcontainers 会创建临时 PostgreSQL;E2E Host 也应使用专用测试数据库和秘密。
3. PostgreSQL 集成测试
项目:`tests/FormPlatform.PostgreSql.Tests/FormPlatform.PostgreSql.Tests.csproj`。
运行:
dotnet test tests/FormPlatform.PostgreSql.Tests/FormPlatform.PostgreSql.Tests.csproj -c Release
`PostgreSqlFixture` 启动 `postgres:17-alpine` container,实现 SDK 的 `IDatabaseConnectionFactory`。同一 xUnit collection 共享 container,测试通过独立表/数据或清理保证隔离。
现有覆盖:
- `DynamicRepositoryTests`:真实 provider 上的动态 Entity、CRUD、query/reference/transaction 行为;
- `ModuleMigrationTests`:migration 只记录一次、checksum 变化失败、Core/Commerce legacy schema 由 migration 建立。
3.1 增加 ORM 集成测试
测试应使用真正的 Entity Model 和 UnitOfWork,不 mock SQL dialect:
[Fact]
public async Task Query_projects_only_requested_fields()
{
var factory = Factory();
await using var work = await factory.BeginAsync("Management");
// Arrange a unique test row through the repository.
// Query with an explicit projection.
// Assert values and absence of unrequested attributes.
await work.CommitAsync();
}
每个测试使用唯一 ID/name;如果创建固定表,使用唯一表名或 class fixture 一次创建。不要依赖测试执行顺序。
3.2 SQL Server 集成测试
项目:`tests/FormPlatform.SqlServer.Tests/FormPlatform.SqlServer.Tests.csproj`。
dotnet test tests/FormPlatform.SqlServer.Tests/FormPlatform.SqlServer.Tests.csproj -c Release
`SqlServerFixture` 使用 Testcontainers 启动独立 SQL Server 2022。测试真实执行 SQL Server dialect 的 CRUD、filter、offset 分页与数据库生成主键路径,并完整应用 Core/Commerce migration 链两次,验证 checksum 保护;不连接本机安装的 SQL Server。
3.3 增加 migration 测试
至少验证:空库可执行;第二次执行不重复;历史表记录 module/id/checksum;修改同一 ID 内容会失败;新 migration 可接在旧 migration 后。PostgreSQL 和 SQL Server migration 链已自动化;MySQL 当前覆盖原生 `binary(16)` UUID v7 ORM 路径,以后引入或修改 MySQL 专属 DDL 时,应同步增加对应 migration 用例。
3.4 MySQL 集成测试
dotnet test tests/FormPlatform.MySql.Tests/FormPlatform.MySql.Tests.csproj -c Release
`MySqlFixture` 启动 MySQL 8.4,并明确使用 MySqlConnector 的 `GuidFormat=Binary16`。测试会建立真实 `binary(16)` 主键,写入应用生成的 UUID v7,再验证读取规范化、等值筛选、通过 `BIN_TO_UUID` 的前缀文本搜索、更新和删除。它能发现单靠 SQL 字符串快照无法发现的字节序和参数绑定回归。
3.5 Testcontainers 故障排查
- 无法连接 Docker:先运行 `docker info`。
- 拉取镜像失败:检查代理/registry;测试本身不应硬编码本机数据库。
- container 启动慢:首次镜像下载正常,CI 可使用 layer cache。
- 随机主键/表冲突:测试共享 container,使用唯一数据并清理。
- Windows volume/端口问题:当前 fixture 使用随机端口,不要自行固定 5432。
4. 发布 runtime 完整性门禁
所有前置 CI 门禁成功后,`eng/validate-release-runtime.ps1` 会检查 staged runtime:
- Host、SDK、契约程序集、appsettings、SPA 引用的静态资源、模块 manifest 与入口程序集必须齐全;
- `appsettings.json` 不得包含非空连接字符串、密码、secret、加密密钥、API key 或私钥;部署秘密只能放环境变量、User Secrets 或外部 deployment secrets 文件;
- `DataAccess:IncludeSqlParameterValues` 必须为 `false`;
- 再次调用模块二进制兼容验证,并生成列出全部交付文件 SHA-256 的 `release-manifest.json`。
在人工发布前,可在 `eng/prepare-ci-runtime.ps1` 后执行相同检查:
./eng/validate-release-runtime.ps1 -RuntimeDirectory .ci/runtime -WriteManifest
CI 的 `formplatform-release-candidate` artifact 是已测试、带完整性清单的候选发布物;分支保护应要求 `release-readiness` 成功。
5. Playwright E2E
目录:`tests/e2e`,配置默认 base URL `http://localhost:5080`,Chromium 串行执行,失败保留 trace/screenshot。
首次安装:
cd tests/e2e
npm install --no-audit --no-fund
npm run install:browsers
准备测试 Host:
1. 使用独立测试数据库和测试 secrets。
2. 应用 migrations。
3. 创建测试系统用户并分配所需角色。
4. 部署需要测试的 Todo/ResourceBooking 模块。
5. 构建客户端并启动 Host。
设置环境变量并运行:
$env:FORMPLATFORM_E2E_BASE_URL='http://localhost:5080'
$env:FORMPLATFORM_E2E_USER='e2e-admin'
$env:FORMPLATFORM_E2E_PASSWORD='use-a-secret-value'
cd tests/e2e
npm test
交互调试:
npm run test:ui
npx playwright test specs/core.spec.js --headed --debug
npx playwright show-trace test-results/<result>/trace.zip
5.1 现有核心流程
- `/form-designer` 可匿名直接打开;
- runtime 返回 Todo 与 ResourceBooking 客户端扩展;
- 登录后 Form Center 加载;
- `/resource-booking` 直接访问和刷新不被 SPA fallback 重定向;
- 服务器提交 409 后,表单仍挂载并显示统一错误。
登录用例在本地未设置账号环境变量时会 `skip`;CI 缺少账号时则直接失败,避免“绿色”结果掩盖未执行的认证流程。
5.2 编写稳定 E2E
优先使用 role/label/test id,不依赖 Tailwind class 或 DOM 层级:
test('created record navigates to its id', async ({ page }) => {
await page.goto('/forms/<form-id>')
await page.getByLabel('Name').fill('E2E item')
await page.getByRole('button', { name: /submit/i }).click()
await expect(page).toHaveURL(/\/forms\/[^/]+\/[0-9a-f-]{36}$/)
await expect(page.getByText(/saved/i)).toBeVisible()
})
只 mock 不属于当前测试目标的边界。例如测试错误 UX 时 route fulfill 409 是合理的;测试真实 ORM 保存时不要 mock submissions。每个测试自行建立/清理数据,或调用测试专用 API/fixture,不依赖前一个测试。
5.3 必须覆盖的关键矩阵
- Designer / Preview / Viewer 三模式;
- 匿名、普通系统用户、respondent、ACL 授权的系统用户代填;
- 新建、更新、删除、失败保留状态;
- Pagination 草稿/完成、必填 Checkbox/Radio、隐藏字段;
- DataGrid inline/modal/side pane、排序/搜索/导出;
- AsyncSelect/Tree filter 和 Reference;
- 模块直接路由、刷新、client extension 加载;
- 中英文、toast、字段错误位置;
- 打印不包含 Admin Panel;
- Survey 文件上传和授权下载。
6. 全模块构建验证
本地目录默认要求:
workspace/FormPlatform
workspace/Todo
workspace/ResourceBooking
运行:
cd FormPlatform
./eng/build-all.ps1 -Configuration Release
它依次执行 ClientApp npm ci/build、Host/SDK、Sample、Todo、ResourceBooking client/server。缺少模块目录或 manifest 会失败,不会静默跳过。
快速只验证服务器兼容时可使用 `-SkipClientBuild`;也可通过 `-TodoDirectory`、`-ResourceBookingDirectory` 指定路径。这个脚本验证编译兼容,不替代数据库或浏览器测试。
GitHub workflow 需要 repository variables:
- `FORMPLATFORM_TODO_REPOSITORY`
- `FORMPLATFORM_RESOURCE_BOOKING_REPOSITORY`
私有模块仓库另设只读 `FORMPLATFORM_MODULES_TOKEN`。任何模块未构建都应让 CI 失败。
构建 job 使用 `eng/prepare-ci-runtime.ps1` 将 Host、Vue 静态文件和三个模块整理到 `.ci/runtime`。兼容性 job 随后执行:
./eng/validate-module-compatibility.ps1 -RuntimeDirectory .ci/runtime
该命令通过 Host 的 `--validate-modules` 模式加载真实部署包,验证 manifest schema/name、模块版本与程序集版本、SDK/Platform 版本区间、SDK 引用版本,并拒绝仍引用私有 Host 程序集的旧模块。它不连接数据库,也不启动 HTTP 服务。
Playwright job 使用 `postgres:17-alpine` service,禁用与核心用例无关的大型地理数据 seed,执行 migration、系统表单和模块初始化后再运行浏览器测试。Host log、HTML report、trace、screenshot 和三个数据库 provider 的 TRX 都以短期 artifact 保存。仓库分支保护应要求 build、compatibility、frontend、三个数据库、Playwright 和 release-readiness 全部成功。
7. 客户端单元、契约和静态测试
ClientApp 使用 Vitest 测试 substitution、Action Context、validation/cache/debounce、Pagination、Checkbox/Radio、DataGrid model 和内置组件契约:
cd src/FormPlatform.Host/ClientApp
npm run test:unit
npm run test:unit:watch
契约详见客户端组件契约。Vitest 测纯函数和运行时规则;控件真实交互仍优先 Playwright E2E,而不是只对实现细节做 shallow mock。生产构建继续负责 Vue template、动态 import 和 bundling。
手动静态检查还包括:`node --check` 对普通 JS、JSON/XML 解析、`git diff --check`、Markdown 相对链接检查、manifest schema/version 检查。Vue SFC 不能只靠 `node --check`,必须经过 Vite 编译或 Vue parser。
8. API 与错误契约测试
所有失败至少断言 status、content type 和稳定字段:
{
"code": "inventory.conflict",
"messageKey": "inventory.conflict",
"fallback": "...",
"fieldErrors": {},
"status": 409,
"traceId": "..."
}
500 不应包含连接字符串、SQL、密码或堆栈。401/403/404 空结果也应由 status-code middleware 补成同一协议。客户端测试应确认字段错误进入当前 editor/FormReader,而不是主页面或别的嵌套表单。
9. 手工验收
自动测试后仍需针对环境相关功能检查:OIDC callback、SMTP、PayPal/Stripe/WeChat sandbox、打印/PDF、真实浏览器 date/time、IIS/Nginx headers、大文件、生产数据库权限和备份恢复。
最小发布 smoke:健康启动无 migration/module 错误;登录/退出;Form Center;打开 Designer;新建并更新一条普通记录;提交一份 Survey;DataGrid 查询;一个模块路由;日志中无未处理异常。
10. 测试数据和安全
生产或共享环境测试账号/密码进入 CI secret,不写进 spec。当前 E2E workflow 的固定密码只属于每次 job 新建并销毁的隔离数据库,不得在任何真实环境复用。测试文件不含真实个人数据。支付/OIDC 使用 sandbox。SQL 参数日志在测试敏感流程也保持关闭,除非使用完全虚构数据。失败 artifact/trace 可能包含表单内容,设置保留期和访问权限。
11. 新功能的测试策略模板
开发前填写:
| 问题 | 答案示例 |
|---|---|
| 纯领域规则? | xUnit unit test |
| 依赖 ORM/provider? | PostgreSQL integration |
| 改 schema? | migration first/second/checksum test |
| 改 API/错误? | integration + contract assertions |
| 改 Vue 交互/路由? | Playwright |
| 改三 provider SQL? | provider-specific CI/manual environment |
| 涉及身份/权限? | allow/deny matrix |
| 涉及打印/外部服务? | manual/sandbox smoke |
修复 bug 时先建立能重现问题的测试,再修代码;验证解决后,重新评估临时 workaround 是否仍需保留。
12. 推荐 CI 阶段
1. Frontend Vitest 和组件契约;
2. ClientApp production build,以及 Host + SDK + all modules build;
3. 组装部署 runtime 并通过 Host 验证所有 SDK/module;
4. PostgreSQL integration;
5. SQL Server migration/ORM 集成;
6. MySQL 原生 UUID repository 集成;
7. 启动隔离 PostgreSQL/Host,运行 Playwright core;
8. 保存 runtime、TRX、Host log 和失败 traces;
9. 发布 runtime 完整性门禁和 SHA-256 清单;
10. staging 外部服务 smoke,批准后生产。
CI 失败时先保留最早的根因日志,不被后续级联错误淹没。构建成功不等于 migration、模块加载或浏览器流程成功。
---
FormPlatform SDK、模块迁移、错误协议与自动化测试
> 文档导航:完整测试流程已整理到
> 测试项目指南;模块开发主线见
> 模块开发 Step-by-Step。本文保留为 SDK、迁移和错误协议的专题参考。
本文面向平台维护者和模块开发者,说明本次基础设施拆分后的正式边界。旧模块直接引用 `FormPlatform.dll` 的入口不再保留;Todo 和 ResourceBooking 是新结构的参考实现。
1. 程序集边界
发布结果包含三个不同用途的程序集:
| 程序集 | 用途 | 第三方是否引用 |
|---|---|---|
| `FormPlatform.dll` | 闭源 Web Host、身份、安全、内置业务和路由 | 否 |
| `FormPlatform.Sdk.dll` | 表单契约、ORM、事务、查询、Action/Trigger、迁移、错误契约及 `IFormPlatformSdkModule` 模块入口 | 是 |
| `FormPlatform.Extension.Abstractions.dll` | 模块加载的底层共享契约 | 是 |
SDK 项目位于 `src/FormPlatform.Sdk`。主项目通过 ProjectReference 使用它;SDK 源码物理上只属于该项目,因此同一公开类型只会存在于 `FormPlatform.Sdk.dll`,不会产生类型身份冲突。SDK 开启 XML 文档和 NuGet 打包;可以执行:
dotnet pack src/FormPlatform.Sdk/FormPlatform.Sdk.csproj -c Release
模块项目应引用 SDK 和 Abstractions,不得引用 Host:
<Reference Include="FormPlatform.Sdk"
HintPath="$(FormPlatformSdkDirectory)\FormPlatform.Sdk.dll"
Private="false" />
<Reference Include="FormPlatform.Extension.Abstractions"
HintPath="$(FormPlatformSdkDirectory)\FormPlatform.Extension.Abstractions.dll"
Private="false" />
`Private=false` 防止模块包携带另一份平台程序集。模块、Host 必须使用部署目录中的同一版本 SDK。
1.1 SDK 中的主要入口
- `IFormStore`、`IFormSchemaPublisher`:表单与 metadata 读写、系统表单初始化。
- `IUnitOfWorkFactory`、`IDynamicRepository`:事务化的动态 ORM CRUD。
- `IEntityModelResolver`、查询和关系 API:Data Model、筛选、排序、Join、集合加载。
- `IFormEntityDataService`、`IFormDataGridService`、`IFormAsyncSelectService`:复用平台的表单数据映射与集合控件后端能力。
- `AddFormPlatformDataAccess()`:注册 SDK ORM默认实现;Host或独立测试仍需提供 `IDatabaseConnectionFactory`。
- `IServerActionsProvider`、Trigger 契约:模块 Action 与生命周期扩展。
- `IDatabaseMigrationModule`:模块数据库版本管理。
- `PlatformApiException`、`PlatformErrorResponse`:稳定且可本地化的错误协议。
数据库驱动仍由 Host 提供。SDK 的 `IDatabaseConnectionFactory` 只定义创建连接的边界,避免 SDK 对 Npgsql、MySqlConnector 或 SqlClient 产生硬依赖。
2. 模块化数据库迁移
每个模块注册一个或多个 `IDatabaseMigrationModule`:
public sealed class TodoMigrations : IDatabaseMigrationModule
{
public string Name => "Todo";
public string DataSource => "Management";
public IReadOnlyList<ModuleDatabaseMigration> Migrations { get; } =
[
new("001_create_todo_items",
PostgreSql: ["CREATE TABLE ..."],
MySql: ["CREATE TABLE ..."],
SqlServer: ["IF OBJECT_ID(...) IS NULL CREATE TABLE ..."])
];
}
// IFormPlatformSdkModule.ConfigureServices
services.AddSingleton<IDatabaseMigrationModule, TodoMigrations>();
启动时 `ModuleDatabaseMigrationRunner` 按 DataSource 分组并执行:
1. 打开一个 Unit of Work;
2. 获得数据库级迁移锁,避免多实例同时迁移;
3. 幂等建立 `app_schema_migrations`;
4. 同一数据源先执行 `FormPlatform.Core`,再按模块名和 migration ID 检查并执行扩展迁移;
5. 按当前 provider 选择 SQL 并在事务中执行;
6. 写入 SHA-256 checksum、UTC 时间和平台版本;
7. 提交整个数据源的迁移事务。
Core 优先顺序由 Host Runner 统一保证,已编译的第三方模块不需要实现新的 SDK 接口。这样模块建立指向 `app_users`、`app_departments`、共享媒体或 Survey 基础表的外键时,目标表已经存在。模块仍必须与它引用的 Core 表使用同一 DataSource;该顺序规则不会推断或建立跨数据库外键。
PostgreSQL 和 SQL Server 的常用 DDL可由该事务回滚;MySQL 的部分 DDL会隐式提交,这是数据库自身限制。因此 MySQL migration 必须格外强调幂等、单步和可恢复性,不能把“Rollback 已调用”等同于 DDL一定撤销。
已经执行过的 migration 禁止修改。内容改变会触发 checksum 错误;正确做法是新增 `002_...`。同一模块内 ID 必须唯一,并使用可按字符串稳定排序的前缀。迁移可通过配置关闭:
"DatabaseMigrations": { "Enabled": false }
生产建议保持启用,但运行服务的数据库账号必须拥有模块 DDL 所需权限。若组织要求 DBA 审批,可在预发布环境先执行相同模块版本并审计 SQL,然后再部署应用。ResourceBooking 的 PostgreSQL migration 需要 `btree_gist` 扩展权限。
3. 统一错误响应
API 错误采用 `application/problem+json`,标准字段之外固定提供:
{
"status": 409,
"code": "booking.conflict",
"messageKey": "resourceBooking.conflict",
"fallback": "The resource is already booked.",
"parameters": { "resource": "Room A" },
"fieldErrors": {
"startAt": [{ "code": "booking.periodConflict", "messageKey": "resourceBooking.periodConflict", "fallback": "The selected period conflicts." }]
},
"traceId": "..."
}
- `code` 给程序判断,不用于显示。
- `messageKey` 由客户端 i18n 翻译。
- `fallback` 是缺少翻译时可直接显示的英文。
- `parameters` 是翻译占位参数,不放秘密数据。
- `fieldErrors` 可交给 FormReader 或 inline editor 定位字段。
- `traceId` 用来对应服务端日志。
模块业务代码可以抛出:
throw new PlatformApiException(
StatusCodes.Status409Conflict,
"booking.conflict",
"resourceBooking.conflict",
"The resource is already booked.",
new { resource = resourceName },
new Dictionary<string, ClientMessage[]> {
["startAt"] = [ValidationMessages.Create(
"resourceBooking.periodConflict",
"The selected period conflicts.",
code: "booking.periodConflict")]
});
`PlatformExceptionHandler` 会记录异常并生成统一响应;框架验证错误以及没有 body 的 401/403/404 也会被 Problem Details 和 StatusCodePages 补齐。客户端 `api.js`、Action Runtime 通过 `errorFromResponse` 使用同一解析器,生成带 `status`、`code`、`validationErrors`、`payload` 的 Error。页面应显示 `error.message`,并把 `error.validationErrors` 交给当前表单或 DataGrid editor;不要手工解析某一个 API 的特殊格式。
未知异常只向客户端返回通用文字,真实堆栈写入服务器日志。排错时让用户提供 `traceId`,不要把堆栈、SQL 参数或连接字符串返回浏览器。
4. PostgreSQL 集成测试
项目:`tests/FormPlatform.PostgreSql.Tests`。测试通过 Testcontainers 启动临时 `postgres:17-alpine`,不连接开发数据库。当前覆盖:
- 动态 ORM 的 insert、filter query、patch、delete 和 count;
- 数据库生成 varchar UUID 主键回填;
- migration 只执行一次;
- 已执行 migration 被修改时 checksum 拒绝启动。
前提是 Docker Desktop 或兼容 Docker Engine 正在运行。执行:
dotnet test tests/FormPlatform.PostgreSql.Tests/FormPlatform.PostgreSql.Tests.csproj
新增 ORM 能力时,应同时增加真实 PostgreSQL 测试。测试表只能建立在容器数据库中,不读取 `appsettings.json`,也不得依赖本机已有数据。
5. Playwright 核心流程
项目:`tests/e2e`。它针对一个已经启动、已部署测试模块的 FormPlatform 实例,覆盖公开 Designer、登录后的 Form Center、扩展路由直接访问/刷新,以及提交错误后表单仍保持挂载。
cd tests/e2e
npm install
npm run install:browsers
$env:FORMPLATFORM_E2E_BASE_URL='http://localhost:5080'
$env:FORMPLATFORM_E2E_USER='e2e-admin'
$env:FORMPLATFORM_E2E_PASSWORD='use-a-secret-value'
npm test
不要把测试密码提交到 Git。CI 中使用 secret variables。失败结果写入 `playwright-report` 和 `test-results`,这两个目录已忽略。
测试采用“外部启动 Web 应用”方式,便于使用和生产一致的 PostgreSQL、模块目录与已构建 Vue 资源。以后可增加专用测试 seed 命令,并让 CI 在运行 Playwright 前依次启动 PostgreSQL、发布 Host、部署模块、启动 Host、等待 health endpoint。
6. 发布和版本规则
1. SDK 的破坏性 API 修改提升 major;兼容新增提升 minor;修复提升 patch。
2. 发布 Host 时同时交付 `FormPlatform.Sdk.dll/.xml` 和 Abstractions DLL/XML。
3. 模块 manifest 应声明它验证过的平台/SDK 版本;更新 Host 前先重新构建模块。
4. 数据库 migration 发布后不可修改或复用 ID。
5. 服务器错误 `code` 和 `messageKey` 视为客户端契约,不随意改名。
7. 版本化模块清单
所有服务器模块必须提供 manifest version 1:
{
"manifestVersion": 1,
"name": "Todo",
"version": "1.0.0",
"entryAssembly": "TodoModule.dll",
"type": "TodoModule.TodoModuleEntry",
"sdk": { "minimum": "1.0.0", "maximumExclusive": "2.0.0" },
"platform": { "minimum": "1.0.0", "maximumExclusive": "2.0.0" }
}
- `name` 是跨版本不变的唯一标识,同一 Modules目录不能重复。
- `version` 必须是三段版本,并与 entry assembly的 release version一致。
- `sdk`、`platform` 都采用 `[minimum, maximumExclusive)`,允许兼容 minor/patch升级,但不会误接收下一个 major。
- Loader拒绝越界路径、未知 manifest schema、缺失入口类型、旧 Host API引用、缺失 SDK引用、比 Host更新的编译 SDK以及重复名称。
- SDK、Abstractions和 Host都由默认 AssemblyLoadContext共享;模块包不得携带它们的私人副本。
8. CI 构建全部模块
`eng/build-all.ps1` 是本地与 CI共用的入口。默认要求目录布局为:
workspace/
FormPlatform/
Todo/
ResourceBooking/
执行:
cd FormPlatform
./eng/build-all.ps1 -Configuration Release
它依次构建客户端、Host/SDK、Sample、Todo、ResourceBooking客户端和 ResourceBooking服务器模块。任何外部模块目录缺失都会失败,避免 CI只构建 Core却错误显示“全部通过”。
GitHub Actions工作流位于 `.github/workflows/ci.yml`。由于 Todo与 ResourceBooking是独立仓库,需要设置 repository variables:
- `FORMPLATFORM_TODO_REPOSITORY`,例如 `owner/Todo`;
- `FORMPLATFORM_RESOURCE_BOOKING_REPOSITORY`,例如 `owner/ResourceBooking`。
私有跨仓库 checkout还需 secret `FORMPLATFORM_MODULES_TOKEN`,其 token只授予这两个模块仓库的只读 Contents权限。CI另行运行 Testcontainers PostgreSQL集成测试。
9. Program.cs 已完成拆分
`Program.cs` 现在只保留 configuration、composition root、认证授权、middleware pipeline、模块生命周期和 SPA fallback。官方 endpoint 已按领域拆到 `Hosting/Endpoints/*Endpoints.cs`;完整职责、固定 schema migration 顺序和错误协议见 `STARTUP_MIGRATION_ERROR_ARCHITECTURE.zh-CN.md`。
新增路由应加入所属 `Map...Endpoints` 文件。不要直接把业务 lambda 添加回 `Program.cs`,也不要绕过 `PlatformResults` / `PlatformApiException` 创建私有错误格式。