开发者指南
从第一个可编辑表单到高级服务器端和客户端扩展的实用路径。
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. 下一步阅读
- 独立模块:模块开发 Step-by-Step
- 自动化验证:测试项目指南
- ACL 深入:表单访问控制
- i18n 深入:国际化
- 生产配置:安全配置与部署