allbytaskid:一个接口扛六个DataStore
非科班野生程序员,深耕政务信息化20年,这套自研Java Web框架支撑过省级社保、跨省医保结算等核心民生系统。打开一个审批表单需要多少次网络请求?表单数据、字段权限、流程进度、下一步选项、会签意见、审批历史------如果每个单独请求,就是6次。政务系统的网络环境不比互联网,内网有时候很慢。这篇拆解一个接口怎么一次拿完6组数据。最后感谢豆包、智谱、OpenCode,决策是我做的,代码是我搓的,文字是他们总结的。
背景
审批表单加载时需要的数据:
| 数据 | 用途 | 不拿会怎样 |
|---|---|---|
| 表单数据 | 填到表单控件里 | 表单是空的 |
| 字段权限 | 某些字段只读 | 所有字段都能改 |
| 流程进度 | 顶部显示走到哪了 | 不知道当前环节 |
| 下一步选项 | 下拉框里选下一步 | 不知道能选什么 |
| 会签意见 | 如果有会签,显示意见表 | 看不到别人签了什么 |
| 审批历史 | 底部显示审批流 | 不知道前面谁签过 |
如果每个单独请求,浏览器到服务器6次往返。在内网环境下,每次往返可能100-300ms(包括网络延迟+服务器处理),6次就是600-1800ms。用户打开一个表单要等1-2秒。
能不能一次拿完?
核心思路
一个 allbytaskid 接口,接收 taskid、insid、formid 三个参数,返回一个 DataCenter 对象,里面包含4个命名的 DataStore 和1个参数:
DataCenter
├── DataStore "next" ------ 下一步选项
├── Parameter "lcmx" ------ 流程进度(字符串)
├── DataStore "frole" ------ 字段权限
├── DataStore "common" ------ 表单数据
├── DataStore "hqyj" ------ 会签意见
├── DataStore "tasks" ------ 审批历史
└── code ------ 0=会签完成/-1=会签未完成
后端实现
allbytaskid() 完整拆解
java
public DataCenter allbytaskid(DataCenter center, ...) throws Exception {
DBUtil.BeginTrans(false);
DataCenter newdc = new DataCenter();
String taskid = request.getParameter("taskid").trim();
1. 下一步选项(DataStore "next")
java
DataStore ds = processService.getNextBranches(taskid);
String CODENAME = (String) ds.getRowset().getrow(0).getItemValue("CODENAME");
String codeName = CODENAME.replaceAll("<br>", "")
.replaceAll("\r", "").replaceAll("\n", "");
ds.getRowset().getrow(0).setItemValue("CODENAME", codeName);
ds.getRowset().resetUpdate();
ds.setName("next");
newdc.addStore(ds);
getNextBranches() 查Activiti的流程定义,找到当前节点后面可以走的所有分支。返回的 CODENAME 可能有HTML换行符,清理一下。
2. 流程进度(Parameter "lcmx")
java
String lc = "";
HashMap<String, String> omap = processService.getTask(taskid);
String processDefinitionId = omap.get("pid");
DataStore detail = processService
.getProcessDefinitionDetailByProcessDefinitionId(processDefinitionId);
String currtaskkey = omap.get("taskdefid");
String lcmx = "";
for(int i = 0; i < detail.getRowset().getPrimary().size(); i++) {
Row row = detail.getRowset().getrow(i);
String name = row.getItemStringValue("name");
String id = row.getItemStringValue("id");
if(currtaskkey.equals(id.replace(omap.get("pid") + "_", "")))
name = "[" + name + "]"; // 当前环节加方括号
if(i != 0) lcmx = lcmx + "---→" + name;
else lcmx = name;
}
lc = lcmx.replaceAll("<br>", "").replaceAll("\r", "").replaceAll("\n", "");
newdc.setParameters("lcmx", lc);
遍历流程定义的所有活动节点,当前环节加方括号标红。比如:
申请---→科长审批---→[处长审批]---→局长审批
3. 字段权限(DataStore "frole")
java
DataStore ds1 = new DataStore("frole");
if(!"".equals(taskid) && !"null".equals(taskid) && taskid != null) {
HashMap<String, String> tasks = processService.getTask(taskid);
String pid = tasks.get("pid");
String taskdefid = tasks.get("taskdefid");
t_from_readonly dao = new t_from_readonly();
dao.setPid(pid);
dao.setTaskdefid(taskdefid);
Object para[] = { dao };
ds1 = DBUtil.Query(t_from_readonlyMapper.class,
dao.getSearchMethod(), para, "frole");
}
newdc.addStore(ds1);
用流程定义ID + 步骤ID 查 t_from_readonly 表,拿到当前步骤应该只读的字段列表。
4. 表单数据(DataStore "common")
java
String insid = request.getParameter("insid").trim();
String fromid = request.getParameter("formid").trim();
if(!"".equals(insid) && !"null".equals(insid) && insid != null) {
// 查表单定义获取SQL ID
List<myform> list = (List<myform>) DBUtil.getDao(
myformMapper.class, "selectByPrimaryKey", fromid);
String sqlid = list.get(0).getSqlid();
// 用 proc_inst_id_ 查业务数据
DataStore pds = center.getSingleDataStore();
pds.setParameter("proc_inst_id_", insid);
pds.setParameter("fromid", fromid);
pds.setParameter("sqlId", sqlid);
CommonSQLcontrol sqlcl =
(CommonSQLcontrol) BeanFactory.getBean("CommonSQL");
DataCenter dc = sqlcl.CommonQuery(center, request, response);
ds = dc.getSingleDataStore();
ds.setName("common");
newdc.addStore(ds);
}
表单数据走通用SQL查询------表单定义里有 sqlid,框架的 CommonSQL 组件根据 sqlid 找到对应的SQL模板,用 proc_inst_id_ 作参数执行查询。
5. 会签意见(DataStore "hqyj")
java
hq dao = new hq();
dao.setSearchMethod("selectC");
dao.setPreProcInstId(insid);
List<hq> list = (List<hq>) dao.search();
int count = Integer.parseInt(list.get(0).getTaskId());
boolean canNext = true;
if(count > 0) canNext = false;
dao.setSearchMethod("selectAll");
dao.setPreProcInstId(insid);
list = (List<hq>) dao.search();
ds = new DataStore("hqyj");
for(int i = 0; i < list.size(); i++) {
Row row = ds.getRowset().add();
User userdao = new User();
userdao.setPsn_id(list.get(i).getUserId());
List<User> userlist = (List<User>) DBUtil.getDao(
userMapper.class, "getUserInfo_new", userdao);
if(userlist != null && userlist.size() != 0) {
User user = userlist.get(0);
row.setItemValue("dept", user.getUnit_name());
row.setItemValue("username", user.getPsn_name());
String date1 = list.get(i).getEnddate();
if(date1 == null || "".equals(date1) || "null".equals(date1)) {
row.setItemValue("result", "未签批");
} else if("null".equals(list.get(i).getProcInstId())) {
row.setItemValue("result", "放弃");
} else {
String temp = "1".equals(list.get(i).getPass()) ? "通过" : "不通过";
row.setItemValue("result", temp);
row.setItemValue("cause", list.get(i).getCause());
row.setItemValue("rtime", date1);
}
}
if("0".equals(list.get(i).getPass())) canNext = false;
}
ds.setRecordCount(ds.getRowset().getPrimary().size());
newdc.addStore(ds);
if(canNext) newdc.setCode(0);
else newdc.setCode(-1);
查 T_HJS_s 表------先查剩余未完成数(selectC),再查全部会签记录(selectAll)。每条记录填充:部门、姓名、结果(通过/不通过/未签批/放弃)、意见、时间。
canNext 是关键------有人没签完或有人不通过,code 设为 -1,前端据此禁用提交按钮。
6. 审批历史(DataStore "tasks")
java
List<HashMap<String, String>> tasks = processService.getTasks(insid);
ds = new DataStore("tasks");
if(tasks != null) {
for(int i = 0; i < tasks.size(); i++) {
Row row = ds.getRowset().add();
HashMap<String, String> map = tasks.get(i);
String userid = map.get("userid");
// 查用户名、部门名
User userdao = new User();
userdao.setPsn_id(userid);
List<User> userlist = (List<User>) DBUtil.getDao(
userMapper.class, "getUserInfo_new", userdao);
if(userlist != null && userlist.size() != 0) {
row.setItemValue("usrename", userlist.get(0).getPsn_name());
row.setItemValue("areano", userlist.get(0).getAreano());
row.setItemValue("unitname", userlist.get(0).getUnitname());
}
row.setItemValue("taskname", map.get("taskname"));
row.setItemValue("endTime", map.get("endTime"));
row.setItemValue("suggestion", map.get("suggestion"));
}
}
ds.setRecordCount(ds.getRowset().getPrimary().size());
newdc.addStore(ds);
DBUtil.EndTrans();
return newdc;
getTasks() 查Activiti的历史任务表,拿到这个流程实例的所有历史任务。然后逐条查用户信息,填充姓名和部门。
前端消费
onlyFrom.jsp 中的 search()
javascript
function search() {
var URL = webRootDir
+ "/BusinessAction?Business=flow&Action=allbytaskid"
+ "&taskid=<%=taskid%>&insid=<%=insid%>&formid=<%=sqlId%>";
var data = { url:URL, sync:false, load:function (dc1) {
seachGrid();
// 1. 表单数据
var ds = dc1.getDataStore("common");
if(!ds) ds = new browise.ds.DataStore();
var row = ds.getRowSet().getRow(0);
if(!row) row = ds.getRowSet().addRow();
// 绑定到表单控件
var from01 = browise.byId("...");
from01.getBinding().bind(row);
// 2. 流程进度
setmx(dc1.getParameter("lcmx"));
// 3. 字段权限
setrolebydata(dc1.getDataStore("frole"));
// 4. 会签意见
sethqyj(dc1);
// 5. 下一步选项
setNext(dc1.getDataStore("next"));
// 6. 审批历史
var ds1 = dc1.getDataStore("tasks");
browise.byId("grid_spyj").getBinding().setDataStore(ds1);
// 7. 表单插件钩子
try { if(searchothers) searchothers(); } catch(E) { }
// 8. 工作时间校验
try { QueryTime(); } catch(E) { }
}};
browise.Action.requestData(data, dc, true);
}
一次请求,8个步骤顺序处理,6组数据全部到位。
性能对比
| 方案 | 网络请求 | 预估耗时(内网) |
|---|---|---|
| 6次独立请求 | 6次 | 600-1800ms |
| allbytaskid | 1次 | 100-300ms |
而且 allbytaskid 内部在一个数据库事务里执行6个查询(DBUtil.BeginTrans / DBUtil.EndTrans),避免了6次事务开启/提交的开销。
为什么这样做
为什么不用6个独立接口?
能做。但内网环境慢,6次网络往返的延迟用户能感觉到。而且6个接口的调用顺序有依赖------先拿表单数据才能处理字段权限,先拿会签状态才能决定是否禁用提交按钮。串行6次比并行1次慢得多。
为什么不用GraphQL?
框架没用任何现代前端框架。前端是Dojo的RIA组件库,后端是自研的Servlet+BeanFactory。没有Node.js、没有GraphQL Runtime。用一个Servlet接口返回一个包含多个DataStore的DataCenter对象,在框架自己的体系内就是最高效的做法。
这不就是BFF吗?
是的。后来在前端圈这叫BFF(Backend For Frontend)。我当时不知道这个词,只知道一次拿完比请求6次快。务实主义不需要先知道术语再做事。
决策原则
减少网络往返比优化单个查询更重要。
6个查询合并成1个请求,网络延迟降了一个量级。单个查询再怎么优化SQL,也省不出6次网络往返的时间。先把"胖"做对,再做"瘦"。
你的项目里表单加载是怎么做的?是多个接口还是一个聚合接口?欢迎评论区聊聊。
系列导航:
- 回到总纲:政务工作流实战(二):BPMN画不出来的七个设计决策
- 上一篇:一人多岗+审批顺序可调
- 下一篇:(无,系列完)
作者:许彰午 | 非科班野生程序员,深耕政务信息化20年
标签: #Java #Activiti #工作流 #BFF #DataStore #政务信息化