1. 前言

 開發過程中,一些集合 的變動會觸發任務去改變 其他的集合,為了保障任務的正確執行,應避免出現死迴圈呼叫,即對集合之間的影響關係進行一些限制。怕日後遺忘,特在此記錄。

2. 場景

  • A 集合影響 A 集合。

  • A 集合影響 B 集合,B 集合影響了 A 集合。

  • A 集合影響 B 集合,B 集合影響了 C 集合,C 集合影響了 A 集合。

  • A 集合影響 B 集合、C 集合,B 集合影響了 D 集合,C 集合影響了 E 集合,E 集合影響 A 集合。

3. 環境

3.1 開發環境準備

  • JDK 1.8
  • SpringBoot 2.x
  • Mysql 8
  • redis

3.2 資料準備

3.2.1 Mysql資料庫表及資料

dp_process表

CREATE TABLE `dp_process` (
`ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
`NAME` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '名稱',
`CODE` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '程式碼',
`CATEGORY` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '型別 1=樓宇,2=房地產',
`IN_COLS` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '輸入集合',
`OUT_COLS` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '影響集合',
`REMARK` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '備註',
`ENABLED` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '是否開啟',
`STATUS` int DEFAULT NULL COMMENT '狀態 資料狀態:0=正常,1=刪除,失效',
`CREATED_BY` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '建立人',
`CREATED_TIME` datetime DEFAULT NULL COMMENT '建立時間',
`UPDATED_BY` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人',
`UPDATED_TIME` datetime DEFAULT NULL COMMENT '更新時間',
`REVISION` int DEFAULT '0' COMMENT '樂觀鎖',
PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='資料處理 ';

dp_process 表中的資料

INSERT INTO `gccs`.`dp_process`(`ID`, `NAME`, `CODE`, `CATEGORY`, `IN_COLS`, `OUT_COLS`, `REMARK`, `ENABLED`, `STATUS`, `CREATED_BY`, `CREATED_TIME`, `UPDATED_BY`, `UPDATED_TIME`, `REVISION`) VALUES ('1', 'B', 'B', 'ly', 'A', 'B', 'B', '1', 0, NULL, NULL, NULL, NULL, 0);
INSERT INTO `gccs`.`dp_process`(`ID`, `NAME`, `CODE`, `CATEGORY`, `IN_COLS`, `OUT_COLS`, `REMARK`, `ENABLED`, `STATUS`, `CREATED_BY`, `CREATED_TIME`, `UPDATED_BY`, `UPDATED_TIME`, `REVISION`) VALUES ('2', 'D', 'D', 'ly', 'B', 'D', 'D', '1', 0, NULL, NULL, NULL, NULL, 0);
INSERT INTO `gccs`.`dp_process`(`ID`, `NAME`, `CODE`, `CATEGORY`, `IN_COLS`, `OUT_COLS`, `REMARK`, `ENABLED`, `STATUS`, `CREATED_BY`, `CREATED_TIME`, `UPDATED_BY`, `UPDATED_TIME`, `REVISION`) VALUES ('3', 'E', 'E', 'ly', 'B', 'E', 'E', '1', 0, NULL, NULL, NULL, NULL, 0);
INSERT INTO `gccs`.`dp_process`(`ID`, `NAME`, `CODE`, `CATEGORY`, `IN_COLS`, `OUT_COLS`, `REMARK`, `ENABLED`, `STATUS`, `CREATED_BY`, `CREATED_TIME`, `UPDATED_BY`, `UPDATED_TIME`, `REVISION`) VALUES ('4', 'G', 'G', 'ly', 'D', 'G', 'G', '1', 0, NULL, NULL, NULL, NULL, 0);
INSERT INTO `gccs`.`dp_process`(`ID`, `NAME`, `CODE`, `CATEGORY`, `IN_COLS`, `OUT_COLS`, `REMARK`, `ENABLED`, `STATUS`, `CREATED_BY`, `CREATED_TIME`, `UPDATED_BY`, `UPDATED_TIME`, `REVISION`) VALUES ('5', 'F', 'F', 'ly', 'D', 'F', 'F', '1', 0, NULL, NULL, NULL, NULL, 0);

3.2.2 redis庫資料

key Value
A [{ "id": "1","outCols": "B"}]
B [{"id": "2","outCols": "D"},{"id": "3","outCols": "E"}]
D [{"id": "4","outCols": "G"},{"id": "5","outCols": "F"}]

4. 解決方式

通過遞迴的方式迴圈查詢、對比。

 本例主要牽扯到的知識點有:

  • Stack (棧,先進後出)
  • 遞迴
  • redis簡單增刪操作

 本文以修改方法程式碼為例,介紹如何實現防死鏈呼叫,非常簡單。

	/**
* @create 2021-07-08 更新 資料處理
* @param dpProcess 資料處理 模型
* @param updateNil 全欄位更新(新增時此欄位可以忽略): 是:Y 否:N {@link SystemConst.Whether}
* @return
*/
@Override
public int modify(DpProcess dpProcess, String updateNil){ // **省略一堆程式碼** // 輸入集合統一處理
operInclos(dpProcess, orignDpProcess.getInCols()); // **省略一堆程式碼**
}

 operInclos() 方法 :本文重點,主要做了資料校驗、redis中資料更新等一系列操作

	/**
* @create 輸入集合統一處理 2021/7/11 14:13
* @param dpProcess 新資料處理物件
* @param oldClos 原資料處理物件中的輸入集合
* @return
*/
private void operInclos(DpProcess dpProcess, String oldClos) {
// 新資料處理物件中的輸入集合
String inCols = dpProcess.getInCols(); // 若新資料處理物件中的輸入集合沒有值,則直接跳過,不進行操作
if(StringUtils.isNotBlank(inCols)){
if(dpProcess.getInCols().contains(dpProcess.getOutCols())){
throw new ServiceException("資料處理流程配置輸入流程呼叫了輸出集合!");
} // 資料型別轉換
Set<String> set = new HashSet(Arrays.asList(inCols.split(","))); // 迴圈遍歷輸入集合
for (String inClo : set) { // 最終需要遍歷的list
List<DpProcessVo> childFinalList = new ArrayList<>(); // 從redis中獲取當前集合的影響關係
String dpProcessJson = (String) redisUtil.get(inClo); // 如果redis中儲存的集合影響關係不為空,做簡單的遍歷去重處理
if(StringUtils.isNotBlank(dpProcessJson)){ // redis中儲存的集合影響關係列表
List<DpProcessVo> children = new ArrayList<>(); // 進行資料型別轉換
children = JSONArray.parseArray(dpProcessJson, DpProcessVo.class);
for (DpProcessVo dpProcessVo1 : children) {
if(dpProcess.getId().equals(dpProcessVo1.getId())){
continue;
}
childFinalList.add(dpProcessVo1);
}
// 新增本次影響的集合
DpProcessVo dpProcess1 = new DpProcessVo();
dpProcess1.setId(dpProcess.getId());
dpProcess1.setOutCols(dpProcess.getOutCols());
childFinalList.add(dpProcess1);
}
// 如果redis中沒有此輸入集合的影響關係,則可以直接進行新增
else{
DpProcessVo dpProcess1 = new DpProcessVo();
dpProcess1.setId(dpProcess.getId());
dpProcess1.setOutCols(dpProcess.getOutCols());
childFinalList.add(dpProcess1);
} // 驗證資料處理流程配置輸入流程是否呼叫了輸出集合
Stack<DpProcessVo> nodeStack = new Stack<>();
// 設定模型
DpProcessVo dpProcessVoTop = new DpProcessVo();
dpProcessVoTop.setOutCols(inClo);
dpProcessVoTop.setId(dpProcess.getId());
nodeStack.add(dpProcessVoTop); // 遍歷需要進行死鏈校驗的資料
for (DpProcessVo dpProcessVo : childFinalList) { // 是否新增標識(預設為新增,如果集合為死鏈,則進行提示)
boolean addFlag = true; // 迴圈遍歷棧
for (DpProcessVo processVo : nodeStack) {
if(processVo.getOutCols().equals(dpProcessVo.getOutCols())){
addFlag = false;
break;
}
}
if(!addFlag){ throw new ServiceException("資料處理流程配置輸入流程呼叫了輸出集合!");
}
// 將dpProcessVo推到這個堆疊的頂部
nodeStack.push(dpProcessVo); // 驗證資料處理流程配置輸入流程是否呼叫了輸出集合
invaldClosInfo(nodeStack); // 移除此堆疊頂部的物件並將該物件作為此函式的值返回
nodeStack.pop(); }
} // 處理需要刪除的集合
dealNeedDeleteCols(dpProcess, oldClos, set); // 獲取並設定最終的集合名稱
String finallyCols = StringUtils.join(set.toArray(), ",");
dpProcess.setInCols(finallyCols); // 省略一堆更新redis的操作
}
}

 invaldClosInfo()方法: 遞迴深度遍歷

/**
* @create 驗證資料處理流程配置輸入流程是否呼叫了輸出集合 2021/7/20 22:10
* @param nodeStack 深度遍歷棧
* @return void
*/
public void invaldClosInfo(Stack<DpProcessVo> nodeStack) { // 檢視此堆疊頂部的物件而不將其從堆疊中移除
DpProcessVo dpProcessVo = nodeStack.peek(); // 從redis中查詢此集合影響的流程關係
String dpProcessJson = (String) redisUtil.get(dpProcessVo.getOutCols());
// 如果集合沒有影響其他集合,則直接返回
if(StringUtils.isBlank(dpProcessJson)){
return;
} //獲得節點的子節點,對於二叉樹就是獲得節點的左子結點和右子節點
List<DpProcessVo> children = new ArrayList<>();
// redis中原來儲存的資訊
children = JSONArray.parseArray(dpProcessJson, DpProcessVo.class); // 遍歷集合影響的集合關係
for (DpProcessVo dpProcessVo1 : children) {
boolean addFlag = true;
for (DpProcessVo processVo : nodeStack) {
if(processVo.getOutCols().equals(dpProcessVo1.getOutCols())){
addFlag = false;
break;
}
}
if(!addFlag){ throw new ServiceException("資料處理流程配置輸入流程呼叫了輸出集合!");
} // 將dpProcessVo推到這個堆疊的頂部
nodeStack.push(dpProcessVo1); // 驗證資料處理流程配置輸入流程是否呼叫了輸出集合
invaldClosInfo(nodeStack); // 移除此堆疊頂部的物件並將該物件作為此函式的值返回
nodeStack.pop();
}
}

5.完整程式碼

 記錄程式碼,方便日後複習、呼叫、重構。

5.1Model

 模型主要分兩部分:資料處理模型和簡化版的資料處理模型。

 DpProcess:資料處理模型,資料完整的Sql操作


import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import java.io.Serializable;
import java.util.Date; /**
* <p>
* 資料處理
* </p>
*
* @since 2021-07-08
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="DpProcess物件", description="資料處理 ")
@TableName("dp_process")
public class DpProcess implements Serializable { @TableField(exist = false)
public static final String ENABLED = "ENABLED"; @TableField(exist = false)
public static final String STATUS = "STATUS"; @TableField(exist = false)
public static final String CATEGORY = "CATEGORY"; private static final long serialVersionUID = 1L; @ApiModelProperty(value = "ID")
@TableId(value = "ID", type = IdType.ASSIGN_ID)
private String id; @ApiModelProperty(value = "名稱")
@TableField("NAME")
private String name; @ApiModelProperty(value = "程式碼")
@TableField("CODE")
private String code; @ApiModelProperty(value = "型別 1=樓宇,2=房地產")
@TableField("CATEGORY")
private String category; @ApiModelProperty(value = "輸入集合")
@TableField("IN_COLS")
private String inCols; @ApiModelProperty(value = "影響集合")
@TableField("OUT_COLS")
private String outCols; @ApiModelProperty(value = "備註")
@TableField("REMARK")
private String remark; @ApiModelProperty(value = "是否開啟 0:否 1:是")
@TableField("ENABLED")
private String enabled; @ApiModelProperty(value = "狀態 資料狀態:0=正常,1=刪除,失效")
@TableField(value = "STATUS", fill = FieldFill.INSERT)
private Integer status; @ApiModelProperty(value = "建立人")
@TableField(value = "CREATED_BY", fill = FieldFill.INSERT)
private String createdBy; @ApiModelProperty(value = "建立時間")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
@TableField(value = "CREATED_TIME", fill = FieldFill.INSERT)
private Date createdTime; @ApiModelProperty(value = "更新人")
@TableField(value = "UPDATED_BY", fill = FieldFill.UPDATE)
private String updatedBy; @ApiModelProperty(value = "更新時間")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
@TableField(value = "UPDATED_TIME", fill = FieldFill.UPDATE)
private Date updatedTime; @ApiModelProperty(value = "樂觀鎖")
@Version
@TableField(value = "REVISION", fill = FieldFill.INSERT)
private Integer revision; }

DpProcessVo: 資料處理簡單模型,處理redis資料結構資料。


import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; @Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="DpProcessVo物件", description="資料處理簡單模型 ")
public class DpProcessVo{ @ApiModelProperty(value = "ID")
private String id; @ApiModelProperty(value = "影響集合")
private String outCols; }

5.2 Controller

 updateNil:讓使用者選擇使用那種更新方式,也可以把介面一拆為二,主要看個人習慣。

/**
* @create 2021-07-08 更新 資料處理
* @param dpProcess 資料處理 模型
* @param updateNil 全欄位更新(新增時此欄位可以忽略): 是:Y 否:N {@link SystemConst.Whether}
* @return
*/
@ApiOperation(value="更新",notes = "更新")
@PostMapping("/modify")
public Result modify(
@ApiParam(name = "dpProcess", value = "資料處理 模型", required = true) @RequestBody DpProcess dpProcess,
@ApiParam(name = "updateNil", value = "全欄位更新(新增時此欄位可以忽略): 是:Y 否:不傳或者隨意傳") @RequestParam(required = false) String updateNil) {
int addResult = dpProcessService.modify(dpProcess, updateNil);
if (addResult > 0) {
return new Result(CommonCode.SUCCESS, "更新成功!");
}
return new Result(CommonCode.FAIL, "更新失敗!");
}

5.3 Service

 沒啥好說的,就是一個介面。

    /**
* @create 2021-07-08 更新 資料處理
* @param dpProcess 資料處理 模型
* @param updateNil 全欄位更新(新增時此欄位可以忽略): 是:Y 否:N {@link SystemConst.Whether}
* @return
*/
int modify(DpProcess dpProcess, String updateNil);

5.4 Service 實現類

 DpRecord:資料處理記錄,不是本文重點,此處可直接忽略,相關說明 待 資料流程處理文章中提現。

/**
* @create 2021-07-08 更新 資料處理
* @param dpProcess 資料處理 模型
* @param updateNil 全欄位更新(新增時此欄位可以忽略): 是:Y 否:N {@link SystemConst.Whether}
* @return
*/
@Override
public int modify(DpProcess dpProcess, String updateNil){
if(dpProcess == null){
throw new ServiceException("資料處理模型不能為空!");
}
// 走更新方法
// 通過id查詢資料處理 詳情
DpProcess orignDpProcess = this.detail(dpProcess.getId());
if(dpProcess == null){
throw new ServiceException("資料處理模型資訊不能為空!");
} // 如果當前任務已存在,需要先進行取消
if("0".equals(dpProcess.getEnabled())){
if(defaultSchedulingConfigurer.hasTask(dpProcess.getId())){
defaultSchedulingConfigurer.cancelTriggerTask(dpProcess.getId());
}
// 根據資料處理ID檢視資料庫中是否有需要執行的資料處理記錄
DpRecord dpRecord = dpRecordService.getNeedExecRecordByDppId(dpProcess.getId());
// 如果資料處理記錄資訊為空,則進行新增
if(dpRecord != null){
// 設定結束時間為當前時間
dpRecord.setEndTime(new Date());
// 執行失敗
dpRecord.setSucceed("2");
dpRecord.setFailedResult("使用者取消操作");
}
// 對資料處理記錄進行更新或者儲存
dpRecordService.addOrUpdate(dpRecord, null);
} // 限制輸出集合不能為空
dpProcess.setOutCols(StringUtils.isNotBlank(dpProcess.getOutCols()) ? dpProcess.getOutCols() : orignDpProcess.getOutCols());
if(StringUtils.isBlank(dpProcess.getOutCols())){
throw new ServiceException("資料影響集合不能為空!");
} // 輸入集合統一處理
operInclos(dpProcess, orignDpProcess.getInCols()); // 全欄位更新
if(SystemConst.Whether.Yes.getCode().equals(updateNil)){
if(StringUtils.isBlank(dpProcess.getRemark())){
throw new ServiceException("資料處理備註不能為空!");
}
// 備註不能小於20字
if(dpProcess.getRemark().length() < 20){
throw new ServiceException("資料處理備註不能小於20字!");
}
return dpProcessMapper.alwaysUpdateSomeColumnById(dpProcess);
}
// 資料處理程式碼自動填充
dpProcess.setCode(StringUtils.isBlank(dpProcess.getCode()) ? orignDpProcess.getCode() : dpProcess.getCode()); return dpProcessMapper.updateById(dpProcess);
}

operInclos() : 處理輸入集合的方法

/**
* @create 輸入集合統一處理 2021/7/11 14:13
* @param dpProcess 新資料處理物件
* @param oldClos 原資料處理物件中的而輸入集合
* @return
*/
private void operInclos(DpProcess dpProcess, String oldClos) {
// 新資料處理物件中的輸入集合
String inCols = dpProcess.getInCols(); // 若新資料處理物件中的輸入集合沒有值,則直接跳過,不進行操作
if(StringUtils.isNotBlank(inCols)){
if(dpProcess.getInCols().contains(dpProcess.getOutCols())){
throw new ServiceException("資料處理流程配置輸入流程呼叫了輸出集合!");
} // 資料型別轉換
Set<String> set = new HashSet(Arrays.asList(inCols.split(","))); // 迴圈遍歷輸入集合
for (String inClo : set) { // 最終需要遍歷的list
List<DpProcessVo> childFinalList = new ArrayList<>(); // 從redis中獲取當前集合的影響關係
String dpProcessJson = (String) redisUtil.get(inClo); // 如果redis中儲存的集合影響關係不為空,做簡單的遍歷去重處理
if(StringUtils.isNotBlank(dpProcessJson)){ // redis中儲存的集合影響關係列表
List<DpProcessVo> children = new ArrayList<>(); // 進行資料型別轉換
children = JSONArray.parseArray(dpProcessJson, DpProcessVo.class);
for (DpProcessVo dpProcessVo1 : children) {
if(dpProcess.getId().equals(dpProcessVo1.getId())){
continue;
}
childFinalList.add(dpProcessVo1);
}
// 新增本次影響的集合
DpProcessVo dpProcess1 = new DpProcessVo();
dpProcess1.setId(dpProcess.getId());
dpProcess1.setOutCols(dpProcess.getOutCols());
childFinalList.add(dpProcess1);
}
// 如果redis中沒有此輸入集合的影響關係,則可以直接進行新增
else{
DpProcessVo dpProcess1 = new DpProcessVo();
dpProcess1.setId(dpProcess.getId());
dpProcess1.setOutCols(dpProcess.getOutCols());
childFinalList.add(dpProcess1);
} // 驗證資料處理流程配置輸入流程是否呼叫了輸出集合
Stack<DpProcessVo> nodeStack = new Stack<>();
// 設定模型
DpProcessVo dpProcessVoTop = new DpProcessVo();
dpProcessVoTop.setOutCols(inClo);
dpProcessVoTop.setId(dpProcess.getId());
nodeStack.add(dpProcessVoTop); // 遍歷需要進行死鏈校驗的資料
for (DpProcessVo dpProcessVo : childFinalList) { // 是否新增標識(預設為新增,如果集合為死鏈,則進行提示)
boolean addFlag = true; // 迴圈遍歷棧
for (DpProcessVo processVo : nodeStack) {
if(processVo.getOutCols().equals(dpProcessVo.getOutCols())){
addFlag = false;
break;
}
}
if(!addFlag){ throw new ServiceException("資料處理流程配置輸入流程呼叫了輸出集合!");
}
// 將dpProcessVo推到這個堆疊的頂部
nodeStack.push(dpProcessVo); // 驗證資料處理流程配置輸入流程是否呼叫了輸出集合
invaldClosInfo(nodeStack); // 移除此堆疊頂部的物件並將該物件作為此函式的值返回
nodeStack.pop(); }
} // 處理需要刪除的集合
dealNeedDeleteCols(dpProcess, oldClos, set); // 獲取並設定最終的集合名稱
String finallyCols = StringUtils.join(set.toArray(), ",");
dpProcess.setInCols(finallyCols); // 能走到這一步,說明所有的集合沒有問題,可以進行更新操作了(再一次遍歷是為了和上面的校驗分開,避免部分資料被更新)
for (String inClo : set) { List<DpProcessVo> dpProcessVoList = new ArrayList<>();
// 首先獲取當前集合影響的資料處理物件
String dpProcessJson = (String) redisUtil.get(inClo);
if(StringUtils.isBlank(dpProcessJson)){
DpProcessVo dpProcessVo = new DpProcessVo();
dpProcessVo.setId(dpProcess.getId());
dpProcessVo.setOutCols(dpProcess.getOutCols());
dpProcessVoList.add(dpProcessVo);
// 進行資料的儲存
redisUtil.set(inClo, JSONArray.toJSON(dpProcessVoList).toString());
continue;
} // redis中原來儲存的資訊
List<DpProcessVo> dpProcessVos = JSONArray.parseArray(dpProcessJson, DpProcessVo.class); // 把資料處理物件轉換為HashSet
HashSet<DpProcessVo> hashSet = new HashSet(dpProcessVos);
// 當前集合影響的 其他集合列表
List<DpProcessVo> childFinalList = new ArrayList<>(); // 遍歷redis中儲存的集合影響關係,並進行簡單去重處理
for (DpProcessVo dpProcessVo : hashSet) {
if(dpProcessVo.getId().equals(dpProcess.getId())){
continue;
}
childFinalList.add(dpProcessVo);
} // 新增上本次影響的集合
DpProcessVo dpProcessVo = new DpProcessVo();
dpProcessVo.setId(dpProcess.getId());
dpProcessVo.setOutCols(dpProcess.getOutCols());
// 添加當前資料資料物件
childFinalList.add(dpProcessVo);
// 進行資料的儲存
redisUtil.set(inClo, JSONArray.toJSON(childFinalList).toString());
}
}
}

invaldClosInfo() : 驗證資料處理流程配置輸入流程是否呼叫了輸出集合

/**
* @create 驗證資料處理流程配置輸入流程是否呼叫了輸出集合 2021/7/20 22:10
* @param nodeStack 深度遍歷棧
* @return void
*/
public void invaldClosInfo(Stack<DpProcessVo> nodeStack) { // 檢視此堆疊頂部的物件而不將其從堆疊中移除
DpProcessVo dpProcessVo = nodeStack.peek(); // 從redis中查詢此集合影響的流程關係
String dpProcessJson = (String) redisUtil.get(dpProcessVo.getOutCols());
// 如果集合沒有影響其他集合,則直接返回
if(StringUtils.isBlank(dpProcessJson)){
return;
} //獲得節點的子節點,對於二叉樹就是獲得節點的左子結點和右子節點
List<DpProcessVo> children = new ArrayList<>();
// redis中原來儲存的資訊
children = JSONArray.parseArray(dpProcessJson, DpProcessVo.class); // 遍歷集合影響的集合關係
for (DpProcessVo dpProcessVo1 : children) {
boolean addFlag = true;
for (DpProcessVo processVo : nodeStack) {
if(processVo.getOutCols().equals(dpProcessVo1.getOutCols())){
addFlag = false;
break;
}
}
if(!addFlag){ throw new ServiceException("資料處理流程配置輸入流程呼叫了輸出集合!");
} // 將dpProcessVo推到這個堆疊的頂部
nodeStack.push(dpProcessVo1); // 驗證資料處理流程配置輸入流程是否呼叫了輸出集合
invaldClosInfo(nodeStack); // 移除此堆疊頂部的物件並將該物件作為此函式的值返回
nodeStack.pop();
}
}

dealNeedDeleteCols() : 主要處理--原資料為 A 集合影響 B 集合,修改為 C 集合影響了 B 集合,此時需要刪除 A 對 B的影響關係

/**
* @create 處理需要刪除的集合 2021/7/20 17:58
* @param dpProcess 資料處理模型
* @param oldClos 原來的資料處理模型中的集合資訊
* @param set 最新的集合名稱資訊
* @return void
*/
private void dealNeedDeleteCols(DpProcess dpProcess, String oldClos, Set<String> set) { if(StringUtils.isBlank(oldClos)){
return;
}
// 獲取去重後的集合陣列
List<String> newColsList = new ArrayList<>(set); // 原來的集合陣列
List<String> oldColsList = Arrays.asList(oldClos.split(",")); // 獲取兩個集合的差集
List<String> reduceList = oldColsList.stream().filter(item -> !newColsList.contains(item)).collect(toList());
if(reduceList == null || reduceList.size() == 0){
return;
}
for (String clos : reduceList) {
// 獲取redis中的集合
String dpProcessJson = (String) redisUtil.get(clos);
if(StringUtils.isBlank(dpProcessJson)){
continue;
}
// redis中原來儲存的資訊
List<DpProcessVo> dpProcessVos = JSONArray.parseArray(dpProcessJson, DpProcessVo.class);
// 遍歷刪除的集合中影響的流程ID
HashSet<DpProcessVo> hashSet = new HashSet(dpProcessVos);
Iterator<DpProcessVo> it = hashSet.iterator();
while(it.hasNext()){
DpProcessVo dpProcessVo = it.next();
if(dpProcessVo.getId().equals(dpProcess.getId())){
it.remove();
}
}
// 如果當前集合影響的流程為空,則進行刪除
if(hashSet.isEmpty()){
// 進行資料的儲存
redisUtil.delete(clos);
continue;
}
// 進行資料的儲存
redisUtil.set(clos, JSONArray.toJSON(hashSet.toArray()).toString());
} }

6.測試

 可通過單元測試等多種方式,本文提供簡單的測試資料。

{
"category": "ly",
"code": "F",
"createdBy": "",
"createdTime": null,
"enabled": "1",
"id": "5",
"inCols": "D",
"name": "F",
"outCols": "L",
"remark": "F",
"revision": 0,
"status": 0,
"updatedBy": "",
"updatedTime": null
}

7.總結

 僅對今日工作進行簡單記錄,程式碼還需進一步重構,記錄永不止步。