在实验阶段,我们定义了三个结构,分别是流程图结构,流程节点结构和流程边结构。这三个结构构造的流程在行为上,通过测试,可以得出基本是可以满足流程的基本需求的。那么,我们就可以开始进入实际的开发工作中,来完善这一抽象的实现。
如何与业务耦合
完善抽象首先需要考虑的点是如何与业务耦合,即,这样的数据结构,我们该应用到业务场景中呢?通用流程的抽象我们已经做完了,现在就需要让这个抽象跟业务耦合。
我首先想到了两种思路:
- 在业务表里面添加控制字段,用来标识该业务进展到了哪个阶段
- 建一个单独的表来存储流程流转的历史
首先,第一种思路存在的问题是,无法知道历史的流转,如果同一个业务行参与了不同的流程,那么很难通过建字段的方式来维护不同的流程。第二种的思路,每次都需要从整个流转里面获取当前业务在流程中的最新情况,实际上是比较消耗性能的。所以综合两种思路,最终选择采取建一个历史流转表存储流转情况,再建一个业务当前阶段表来存储当前阶段,从而作为缓存而存在。
需要建立哪些数据库表
也就是说,我们现在一共需要五个表
- 流程表
- 流程节点表
- 流程边表
- 流程历史流转表
- 业务流程当前历史节点表
流程表的基础结构如下:
CREATE TABLE IF NOT EXISTS process_tree
(
id bigint PRIMARY KEY DEFAULT random_id(), --记录标识
name varchar(64) NOT NULL DEFAULT '--', --流程名
desc text NOT NULL DEFAULT '0', --流程描述
);
流程节点表的基础结构如下:
CREATE TABLE IF NOT EXISTS process_node
(
id bigint PRIMARY KEY DEFAULT random_id(), --记录标识
process_id bigint NOT NULL DEFAULT '0', --流程id
node_name varchar(64) NOT NULL DEFAULT '--', --节点名
node_type text NOT NULL DEFAULT '0', --节点类型
process_start boolean NOT NULL DEFAULT 'F', --开始节点
process_end boolean NOT NULL DEFAULT 'F', --结束节点
);
流程边表的基础结构如下:
CREATE TABLE IF NOT EXISTS process_side
(
id bigint PRIMARY KEY DEFAULT random_id(), --记录标识
desc text NOT NULL DEFAULT '--', --边描述
process_id bigint NOT NULL DEFAULT '0', --流程id
from_node_id bigint NOT NULL DEFAULT '0', --出度节点
to_node_id bigint NOT NULL DEFAULT '0', --入度节点
side_type varchar(64) NOT NULL DEFAULT '--', --边类型
);
业务在流程中的发展历史表:
CREATE TABLE IF NOT EXISTS process_dev_history
(
id bigint PRIMARY KEY DEFAULT random_id(), --记录标识
business_id bigint NOT NULL DEFAULT '0', --业务id
process_id bigint NOT NULL DEFAULT '0', --流程id
cur_node_id bigint NOT NULL DEFAULT '0', --当前节点id
cur_node_sta integer NOT NULL DEFAULT '0', --当前节点状态
from_node_id bigint NOT NULL DEFAULT '0', --出度节点(如果有)
to_node_id bigint NOT NULL DEFAULT '0', --入度节点(如果有)
side_id bigint NOT NULL DEFAULT '0', --边id
father_dev_id bigint NOT NULL DEFAULT '0', --从哪个id传过来的
begin_time bigint NOT NULL DEFAULT '0', --开始时间
update_time bigint NOT NULL DEFAULT '0', --更新时间
end_time bigint NOT NULL DEFAULT '0', --结束时间
);
业务的当前历史节点表:
CREATE TABLE IF NOT EXISTS process_business_cursta
(
id bigint PRIMARY KEY DEFAULT random_id(), --记录标识
process_name varchar(64) NOT NULL DEFAULT '--', --流程名
business_id bigint NOT NULL DEFAULT '0', --业务id
cur_history_id varchar(1000) NOT NULL DEFAULT '0', --当前历史id列表
);
需要实现的基础逻辑
有了数据表和生成的流程结构,现在我们需要考虑如何将业务串起来。即,我们需要提供哪些方法?
下面我列出了最基础的方法。
- 获取流程的当前节点情况
- 判断某流程的当前节点是否可以流转到下一个节点
- 获取整个流程的结构
- 获取当前流程已经走过的节点生成树
- 进入到流程
- 流程转换
- 获取某个节点的所有下级节点
- 修改当前流程点
- 获取某个历史节点的所有下级节点的id
上层要支撑起基本的创建业务流程,业务流程的流转,获取业务流程的状态等等工作,所以在真正实施的过程中还需要根据相应的场景去新增基础方法。
下面,给出目前已知需要实现方法的实现代码:
- 获取流程的当前节点情况
@Override
public List<ProcessDevHistory> getCurDevNode(String processName, Long businessId) {
String sql = "select b.*\n" +
"from process_business_cursta as a\n" +
" left join process_dev_history as b on string_to_array(b.id::varchar,',') <@ string_to_array(a.cur_history_id,',')\n" +
"where a.process_name = :process_name\n" +
" and a.business_id = :business_id;";
Map<String, Object> params = new HashMap<>();
params.put("process_name", processName);
params.put("business_id", businessId);
List<ProcessDevHistory> processDevHistories = new ArrayList<>();
List<Map<String,Object>> tmp = namedParameterJdbcTemplate.queryForList(sql, params);
for (Map<String,Object> tmpA : tmp){
processDevHistories.add(Json.toObject(Json.toJson(tmpA),ProcessDevHistory.class));
}
return processDevHistories;
}
- 判断某流程的当前节点是否可以流转到下一个节点
@Override
public Boolean checkCirculation(Long historyId) {
ProcessDevHistory processDevHistory = processDevHistoryRepository.findProcessDevHistoryById(historyId);
if (Objects.nonNull(processDevHistory)) {
// TODO: 2022/1/10 检查当前节点的情况,是否结束,只有上一个状态结束了才能接着往下走
if (processDevHistory.getCurNodeSta().equals(2)) {
return true;
}
}
return false;
}
@Override
public Boolean checkCirculation(String processName, Long business) {
List<ProcessDevHistory> processDevHistories = getCurDevNode(processName, business);
int flag = 0;
for (ProcessDevHistory processDevHistory : processDevHistories){
if (Objects.nonNull(processDevHistory)) {
// TODO: 2022/1/10 检查当前节点的情况,是否结束,只有上一个状态结束了才能接着往下走
if (!processDevHistory.getCurNodeSta().equals(2)) {
flag = 1;
break;
}
}
}
if (flag == 0){
return true;
}
return false;
}
- 获取整个流程的结构
@Override
public CcsProcessTree getTotalProcessTree(String processName) {
String sql = "select as name,tree.description as description, node.*\n" +
"from process_tree as tree\n" +
" left join process_node as node on tree.id = node.process_id\n" +
"where = :name;";
Map<String, Object> params = new HashMap<>();
params.put("name", processName);
List<Map<String, Object>> nodes = namedParameterJdbcTemplate.queryForList(sql, params);
String sql1 = "select side.*\n" +
"from process_tree as tree left join process_side as side on tree.id = side.process_id\n" +
"where = :name;";
List<Map<String, Object>> sides = namedParameterJdbcTemplate.queryForList(sql1, params);
CcsProcessTree ccsProcessTree = GenerateProcessUtil.generateTree(nodes, sides);
return ccsProcessTree;
}
- 获取当前流程已经走过的节点生成树
@Override
public CcsProcessTree getCurProcessTree(String processName, Long businessId) {
String sql = "select tre.description as description, his.*\n" +
"from process_dev_history as his\n" +
" left join process_tree as tre on his.process_id = tre.id\n" +
"where = :name\n" +
" and his.business_id = :business_id;";
Map<String, Object> params = new HashMap<>();
params.put("name", processName);
params.put("business_id", businessId);
List<Map<String, Object>> nodeSides = namedParameterJdbcTemplate.queryForList(sql, params);
String sql1 = "select as name,tree.description as description, node.*\n" +
"from process_tree as tree\n" +
" left join process_node as node on tree.id = node.process_id\n" +
"where = :name;";
List<Map<String, Object>> nodes = namedParameterJdbcTemplate.queryForList(sql1, params);
return GenerateProcessUtil.generateCurTree(nodeSides, nodes);
}
- 进入到流程
@Override
public Boolean beginProcess(String processName, Long businessId) {
String sql = "select node.*\n" +
"from process_tree as tree\n" +
" left join process_node as node on tree.id = node.process_id\n" +
"where = :name and node.process_start = true;";
Map<String, Object> params = new HashMap<>();
params.put("name", processName);
List<Map<String,Object>> tmp = namedParameterJdbcTemplate.queryForList(sql, params);
ProcessNode processNode = Json.toObject(Json.toJson(tmp.get(0)),ProcessNode.class);
if (Objects.nonNull(processNode)) {
ProcessDevHistory processDevHistory = new ProcessDevHistory();
Long time = DatetimeUtil.getTimestampOfDatetime(LocalDateTime.now());
processDevHistory.setBeginTime(time);
processDevHistory.setBusinessId(businessId);
processDevHistory.setCurNodeId(processNode.getId());
processDevHistory.setProcessId(processNode.getProcessId());
processDevHistory.setCurNodeSta(0);
processDevHistory.setUpdateTime(time);
Long hisId = processDevHistoryRepository.save(processDevHistory);
changeCurProcessHistoryIds(processName, businessId, hisId + "");
return true;
}
return false;
}
- 流程转换
@Override
public Boolean convertProcess(String processName, Long businessId, Long historyId) {
ProcessDevHistory processDevHistory = processDevHistoryRepository.findProcessDevHistoryById(historyId);
if (Objects.nonNull(processDevHistory)) {
// TODO: 2022/1/11 判断是否能转换
if (GenerateProcessNode.NodeState.getInfo(processDevHistory.getCurNodeSta()).equals("end")) {
// TODO: 2022/1/11 可以转换到下一步
// TODO: 2022/1/11 判断下一步是否存在,即父节点为自己的
ProcessDevHistory condition = new ProcessDevHistory();
condition.setFatherDevId(historyId);
List<ProcessDevHistory> processDevHistories = processDevHistoryRepository.findAllByCondition(condition);
if (processDevHistories.size() == 0) {
// TODO: 2022/1/11 下一步不存在,创建下一步
// TODO: 2022/1/11 需要解决下一步有多个的情况
List<ProcessSide> nextProcessSides = getAllChildrenProcessSide(processDevHistory.getCurNodeId());
// TODO: 2022/1/11 下一步可能有多个
Long time = DatetimeUtil.getTimestampOfDatetime(LocalDateTime.now());
List<ProcessDevHistory> processDevHistoryList = new ArrayList<>();
for (ProcessSide nextProcessSide : nextProcessSides) {
ProcessDevHistory devHistory = new ProcessDevHistory();
devHistory.setFatherDevId(historyId);
devHistory.setBeginTime(time);
devHistory.setUpdateTime(time);
devHistory.setProcessId(processDevHistory.getProcessId());
devHistory.setToNodeId(nextProcessSide.getToNodeId());
devHistory.setFromNodeId(processDevHistory.getCurNodeId());
devHistory.setSideId(nextProcessSide.getId());
devHistory.setBusinessId(businessId);
devHistory.setCurNodeId(nextProcessSide.getToNodeId());
devHistory.setCurNodeSta(0);
processDevHistoryList.add(devHistory);
}
processDevHistoryRepository.saveAll(processDevHistoryList);
changeCurProcessHistoryIds(processName, businessId, ArrayStrUtil.llist2Str(getChildrenHis(historyId), ","));
return true;
} else {
return false;
}
} else {
// TODO: 2022/1/11 不能转换到下一步
return false;
}
}
return false;
}
- 获取某个节点的所有下级节点
@Override
public List<ProcessSide> getAllChildrenProcessSide(Long processNodeId) {
ProcessSide processSideCondition = new ProcessSide();
processSideCondition.setFromNodeId(processNodeId);
return processSideRepository.findAllByCondition(processSideCondition);
}
- 修改当前流程点
@Override
public Boolean changeCurProcessHistoryIds(String processName, Long businessId, String hisIds) {
// TODO: 2022/1/11 修改当前流程表
ProcessBusinessCursta processBusinessCurstaCondition = new ProcessBusinessCursta();
processBusinessCurstaCondition.setBusinessId(businessId);
processBusinessCurstaCondition.setProcessName(processName);
ProcessBusinessCursta processBusinessCursta = findProcessBusinessCurstaByCondition(processBusinessCurstaCondition);
if (Objects.nonNull(processBusinessCursta)){
ProcessBusinessCursta processBusinessCursta1 = new ProcessBusinessCursta();
processBusinessCursta1.setCurHistoryId(hisIds);
update(processBusinessCurstaCondition, processBusinessCursta1);
}else {
processBusinessCurstaCondition.setCurHistoryId(hisIds);
save(processBusinessCurstaCondition);
}
return true;
}
- 获取某个历史节点的所有下级节点的id
@Override
public List<Long> getChildrenHis(Long historyId) {
ProcessDevHistory condition = new ProcessDevHistory();
condition.setFatherDevId(historyId);
List<ProcessDevHistory> processDevHistories = processDevHistoryRepository.findAllByCondition(condition);
List<Long> ans = new ArrayList<>();
for (ProcessDevHistory processDevHistory : processDevHistories) {
ans.add(processDevHistory.getId());
}
return ans;
}
有了这些基本的方法,就可以到项目中去实际拆分一个业务流程为业务+流程的方式,进行处理了。