FForm Platform
enzh-CN

测试与质量

使用分层自动测试和部署门禁,让可配置平台能够安全演进。

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/checksumDocker
SQL Server 集成测试`tests/FormPlatform.SqlServer.Tests`真实 SQL Server ORM、生成主键/分页、migration/checksumDocker
MySQL 集成测试`tests/FormPlatform.MySql.Tests`真实 MySQL `binary(16)` UUID v7 CRUD/filterDocker
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` 创建私有错误格式。