FForm Platform
enzh-CN

开发者指南

从第一个可编辑表单到高级服务器端和客户端扩展的实用路径。

FormPlatform 初级、中级和高级开发者指南

开发或修改表单控件前,同时阅读客户端组件契约

English: DEVELOPER_GUIDE.md

这份指南按能力分级,而不是按职位分级。初级开发者可以只用 Designer 和既有 Data Model 完成功能;中级开发者扩展服务器和控件;高级开发者维护平台边界、性能、安全、迁移和发布兼容性。

1. 所有人都要先建立的概念

开始前阅读 系统架构,并记住:表单结构、表单 metadata、Management Database 和业务记录不是同一种数据;客户端可见性不是安全;固定 DDL 不属于 initializer;第三方模块不引用 Host DLL。

推荐环境:.NET 10 SDK、Node.js 22、PostgreSQL 17(也支持 MySQL/MSSQL)、Git、浏览器开发者工具。PostgreSQL 集成测试另需 Docker。

典型本地配置使用 User Secrets:

dotnet user-secrets --project src/FormPlatform.Host/FormPlatform.csproj set "ManagementDatabase:ConnectionString" "Host=localhost;Database=formplatform;Username=postgres;Password=..."
dotnet user-secrets --project src/FormPlatform.Host/FormPlatform.csproj set "FormStorage:ConnectionString" "Host=localhost;Database=formplatform;Username=postgres;Password=..."

不要把真实密码写回 `appsettings.json`。

2. 初级开发者:用表单完成业务

2.1 目标能力

完成此阶段后,你应该能:

  • 启动前后端并判断改动是否需要重新 build;
  • 创建 Data Model 和表单映射;
  • 使用 Designer、Viewer、DataGrid、AsyncSelect、Tree、Pagination;
  • 配置必填、可见、只读、事件和 substitution;
  • 从浏览器 Network/Console 和服务器日志定位常见错误;
  • 不修改 Host 代码地完成一个 CRUD 页面。

2.2 项目运行方式

开发时可以分开运行:

# terminal 1
dotnet run --project src/FormPlatform.Host/FormPlatform.csproj --urls http://localhost:5080

# terminal 2
cd src/FormPlatform.Host/ClientApp
npm run dev

访问 Vite 的 `http://localhost:5173`。如果只改数据库中的表单,无需 `npm run build` 或重新编译;改 Vue/JS/CSS 后由 Vite 热更新;改 C# 后需要重新构建/启动。发布模式由 .NET 提供 `wwwroot`,客户端改动必须先 `npm run build`。

2.3 从数据库表到表单

最省力流程:

1. 在 Manage Data Models 选择数据库表并预览导入。

2. 核对 Name、DB Object、Schema、Primary Key、Calculated/Nullable 和类型。

3. 数据库生成的 `id` 标为 Calculated/Generated。

4. 对外键建立有语义的 Reference,例如 `createdBy`、`updatedBy`。

5. 从 Data Model 生成表单。

6. 在 Form and Data Mapping 检查每个输入控件的 `propertyName` 到 Attribute 的映射。

7. 用 Viewer 新建、跳转到返回的 record id,再重新加载验证更新。

`propertyName` 是表单数据键;Data Model Attribute 才映射数据库列。不要在控件中写 `app_users.user_name` 作为保存键。

2.4 控件和数据规则

  • Textbox/Textarea 输入采用 debounce/blur 等策略,不要每个字符触发全部依赖链。
  • Radio/Checkbox 的布尔值必须保持 boolean,不能用字符串冒充;未选择且可空时为 null。
  • Hidden 控件不提交。
  • 必填和自定义验证是 validation;Tooltip 不是验证。
  • Error CSS Class 作用于控件本身;错误消息位置独立配置。
  • Pagination 的 Next 可按页面规则验证/保存草稿,Submit 验证全部页。
  • DataGrid/ItemRenderer/AsyncSelect/Tree 运行时可访问 API;Designer 使用模拟数据。

2.5 Substitution 和 i18n

常见表达式:`{name}`、`{row.name}`、`{amount:0,000.00}`、日期格式。Action 参数也可以 substitution。系统表单文本使用 `@message.key`,在 i18n catalog 中提供中英文;业务录入内容通常直接保存,不当作系统 key。

2.6 初级调试清单

1. Network 看 URL、method、status、request payload 和统一错误 body。

2. 确认当前是 Designer、Preview 还是 Viewer。

3. 确认表单 id、record id、deployment id 是否进入 URL。

4. 检查 metadata 的 form type、anonymous、mapping/entity。

5. SQL 日志确认查询列、filter、sort 和参数,不只看 UI。

6. 修改系统表单后若重启被覆盖,检查 initializer 的语义同步策略及反向同步工具。

2.7 初级练习

建立 `InventoryCategory` 表和 Data Model,生成 CRUD 表单;再建立一个商品表单,使用 AsyncSelect 选择 Category,并用 DataGrid 显示、搜索、排序、导出商品。完成后验证匿名/登录权限和中英文标签。

3. 中级开发者:扩展服务和运行时

3.1 目标能力

  • 使用 ORM 编写安全的 Query/Mutation 和事务;
  • 增加领域服务、Minimal API、Server Action 和 Trigger;
  • 编写可在 Designer/Viewer 正确工作的 Vue 控件;
  • 使用统一错误、toast、i18n 和 ACL;
  • 为 schema 变化增加 migration;
  • 编写集成和 E2E 测试。

3.2 增加服务器功能

官方 Host 端点放入对应的 `Hosting/Endpoints/*Endpoints.cs`;客户功能优先独立模块。端点只做绑定、鉴权和状态码,业务逻辑放服务中。

group.MapPost("/", async Task<IResult> (
    SaveRequest request,
    ClaimsPrincipal principal,
    ItemService service,
    CancellationToken ct) =>
{
    var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier)
        ?? throw new UnauthorizedAccessException("Login is required.");
    var saved = await service.SaveAsync(userId, request, ct);
    return Results.Created($"/api/items/{saved.Id}", saved);
});

领域冲突抛出:

throw new PlatformApiException(
    StatusCodes.Status409Conflict,
    "inventory.versionConflict",
    "inventory.versionConflict",
    "The record was modified by another user.");

不要新建 `{ message }`、`{ error }` 私有格式。

3.3 ORM 使用原则

从 `IUnitOfWorkFactory` 开始事务,在同一 work 中完成读取、验证和写入;成功显式 commit,异常 rollback/dispose。Query Specification 使用已注册的 entity/attribute/reference 名称,ORM 负责标识符引用和参数。

高效查询应:

  • 只 select 需要的字段;
  • filter/sort 在数据库完成;
  • 大数据用 cursor/keyset,而不是不断增大的 offset;
  • 关联相同目标表时使用不同 Reference 名称;
  • 所有租户/用户边界都进入服务器 filter。

保留 SQL 的条件见架构文档。保留时也必须参数化值、白名单标识符并写 SQL 日志。

3.4 Action、Trigger 和提交链

Server Action 实现 `IServerActionsProvider`;Trigger 返回稳定结果,before trigger 可修改待保存字段,after trigger 不应假装回滚已经提交的事务。Action 参数先做 substitution,再解析类型。

`SetFields` 之类通用 Action 应区分 token 与普通字符串,例如 `@userId`、`@Datetime`、`@id`、密码哈希 token。Calculated/不可写字段需要明确的受信 Action 写入策略,不能简单开放所有客户端写入。

Trigger 的性能取决于依赖追踪:只在依赖字段变化或相应生命周期发生时求值,不因 hover 无关控件而重跑全表单验证。

3.5 新控件

控件至少要定义:runtime component、Designer preview、属性 schema、事件清单、默认值、数据/非数据标志和异步加载方式。使用 Vue 响应式状态,不用 `querySelector` 修改业务状态。确实需要 DOM 的焦点、尺寸、打印或第三方库集成要封装在 ref/lifecycle 中。

控件必须验证:

  • Designer 不请求真实 API且外观不为空;
  • Preview 与 Viewer 的 value/null/boolean 行为一致;
  • `disabled`、`readOnly`、`required`、validation class 正确;
  • 事件只出现在适用控件;
  • 外来 attributes/listeners 通过 props/emits/`inheritAttrs` 正确处理;
  • i18n、打印和布局容器下正常。

3.6 权限

先定义能力再写按钮。读取、设计、删除、管理员代填分别调用相应 ACL;Respondent 路径还需 deployment/list/time-window 验证。客户端 `visibleCondition` 可以使用 context 权限改善体验,但服务器端检查不可省略。

3.7 Migration

每次 schema 变化增加新的 ID:

new ModuleDatabaseMigration(
    "002_add_version",
    ["ALTER TABLE inventory_item ADD COLUMN IF NOT EXISTS version integer NOT NULL DEFAULT 1"],
    ["ALTER TABLE inventory_item ADD COLUMN IF NOT EXISTS version int NOT NULL DEFAULT 1"],
    ["IF COL_LENGTH(N'inventory_item',N'version') IS NULL ALTER TABLE inventory_item ADD version int NOT NULL CONSTRAINT df_inventory_version DEFAULT 1"])

不要改已经进入 `app_schema_migrations` 的内容,否则 checksum 会阻止启动。数据回填要考虑锁、批量和回滚策略。

3.8 中级练习

为初级练习增加库存调整 Server Action:事务内写库存和审计表,冲突返回 409;再做一个图表控件和 `/api/inventory/summary`,Designer 显示三条模拟数据;补 PostgreSQL 集成测试和 Playwright 保存流程。

4. 高级开发者:维护平台和扩展生态

4.1 目标能力

  • 设计 SDK/Host 边界和兼容策略;
  • 评审认证、ACL、多身份和数据泄露风险;
  • 设计高数据量查询、缓存和异步工作;
  • 维护跨 provider migrations;
  • 建立可重复 CI、升级和回滚流程;
  • 判断功能应进入表单、控件、官方服务还是客户模块。

4.2 边界决策

选择顺序:

1. 仅布局/字段/事件变化:编辑表单。

2. 多个表单需要相同交互:新控件或 Runtime API。

3. 需要数据库表、API、Action/Trigger:独立模块。

4. 所有安装都必须具备且涉及平台安全/生命周期:才考虑进入 Core。

公共 SDK 只暴露稳定契约,Host implementation 不进入模块引用。新增 SDK API 要考虑版本范围、XML 文档、二进制兼容和所有模块 CI。

4.3 性能设计

  • 用数据库端 ACL 候选过滤和 cursor pagination。
  • Query projection 避免关联表 `SELECT *`。
  • 批量接口避免 N+1 读取表单和 metadata。
  • 全局表单 CSS 使用内容 hash/ETag,未变化不重建。
  • 复杂通知、邮件、导出和大文件可迁到后台队列;请求链保持可取消。
  • 日志结构化且限制敏感参数。

Keyset cursor 通常包含稳定排序键和 id;参照记录删除不会破坏语义,查询使用 `(sortKey,id) > cursor` 继续,但客户端必须接受并发插入/删除导致的“弱快照”。要求严格快照时使用事务快照或服务端导出任务。

4.4 安全设计

模块是可信同进程代码;不可信第三方需要独立进程/API,而不是当前 loader。上传文件验证大小、类型、访问部署和所有者,下载也重新鉴权。OIDC 使用 provider subject 建立内部 identity,不直接把 email 当稳定主键。密码只保存强哈希。

错误响应对 500 隐藏内部异常,详细堆栈只写受保护日志。配置采用 User Secrets/systemd credentials/环境变量;定期轮换数据库、SMTP、OIDC 和支付密钥。

4.5 跨 provider 与时间

逻辑时间点使用 `DateTimeOffset`/UTC 和 `timestamptz`;纯本地日期使用 Date。浏览器 `datetime-local` 没有 offset,服务器必须按明确时区转换。Provider migration 要同时考虑 PostgreSQL transactional DDL、MySQL implicit commit 和 SQL Server 条件 DDL。

4.6 发布与兼容

模块 manifest 版本必须匹配程序集 Major/Minor/Build。兼容范围 minimum inclusive、maximum exclusive。发布顺序通常是:备份、迁移验证、发布 Host/SDK、重编译所有模块、部署客户端资产、冒烟测试。不要只复制新 `module.json` 配旧 DLL。

4.7 高级评审清单

  • 是否突破 SDK 边界或复制 Core implementation?
  • 是否有无界查询、大 OFFSET、N+1 或全表序列化?
  • 是否所有入口都鉴权并限制 owner/tenant?
  • 是否统一错误/i18n/traceId?
  • 是否有新的不可逆 migration 和恢复计划?
  • Designer/Preview/Viewer/打印是否一致?
  • PostgreSQL 集成和关键 Playwright 流程是否覆盖?
  • 升级时旧表单、旧 module manifest 和缓存如何处理?

5. 团队工作流

每个改动先写清“表单、客户端、服务、数据库、权限、测试”的影响面。保持小提交,不混入无关格式化;不覆盖他人 dirty worktree。完成后更新中英文核心文档(若公共行为变化)和 `docs/OPENCODE_CHANGES.md`。

建议 Code Review 顺序:契约和安全 → 数据/migration → 领域逻辑 → API 错误 → Vue 响应式与 Designer → i18n/UX → 测试与部署。

6. 下一步阅读