政务低代码平台实战⑤:双输出模式——存DB与导出JSP的完整链路

📅 2026/8/12 10:17:58
政务低代码平台实战⑤:双输出模式——存DB与导出JSP的完整链路
政务低代码平台实战⑤双输出模式——存DB与导出JSP的完整链路文章目录政务低代码平台实战⑤双输出模式——存DB与导出JSP的完整链路背景两条出路的数据流保存到数据库涉及的5张表保存过程导出JSP文件三种表单类型的运行时调度载入回设计器完整闭环决策原则非科班野生程序员深耕政务信息化20年。表单设计器设计好了设计结果存哪运行时怎么还原这篇拆解设计器的两条出路——存数据库动态渲染和导出JSP静态加载。两种模式还能互相转换导出的JSP可以载回设计器继续编辑。最后感谢豆包、智谱、OpenCode决策是我做的代码是我搓的文字是他们总结的。背景表单设计器解决了怎么设计的问题。但设计结果有两条出路开发阶段频繁调整布局、改字段、改SQL。需要存数据库随时载回设计器修改运行时动态渲染。上线后表单不再变化。动态渲染每次都读DB、解析组件树、生成HTML有性能开销。不如导出成JSP文件直接部署。两条路都要走通而且要能互相切换。两条出路的数据流设计器formEdit.jsp │ ├── 保存到DBadd()按钮 │ ├── T_FORM → 表单定义HTML、JS脚本、参数 │ ├── T_FIELD → 字段定义名称、类型、代码表、默认值 │ ├── T_TABLE → 数据源绑定form_id关联 │ ├── T_EL → 元素树组件ID、父ID、类型、JSON配置、事件 │ └── T_EL_COLUMS → 元素属性每个组件的详细属性键值对 │ │ │ ↓ type1/2 │ onlyFrom.jsp 从DB读取 → 动态渲染 │ └── 导出JSPsaveto()按钮 ├── 写 xxx.jsp 文件到磁盘 ├── 写 xxx.js 文件到磁盘 └── T_FORM.type3, filepathxxx.jsp │ ↓ type3 运行时读JSP文件直接输出保存到数据库涉及的5张表T_FORM表单主表字段含义ID_表单唯一IDNAME_表单名称DESCRIPTION_描述TYPE_类型1列表, 2表单, 3JSP文件HTML_表单HTML内容CLOBSCRIPT_表单JS脚本CLOBCOLS列数H高度W宽度PARAMETERS_参数REPORT_打印模板SQLID关联的SQL编号FILEPATHJSP文件路径type3时T_FIELD字段表每个绑定数据的控件一条记录字段含义FIELDID字段IDID_所属表单IDNAME_英文名binding字段名DESCRIPTION_中文名COLTPYE控件类型DATATYPE数据类型N_COLS新增时列宽D_COLS显示时列宽P_VAL是否流程变量COMMENS自定义选项JSONCODENAME二级代码编码READONLY是否只读T_EL元素树设计器组件树的每个节点字段含义EL_ID元素ID组件IDPARENT_EL_ID父元素IDTYPE_组件类型browise.form.TextBox等JSON_创建时的JSON配置OTHER_其他参数ORDERID排序EVENT_事件绑定JSONT_EL_COLUMS元素属性每个组件的详细属性字段含义EL_ID元素IDCOL_属性名dojoType/width/binding/disabled等VALUE_属性值T_TABLE数据源绑定表单关联的数据表字段含义ID_表单IDFORM_ID数据源form的ID保存过程uiglobal.js中的add()函数functionadd(){// 1. 收集所有组件信息varnodesgetformorgrid();// 获取所有Form和Grid节点// 2. 遍历组件树生成HTMLvarshtml;for(vari0;inodes.length;i){if(nodes[i].declaredClassbrowise.form.Form){shtmlgettable(nodes[i].id,parent_id);// FormTable序列化为HTML}elseif(nodes[i].declaredClassbrowise.grid.Grid){shtmlgetgrid(nodes[i].id,parent_id);// Grid序列化为HTML}}// 3. 发送到后端vardcnewbrowise.ds.DataCenter();dc.addStore(dsForm);// 表单元数据dc.addStore(dsField);// 字段定义dc.addStore(dsEl);// 元素树dc.addStore(dsElCol);// 元素属性dc.addStore(dstable);// 数据源varURLwebRootDir/BusinessAction?BusinessformManagerActionBusiness;browise.Action.requestData({url:URL,load:function(dc1){alert(保存成功!);}},dc,true);}后端formManagerControl.Business()接收数据// 如果是type2修改先删后插if(2.equals(type)){formService.deleteform(id);// 删表单formService.deletefield(id);// 删字段formService.deleteel(id);// 删元素formService.deleteelcloum(id);// 删元素属性formService.deletetable(id);// 删数据源}// 重新插入formService.insertform(dsForm);formService.insertfield(dsField);formService.insertel(dsEl);formService.insertelcloum(dsElCol);formService.inserttable(dsTable);先删后插的策略虽然粗暴但保证了数据一致性——不会出现修改后组件树和属性不同步的问题。导出JSP文件uiglobal.js中的saveto()弹出对话框输入文件名和路径然后调用后端// formManagerControl.SaveToJSP()publicDataCenterSaveToJSP(DataCentercenter,...)throwsException{DataStoredsea01center.getStore(ea01);Stringfilenamedsea01.getRowset().getrow(0).getItemStringValue(name_);Stringpathdsea01.getRowset().getrow(0).getItemStringValue(description_);Stringhtmldsea01.getRowset().getrow(0).getItemStringValue(html_);Stringscriptdsea01.getRowset().getrow(0).getItemStringValue(script_);// 拼JSP文件头StringjspContent!DOCTYPE html PUBLIC ...\n%page contentType\text/html; charsetUTF-8\%\n%\n\tString sessionid \0000000000000000000\;\n%\nhtml\nhead\nmeta http-equiv\Content-Type\ content\text/html; charsetutf-8\/\n% include file\/browise/ria3.3/pages/config.jsp\%\n;// 拼JS文件引用jspContentscript type\text/javascript\ src\filename.js\/script\n;// 拼HTML bodyjspContent/head\nbody class\unieap\\nhtml\n/body\n/html;// 写JSP文件StringjspPathrequest.getRealPath(/)pages/path/filename.jsp;FileOutputStreamfosnewFileOutputStream(jspPath);fos.write(jspContent.getBytes(UTF-8));fos.close();// 写JS文件StringjsPathrequest.getRealPath(/)pages/path/filename.js;fosnewFileOutputStream(jsPath);fos.write(script.getBytes(UTF-8));fos.close();// 更新T_FORMtype3, filepathdsea01.getRowset().getrow(0).setItemValue(type_,3);dsea01.getRowset().getrow(0).setItemValue(filepath,pages/path/filename.jsp);formService.updateform(dsea01);returndc;}导出做了三件事把设计器的HTML和JS分别写成.jsp和.js文件JSP文件包含标准的% page %声明和config.jsp引用更新T_FORM的type3和filepath三种表单类型的运行时调度万能表单onlyFrom.jsp根据type决定渲染方式% String type1 formds.getRowset().getrow(0).getItemStringValue(type); if (3.equals(type1)) { // type3读JSP文件直接输出 String html FileUtil.readJspFile( formds.getRowset().getrow(0).getItemStringValue(filepath)); out.println(html); } else { // type1/2从数据库的html_字段读HTML String html String.valueOf(formds.getRowset().getrow(0).getItemValue(html_)); out.println(html); } %type来源性能适用场景1T_FORM.HTML_字段中列表页面开发阶段2T_FORM.HTML_字段中表单页面开发阶段3JSP文件优上线后不再修改type1和type2的区别不在渲染而在设计器模式——UI版本设计列表页含Grid条件UI2版本设计表单页纯表单oneSqltrue。载入回设计器已保存的表单可以载回设计器继续编辑。openload()弹窗选择表单后端detail()方法查出T_FORM、T_FIELD、T_EL、T_EL_COLUMS数据返回前端loadformdb()函数还原组件树functionloadformdb(rowdata){// rowdata包含T_EL元素列表// 按orderid排序逐个还原// 1. 先创建Form容器createFormofDb// 2. 再创建TablecreateTABLEofDb// 3. 创建TRcreateTRofDb// 4. 创建TDcreateTDofDb// 5. 在TD里创建控件根据type_创建对应组件// 6. 恢复属性从T_EL_COLUMS读回// 7. 恢复事件绑定}还原顺序严格按照orderid——先父后子保证DOM树层级正确。完整闭环设计器设计表单 ↓ 保存到DBtype1/2 ↓ 运行测试 → 不满意 → 载回设计器修改 ↓ ↓ 满意了 修改完再保存 ↓ ↓ 导出JSPtype3 继续测试 ↓ 上线运行读JSP文件高性能 ↓ 政策变了要改表单→ 载回设计器type改回1/2→ 修改 → 再导出设计器 → DB → 测试 → JSP → 上线 → 载回修改 → 再导出。整个生命周期都走通了。决策原则开发阶段求灵活上线后求性能。存DB的好处是随时可编辑、可载回、可修改。导JSP的好处是零数据库访问、直接输出HTML。两种模式不是互斥的是同一个设计结果在不同阶段的不同表现形式。关键设计是T_FORM.type字段——一个字段控制渲染方式业务代码不用变只有渲染层根据type走不同的分支。而且type可以随时改回来导出JSP后载入设计器时type自动改回1/2修改完再导出。你项目里的表单是怎么管理生命周期的欢迎评论区聊聊。系列导航总纲[政务低代码平台实战——从元数据引擎到可视化设计器的五个关键决策]上一篇[政务低代码平台实战④可视化表单设计器——拖拽、合并拆分、代码生成]框架系列[Java Web自研框架18年架构决策复盘系列文总纲]工作流系列[政务工作流实战二——BPMN画不出来的七个设计决策]作者许彰午| 非科班野生程序员深耕政务信息化20年标签#Java #低代码 #JSP导出 #动态渲染 #表单管理 #政务信息化