FForm Platform
enzh-CN

系统表单与启动维护

保持源码定义系统表单可恢复,并安全诊断 migration 完整性失败。

系统表单反向同步到源码

FormPlatform 把可编辑表单定义保存在管理数据库中。下面列出的表单同时具有稳定的 C# 初始化器,因此全新空数据库可以重新建立它们。`tools/sync-system-forms.js` 用于把这些表单当前的 `schema_json` 和 Action Code 从数据库明确快照回源码。

覆盖范围

当前 Core 源码快照包含 31 个表单:

C# 源文件数据库表单名
`AppUsersForm.cs``AppUsersForm`
`FormCenterSystemForms.cs``Form_Center`
`FeatureSystemForms.cs``Feature_PersonalInfo`
`FormAccessSystemForms.cs``System_Form_Access`
`Licensing/LicenseSystemForm.cs``System_About`
`ModuleStarterSystemForm.cs``System_Module_Starter`
`SubmissionCenterSystemForms.cs``Submission_Center`
`SystemAccessSystemForms.cs``System_Users`、`System_Departments`、`System_Roles`、`System_User_Roles`
`SurveySystemForms.cs``Survey_Deployments`、`Survey_Lists`、`Survey_Deployment_Responses`、`Survey_Push_Notification`、`Survey_Respondents`、`Survey_Deployment`、`Survey_List`、`Survey_Respondent`、`Survey_Respondent_Login`、`Survey_Respondent_Dashboard`、`System_Notifications`、`Survey_Respondent_Reset_Password`
`SystemManagementForms.cs``ManageDataModels`、`ManageFormDataMappings`、`FormCenter`
`EnvironmentSurveyForms.cs``Survey_Environment`
`SystemLayoutForms.cs``SystemHeader`、`SystemLeftPane`、`SystemRightPane`、`SystemFooter`

同步器会把数据库中不属于该清单的表单报告为 `[无 C# 初始化器]`,不会擅自把用户表单或独立安装模块的表单归入 Core。

操作流程

在仓库根目录针对可信开发数据库执行:

node tools/sync-system-forms.js --dry-run
node tools/sync-system-forms.js
node tools/sync-system-forms.js --dry-run

最后一次 dry-run 应报告 0 个变化。可以用 `--form <名称>` 或 `--file <路径>` 限定同步范围。脚本目前通过 `psql` 读取 PostgreSQL,并按安全部署文档所述的配置优先级解析管理数据库连接。应优先使用 User Secrets 或部署 secrets JSON,不要把连接字符串放进命令行历史。

同步后必须检查完整 Git diff。该操作有意信任所选数据库:写回的 Action Code 会成为表单加载时可由浏览器运行时执行的源码。

可以还原的内容

对清单内表单,快照会写入:

  • 完整表单 Schema,包括控件翻译、数据库已有的 `usedCssClasses` 和版本化 `systemExtensions`;
  • C# 定义具有 Action Code 原始字符串位置时的 Action Code,也包括数据库中有意保存的空值。空的多行原始字符串会保留一行空内容,使 C# 接受源码,同时运行时值仍为空。

对空数据库,正常 migration 和 hosted initializer 会用稳定 ID、名称、描述、类型和标准 metadata 建立表单,发布已保存的 Schema,然后执行必要的窄范围兼容修补。生成源码快照本身不会修改现有数据库。

Survey 概览系统表单还有一个窄范围兼容修补:当旧 DataGrid 已允许新增并设置了 Edit Form、但尚未保存 `createShowType`,而该表单的当前源码快照定义了此默认值时,启动初始化器会补入源码值。目前 `Survey_Deployments` 的源码默认是 `newWindow`,因此旧数据库与空库基线都会使用详情 Edit Form 新建 deployment。未定义源码默认值的其它概览表单保持旧行为;Designer 已明确保存的 `inline` 或其它有效值也不会被覆盖。

`AppUsersFormInitializer` 采用严格的“只在缺失时创建”规则:已有数据库中的 Designer Schema 和 metadata 不会在重启时被代码默认值覆盖。空库基线映射到 Core 拥有的 `SystemAccessUser` Data Model;初始化器根据快照 Schema 中实际存在的 `userName`/`user_name`、`isActive`/`is_active`、`departmentId`/`department_id` 等属性别名建立 Mapping。密码字段始终只写到 `passwordHash`,并由服务器 `SetFields @password` Trigger 哈希,数据库 hash 不会回显。

边界和恢复缺口

这不是数据库备份,不能单独精确复制整个当前环境:

  • 没有 C# 初始化器的用户表单不会恢复;`AppUsersForm` 已成为 Core 基线,但其它普通用户表单仍不在覆盖范围内;
  • 表单 ACL、部门归属、审计时间、提交数据、Survey 业务数据、上传媒体和模块数据不会写入源码;
  • 除 Action Code 外的 metadata 仍由初始化器代码负责。若只在 Designer 中修改了 Data Mapping、Trigger、匿名发布、Form Type、功能分发或其它 metadata,还必须把相同规则落实到相应初始化器,空数据库才能复现;
  • Data Model 和物理表只有在 migration 或模型初始化器拥有它们时才会重建。表单 Schema 引用了用户导入的 Data Model,并不表示该模型会随表单恢复;
  • 已安装二进制/私有模块的表单应由模块自己的源码或 migration 保存,不能反向复制到 Core 冒充平台表单。

Survey 业务行不会隐式携带目标表单定义。补充 deployment `01a09aa5-3614-7c8b-be9d-a2af3a1cb5fe` 引用的非 Core `surveyform1`(表单 ID `01a09a85-7a4f-7b2c-b837-b768e252e143`)现由 `SupplementalFormDefinitionInitializer` 在独立配置的 FormStorage 表结构建立后从可信定义恢复。其三数据库 SQL 生成在 `SupplementalInitializationMigration.cs` 中,但有意不进入 Management migration 017,以保证分库正确并保持已发布 checksum 不变。若其他 deployment 仍引用缺失表单,管理代填接口继续以 `409 survey.deploymentFormMissing` 失败关闭;服务器不会替换占位表单,也不会跳过目标表单 ACL。

`Survey_Respondent_Dashboard` 使用 `respondent-dashboard-i18n-v3` 增量标记。初始化器只在对应基础属性仍等于平台默认值时补入缺失的中文翻译,保留 Designer 自定义文案和已有语言覆盖。Dashboard API 的显示令牌使用显式 `@surveyStatus.*` 与 `@common.yes|no` 引用;ItemRenderer 在渲染时翻译这些字段,并通过浏览器 `Intl.DateTimeFormat` 按当前语言格式化配置在 `dateTimeFields` 中的日期。原始 API 值继续保留,格式化结果以 `<field>Text` 提供。服务端分页的 ItemRenderer 仅在没有任何可见记录时显示 `noRecordLabel`,到达最后一页不再错误显示空状态。

`Survey_Respondent_Login` 使用 `respondent-login-i18n-v1` 增量标记。源码 Schema 包含标题、副标题、用户名与密码的标签和占位文字、必填提示、记住我、登录按钮以及通知分页/空状态的中文翻译。已有表单只会在对应基础属性仍等于源码默认值时补入缺失翻译,因此 Designer 自定义文案和已有语言覆盖都会保留。忘记密码、重置密码和外部登录提供商属于页面视图,继续使用 `RespondentLoginView.vue` 中的应用消息目录;通知主题与正文属于用户内容,不进行机器翻译。

灾难恢复必须使用真实数据库备份。C# 快照只应作为代码拥有表单的可重复基线;发布前应在隔离的空数据库中实际验证初始化结果。

English edition: SYSTEM_FORM_SOURCE_SYNC.md.

---

启动入口、数据库迁移与 API 错误架构

1. Program.cs 的职责

`Program.cs` 现在只负责组合根:读取配置、注册官方/第三方模块、配置认证授权、中间件顺序、挂载端点组和 SPA fallback。业务端点位于 `Hosting/Endpoints`:

  • `PlatformEndpoints`:全局 CSS、客户端扩展与扩展静态资源;
  • `CommerceEndpoints`:商城与支付;
  • `AuthenticationEndpoints`:系统用户、受访者和外部登录;
  • `SurveySubmissionEndpoints`:受访者问卷读取、附件和提交;
  • `FormEndpoints`:表单定义、metadata、数据映射、记录和控件数据;
  • `SystemDataEndpoints`、`SystemGridEndpoints`:ORM、系统 DataGrid 和功能表单;
  • `SurveyAdministrationEndpoints`:按表单 ACL 与部门范围授权的问卷管理及系统用户代填;
  • `AccessAdministrationEndpoints`:表单和系统资源 ACL;
  • `DataModelEndpoints`:Data Model 管理。

所有文件组成同一个 `PlatformEndpointMappings` partial class,因此共享少量认证和响应 helper,但每个领域只有一个公开的 `Map...Endpoints` 入口。增加端点时应放入所属领域文件,不要重新堆回 `Program.cs`。

2. 固定数据库结构的启动顺序

ModuleDatabaseMigrationRunner
  ├─ FormPlatform.Core migrations
  ├─ FormPlatform.Commerce migrations
  └─ 第三方 module migrations
          ↓
ManagementDatabaseInitializer(只 seed 受保护角色/初始身份)
          ↓
Data Model、系统表单、地理数据等幂等 seeders

原 `ManagementDatabaseInitializer` 和 `CommerceDatabaseInitializer` 的建表 SQL 已迁入 `PlatformMigrationModule` 与 `CommerceMigrationModule`。迁移使用数据库级锁、`app_schema_migrations` 历史表和 SHA-256 checksum。已发布 migration 不得修改;结构变化只能增加更大的 migration ID。

`ManagementDatabase:InitializeSchema` 和 `ApplyFeatureMigrations` 仅保留为 SDK 源码兼容属性,配置已不再使用。统一开关是:

"DatabaseMigrations": { "Enabled": true }

以下 SQL 不应迁入固定 migration:

  • 问卷部署按表单字段生成的 `data_*` 响应表;
  • 关系型 FormStore 自身按 provider 创建的存储结构;
  • 普通 seed 数据写入。它们分别属于运行时业务表生成、可替换存储 provider 和数据播种,不是固定平台 schema。

3. API 错误的唯一协议

所有失败响应使用 `PlatformErrorResponse`:

{
  "code": "validation.failed",
  "messageKey": "validation.failed",
  "fallback": "One or more validation errors occurred.",
  "parameters": null,
  "fieldErrors": {
    "name": [{ "code": "validation.error", "messageKey": "validation.error", "fallback": "Name is required." }]
  },
  "status": 400,
  "traceId": "..."
}

入口按来源分为:

1. endpoint 主动返回:使用 `PlatformResults.Error` 或 `PlatformResults.Validation`;

2. Core/模块业务代码:抛出 `PlatformApiException`,由 `PlatformExceptionHandler` 转换;

3. 未捕获异常:handler 记录日志,500 响应不泄露内部异常内容;

4. 空的 401/403/404:`StatusCodePages` 使用相同 writer 补齐协议;

5. ASP.NET 参数绑定错误:ProblemDetails customizer 补齐相同的 machine-readable 字段。

客户端所有 JSON/blob 请求经 `ClientApp/src/httpClient.js`。它负责语言 header、解析错误协议以及建立带 `status`、`code`、`validationErrors` 的 JavaScript `Error`。控件和 Action Runtime 不应各自重新写 `fetch + response.ok` 错误逻辑。

4. 新代码示例

端点参数错误:

return PlatformResults.Validation(new Dictionary<string, string[]>
{
    ["name"] = ["Name is required."]
});

可复用领域/模块错误:

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

客户端:

import { requestJson } from './httpClient.js'

const item = await requestJson('/api/todo/items/42')

不要返回 `{ message: ... }`、`{ error: ... }` 或新的私有 validation payload;否则 toast、本地化和表单字段错误会再次分叉。