政务低代码平台实战⑤:双输出模式——存DB与导出JSP的完整链路
2026/9/10 16:24:20 网站建设 项目流程

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

文章目录

  • 政务低代码平台实战⑤:双输出模式——存DB与导出JSP的完整链路
    • 背景
    • 两条出路的数据流
    • 保存到数据库
      • 涉及的5张表
      • 保存过程
    • 导出JSP文件
    • 三种表单类型的运行时调度
    • 载入回设计器
    • 完整闭环
    • 决策原则

非科班野生程序员,深耕政务信息化20年。表单设计器设计好了,设计结果存哪?运行时怎么还原?这篇拆解设计器的两条出路——存数据库动态渲染,和导出JSP静态加载。两种模式还能互相转换,导出的JSP可以载回设计器继续编辑。最后感谢豆包、智谱、OpenCode,决策是我做的,代码是我搓的,文字是他们总结的。


背景

表单设计器解决了"怎么设计"的问题。但设计结果有两条出路:

  1. 开发阶段:频繁调整布局、改字段、改SQL。需要存数据库,随时载回设计器修改,运行时动态渲染。
  2. 上线后:表单不再变化。动态渲染每次都读DB、解析组件树、生成HTML,有性能开销。不如导出成JSP文件直接部署。

两条路都要走通,而且要能互相切换。


两条出路的数据流

设计器(formEdit.jsp) │ ├── 保存到DB(add()按钮) │ ├── T_FORM → 表单定义(HTML、JS脚本、参数) │ ├── T_FIELD → 字段定义(名称、类型、代码表、默认值) │ ├── T_TABLE → 数据源绑定(form_id关联) │ ├── T_EL → 元素树(组件ID、父ID、类型、JSON配置、事件) │ └── T_EL_COLUMS → 元素属性(每个组件的详细属性键值对) │ │ │ ↓ type=1/2 │ onlyFrom.jsp 从DB读取 → 动态渲染 │ └── 导出JSP(saveto()按钮) ├── 写 xxx.jsp 文件到磁盘 ├── 写 xxx.js 文件到磁盘 └── T_FORM.type=3, filepath='xxx.jsp' │ ↓ type=3 运行时读JSP文件直接输出

保存到数据库

涉及的5张表

T_FORM(表单主表):

字段含义
ID_表单唯一ID
NAME_表单名称
DESCRIPTION_描述
TYPE_类型:1=列表, 2=表单, 3=JSP文件
HTML_表单HTML内容(CLOB)
SCRIPT_表单JS脚本(CLOB)
COLS列数
H高度
W宽度
PARAMETERS_参数
REPORT_打印模板
SQLID关联的SQL编号
FILEPATHJSP文件路径(type=3时)

T_FIELD(字段表):每个绑定数据的控件一条记录

字段含义
FIELDID字段ID
ID_所属表单ID
NAME_英文名(binding字段名)
DESCRIPTION_中文名
COLTPYE控件类型
DATATYPE数据类型
N_COLS新增时列宽
D_COLS显示时列宽
P_VAL是否流程变量
COMMENS自定义选项JSON
CODENAME二级代码编码
READONLY是否只读

T_EL(元素树):设计器组件树的每个节点

字段含义
EL_ID元素ID(组件ID)
PARENT_EL_ID父元素ID
TYPE_组件类型(browise.form.TextBox等)
JSON_创建时的JSON配置
OTHER_其他参数
ORDERID排序
EVENT_事件绑定JSON

T_EL_COLUMS(元素属性):每个组件的详细属性

字段含义
EL_ID元素ID
COL_属性名(dojoType/width/binding/disabled等)
VALUE_属性值

T_TABLE(数据源绑定):表单关联的数据表

字段含义
ID_表单ID
FORM_ID数据源form的ID

保存过程

uiglobal.js中的add()函数:

functionadd(){// 1. 收集所有组件信息varnodes=getformorgrid();// 获取所有Form和Grid节点// 2. 遍历组件树,生成HTMLvarshtml="";for(vari=0;i<nodes.length;i++){if(nodes[i].declaredClass=='browise.form.Form'){shtml+=gettable(nodes[i].id,parent_id);// Form+Table序列化为HTML}elseif(nodes[i].declaredClass=='browise.grid.Grid'){shtml+=getgrid(nodes[i].id,parent_id);// Grid序列化为HTML}}// 3. 发送到后端vardc=newbrowise.ds.DataCenter();dc.addStore(dsForm);// 表单元数据dc.addStore(dsField);// 字段定义dc.addStore(dsEl);// 元素树dc.addStore(dsElCol);// 元素属性dc.addStore(dstable);// 数据源varURL=webRootDir+"/BusinessAction?Business=formManager&Action=Business";browise.Action.requestData({url:URL,load:function(dc1){alert("保存成功!");}},dc,true);}

后端formManagerControl.Business()接收数据:

// 如果是type=2(修改),先删后插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{DataStoredsea01=center.getStore("ea01");Stringfilename=dsea01.getRowset().getrow(0).getItemStringValue("name_");Stringpath=dsea01.getRowset().getrow(0).getItemStringValue("description_");Stringhtml=dsea01.getRowset().getrow(0).getItemStringValue("html_");Stringscript=dsea01.getRowset().getrow(0).getItemStringValue("script_");// 拼JSP文件头StringjspContent="<!DOCTYPE html PUBLIC ...>\n"+"<%@page contentType=\"text/html; charset=UTF-8\"%>\n"+"<%\n"+"\tString sessionid = \"0000000000000000000\";\n"+"%>\n"+"<html>\n<head>\n"+"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n"+"<%@ include file=\"/browise/ria3.3/pages/config.jsp\"%>\n";// 拼JS文件引用jspContent+="<script type=\"text/javascript\" src=\""+filename+".js\"></script>\n";// 拼HTML bodyjspContent+="</head>\n<body class=\"unieap\">\n"+html+"\n</body>\n</html>";// 写JSP文件StringjspPath=request.getRealPath("/")+"pages/"+path+"/"+filename+".jsp";FileOutputStreamfos=newFileOutputStream(jspPath);fos.write(jspContent.getBytes("UTF-8"));fos.close();// 写JS文件StringjsPath=request.getRealPath("/")+"pages/"+path+"/"+filename+".js";fos=newFileOutputStream(jsPath);fos.write(script.getBytes("UTF-8"));fos.close();// 更新T_FORM:type=3, filepathdsea01.getRowset().getrow(0).setItemValue("type_","3");dsea01.getRowset().getrow(0).setItemValue("filepath","pages/"+path+"/"+filename+".jsp");formService.updateform(dsea01);returndc;}

导出做了三件事:

  1. 把设计器的HTML和JS分别写成.jsp.js文件
  2. JSP文件包含标准的<%@ page %>声明和config.jsp引用
  3. 更新T_FORMtype=3filepath

三种表单类型的运行时调度

万能表单onlyFrom.jsp根据type决定渲染方式:

<% String type1 = formds.getRowset().getrow(0).getItemStringValue("type"); if ("3".equals(type1)) { // type=3:读JSP文件直接输出 String html = FileUtil.readJspFile( formds.getRowset().getrow(0).getItemStringValue("filepath")); out.println(html); } else { // type=1/2:从数据库的html_字段读HTML String html = String.valueOf(formds.getRowset().getrow(0).getItemValue("html_")); out.println(html); } %>
type来源性能适用场景
1T_FORM.HTML_字段列表页面(开发阶段)
2T_FORM.HTML_字段表单页面(开发阶段)
3JSP文件上线后(不再修改)

type=1和type=2的区别不在渲染,而在设计器模式——UI版本设计列表页(含Grid+条件),UI2版本设计表单页(纯表单,oneSql=true)。


载入回设计器

已保存的表单可以载回设计器继续编辑。openload()弹窗选择表单,后端detail()方法查出T_FORM、T_FIELD、T_EL、T_EL_COLUMS数据返回前端,loadformdb()函数还原组件树:

functionloadformdb(rowdata){// rowdata包含T_EL元素列表// 按orderid排序,逐个还原:// 1. 先创建Form容器(createFormofDb)// 2. 再创建Table(createTABLEofDb)// 3. 创建TR(createTRofDb)// 4. 创建TD(createTDofDb)// 5. 在TD里创建控件(根据type_创建对应组件)// 6. 恢复属性(从T_EL_COLUMS读回)// 7. 恢复事件绑定}

还原顺序严格按照orderid——先父后子,保证DOM树层级正确。


完整闭环

设计器设计表单 ↓ 保存到DB(type=1/2) ↓ 运行测试 → 不满意? → 载回设计器修改 ↓ ↓ 满意了 修改完再保存 ↓ ↓ 导出JSP(type=3) 继续测试 ↓ 上线运行(读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导出 #动态渲染 #表单管理 #政务信息化

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询