跳转至

动态表单(DynaForm)设计方案

普通表单是 myApps 运行时 PC 端标准表单容器:负责加载表单定义与文档数据、动态渲染设计器 HTML 模板、展示流程操作栏与审批人状态、处理保存/流程/打印/跳转等按钮动作,并向子字段、包含元素、嵌入网格视图提供统一的上下文 API。

层次 实现位置
后端 / Java cn.myapps.core.runtime.dynaform.form.ejb.Formobpm-core/.../form/ejb/Form.java
FormRunTimeServiceImpl(组装 Vue3 所需的 FormDataPacket / formTemplate
cn.myapps.core.common.model.activity.ActivityType(按钮类型常量)
表单设计器 Form.vueobpm-designer-vue3/src/components/Modules/Form.vue
FormBasic.vueFormFormat.vueFormOperation.vuemodulesDetail/
前端运行时(PC) form_normalform.vueobpm-runtime-web/portal/vue3/src/components/form/form_normalform.vue
obpm_open_container.vue(页签 / 弹出层打开容器)
ActivityType.js(与后端 activity.type 一致)
相关运行时组件 activity.vuecollapse_activity.vueapprovers.vueform_flow_dialog.vueform_template.vue

本文档 普通表单 对应后端 FORM_TYPE_NORMALtype = 1);移动端有独立实现,下文仅描述 PC Vue3 运行时。


后端定义(Java)

Form 继承 FileSystemDesignTimeSerializable,实现 ActivityParentCloneable。通过 JAXB 序列化为 XML(根元素 <Form>),JSON 序列化时忽略空字段(@JsonInclude(NON_EMPTY))。设计态持久化在模块目录下,路径后缀见 ModelSuffix.FORM_PATH_SUFFIX / FORM_FILE_SUFFIX

表单类型常量

常量 十六进制 十进制 类型名(getTypeName) 说明
FORM_TYPE_NORMAL 0x0000001 1 NORMALFORM 普通表单(NormalForm 运行时)
FORM_TYPE_FRAGMENT 0x0000002 2 cn.myapps...fragment 标签页
FORM_TYPE_FLOW_PARAMETER 0x0000003 3 流程参数表(平台模式)
FORM_TYPE_DATA_MODEL 0x0000004 4 NORMAL_DATAMODEL 数据模型表单
FORM_TYPE_SUBFORM 0x0000010 16 子表单
FORM_TYPE_SEARCHFORM 0x0000100 256 SEARCHFORM 查询表单
FORM_TYPE_HOMEPAGE 0x0001000 4096 HOMEPAGE 首页
FORM_TYPE_NORMAL_MAPPING 0x0010000 65536 NORMAL_MAPPING 映射表单
FORM_TYPE_TEMPLATEFORM 0x0100000 1048576 TEMPLATEFORM 模板(阅读)表单

设计器 FormBasic 下拉选项与上表 十进制 type 一致。

专有属性

属性 类型 默认值 说明
templatecontext String 表单 HTML 模板源码(CDATA);设计器「内容」页保存至此
type int 表单类型(见上表)
styleId String 样式库 id;运行时 getStyle() 懒加载 StyleRepositoryVO
showType String "old" 模板渲染模式:old / new;兼容旧值 pcoldmobilenew
layoutType String "horizontal" 移动端布局:horizontal / vertical
permissionType String "public" 授权:public / private(setter 空值兜底为 private
orderno int 模块内排序(0–1000000,JSR303 @Range
version int 表单版本号
showLog boolean false 是否记录/展示操作日志
showLogType String "default" 日志展示:default / table
recordMode String 记录方式:every / other / script
recordModeScript String 记录方式脚本
openComment boolean false 是否开通评论
commentTitleScript String 评论标题脚本
commentFlagScript String 评论标识脚本(绑定 docId)
commentHiddenScrtipt String 隐藏评论区脚本(返回 true 则关闭评论)
commentExecuteScript String 评论/回复提交后执行脚本
showWaterMark Boolean false 是否显示水印
waterMarkScript String 水印文本脚本
isopenablescript String 是否可打开脚本(布尔);空时运行时按 "true" 处理
iseditablescript String 是否可编辑脚本(布尔);参与 Document.isEditable()
confirmLeaveEdit boolean false 离开编辑时弹窗确认;开启后,可编辑状态下关闭页签、返回、刷新或离开页面时提示未保存
mappingStr String 映射表 JSON/XML 字符串(映射表单)
mapping TableMapping 懒加载字段映射对象
documentSummaryXML String 文档摘要配置 XML
summaryCfg Collection<SummaryCfgVO> 摘要配置集合(懒加载)
checkout / checkoutHandler 设计态签出状态与签出人
sortId String 排序标识
activities List<Activity> 表单工具栏按钮;懒加载自 ActivityDesignTimeService

继承 / 关联结构

成员 说明
_fields Map<String, FormField>,字段 id → 字段对象
_elements ArrayList<FormElement>,模板元素顺序(旧版 showType=old 时字段也入列)
_textparts 模板文本片段
subFormMap Tab 等引用的子表单
module / getParent() 所属模块
style 运行时解析的样式库 VO(非 XML 直出字段)

模板解析与 HTML 生成

初始化inited()):首次调用时 TemplateParser.parseTemplate(this, templatecontext),将 templatecontext 解析为 _fields_textparts_elementsaddFieldshowType=old 会把字段加入 _elements

运行时 HTMLgetHtmlTemplate(doc, runner, user)):

showType 行为
old 遍历 _elements,各 FormElement.toHtmlTemplate(...) 拼接
new 遍历 _elements 中非 FormField 节点;含 fieldid 占位时替换为对应 FormField.toHtmlTemplate

多语言占位 {*[...]*} 在输出前编码替换。Vue3 运行时最终模板字符串由 FormRunTimeServiceImpl 组装(含 hidden 刷新域 + getHtmlTemplate 结果),写入 formTemplate.template

文档生命周期(Form 核心方法)

方法 说明
createDocument(params, user) 新建临时文档(istmp=true),分配 id,按表单类型设置 mappingIdaddItems + recalculateDocument
addItems(doc, params, user) 遍历 ValueStoreField,按字段 fieldtype 从 params 创建 Item 并挂到文档
recalculateDocument(doc, params, calcAll, user) 执行字段值脚本;受 calculateOnRefreshisDefaultValue、新建 istmp 等条件控制
recalculateDocument4Local(...) 控件变更触发的局部重算(refreshOnChanged + refreshFields
validate(doc, params, user) 执行字段校验脚本;跳过 hidden 且 displayType=HIDDEN 的字段
findField / findFieldByName 按 id / name 查找字段(含子表单 getAllFieldMap
getAllFields / getValueStoreFields 全部字段 / 需持久化值的字段
getIncludeViewList 收集 IncludeFieldTabField 内嵌视图
findActivity / getActivityByType 查找工具栏按钮(含 ButtonField 内嵌 activity)
getOnActionFlow 取「流程处理」按钮关联的 flowId

mappingId 规则FORM_TYPE_NORMAL / FORM_TYPE_DATA_MODELmappingId = docIdFORM_TYPE_NORMAL_MAPPING → 独立 UUID。

DDL 建表/变更(动态表同步)

普通表单(以及其它需要持久化的表单类型)在 保存即建表/改表——平台没有独立的「发布/部署」步骤。设计器 FormController 调用 FormDesignTimeService.save/update/delete 时,会**在同一事务内**把表单定义同步为运行库的物理表结构。

是否建表

FormTableProcessBean.isHasDynaTable(form) 决定该表单类型是否生成动态表(1 为真):

Form.type 建表 表前缀
FORM_TYPE_NORMAL(1) TLK_
FORM_TYPE_FLOW_PARAMETER(3) PARM_
FORM_TYPE_NORMAL_MAPPING(65536) 自定义(mappingStr.tableName
FORM_TYPE_SUBFORM(16) TLK_
FORM_TYPE_DATA_MODEL(4) 外部指定
FORM_TYPE_FRAGMENT(2)/ SEARCHFORM(256)/ TEMPLATEFORM(1048576)

整体调用链

FormController (designer)
  └─ FormDesignTimeServiceImpl.save / update / delete        // 事务边界
       └─ FormTableProcessBean.createDynaTable / createOrUpdateDynaTable / dropDynaTable / synDynaTable
            └─ RuntimeDaoManager.getFormTableDAO(conn)        // 按数据库类型分发
                 └─ AbstractFormTableDAO.createDynaTable / updateDynaTable / dropDynaTable
                      └─ ChangeLog.compare(newForm, oldForm)              // 新旧表单 → 变更列表
                      └─ AbstractTableDefinition.processChanges(log)      // 反射分发
                           ├─ processChange(AddTableChange)    → createTable
                           ├─ processChange(AddColumnChange)   → addColumn
                           ├─ processChange(DropColumnChange)  → dropColumn
                           ├─ processChange(ColumnRenameChange)→ columnRename
                           ├─ processChange(ColumnDataTypeChange)→ columnRename(4 步只启用第 1 步)
                           ├─ processChange(TableRenameChange) → createTableAsSelect + dropTable
                           └─ processChange(DropTableChange)   → dropTable
                                └─ SQLBuilder(各数据库子类)拼出 SQL
                                └─ evaluateBatch(sql) → statement.executeUpdate(sql)

设计态入口(FormDesignTimeServiceImpl)

方法 行为
save(user, Form) 新建表单;事务内 tableProcess.createDynaTable(vo) 后持久化 XML
update(user, Form) 更新表单;遍历表单及其父表单(getSuperiors),对每个 createOrUpdateDynaTable(form, oldForm)version + 1
delete(user, appId, id) docProcess.doRemoveByFormName(form) 清数据,再 dropDynaTable(form)
doChangeValidate(Form) 保存前用 ChangeLog.compare + process.doChangeValidate(log) 做破坏性变更校验(如删列、缩长度)
doClearColumnData(Form, fields) 字段被删/改类型时清空对应列数据(DML:update TLK_xxx set ITEM_xx = null

事务由 tableProcess.beginTransaction() / commitTransaction() / rollbackTransaction() 显式包裹;失败即整体回滚,XML 与数据库保持一致。

表名与列名命名规则

命名常量集中在 DQLASTUtil

常量 用途
TBL_PREFIX TLK_ 普通表单/子表单/主页表单动态表前缀
PARM_PREFIX PARM_ 流程参数表前缀
LOG_PREFIX LOG_ 操作日志表前缀(加在 TLK_ 之后)
ITEM_FIELD_PREFIX ITEM_ 业务字段列前缀
TEMP_PREFIX TMP_ 改列类型时的临时列前缀
T_DOCUMENT T_DOCUMENT 所有映射表单共用的文档头表

表名与列名映射核心在 TableMapping(构造时按 Form.type 分支):

  • FORM_TYPE_NORMAL / SUBFORM / HOMEPAGE / SEARCHFORM:表名 TLK_ + form.name;列名规则:$ 开头字段 → 去掉 $ 大写;其它 → ITEM_ + 字段名大写。
  • FORM_TYPE_FLOW_PARAMETER:表名 PARM_ + form.name;列名 ITEM_ + 字段名大写。
  • FORM_TYPE_NORMAL_MAPPING:解析 mappingStr JSON,表名与列名完全由设计器配置;主键列固定 MAPPINGID
  • FORM_TYPE_DATA_MODEL:表名留空(外部指定),列名按字段名直取。

DQLASTUtil.getItemTblName(formname, tableType) 负责按表类型加 LOG_ 等前缀;TableMapping.getTableName(tableType) 在其上再加 schema 前缀。

建表字段映射

字段类型映射分两步:

步骤 1:FormField.fieldtype → JDBC TypesFieldConstant.getTypeCode

fieldtype JDBC Types
null Types.NULL(0)
VALUE_TYPE_VARCHAR Types.VARCHAR(1)
VALUE_TYPE_NUMBER Types.NUMERIC(2)
VALUE_TYPE_DATE Types.TIMESTAMP(3)
VALUE_TYPE_TEXT Types.CLOB(4)
VALUE_TYPE_BLOB Types.BLOB(5)

步骤 2:JDBC Types → 各数据库列类型(各 *Builder 构造函数 registerColumnType

JDBC Type MySQL Oracle SQL Server PostgreSQL KingBase DM OceanBase Oscar
VARCHAR VARCHAR(100) VARCHAR2(100) NVARCHAR(100) VARCHAR(100) VARCHAR(100) VARCHAR(100) VARCHAR(100) VARCHAR(100)
LONGVARCHAR / CLOB LONGTEXT CLOB NTEXT TEXT TEXT CLOB TEXT TEXT
NUMERIC DECIMAL(22,10) NUMBER(22,5) NUMERIC(22,5) DECIMAL(22,10) DECIMAL(22,10) DECIMAL(22,10) DECIMAL(22,10) DECIMAL(22,10)
INTEGER INT NUMBER(20,0) NUMERIC(10,0) INT INT INT INT INT
BIT BIT(1) NUMBER(1,0) BIT BOOLEAN BIT(1) BIT BIT(1) BIT(1)
TIMESTAMP DATETIME TIMESTAMP(6) DATETIME TIMESTAMP(14) TIMESTAMP(6) TIMESTAMP DATETIME TIMESTAMP(6)
BLOB MEDIUMBLOB BLOB IMAGE BYTEA BLOB BINARY BLOB BLOB

长度/精度由 Column.length / Column.precision 覆盖(SQLBuilder.getSqlTypeName)。

固定列(公共系统字段)

每张动态表(TLK_* / PARM_* / LOG_TLK_*)除了 ITEM_ 业务列之外,都会先建一组**固定系统字段**承载文档元数据。固定列清单在 DDL 端由 ChangeLog.getFixedColumns() 维护(不含主键,主键在 createTableByType 末尾按 TableMapping.getPrimaryKeyName() 单独追加),共 34 列

列详细定义(按建表顺序)

# 列名 JDBC 类型 取值来源(setBaseParameters / doc.* 用途
1 PARENT VARCHAR doc.getParentid() 父文档 id;IncludeField/子表单文档关联父文档
2 LASTMODIFIED TIMESTAMP doc.getLastmodified() 最后修改时间(增量同步 LASTMODIFIED > ? 用)
3 FORMNAME VARCHAR doc.getFormname() 表单名(含模块路径);用于按表单过滤/分组
4 STATE VARCHAR doc.getStateid() 当前流程状态 id
5 AUDITUSER VARCHAR doc.getAudituser() 当前审批人 id
6 AUDITDATE TIMESTAMP doc.getAuditdate() 审批时间
7 AUTHOR VARCHAR doc.getAuthor().getId() 创建人 id
8 AUTHORDEPTID VARCHAR doc.getAuthorDeptId() 创建人所属部门 id
9 AUTHOR_DEPT_INDEX VARCHAR(2000) doc.getAuthorDeptIndex() 作者部门索引(路径型,长度 2000)
10 AUTHOR_USER_INDEX VARCHAR(2000) doc.getAuthorUserIndex() 作者用户索引(路径型,长度 2000)
11 CREATED TIMESTAMP doc.getCreated() 创建时间
12 FORMID VARCHAR ((Document)doc).getFormid() 表单 id(设计态)
13 SUBFORMIDS CLOB ((Document)doc).getSubFormids() 子表单 id 列表(多值序列化)
14 INITIATOR VARCHAR doc.getInitiator() 流程发起人 id
15 ISTMP BIT doc.getIstmp() ? 1 : 0 是否临时文档(新建未保存/草稿)
16 VERSIONS INTEGER doc.getVersions() + 1 文档版本(每次更新 +1)
17 APPLICATIONID VARCHAR doc.getApplicationid() 所属软件 id(多租户隔离)
18 STATEINT INTEGER doc.getStateInt() 流程状态数值(运行/草稿/挂起/结束等)
19 STATELABEL VARCHAR doc.getStateLabel() 流程状态显示标签
20 AUDITORNAMES CLOB doc.getAuditorNames() 当前审批人姓名列表(多值)
21 LASTFLOWOPERATION VARCHAR doc.getLastFlowOperation() 最后一次流程操作类型
22 LASTMODIFIER VARCHAR doc.getLastmodifier() 最后修改人
23 DOMAINID VARCHAR doc.getDomainid() 所属域 id(多租户隔离)
24 AUDITORLIST CLOB doc.getAuditorList() 审批人候选列表(结构化)
25 COAUDITORLIST CLOB doc.getCoAuditorList() 协办审批人候选列表
26 STATELABELINFO CLOB doc.getStateLabelInfo() 流程状态扩展信息
27 PREVAUDITNODE CLOB doc.getPrevAuditNode() 上一审批节点信息
28 PREVAUDITUSER CLOB doc.getPrevAuditUser() 上一审批审批人
29 OPTIONITEM CLOB ((Document)doc).getOptionItem() 选项项缓存(用于下拉/复选快速渲染)
30 SIGN CLOB ((Document)doc).getSign() 签名数据
31 KINGGRIDSIGNATURE CLOB doc.getKinggridSignature() 金格签章数据
32 SECRET VARCHAR (由密级相关逻辑写入) 文档密级

特殊列(非 getFixedColumns() 维护)

列名 类型 来源 说明
ID VARCHAR createTableByType 追加;INSERT 写 doc.getId();UPDATE 不写 普通表单/子表单/主页/参数表主键(primaryKey=true
MAPPINGID VARCHAR 同上(TableMapping.MAPPINGID 映射表单(FORM_TYPE_NORMAL_MAPPING)主键
DOC_ID VARCHAR createTableByTypeTABEL_TYPE_LOG 时追加 操作日志表(LOG_TLK_*)关联主文档 id

注释掉的列(已废弃,源码中保留但不建表)

列名 原用途
ISSUBDOC 是否子文档
FLOWID 流程 id
SORTID 排序 id
ID(在 getFixedColumns 内) 已挪到 createTableByType 末尾按主键单独追加

三种表的列差异

表类型 表名前缀 主键 额外列 固定列来源
内容表(CONTENT) TLK_ ID getFixedColumns() + ID
流程参数表(PARM) PARM_ ID 同上
映射表(MAPPING) 自定义 MAPPINGID 同上(列名由 mappingStr 配置)
操作日志表(LOG) LOG_TLK_ ID DOC_ID getFixedColumns() + ID + DOC_ID

注:映射表(FORM_TYPE_NORMAL_MAPPING)的列名由设计器 mappingStr.columnMappings 配置,**不**走 ITEM_ 前缀规则;但固定列名仍按上表。

写入流程(INSERT/UPDATE)

固定列由 AbstractDocStaticTblDAO.setBaseParameters(...) 统一赋值:

```496:578:obpm-core/src/main/java/cn/myapps/core/runtime/dynaform/document/dao/AbstractDocStaticTblDAO.java protected int setBaseParameters(int currentIndex, PreparedStatement statement, IDocument doc, int tabelType, boolean withoutBaseAttirbute,boolean isUpdate) throws Exception { if (tabelType == DQLASTUtil.TABEL_TYPE_CONTENT) { if(!isUpdate){ statement.setString(++currentIndex, doc.getId()); // ID(仅 INSERT) } } else if (tabelType == DQLASTUtil.TABEL_TYPE_LOG) { statement.setString(++currentIndex, Sequence.getTimeSequence()); // LOG 表主键 }

statement.setString(++currentIndex, doc.getParentid());         // PARENT
// ...LASTMODIFIED / FORMNAME / STATE / STATEINT / INITIATOR / AUDITUSER / AUDITDATE
//    / AUTHOR / AUTHORDEPTID / AUTHOR_DEPT_INDEX / AUTHOR_USER_INDEX / CREATED
//    / FORMID / SUBFORMIDS / ISTMP / VERSIONS(=doc.getVersions()+1)
//    / APPLICATIONID / STATELABEL / OPTIONITEM / SIGN / AUDITORNAMES
//    / LASTFLOWOPERATION / LASTMODIFIER / DOMAINID / AUDITORLIST / COAUDITORLIST
//    / STATELABELINFO / PREVAUDITNODE / PREVAUDITUSER / KINGGRIDSIGNATURE / INITIATOR
return currentIndex;

} - `AUTHOR_DEPT_INDEX` / `AUTHOR_USER_INDEX`:固定长度 `2000`(通过 `new Column("", "AUTHOR_DEPT_INDEX", Types.VARCHAR, "2000")` 传入,覆盖默认 `100`)。 - `VERSIONS`:写入时自增(`doc.getVersions() + 1`),UPDATE 时同样自增,**每次保存版本号都会前进**。 - `withoutBaseAttirbute` 标志:日志表与内容表对 `INITIATOR` / `KINGGRIDSIGNATURE` 的写入顺序略有差异(控制这两个字段在 SQL 列中的位置)。 - `MAPPINGID`:仅映射表单的 INSERT/UPDATE 末尾写入。 **读出流程(SELECT)** 读取结果集时,固定列会被 `DQLASTUtil.isDocumentFixedColumn(name)` 过滤掉,**不会**被误当业务 Item 拼成 `ITEM_xxx` 写回 `Document.items`:2062:2111:obpm-core/src/main/java/cn/myapps/core/runtime/dynaform/document/dao/AbstractDocStaticTblDAO.java protected String resolveItemFieldName(String columnLabel, TableMapping tableMapping, Form form, ResultSetMetaData metaData) throws SQLException { // 1. 优先按 ITEM_ 前缀解析 if (DQLASTUtil.isDocumentFixedColumn(columnLabel)) { return null; // 固定列不当业务 Item } // 2. 解析 ITEM_xxx 前缀,剥离后得到业务字段名 // 3. 不存在 ITEM_ 前缀时回退裸列名 // ... } ```

三方同步约束(关键)

固定列清单在 DDL 端与 DML/DQL 端**各维护一份**,必须**手动保持同步**:

维护位置 作用
DDL(建表) ChangeLog.getFixedColumns() 决定建表建哪些列
DML(写库) AbstractDocStaticTblDAO 的 INSERT/UPDATE SQL 列清单 + setBaseParameters 决定写入哪些列、写入什么值
DQL(读库) DQLASTUtil.DOCUMENT_FIXED_COLUMNS 读取时过滤,不把固定列当业务 Item

新增固定列时,必须同时修改三处,否则会出现以下错位: - 只改 DDL:DML 不写值,列始终为 NULL; - 只改 DML:DDL 未建列,INSERT 报「列不存在」; - 只改 DQL:固定列被当成业务 Item 写入 Document.items,且会以 ITEM_xxx 列名参与 DQL,引发查询错误。

重要DQLASTUtil.DOCUMENT_FIXED_COLUMNS 还包含了 MAPPINGID / DOC_ID 这两个主键/日志表专用列,覆盖范围比 ChangeLog.getFixedColumns() 略广(38 vs 34),目的是让 DQL 过滤一次性覆盖所有特殊列。

表变更(ALTER)类型

变更事件抽象在 obpm-commoncn.myapps.common.util.table.alteration 包,基类 ModelChange

变更类 DDL 动作 生成处
AddTableChange CREATE TABLE ChangeLog.compare:旧表不存在时
DropTableChange DROP TABLE ChangeLog.compare:新表为空、旧表存在时
TableRenameChange CREATE TABLE AS SELECT + DROP TABLE 表单改名时(processChange 注释掉了独立 create+copy,改用 CTAS)
AddColumnChange ALTER TABLE ADD 新字段、固定列缺失
DropColumnChange ALTER TABLE DROP COLUMN 字段被删除
ColumnRenameChange 见方言 字段改名
ColumnDataTypeChange 仅执行改名步骤 字段类型改变(add/copy/drop 三步被注释,等价于 rename)
ColumnRemarksChange 修改列注释 列描述变化

特殊抵消ChangeLog.filter() 会把「同列名+同类型」的 AddColumnChangeDropColumnChange 互相抵消(避免无意义 DDL)。

多数据库方言差异(DDL 层)

操作 MySQL Oracle SQL Server PostgreSQL / KingBase DM
改列名 ALTER TABLE t CHANGE old new TYPE ALTER TABLE t RENAME COLUMN old TO new EXEC SP_RENAME 't.old', 'new', 'COLUMN' ALTER TABLE t ALTER old TYPE(合并改名与改类型) RENAME COLUMN
改列类型 CHANGE col col NEWTYPE MODIFY col NEWTYPE ALTER COLUMN ALTER COLUMN ... TYPE CHANGE col col NEWTYPE
COMMENT 行内 COMMENT 'xxx' 独立 COMMENT ON COLUMN 无原生 无原生 独立 COMMENT ON COLUMN
表全名 schema.t schema.t DBO.t schema.t schema.t
schema 强制 KingBase / OceanBase 强制 PUBLIC.

说明processChange(ColumnDataTypeChange)AbstractTableDefinition 行 110–143 中只启用了 4 步流程的第 1 步(columnRename),第 2–4 步(add 新列、copy 数据、drop 临时列)被注释掉了。这是平台为避免数据迁移风险做的简化,副作用是改类型不会真正改库表列类型——需在变更前/后由 doClearColumnData 清空数据。

数据库方言分发

入口 行为
DbTypeUtil.getDBType(conn) 通过 conn.getMetaData().getDatabaseProductName() 判断数据库类型,返回字符串常量(MYSQL / ORACLE / MSSQL / POSTGRESQL / DM / KINGBASE / OCEANBASE / OSCAR / DB2 / HSQLDB / H2
RuntimeDaoManager.getFormTableDAO(conn) 按数据库类型 new 出对应 *FormTableDAOMysqlFormTableDAO / OracleFormTableDAO / …)
RuntimeDaoManager.getFormTableDAODtId(conn, datasourceId) 多数据源场景:根据 datasourceId 取连接后分发
*FormTableDAO 实现两个工厂方法:getTableDefinition() 返回对应 *TableDefinitiongetValidator() 返回对应 *Validator

每个数据库一套实现(*Builder 生成 SQL + *TableDefinition 执行 + *Validator 校验破坏性变更),统一位于 obpm-core/src/main/java/cn/myapps/core/designtime/table/ddlutil/<db>/。共 10 套(MySQL / Oracle / SQL Server / PostgreSQL / KingBase / DM / OceanBase / Oscar / DB2 / HSQLDB)。

操作日志表(LOG 表)

showLog=true 的表单额外维护一张操作日志表:

  • 表名 = LOG_ + 原表名(如 TLK_FORM1LOG_TLK_FORM1,由 DQLASTUtil.getItemTblName(name, TABEL_TYPE_LOG) 生成)
  • 列结构与主表一致,额外加 DOC_ID 列关联主文档
  • 生命周期由 AbstractFormTableDAO.createDynaTable/updateDynaTable/dropDynaTable 内部 if (form.isShowLog()) 分支驱动:开启日志 → 创建/更新 LOG 表;关闭日志 → 删除 LOG 表

多数据源同步(synDynaTable)

支持把同一表单同步到额外数据源(用于读写分离、报表库等):

  1. FormTableProcessBean.setDatasourceId(dsId) 指定目标数据源;
  2. 调用 synDynaTable(newForm, oldForm) → 经 DataSourceDesignTimeService 查到 DataSourceRuntimeDaoManager.getFormTableDAODtId(conn, dsId) 在该数据源连接上执行同样的 updateDynaTable(newForm, oldForm, dt)

入口:设计器 DataSourceController 在数据源维护时遍历表单触发同步。

系统初始化建表(区别于动态表)

平台有两套独立的 DDL 机制,不要混淆

机制 用途 入口
动态表 DDL(本节主题) 表单设计→运行库表 FormTableDAO + TableDefinition + SQLBuilder
系统表 DDL 平台自身表(用户、部门、流程实例等) AbstractApplicationInitDAOinitTables + compare,只做新增表/列,不做类型变更)
Schema 查询 查库表结构供 ChangeLog 对比 AbstractSystemSchemaDAOMYSQLSchemaDAO / ORACLESchemaDAO 等)
Liquibase(数据模型表单专用) FORM_TYPE_DATA_MODEL 的版本化迁移 LiquibaseAPIGenerator(**不**用于普通动态表单 DDL)

启动时 InitApplicationTable 会对所有应用的所有表单调用 formService.update(...),确保 DT(设计态)与 RT(运行态)数据库表结构同步。

运行时数据包组装(FormRunTimeServiceImpl)

GET .../forms/{formId}/documents/{docId} 与 empty 流程最终由 FormRunTimeServiceImpl 读取 Form + Document,输出 FormDataPacket

Form 属性 下发 / 行为
isopenablescript 空则视为 "true";脚本非 Boolean falseformTemplate.template 为 HTML,否则 template=false
iseditablescript Document.isEditable() 与流程权限叠加;false 则整单只读
confirmLeaveEdit formTemplate.confirmLeaveEdittrue 且文档可编辑时,离开编辑场景弹窗确认
showWaterMark + waterMarkScript waterMarkText
openComment + 评论脚本 隐藏脚本可关闭;→ openCommentcommentTitlecommentFlag
showLog + showLogType formTemplate.showLog / showLogTypedefault 模式标记 isModified
showType formTemplate.showType
styleId 样式库 → style.content
description 优先作为 formTemplate.formName
各字段 toAttributes(...)fields[](Tab 递归 buildTabModifyField
activities 工具栏按钮列表(含排序、只读标记)

继承自 FileSystemDesignTimeSerializable 的常用属性

属性 说明
id / name 表单标识与名称
applicationid 所属软件 id
description / remark 描述与备注
parent 所属模块(Module
uri 资源 URI,脚本 ScriptLabel 使用

设计器配置

表单设计器入口为模块详情页 Form.vue,页签「基本」由 FormBasic.vue 承载。保存时 FormBasic.save()FormFormat.getHtmlTemplate() 合并为 templatecontext 一并提交。

设计器接口:GET /designer/api/designtime/applications/{appId}/modules/forms/{formId}getModuleForm);样式库列表 getStyleLibsList

设计器页签(Form.vue)

页签 组件 普通表单(type=1) 说明
基本 FormBasic 表单元数据、脚本、评论、日志、水印等
内容 FormFormat 可视化/源码编辑表单 HTML 模板
操作 FormOperation ✓(已保存且 type≠2、256) 表单工具栏按钮(activities)
摘要 FormSummary ✓(已保存且 type≠2、256、1048576) 流程摘要提醒
映射 FormMapping —(仅 type=65536) 映射表单专用

新建未保存时仅「基本」「内容」可用;操作 / 摘要 需先保存拿到 formId

表单类型(设计器 type)

type 设计器标签 运行时组件
普通表单 1 normal_forms form_normalform
标签页 2 tab 标签页专用
查询表单 256 search_form form_searchform / form_template
映射表单 65536 normal_mapping 映射运行时
模板表单 1048576 template_form NormalForm + realformId / templateForm

基本页签可配置属性(FormBasic.params)

属性 类型 默认值 可见性 说明
name String 全部 表单名称;新建可编辑,已保存后默认只读展示
type Number 1 全部 表单类型;编辑时不可改
styleId String "" 全部 样式库 id
permissionType String "public" type≠2 且 ≠256 public / private
orderno Number 1 全部 模块内排序号
description / remark String 全部 描述 / 备注
showType String "old" old / new,对应 formTemplate.showType
isopenablescript String type≠2 是否可打开脚本(布尔)
iseditablescript String type≠2 是否可编辑脚本(布尔)
confirmLeaveEdit Boolean false type≠2 离开编辑时弹窗确认
showWaterMark Boolean false type=1 / 65536 / 1048576 是否启用水印
waterMarkScript String 同上 水印脚本
openComment Boolean false type≠256 且 ≠2 是否开通评论
commentTitleScript String 开通评论时 评论标题脚本
commentFlagScript String 同上 评论标识脚本
commentHiddenScrtipt String 同上 隐藏评论区脚本
commentExecuteScript String 同上 评论/回复执行脚本
showLog Boolean false type≠2 是否显示操作日志
showLogType String "default" showLog=true default / table
recordMode String "every" showLog=true every / other / script
recordModeScript String recordMode=script 记录方式脚本
mappingStr Object {} 映射表单 映射表数据
templatecontext String 保存时由 FormFormat 注入

脚本类字段支持 textarea + ScriptEditor 弹窗编辑。

保存流程(Form.vue.saveBtn)

  1. FormBasic.save():校验名称,返回 params
  2. FormFormat.getHtmlTemplate()basicData.templatecontext
  3. 映射表单:FormMapping.verifyMapping()mappingStr
  4. updateForm / createForms

操作按钮、摘要、流程预览在 操作 / 摘要 页签及内容设计器中维护,运行时由 activitiesformTemplate.processPreview 下发。


前端运行时(PC)

NormalForm 是 LINK_TYPE.FORM 在 PC 运行时的默认实现,由 obpm_open_containeropenParams.linkType 动态加载。

挂载场景

场景 挂载方式
主框架页签打开表单 obpm_open_containerform_normalform
视图列跳转 / 按钮弹出层 TopWindowUtilso_action_dialog
树形视图内嵌表单 view_treeview.vue
列表 Widget 内打开 view_listview_widget.vue
自定义消息弹窗内嵌 NormalForm 自身 el-dialog + form_template

组件接口

Props

属性 类型 必填 说明
openParams Object 表单打开上下文(见 openParams
callback Function 预留回调
emptyParams Array 视图列带参新建的空白字段默认值
openType String 打开类型标识(如弹出层)

Inject(宿主 provide)

用途
addTab 选项卡内打开新页签
openInCurrentContainer 当前容器栈内再开视图/表单
onCloseBtnClick 流程提交成功后关闭表单标签
doBack 返回、关闭表单

Provide(向子组件暴露)

说明
findField / getAllFields / getStateId 字段查找、流程状态
buildFormData / checkData 提交快照 / 校验快照
refresh / initialRefresh 字段重计算刷新
registerGridviewCommitAll 嵌入网格行内编辑提交注册
updateFormSubDocumentsData 表单子文档(IncludeField 网格)
clickControlBtn / flowHandle 按钮控件 / 流程按钮入口
refreshId 当前实例 id(openParams.id
addTabsgetOpenParamspromptBox 页签、参数、提示

Emitevent(加载完成)、action(保存并新建等)、treeLeftRefreshrefresh

页面结构

formwrapper (#formTemplate_{docId})
├── formcontent (act-btns + approvers)
│   ├── activity × N              ← label/name 不含 "/"
│   ├── collapse_activity         ← label/name 含 "/"
│   └── approvers
├── formTable-wrap (el-scrollbar)
│   ├── processPreview
│   ├── form_signlist / form_stamp / form_signature
│   └── dynamic formTemplate
├── form_flow_dialog / 审批人 / 终止 / 邮件短信 等弹窗
├── o_action_dialog
└── el-dialog + form_template

操作栏显示:isActBox 且(有 stateId + isRouterAlive,或 isShowFormActivity)。formTemplate.template == false 时仅渲染 <authority />

后端配置在前端的消费

后端 / 设计器来源 前端行为
formTemplate.template 动态编译为 formHtmlfalse → authority
style.content 注入 #formTemplate_{docId} 作用域 CSS
waterMarkText Watermark.set
openComment / commentTitle / commentFlag 追加 <form_comment> 虚拟字段
showLog + showLogType=table _template 嵌入 form_comment
showType 弹出层滚动、操作日志展示等
confirmLeaveEdit 可编辑时:doBack、页签关闭、beforeunload 等离开路径弹窗确认;配合 formSuppressBeforeUnloadOnce 跳过单次提示
Tab 内 openComment / commentFlag initForm / loadFormloopOpe 动态插入评论

数据加载

入口onBeforeMountinitForm({ appId, formId, docId })docIdopenParams._select,空为新建)。

编辑(docId 非空)→ GET /runtime/{appId}/forms/{formId}/documents/{docId}getFormAPI):

  • 合并 exparams / formParams / urlParams、只读、重算等参数;
  • 响应后处理:字段 % 解码、注入运行时属性、评论/样式/水印/流程预览、包含元素内隐藏返回按钮。

新建(docId 空)→ GET .../emptygetDocumentEmptyAPI)→ 可选新建后脚本 → loadForm 拉完整表单。

带旧数据新建:保存后 oldBuildFormData 在下次 loadForm 按字段 name 写回。

动态模板(_template

条件 渲染
template == false <authority />
评论或 table 型操作日志 formHtml + form_comment
默认 formHtml

注入 methods:findFieldrefreshcheckDatagetAllFieldsaddTabsgetStateId

表单提交(buildFormData)

{ applicationId, formId, id, items, parentId, sign, subDocuments, versions: '0' }

字段纳入 items:有 name 且非 ButtonField、非 hidden 空值(例外见源码 addInTtems / Checkbox 默认项 / 附件 isEdit)。模板表单:realformId + templateForm

嵌入网格视图同步

gridviewCommitAllCallbacksonActioncommitAllGridviews(),确保行内编辑落盘后再保存/流程;子文档经 formSubDocumentsDatasubDocuments

按钮动作

入口 说明
onAction(act) 工具栏 / 折叠区
clickControlBtn(act) 表单内 ButtonField

流程类flowHandle):流程处理(5)、启动(33)、回退/催办/挂起/恢复/撤回/终止、编辑审批人(53)、点评/补签/加签(55–58)、邮件短信(37) 等。成功后 notifyFlowActionSuccess() → 本地 reload + 通知父视图 refreshId不向自身 id dispatch,防死循环)。

脚本流水线类prepareActionContext → before 脚本 → 业务 API → after 脚本。含保存(34)、保存并返回(11)、保存并新建(42)、草稿(19)、复制(21)、打印(14/30)、导出、跳转(43) 等。

简单动作:返回(10) → doBack()

openParams 关键字段

字段 说明
appId / actionContent / _select 软件 id、表单 id、docId(空=新建)
realformId / templateForm 模板表单
id / refreshId 实例 id / 父级刷新目标
parentId / isRelate 包含元素 / 关联新建
type / openType / showtype 打开方式;include/tab 内隐藏返回
exparams / formParams / urlParams / queryString 扩展参数
isNewCreate / runAfterParams 视图新建后脚本
originalFormDocid 保存并新建带旧数据
isIncludeCreate / randomRefreshId 包含元素新建后刷新父视图
dialogId / jumpDialogId 弹出层内 IncludeField

字段查找与刷新

  • findField(id):主字段按 id 后缀段匹配(复制文档兼容);Tab 内完整 id;未找到返回 HIDDEN 占位。
  • refresh(fieldId)refreshFormAPI 原地更新字段;IncludeField 改 refreshNumber;InputField 设 addInTtems
  • checkData(true):返回 buildFormData() 快照。

辅助能力

能力 要点
操作日志 isModifiedhisLogsStore
盖章 / 签章 form_stampform_signature
网页打印 路由 formPrint 或对话框 form_print
流程预览 内嵌或弹窗,previewWorkflow
跳转 o_action_dialog.openOverlayJumpdialogHost: 'form'
离开确认 formTemplate.confirmLeaveEdit=true 时启用;beforeunload + 容器内返回/关页签;formSuppressBeforeUnloadOnce 可跳过单次浏览器提示
父视图刷新 dispatchRefresh(refreshId)

与 form_template 的区别

维度 form_normalform form_template
用途 完整运行时(按钮栏 + 流程 + 保存) 轻量渲染(查询表单、消息弹窗)
操作按钮 完整 activities
数据加载 initForm + loadForm 全分支 简化 initForm

运行时 API 汇总

方法 路径 / 接口 用途
GET /runtime/{appId}/forms/{formId}/empty 新建空文档
GET /runtime/{appId}/forms/{formId}/documents/{docId} 加载表单与文档
PUT /runtime/{appId}/documents/{docId} 保存文档
saveWithoutValidDocument 保存草稿
refreshFormAPI 字段刷新重计算
runBeforeActionScriptAPI / runAfterActionScriptAPI 按钮前后脚本
initWorkFlow 流程启动与处理

已知限制与注意事项

  • 动态模板安全formTemplate.template 为 HTML 字符串,非沙箱隔离。
  • fieldId 匹配:复制文档依赖 id 后缀匹配,Tab 内仍用完整 id。
  • refreshId 死循环:向自身 openParams.id dispatch 会导致 remount 死循环,已排除。
  • 流程状态栏:审批人变更后 isRouterAlive 强制 remount approvers
  • 新表单弹出层滚动showType=new 且弹出层,或存在 isModified 时修正滚动条。
  • PDF 导出按钮:新建未保存时表单内 EXPORT_PDF 按钮设为只读。
  • 移动端:PC 与移动为不同代码库。
  • 保存即建表:平台无独立「发布」步骤,FormDesignTimeService.save/update/delete 在事务内直接触发 DDL;表单保存失败会回滚 XML 与库表。
  • 改列类型不迁移数据processChange(ColumnDataTypeChange) 仅执行改名,第 2–4 步(add→copy→drop)被注释;变更字段类型前应清空该列数据(由 doClearColumnData 完成),否则类型可能不一致。
  • 固定列清单需三方同步:新增文档元数据列时,ChangeLog.getFixedColumns()(DDL)、DQLASTUtil.DOCUMENT_FIXED_COLUMNS(DQL/DML 过滤)、AbstractDocStaticTblDAO 的 INSERT/UPDATE SQL 列清单必须同时维护,否则会出现「建表有列但读写漏列」或反之。
  • 动态表 DDL ≠ 系统表 DDLFormTableDAO 仅供表单动态表使用;平台自身表走 AbstractApplicationInitDAO,数据模型表单可选用 Liquibase——三套机制互不相通。