From b15ec4aba95ed179f41bf339b3a9b6053e63bf87 Mon Sep 17 00:00:00 2001 From: chenchuchuan <13554600537@163.com> Date: Fri, 7 Aug 2026 17:12:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(online):=20=E4=BC=98=E5=8C=96=E5=9C=A8?= =?UTF-8?q?=E7=BA=BF=E8=A1=A8=E5=8D=95=E5=85=AC=E5=BC=8F=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E5=92=8C=E5=AD=97=E6=AE=B5=E5=90=8C=E6=AD=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将值改变事件重命名为值更新事件以提高语义清晰度 - 实现公式引用表单字段的解析和计算功能,支持表.表.字段格式引用 - 替换VALUE_CHANGE为DATA_VALUE_CHANGE属性枚举以统一数据变更处理 - 添加resolveFormulaField方法解析关联字段和分录字段的实际值 - 重构syncNewFieldsAndSubTables方法优化字段和子表同步逻辑 - 移除废弃的关联控件校验代码并简化数据验证流程 - 实现移动端子表组件的递归遍历和字段同步功能 - 添加逻辑删除标记和时间戳字段到表单字段创建过程 --- .../abstractplugin/MyInterfaceRulesExecute.java | 45 +- .../online/controller/OnlineFormController.java | 675 +++++++-------------- .../apelet/common/online/util/WidgetJsonUtil.java | 478 +++++++-------- 3 files changed, 449 insertions(+), 749 deletions(-) diff --git a/common/common-online/src/main/java/apelet/common/online/abstractplugin/MyInterfaceRulesExecute.java b/common/common-online/src/main/java/apelet/common/online/abstractplugin/MyInterfaceRulesExecute.java index 479d79d..f2c57bf 100644 --- a/common/common-online/src/main/java/apelet/common/online/abstractplugin/MyInterfaceRulesExecute.java +++ b/common/common-online/src/main/java/apelet/common/online/abstractplugin/MyInterfaceRulesExecute.java @@ -40,9 +40,9 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent { public void change(String widgetVariableName, ObjectValue objectValue) { Map values = objectValue.getValues(); List> conditional = (List) values.get("conditional"); - conditional("值改变", widgetVariableName, conditional, objectValue); + conditional("值更新", widgetVariableName, conditional, objectValue); List> business = (List) values.get("business"); - conditional("值改变", widgetVariableName, business, objectValue); + conditional("值更新", widgetVariableName, business, objectValue); } void conditional(String type, String widgetVariableName, List> isEstablish, ObjectValue objectValue) { @@ -86,13 +86,20 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent { String tag = split.get(0); String src = split.get(1); if (src.contains(objectValue.getTableName())) { - List list = Arrays.asList(tag.split(" ")); - list.forEach(f -> { - - }); + // 公式引用表单字段(如 amount = price * qty):把 表.表.字段 引用替换为实际值后求值,再赋给目标字段 + Map context = new HashMap<>(); + for (String ref : src.split("[^a-zA-Z0-9_.]+")) { + if (!ref.contains(objectValue.getTableName()) || context.containsKey(ref)) { + continue; + } + context.put(ref, this.resolveFormulaField(ref, objectValue)); + } + Object computed = jexl3Util.expression(src, context); + List list = Arrays.asList(tag.split("\\.")); + super.setWidgetAttribute(list.get(list.size() - 1).trim(), AttributeEnum.DATA_VALUE_CHANGE, computed); } else { List list = Arrays.asList(tag.split("\\.")); - super.setWidgetAttribute(list.get(list.size() - 1), AttributeEnum.VALUE_CHANGE, src); + super.setWidgetAttribute(list.get(list.size() - 1), AttributeEnum.DATA_VALUE_CHANGE, src); } } else { fun = jexl3Util.getMySplit("\\$(.*?)\\$", fun); @@ -100,7 +107,7 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent { Object[] objects = jexl3Util.getFunParam(funParam, objectValue); Object o = onlineFormService.executeFunction(obj, fun, objects); List list = Arrays.asList(split.get(0).split("\\.")); - super.setWidgetAttribute(list.get(list.size() - 1).trim(), AttributeEnum.VALUE_CHANGE, o); + super.setWidgetAttribute(list.get(list.size() - 1).trim(), AttributeEnum.DATA_VALUE_CHANGE, o); } break; } @@ -279,4 +286,26 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent { // 使用正则表达式替换所有非字母数字字符为空字符串 return input.replaceAll("_", "").toLowerCase(); } + + /** + * 解析公式中 表.表.字段 引用的实际值。 + * 主表字段直接取值;关联字段(表.表.关联列.子字段)取子对象属性;分录字段取第一行值;空值统一补 0。 + */ + private Object resolveFormulaField(String ref, ObjectValue objectValue) { + String[] parts = ref.split("\\."); + Object value; + if (parts[0].equals(parts[1])) { + if (parts.length > 3) { + Object linked = objectValue.get(parts[2]); + value = linked instanceof ObjectValue ? ((ObjectValue) linked).get(parts[3]) : null; + } else { + value = objectValue.get(parts[2]); + } + } else { + ObjectCollection collection = (ObjectCollection) objectValue.get(parts[1]); + value = (collection == null || collection.isEmpty()) + ? null : ((ObjectValue) collection.iterator().next()).get(parts[2]); + } + return value == null || value.toString().isEmpty() ? 0 : value; + } } diff --git a/common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java b/common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java index d04a798..c2f18e1 100644 --- a/common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java +++ b/common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java @@ -6,6 +6,7 @@ import apelet.common.core.cache.CacheConfig; import apelet.common.core.cache.CacheKey; import apelet.common.core.constant.AppDeviceType; import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.constant.GlobalDeletedFlag; import apelet.common.core.object.*; import apelet.common.core.util.MyCommonUtil; import apelet.common.core.util.MyModelUtil; @@ -32,7 +33,6 @@ import apelet.common.online.util.WidgetFieldTypeMapping; import apelet.common.online.util.WidgetJsonUtil; import apelet.common.online.vo.OnlineFormVo; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -57,6 +57,7 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.groups.Default; import java.util.*; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -229,116 +230,6 @@ public class OnlineFormController { .eq(OnlineDatasourceRelation::getRelationType,0) .eq(OnlineDatasourceRelation::getDatasourceId,onlineFormDto.getDatasourceIdList().get(0))); } -// for (int i = 0; i < jsonArray.size(); i++) { -// JSONObject object = jsonArray.getJSONObject(i); -// Integer widgetType = object.getInteger("widgetType"); -// //外键id -// String columnId = object.getJSONObject("bindData").getString("columnId"); -// //主表id -// String tableId = object.getJSONObject("bindData").getString("tableId"); -// OnlineTable onlineTable = onlineTableService.getById(tableId); -// //从表名称 -// String slaveTableName = object.getJSONObject("bindData").getString("slaveTableName"); -// if (402 == widgetType){ -// if (object.getJSONObject("props") != null && object.getJSONObject("props").getJSONObject("relativeTable") != null){ -// String relationId = object.getJSONObject("props").getJSONObject("relativeTable").getString("relationId");//关联id -// if(StringUtils.isNotEmpty(relationId)){ -// OnlineDatasourceRelation onlineDatasourceRelation = onlineDatasourceRelationService.getById(Long.valueOf(relationId)); -// if(onlineDatasourceRelation != null){ -// OnlineTable sTable = onlineTableService.getById(onlineDatasourceRelation.getSlaveTableId()); -// if( ! (onlineDatasourceRelation.getMasterColumnId() == Long.valueOf(columnId) -// && onlineDatasourceRelation.getRelationType() == 0 -// && onlineDatasourceRelation.getDatasourceId() == onlineFormDto.getDatasourceIdList().get(0) -// && sTable.getTableName().equals(slaveTableName) ) -// ){//已存在关联关系,不符合规则 -// onlineDatasourceRelationService.remove(onlineDatasourceRelation.getRelationId()); -// } -// } -// } -// } -// OnlineDatasourceRelation onr = onlineDatasourceRelationService.getOne(new LambdaQueryWrapper() -// .eq(OnlineDatasourceRelation::getMasterColumnId,columnId) -// .eq(OnlineDatasourceRelation::getRelationType,0) -// .eq(OnlineDatasourceRelation::getDatasourceId,onlineFormDto.getDatasourceIdList().get(0))); -// if(slaveTableName == null){ -// errorMessage = "关联控件校验失败,字段绑定表"; -// return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); -// } -// if(onr != null){ -// OnlineTable sTable = onlineTableService.getById(onr.getSlaveTableId()); -// if(onr != null && !slaveTableName.equals(sTable.getTableName())){ -// errorMessage = "关联控件校验失败,字段关联多张表"; -// return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); -// } -// if(onr != null && slaveTableName.equals(sTable.getTableName())) { -// if (object.getJSONObject("props") != null && object.getJSONObject("props").getJSONObject("relativeTable") != null) { -// if (object.getJSONObject("props").getJSONObject("relativeTable").getString("relativeFormType").equals("2")) {//关联查询数据源\ -// OnlineDatasource datasource = onlineDatasourceService.getById(onlineFormDto.getDatasourceIdList().get(0)); -// object.getJSONObject("props").getJSONObject("relativeTable").put("datasourceId", onlineFormDto.getDatasourceIdList().get(0)); -// object.getJSONObject("props").getJSONObject("relativeTable").put("variableName",datasource.getVariableName() ); -// object.getJSONObject("props").getJSONObject("relativeTable").put("relationId",onr.getRelationId()); -// object.getJSONObject("props").getJSONObject("relativeTable").put("relativeTableName",slaveTableName); -// } -// } -// continue; -// } -// } -// //先删除对应关联关系 -// onlineDatasourceRelationService.remove(new LambdaQueryWrapper() -// .eq(OnlineDatasourceRelation::getMasterColumnId,columnId) -// .eq(OnlineDatasourceRelation::getRelationType,0) -// .eq(OnlineDatasourceRelation::getDatasourceId,onlineFormDto.getDatasourceIdList().get(0))); -// OnlineDatasourceRelationDto onlineDatasourceRelationDto = new OnlineDatasourceRelationDto(); -// onlineDatasourceRelationDto.setDatasourceId(onlineFormDto.getDatasourceIdList().get(0)); -// //关联名称 -// onlineDatasourceRelationDto.setRelationName(object.getString("showName")); -// //关联标识-组件标识 -// onlineDatasourceRelationDto.setVariableName(onlineTable.getTableName()+"_"+object.getString("variableName")+"_"+slaveTableName); -// //关联类型 -// onlineDatasourceRelationDto.setRelationType(0); -// //主键字段id -// onlineDatasourceRelationDto.setMasterColumnId(Long.valueOf(columnId)); -// //从表名称 -// onlineDatasourceRelationDto.setSlaveTableName(slaveTableName); -// //从表字段名称 -// onlineDatasourceRelationDto.setSlaveColumnName("id"); -// //是否级联删除 -// onlineDatasourceRelationDto.setCascadeDelete(false); -// //是否左连接查询 -// onlineDatasourceRelationDto.setLeftJoin(true); -// OnlineDatasourceRelation onlineDatasourceRelation = -// MyModelUtil.copyTo(onlineDatasourceRelationDto, OnlineDatasourceRelation.class); -// SqlTable slaveTable = onlineDblinkService.getDblinkTable( -// onlineDblink, onlineDatasourceRelationDto.getSlaveTableName()); -// if (slaveTable == null) { -// errorMessage = "关联控件建立一对一关系失败:数据验证失败,指定的数据表不存在!"; -// return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); -// } -// SqlTableColumn slaveColumn = null; -// for (SqlTableColumn column : slaveTable.getColumnList()) { -// if (column.getColumnName().equals(onlineDatasourceRelationDto.getSlaveColumnName())) { -// slaveColumn = column; -// break; -// } -// } -// if (slaveColumn == null) { -// errorMessage = "关联控件建立一对一关系失败:数据验证失败,指定的数据表字段 [" + onlineDatasourceRelationDto.getSlaveColumnName() + "] 不存在!"; -// return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); -// } -// onlineDatasourceRelation = onlineDatasourceRelationService.saveNew(onlineDatasourceRelation, slaveTable, slaveColumn); -// if (object.getJSONObject("props") != null && object.getJSONObject("props").getJSONObject("relativeTable") != null){ -// if (object.getJSONObject("props").getJSONObject("relativeTable").getString("relativeFormType").equals("2")) {//关联查询数据源\ -// OnlineDatasource datasource = onlineDatasourceService.getById(onlineFormDto.getDatasourceIdList().get(0)); -// object.getJSONObject("props").getJSONObject("relativeTable").put("datasourceId", onlineFormDto.getDatasourceIdList().get(0)); -// object.getJSONObject("props").getJSONObject("relativeTable").put("variableName",datasource.getVariableName() ); -// object.getJSONObject("props").getJSONObject("relativeTable").put("relativeTableName",slaveTableName); -// } -// object.getJSONObject("props").getJSONObject("relativeTable").put("relationId",onlineDatasourceRelation.getRelationId()); -// } -// -// -// } -// } onlineForm.setWidgetJson(jsonObject.toJSONString()); } @@ -707,256 +598,98 @@ public class OnlineFormController { * @return 修改后的 widgetJson (含回填的 columnId/relationId),无变更则返回 null */ private JSONObject syncNewFieldsAndSubTables(OnlineForm form, OnlineFormDto dto, JSONObject widgetJson) { - // 1. 获取数据源 → OnlineTable → OnlFormHead + // 1. 获取数据源 → 主表 → 主表 head OnlineDatasource datasource = onlineDatasourceService.getById(dto.getDatasourceIdList().get(0)); - OnlineTable onlineTable = onlineTableService.getById(datasource.getMasterTableId()); - - OnlFormHead onlFormHead = onlFormHeadService.getOne( - new LambdaQueryWrapper() - .eq(OnlFormHead::getTableName, onlineTable.getTableName())); - if (onlFormHead == null) { - return null; - } + OnlineTable masterTable = onlineTableService.getById(datasource.getMasterTableId()); + OnlFormHead masterHead = onlFormHeadService.getOne(new LambdaQueryWrapper().eq(OnlFormHead::getTableName, masterTable.getTableName())); + if (masterHead == null) return null; boolean changed = false; - // === Diff 新增字段 === - // 收集 pc/mobile 端所有绑定的字段列名 - Set widgetColumnNames = WidgetJsonUtil.collectBindColumnNames(widgetJson); - - List existingFields = onlFormFieldService.list(new LambdaQueryWrapper().eq(OnlFormField::getHeadId, onlFormHead.getId())); - Set existingFieldNames = existingFields.stream() - .map(OnlFormField::getFieldName).collect(Collectors.toSet()); - Set newColumnNames = new HashSet<>(widgetColumnNames); - newColumnNames.removeAll(existingFieldNames); - // 主表/单表已有字段:如果绑定了从表(slaveTableId),同步更新 refPropert - Map fieldNameToWidget = WidgetJsonUtil.buildFieldNameWidgetMap(widgetJson); - for (OnlFormField existingField : existingFields) { - JSONObject widget = fieldNameToWidget.get(existingField.getFieldName()); - if (widget == null) { - continue; - } - JSONObject bindData = widget.getJSONObject("bindData"); - if (bindData == null) { - continue; - } - String slaveTableId = bindData.getString("slaveTableId"); - if (StrUtil.isNotEmpty(slaveTableId)) { - existingField.setRefPropert(Long.valueOf(slaveTableId)); - onlFormFieldService.updateById(existingField); - } - } - if (!newColumnNames.isEmpty()) { - Map nameToWidget = WidgetJsonUtil.buildColumnNameWidgetMap(widgetJson); - // 收集 mobile 端的 widget 映射(遍历 childWidgetList 路径) - Map mobileNameToWidget = WidgetJsonUtil.buildMobileColumnNameWidgetMap(widgetJson); - OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId()); - - // 预校验:过滤掉 widgetType 不在映射中的控件,避免 getFieldTypeId 抛异常导致部分数据已入库 - Set validColumnNames = new LinkedHashSet<>(); - for (String fieldName : newColumnNames) { - JSONObject widget = nameToWidget.get(fieldName); - if (widget == null) { - widget = mobileNameToWidget.get(fieldName); - } - Integer widgetType = widget != null ? widget.getInteger("widgetType") : null; - if (widgetType == null || !WidgetFieldTypeMapping.isDataWidget(widgetType)) { - continue; - } - validColumnNames.add(fieldName); - } - if (validColumnNames.isEmpty()) { - return changed ? widgetJson : null; - } - - for (String fieldName : validColumnNames) { - JSONObject widget = nameToWidget.get(fieldName); - if (widget == null) { - widget = mobileNameToWidget.get(fieldName); - } - // 从 widget 的 bindData.columnComment 设置字段备注 - JSONObject bindData = widget.getJSONObject("bindData"); - String ref = bindData.containsKey("slaveTableId") ? bindData.getString("slaveTableId") : null; - this.saveField(widget.getInteger("widgetType"), bindData.getString("columnComment"), widget.getString("showName"), onlFormHead, fieldName, ref); - } - - // ALTER TABLE ADD COLUMN - onlFormHeadService.syncDB(onlFormHead, 0L); - - // 批量获取所有列信息,避免 N+1 查询 - List allColumns = onlineDblinkService.getDblinkTableColumnList( - dblink, onlineTable.getTableName()); - Map columnMap = new HashMap<>(); - if (CollUtil.isNotEmpty(allColumns)) { - for (SqlTableColumn col : allColumns) { - if (col.getColumnName() != null) { - columnMap.put(col.getColumnName().toLowerCase(), col); - } - } - } - - // 增量创建 OnlineColumn + 回填 columnId 到 widgetJson - for (String fieldName : validColumnNames) { - SqlTableColumn sqlCol = columnMap.get(fieldName.toLowerCase()); - if (sqlCol != null) { - JSONObject widget = nameToWidget.get(fieldName); - JSONObject mobileWidget = mobileNameToWidget.get(fieldName); - String showName = widget != null ? widget.getString("showName") : (mobileWidget != null ? mobileWidget.getString("showName") : null); - long columnId = onlineColumnService.saveBySqlTable(sqlCol, onlineTable.getTableId(), showName); - // 回写 pc 端 widget - if (widget != null) { - JSONObject bindData2 = widget.getJSONObject("bindData"); - if (bindData2 != null) { - bindData2.put("columnId", columnId); - bindData2.put("tableId", onlineTable.getTableId()); - bindData2.put("columnFieldName", fieldName); - } - } - // 回写 mobile 端 widget - if (mobileWidget != null) { - JSONObject mobileBindData = mobileWidget.getJSONObject("bindData"); - if (mobileBindData != null) { - mobileBindData.put("columnId", columnId); - mobileBindData.put("tableId", onlineTable.getTableId()); - mobileBindData.put("columnFieldName", fieldName); - } + // === 统一取值:pc/mobile 所有"表-字段"绑定关系(表标识 → 字段名 → 控件) === + Map> tableFieldMap = WidgetJsonUtil.buildTableFieldMap(widgetJson); + + // === 新增表:widgetType=100 且无 tableId(只有 tableName)的表统一先建表 === + Set newTableNames = WidgetJsonUtil.collectNewSubTableNames(widgetJson); + if (!newTableNames.isEmpty()) changed |= createNewSubTables(widgetJson, masterHead, datasource, newTableNames); + + // === 逐表 diff 新增字段 === + for (Map.Entry> entry : tableFieldMap.entrySet()) { + String tableKey = entry.getKey(); + Map columnWidgetMap = entry.getValue(); + if (columnWidgetMap.isEmpty()) continue; + // 解析表:纯数字 → 已有表(主表/单表/附表统一处理);字符串 → 刚创建的新表 + OnlineTable table = tableKey.matches("\\d+") ? onlineTableService.getById(Long.valueOf(tableKey)) : onlineTableService.getOne(new LambdaQueryWrapper().eq(OnlineTable::getTableName, tableKey.toLowerCase())); + if (table == null) continue; + OnlFormHead head = onlFormHeadService.getOne(new LambdaQueryWrapper().eq(OnlFormHead::getTableName, table.getTableName())); + if (head == null) continue; + + List existingFields = onlFormFieldService.list(new LambdaQueryWrapper().eq(OnlFormField::getHeadId, head.getId())); + Map fieldByColumnName = existingFields.stream().collect(Collectors.toMap(OnlFormField::getFieldName, Function.identity(), (a, b) -> a)); + + List newColumnNames = new ArrayList<>(); + for (Map.Entry columnEntry : columnWidgetMap.entrySet()) { + String columnName = columnEntry.getKey(); + JSONObject widget = columnEntry.getValue(); + OnlFormField existingField = fieldByColumnName.get(columnName); + if (existingField != null) { + // 已有字段:带 slaveTableId 则更新 refPropert(子表列仅关联列 comType=402 生效) + boolean isTableColumn = !widget.containsKey("bindData"); + Integer comType = widget.getInteger("comType"); + boolean isAssociation = !isTableColumn || (comType != null && comType == 402); + String slaveTableId = extractSlaveTableId(widget); + if (isAssociation && StrUtil.isNotEmpty(slaveTableId)) { + existingField.setRefPropert(Long.valueOf(slaveTableId)); + onlFormFieldService.updateById(existingField); + changed = true; } + continue; } - } - redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineTableKey(onlineTable.getTableId())).delete(); - redissonClient.getBucket(CacheKey.makeTableKey(onlineTable.getTableName())).delete(); - redissonClient.getBucket(CacheKey.makeChildFieldListKey(onlFormHead.getId())).delete(); - metaCacheService.removeData(onlineTable.getTableName()); - changed = true; - } - - // === Diff 新增子表 === - // 取 widgetList->bindData -> tableId 在去 zz_online_table 查询,这样就可以判断哪些是新增,哪些是 已有的表 - // 如果存在附表 - Map tableIdAndPropMap = WidgetJsonUtil.collectSubTableProps(widgetJson); - - if (MapUtil.isNotEmpty(tableIdAndPropMap)) { - //搜集的所有tableId, 要么是 tableId(已存在),要么是 tableName(不存在) - Set allTableIdSet = tableIdAndPropMap.keySet(); - Set newSubTableNames = new HashSet<>(); - Set existingSubTableIdSet = new HashSet<>(); - for (String tableId : allTableIdSet) { - if (tableId.matches("\\d+")) { - existingSubTableIdSet.add(Long.parseLong(tableId)); + // 新增字段:识别"字段控件"还是"子表列",非数据控件跳过 + boolean isTableColumn = !widget.containsKey("bindData"); + Integer widgetType = widget.getInteger("widgetType"); + if (!isTableColumn && (widgetType == null || !WidgetFieldTypeMapping.isDataWidget(widgetType))) continue; + if (isTableColumn) { + // 子表列:字段类型沿用现状,硬编码 750 + this.saveField(750, widget.getString("showName"), null, head, columnName, extractSlaveTableId(widget)); } else { - newSubTableNames.add(tableId); + JSONObject bindData = widget.getJSONObject("bindData"); + String ref = bindData.containsKey("slaveTableId") ? bindData.getString("slaveTableId") : null; + this.saveField(widgetType, bindData.getString("columnComment"), widget.getString("showName"), head, columnName, ref); } - } - //分两步, - //step1 : 看看存在的附表,是否存在不存在的字段 - if (!existingSubTableIdSet.isEmpty()) { - // 拿到tableId 下面的所有字段,先查询 zz_online_table ,在根据 tableName 查询 onl_form_head 表 - List tableList = onlineTableService.listByIds(existingSubTableIdSet); - Map tableIdAndNameMap = tableList.stream().collect(Collectors.toMap(OnlineTable::getTableId, OnlineTable::getTableName)); - List tableNames = tableList.stream().map(OnlineTable::getTableName).collect(Collectors.toList()); - List onlFormHeadList = onlFormHeadService.list(new LambdaQueryWrapper().in(OnlFormHead::getTableName, tableNames)); - Map tableNameAndHeadIdMap = onlFormHeadList.stream().collect(Collectors.toMap(OnlFormHead::getTableName, Function.identity())); - //因为这里是zz_online_column.column_id 的id和 列名的混合,我只要列名 - Map nameToWidget = WidgetJsonUtil.buildSubTableColumnWidgetMap(widgetJson); - Set newSubColumnNameSet = nameToWidget.keySet().stream().filter(s -> !s.matches("\\d+")).collect(Collectors.toSet()); - //这里要走 新增逻辑 - if (!newSubColumnNameSet.isEmpty()) { - // ALTER TABLE ADD COLUMN, 这里取第一个就行,因为绑定的是一个表 - JSONObject columnInfo = nameToWidget.values().iterator().next(); - Long tableId = columnInfo.getLong("tableId"); - String tableName = tableIdAndNameMap.get(tableId); - OnlFormHead entryHead = tableNameAndHeadIdMap.get(tableName); - for (String newSubColumnName : newSubColumnNameSet) { - //这里取出来的结构是 widgetList -》 props -》 tableColumnList - JSONObject columnInfoJson = nameToWidget.get(newSubColumnName); - //step 1 : 先保存 field 表 - String showName = columnInfoJson.getString("showName"); - String ref = columnInfoJson.containsKey("slaveTableId") ? columnInfoJson.getString("slaveTableId") : null; - // 默认是字符串类型 - this.saveField(750, showName, null, entryHead, newSubColumnName, ref); + newColumnNames.add(columnName); + changed = true; + } + + if (!newColumnNames.isEmpty()) { + // ALTER TABLE ADD COLUMN + onlFormHeadService.syncDB(head, 0L); + // 批量获取列信息,避免 N+1 + OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId()); + List allColumns = onlineDblinkService.getDblinkTableColumnList(dblink, table.getTableName()); + Map columnMap = new HashMap<>(); + if (CollUtil.isNotEmpty(allColumns)) { + for (SqlTableColumn col : allColumns) { + if (col.getColumnName() != null) columnMap.put(col.getColumnName().toLowerCase(), col); } - onlFormHeadService.syncDB(entryHead, 0L); - //step 2 :在保存 OnlineColumn 表 - OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId()); - List allColumns = onlineDblinkService.getDblinkTableColumnList(dblink, tableName); - Map columnMap = new HashMap<>(); - if (CollUtil.isNotEmpty(allColumns)) { - columnMap = allColumns.stream().collect(Collectors.toMap(SqlTableColumn::getColumnName, Function.identity())); - } - for (String fieldName : newSubColumnNameSet) { - SqlTableColumn sqlCol = columnMap.get(fieldName); - if (sqlCol != null) { - // 回填 columnId 到 widgetJson - JSONObject widget = nameToWidget.get(fieldName); - String showName = widget == null ? null : widget.getString("showName"); - long columnId = onlineColumnService.saveBySqlTable(sqlCol, tableId, showName); - if (widget != null) { - //step 3 :回填 columnId + showFieldName 到 json里面去 - widget.put("columnId", columnId); - widget.put("showFieldName", fieldName); - } - } - } - //step 4 :删除缓存 - redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineTableKey(tableId)).delete(); - redissonClient.getBucket(CacheKey.makeTableKey(tableName)).delete(); - redissonClient.getBucket(CacheKey.makeChildFieldListKey(entryHead.getId())).delete(); - metaCacheService.removeData(tableName); - changed = true; } - - // 附表已有字段:检查 tableColumnList 中 comType=402 的列是否有 slaveTableId,同步更新 refPropert - for (Long tableId : existingSubTableIdSet) { - String tableName = tableIdAndNameMap.get(tableId); - if (tableName == null) { - continue; - } - OnlFormHead subHead = tableNameAndHeadIdMap.get(tableName); - if (subHead == null) { - continue; - } - List subFields = onlFormFieldService.list( - new LambdaQueryWrapper().eq(OnlFormField::getHeadId, subHead.getId())); - JSONObject props = tableIdAndPropMap.get(String.valueOf(tableId)); - JSONArray tableColumnList = props != null ? props.getJSONArray("tableColumnList") : null; - if (tableColumnList == null) { - continue; - } - for (OnlFormField field : subFields) { - for (int i = 0; i < tableColumnList.size(); i++) { - JSONObject col = tableColumnList.getJSONObject(i); - // 匹配 showFieldName,兼容 showName - String showFieldName = col.getString("showFieldName"); - if (StrUtil.isEmpty(showFieldName)) { - showFieldName = col.getString("showName"); - } - if (!field.getFieldName().equals(showFieldName)) { - continue; - } - // 只有 comType=402(关联选择器)才有 slaveTableId - Integer comType = col.getInteger("comType"); - if (comType != null && comType == 402) { - String slaveTableId = col.getString("slaveTableId"); - if (StrUtil.isNotEmpty(slaveTableId)) { - field.setRefPropert(Long.valueOf(slaveTableId)); - onlFormFieldService.updateById(field); - changed = true; - } - } - break; - } - } + // 增量创建 OnlineColumn + 回填 columnId 到所有匹配控件 + for (String columnName : newColumnNames) { + SqlTableColumn sqlCol = columnMap.get(columnName.toLowerCase()); + if (sqlCol == null) continue; + long columnId = onlineColumnService.saveBySqlTable(sqlCol, table.getTableId(), columnWidgetMap.get(columnName).getString("showName")); + WidgetJsonUtil.backfillColumnId(widgetJson, columnName, columnId, table.getTableId(), table.getTableName()); } } - //step2 : 不存在的附表,新增 - changed |= createNewSubTables(widgetJson, onlFormHead, datasource, newSubTableNames); - } else { - // tableIdAndPropMap 为空,说明走 tableId 取值没取到,兜底通过 tableName 收集附表并创建 - changed |= createNewSubTables(widgetJson, onlFormHead, datasource, new HashSet<>()); + // 删除缓存 + redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineTableKey(table.getTableId())).delete(); + redissonClient.getBucket(CacheKey.makeTableKey(table.getTableName())).delete(); + redissonClient.getBucket(CacheKey.makeChildFieldListKey(head.getId())).delete(); + metaCacheService.removeData(table.getTableName()); } - // === Diff mobile 端子表 === - // 路径: mobile -> widgetList -> [widgetType=102] -> bindData -> tableName/tableId - changed |= syncMobileSubTables(widgetJson, onlFormHead, datasource); + + // === mobile 端子表(widgetType=102)专用同步,保持原有逻辑 === + changed |= syncMobileSubTables(widgetJson, masterHead, datasource); return changed ? widgetJson : null; } @@ -1009,6 +742,21 @@ public class OnlineFormController { } + /** + * 提取控件/子表列绑定的从表 id(slaveTableId)。 + * 字段控件在 bindData 下;子表列对象直接挂在顶层。 + */ + private String extractSlaveTableId(JSONObject widget) { + if (widget == null) { + return null; + } + JSONObject bindData = widget.getJSONObject("bindData"); + if (bindData != null) { + return bindData.containsKey("slaveTableId") ? bindData.getString("slaveTableId") : null; + } + return widget.getString("slaveTableId"); + } + private void saveField(Integer widgetType, String columnComment, String showName, OnlFormHead onlFormHead, String fieldName, String refPropert) { OnlFormField field = new OnlFormField(); field.setHeadId(onlFormHead.getId()); @@ -1025,6 +773,12 @@ public class OnlineFormController { field.setIsDefault(0); field.setRowDisabled(0); field.setSyncFlag(0); + // 逻辑删除标记:默认正常(1),onl_form_field 未配置 @TableLogic,需显式写入 + field.setDeletedFlag(GlobalDeletedFlag.NORMAL); + field.setCreateTime(new Date()); + field.setUpdateTime(new Date()); + field.setCreateUserId(TokenData.takeFromRequest().getUserId()); + field.setUpdateUserId(TokenData.takeFromRequest().getUserId()); // 从 widget 的 bindData.columnComment 设置字段备注 field.setFieldRemark(columnComment); @@ -1037,31 +791,41 @@ public class OnlineFormController { // ============ mobile 端子表同步相关方法 ============ /** + * 递归遍历 mobile 控件树(childWidgetList),收集所有 widgetType=102 的子表组件。 + * 102 可嵌套在分组/卡片等容器内。 + */ + private void walkMobileSubTableWidgets(JSONArray widgetList, Consumer consumer) { + if (CollUtil.isEmpty(widgetList)) return; + for (int i = 0; i < widgetList.size(); i++) { + JSONObject widget = widgetList.getJSONObject(i); + if (widget == null) continue; + Integer widgetType = widget.getInteger("widgetType"); + if (widgetType != null && widgetType == 102) { + consumer.accept(widget); + continue; + } + JSONArray childWidgetList = widget.getJSONArray("childWidgetList"); + if (CollUtil.isNotEmpty(childWidgetList)) walkMobileSubTableWidgets(childWidgetList, consumer); + } + } + + /** * 同步 mobile 端的子表(新增子表 + 已有子表的新增字段)。 + * 规则:bindData 有 tableId → 已有表,向下找字段;只有 tableName → 新增表,不找字段,建表后回写 tableId。 * 子表: mobile -> widgetList -> [widgetType=102] -> bindData -> tableName/tableId * 字段: mobile -> widgetList -> childWidgetList -> childWidgetList -> bindData -> columnName */ private boolean syncMobileSubTables(JSONObject widgetJson, OnlFormHead onlFormHead, OnlineDatasource datasource) { JSONObject mobile = widgetJson.getJSONObject("mobile"); - if (mobile == null) { - return false; - } + if (mobile == null) return false; JSONArray mobileWidgetList = mobile.getJSONArray("widgetList"); - if (mobileWidgetList == null) { - return false; - } + if (mobileWidgetList == null) return false; boolean changed = false; Set newSubTableNames = new LinkedHashSet<>(); Set existingSubTableIds = new LinkedHashSet<>(); - for (int i = 0; i < mobileWidgetList.size(); i++) { - JSONObject widget = mobileWidgetList.getJSONObject(i); - if (widget.getInteger("widgetType") == null || widget.getInteger("widgetType") != 102) { - continue; - } + walkMobileSubTableWidgets(mobileWidgetList, widget -> { JSONObject bindData = widget.getJSONObject("bindData"); - if (bindData == null) { - continue; - } + if (bindData == null) return; String tableName = bindData.getString("tableName"); String tableIdStr = bindData.getString("tableId"); if (StrUtil.isNotEmpty(tableName) && StrUtil.isEmpty(tableIdStr)) { @@ -1069,7 +833,7 @@ public class OnlineFormController { } else if (StrUtil.isNotEmpty(tableIdStr)) { existingSubTableIds.add(Long.valueOf(tableIdStr)); } - } + }); // 处理新子表: 创建后回写 tableId + masterColumnName if (!newSubTableNames.isEmpty()) { changed |= createNewSubTables(widgetJson, onlFormHead, datasource, newSubTableNames); @@ -1088,35 +852,21 @@ public class OnlineFormController { */ private void backfillMobileSubTableId(JSONObject widgetJson, Set newSubTableNames) { JSONObject mobile = widgetJson.getJSONObject("mobile"); - if (mobile == null) { - return; - } + if (mobile == null) return; JSONArray mobileWidgetList = mobile.getJSONArray("widgetList"); - if (mobileWidgetList == null) { - return; - } - for (int i = 0; i < mobileWidgetList.size(); i++) { - JSONObject widget = mobileWidgetList.getJSONObject(i); - if (widget.getInteger("widgetType") == null || widget.getInteger("widgetType") != 102) { - continue; - } + if (mobileWidgetList == null) return; + walkMobileSubTableWidgets(mobileWidgetList, widget -> { JSONObject bindData = widget.getJSONObject("bindData"); - if (bindData == null) { - continue; - } + if (bindData == null) return; String tableName = bindData.getString("tableName"); - if (StrUtil.isEmpty(tableName) || !newSubTableNames.contains(tableName)) { - continue; - } - // createNewSubTables 已通过 WidgetJsonUtil.backfillTableId 回写了 tableId,这里只需补 masterColumnName + if (StrUtil.isEmpty(tableName) || !newSubTableNames.contains(tableName)) return; + // createNewSubTables 只回写了 100 组件的 tableId,102 组件的在这里补 if (!bindData.containsKey("tableId")) { OnlineTable subTable = onlineTableService.getOne(new LambdaQueryWrapper().eq(OnlineTable::getTableName, tableName.toLowerCase())); - if (subTable != null) { - bindData.put("tableId", subTable.getTableId()); - } + if (subTable != null) bindData.put("tableId", subTable.getTableId()); } bindData.put("masterColumnName", "id"); - } + }); } /** @@ -1124,105 +874,92 @@ public class OnlineFormController { * 字段路径: mobile -> widgetList -> [widgetType=102] -> childWidgetList -> childWidgetList -> bindData -> columnName */ private boolean syncMobileSubTableNewFields(JSONObject widgetJson, OnlineDatasource datasource, Set existingSubTableIds) { - // 查询已有子表信息 List subTables = onlineTableService.listByIds(existingSubTableIds); - if (CollUtil.isEmpty(subTables)) { - return false; - } + if (CollUtil.isEmpty(subTables)) return false; Map tableIdToName = subTables.stream().collect(Collectors.toMap(OnlineTable::getTableId, OnlineTable::getTableName)); List tableNames = subTables.stream().map(OnlineTable::getTableName).collect(Collectors.toList()); List subHeads = onlFormHeadService.list(new LambdaQueryWrapper().in(OnlFormHead::getTableName, tableNames)); Map tableNameToHead = subHeads.stream().collect(Collectors.toMap(OnlFormHead::getTableName, Function.identity())); OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId()); - // 遍历 mobile 端已有子表 widget, 搜集新增字段 JSONObject mobile = widgetJson.getJSONObject("mobile"); + if (mobile == null) return false; JSONArray mobileWidgetList = mobile.getJSONArray("widgetList"); - boolean changed = false; - for (int i = 0; i < mobileWidgetList.size(); i++) { - JSONObject widget = mobileWidgetList.getJSONObject(i); - if (widget.getInteger("widgetType") == null || widget.getInteger("widgetType") != 102) { - continue; - } - JSONObject bindData = widget.getJSONObject("bindData"); - if (bindData == null) { - continue; - } - String tableIdStr = bindData.getString("tableId"); - if (StrUtil.isEmpty(tableIdStr)) { - continue; - } - Long tableId = Long.valueOf(tableIdStr); - String tableName = tableIdToName.get(tableId); - if (tableName == null) { - continue; - } - OnlFormHead subHead = tableNameToHead.get(tableName); - if (subHead == null) { - continue; - } - // 搜集该子表下无 columnId 的字段 - List newFieldWidgets = new ArrayList<>(); - JSONArray cardList = widget.getJSONArray("childWidgetList"); - if (cardList != null) { - for (int j = 0; j < cardList.size(); j++) { - JSONObject card = cardList.getJSONObject(j); - JSONArray fieldList = card.getJSONArray("childWidgetList"); - if (fieldList == null) { - continue; - } - for (int k = 0; k < fieldList.size(); k++) { - JSONObject field = fieldList.getJSONObject(k); - JSONObject fieldBd = field.getJSONObject("bindData"); - if (fieldBd != null && fieldBd.containsKey("columnName") && !fieldBd.containsKey("columnId")) { - newFieldWidgets.add(field); - } + if (mobileWidgetList == null) return false; + boolean[] changed = {false}; + walkMobileSubTableWidgets(mobileWidgetList, widget -> { + if (syncMobileSubTableNewFieldForWidget(widget, tableIdToName, tableNameToHead, dblink)) changed[0] = true; + }); + return changed[0]; + } + + /** + * 处理单个已有子表(widgetType=102)下的新增字段,返回是否有变更。 + */ + private boolean syncMobileSubTableNewFieldForWidget(JSONObject widget, Map tableIdToName, Map tableNameToHead, OnlineDblink dblink) { + JSONObject bindData = widget.getJSONObject("bindData"); + if (bindData == null) return false; + String tableIdStr = bindData.getString("tableId"); + if (StrUtil.isEmpty(tableIdStr)) return false; + Long tableId = Long.valueOf(tableIdStr); + String tableName = tableIdToName.get(tableId); + if (tableName == null) return false; + OnlFormHead subHead = tableNameToHead.get(tableName); + if (subHead == null) return false; + // 搜集该子表下无 columnId 的字段 + List newFieldWidgets = new ArrayList<>(); + JSONArray cardList = widget.getJSONArray("childWidgetList"); + if (cardList != null) { + for (int j = 0; j < cardList.size(); j++) { + JSONObject card = cardList.getJSONObject(j); + JSONArray fieldList = card.getJSONArray("childWidgetList"); + if (fieldList == null) continue; + for (int k = 0; k < fieldList.size(); k++) { + JSONObject field = fieldList.getJSONObject(k); + JSONObject fieldBd = field.getJSONObject("bindData"); + if (fieldBd != null && fieldBd.containsKey("columnName") && !fieldBd.containsKey("columnId")) { + newFieldWidgets.add(field); } } } - if (newFieldWidgets.isEmpty()) { - continue; - } - // 创建字段记录 - for (JSONObject fieldWidget : newFieldWidgets) { - JSONObject fieldBd = fieldWidget.getJSONObject("bindData"); - String columnName = fieldBd.getString("columnName"); + } + if (newFieldWidgets.isEmpty()) return false; + // 创建字段记录 + for (JSONObject fieldWidget : newFieldWidgets) { + JSONObject fieldBd = fieldWidget.getJSONObject("bindData"); + String columnName = fieldBd.getString("columnName"); + String showName = fieldWidget.getString("showName"); + this.saveField(fieldWidget.getInteger("widgetType"), fieldBd.getString("columnComment"), showName, subHead, columnName, null); + } + // ALTER TABLE ADD COLUMN + onlFormHeadService.syncDB(subHead, 0L); + // 批量获取列信息 + List allColumns = onlineDblinkService.getDblinkTableColumnList(dblink, tableName); + Map columnMap = new HashMap<>(); + if (CollUtil.isNotEmpty(allColumns)) { + for (SqlTableColumn col : allColumns) { + if (col.getColumnName() != null) columnMap.put(col.getColumnName().toLowerCase(), col); + } + } + // 创建 OnlineColumn + 回写 + for (JSONObject fieldWidget : newFieldWidgets) { + JSONObject fieldBd = fieldWidget.getJSONObject("bindData"); + String columnName = fieldBd.getString("columnName"); + SqlTableColumn sqlCol = columnMap.get(columnName.toLowerCase()); + if (sqlCol != null) { String showName = fieldWidget.getString("showName"); - this.saveField(fieldWidget.getInteger("widgetType"), fieldBd.getString("columnComment"), showName, subHead, columnName, null); - } - // ALTER TABLE ADD COLUMN - onlFormHeadService.syncDB(subHead, 0L); - // 批量获取列信息 - List allColumns = onlineDblinkService.getDblinkTableColumnList(dblink, tableName); - Map columnMap = new HashMap<>(); - if (CollUtil.isNotEmpty(allColumns)) { - for (SqlTableColumn col : allColumns) { - if (col.getColumnName() != null) { - columnMap.put(col.getColumnName().toLowerCase(), col); - } - } - } - // 创建 OnlineColumn + 回写 - for (JSONObject fieldWidget : newFieldWidgets) { - JSONObject fieldBd = fieldWidget.getJSONObject("bindData"); - String columnName = fieldBd.getString("columnName"); - SqlTableColumn sqlCol = columnMap.get(columnName.toLowerCase()); - if (sqlCol != null) { - String showName = fieldWidget.getString("showName"); - long columnId = onlineColumnService.saveBySqlTable(sqlCol, tableId, showName); - fieldBd.put("columnId", columnId); - fieldBd.put("tableId", tableId); - fieldBd.put("columnFieldName", columnName); - } + long columnId = onlineColumnService.saveBySqlTable(sqlCol, tableId, showName); + fieldBd.put("columnId", columnId); + fieldBd.put("tableId", tableId); + fieldBd.put("columnFieldName", columnName); } - // 删除缓存 - redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineTableKey(tableId)).delete(); - redissonClient.getBucket(CacheKey.makeTableKey(tableName)).delete(); - redissonClient.getBucket(CacheKey.makeChildFieldListKey(subHead.getId())).delete(); - metaCacheService.removeData(tableName); - changed = true; } - return changed; + // 删除缓存 + redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineTableKey(tableId)).delete(); + redissonClient.getBucket(CacheKey.makeTableKey(tableName)).delete(); + redissonClient.getBucket(CacheKey.makeChildFieldListKey(subHead.getId())).delete(); + metaCacheService.removeData(tableName); + return true; } /** diff --git a/common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java b/common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java index c9daa36..d0cbf34 100644 --- a/common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java +++ b/common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java @@ -75,152 +75,6 @@ public class WidgetJsonUtil { } /** - * 遍历 pc/mobile 控件树,收集所有控件 bindData.columnName 值。 - *

- * 每个读取源用"设备端 + 字段步骤链"描述,步骤支持对象字段 obj() 和数组字段 arr(): - *

-     * pc:     widgetList -> bindData -> columnName
-     * mobile: tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName
-     * mobile: widgetList -> childWidgetList -> bindData -> columnName
-     * 
- * 后续新增读取源(如 otherWidgetList)时,只需在下方追加一行 - * {@code collectColumnNamesByPath(widgetJson, names, "other", arr("otherWidgetList"))}。 - * - * @param widgetJson 在线表单的 widgetJson。 - * @return 绑定的字段名列集合。 - */ - public static Set collectBindColumnNames(JSONObject widgetJson) { - Set names = new HashSet<>(); - if (widgetJson == null) { - return names; - } - // pc 端: widgetList -> bindData -> columnName - collectColumnNamesByPath(widgetJson, names, "pc", arr("widgetList")); - // mobile 端(原 tableWidget 结构): tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName - collectColumnNamesByPath(widgetJson, names, "mobile", obj("tableWidget"), arr("childWidgetList"), arr("childWidgetList")); - // mobile 端(兜底 widgetList 结构): widgetList -> childWidgetList -> bindData -> columnName - collectColumnNamesByPath(widgetJson, names, "mobile", arr("widgetList"), arr("childWidgetList")); - return names; - } - - /** - * 按"设备端 + 字段步骤链"读取 columnName。 - * 从 widgetJson 的 mode 节点开始,依次下钻 steps 中的每个字段步骤, - * 最后一个步骤的节点即为控件,取其 bindData.columnName。 - * - * @param widgetJson widgetJson 根对象。 - * @param names 收集结果。 - * @param mode 设备端 key,如 pc/mobile。 - * @param steps 字段步骤链,支持对象字段 obj() 与数组字段 arr()。 - */ - private static void collectColumnNamesByPath(JSONObject widgetJson, Set names, String mode, PathStep... steps) { - JSONObject modeObj = widgetJson.getJSONObject(mode); - if (modeObj == null) { - return; - } - collectColumnNamesByStep(modeObj, 0, steps, names); - } - - /** - * 逐层下钻字段步骤链,到达末尾时读取控件的 bindData.columnName。 - */ - private static void collectColumnNamesByStep(JSONObject node, int index, PathStep[] steps, Set names) { - if (index >= steps.length) { - addColumnName(node, names); - return; - } - PathStep step = steps[index]; - if (step.isArray) { - JSONArray array = node.getJSONArray(step.field); - if (CollUtil.isEmpty(array)) { - return; - } - for (int i = 0; i < array.size(); i++) { - collectColumnNamesByStep(array.getJSONObject(i), index + 1, steps, names); - } - } else { - JSONObject child = node.getJSONObject(step.field); - if (child == null) { - return; - } - collectColumnNamesByStep(child, index + 1, steps, names); - } - } - - /** - * 读取路径步骤:对象字段(obj) 或 数组字段(arr)。 - */ - private static class PathStep { - final boolean isArray; - final String field; - - private PathStep(boolean isArray, String field) { - this.isArray = isArray; - this.field = field; - } - } - - /** 对象字段步骤,如 tableWidget。 */ - private static PathStep obj(String field) { - return new PathStep(false, field); - } - - /** 数组字段步骤,如 widgetList、childWidgetList。 */ - private static PathStep arr(String field) { - return new PathStep(true, field); - } - - /** - * 从控件对象中提取 bindData.columnName。 - */ - private static void addColumnName(JSONObject widget, Set names) { - JSONObject bindData = widget.getJSONObject("bindData"); - if (bindData != null) { - String columnName = bindData.getString("columnName"); - if (StrUtil.isNotEmpty(columnName)) { - names.add(columnName); - } - } - } - - /** - * 收集子表控件(widgetType=100)的 bindData.tableId → props 映射。 - * - * @param widgetJson 在线表单的 widgetJson。 - * @return tableId → 子表控件 props 的映射。 - */ - public static Map collectSubTableProps(JSONObject widgetJson) { - Map resultMap = new HashMap<>(); - walkAllModes(widgetJson, widget -> { - JSONArray widgetList = widget.getJSONArray("widgetList"); - if (widgetList == null) { - return; - } - for (int i = 0; i < widgetList.size(); i++) { - JSONObject childWidget = widgetList.getJSONObject(i); - // 只处理 widgetType=100 的子表控件 - Integer widgetType = childWidget.getInteger("widgetType"); - if (widgetType == null || widgetType != 100) { - continue; - } - JSONObject bindData = childWidget.getJSONObject("bindData"); - if (bindData == null) { - continue; - } - String tableId = bindData.getString("tableId"); - if (StrUtil.isBlank(tableId)) { - continue; - } - JSONObject props = childWidget.getJSONObject("props"); - if (props != null) { - resultMap.put(tableId, props); - } - } - }); - return resultMap; - } - - /** * 收集子表控件(widgetType=100) bindData.tableName 的名称集合。 * * @param widgetJson 在线表单的 widgetJson。 @@ -253,160 +107,240 @@ public class WidgetJsonUtil { } /** - * 构建 columnName → 控件 widget 的映射。 + * 收集需要新增的子表名称:widgetType=100 且 bindData 只有 tableName(无 tableId)的表。 + * 新增表下面通常不存在需要处理的字段,故不注册进字段 map(见 collectTableColumns), + * 仅在此单独收集用于建表。递归遍历 childWidgetList,与 buildTableFieldMap 取值保持一致。 * * @param widgetJson 在线表单的 widgetJson。 - * @return 字段名 → widget 的映射。 + * @return 新增子表名称集合。 */ - public static Map buildColumnNameWidgetMap(JSONObject widgetJson) { - Map map = new HashMap<>(); - walkAllModes(widgetJson, widget -> { - JSONObject bindData = widget.getJSONObject("bindData"); - if (bindData != null && bindData.containsKey("columnName")) { - String name = bindData.getString("columnName"); - if (StrUtil.isNotEmpty(name)) { - map.put(name, widget); + public static Set collectNewSubTableNames(JSONObject widgetJson) { + Set tableNameSet = new HashSet<>(); + if (widgetJson == null) return tableNameSet; + for (String mode : new String[]{"pc", "mobile"}) { + JSONObject modeObj = widgetJson.getJSONObject(mode); + if (modeObj == null) continue; + collectNewSubTableNames(modeObj.getJSONArray("widgetList"), tableNameSet); + JSONObject tableWidget = modeObj.getJSONObject("tableWidget"); + if (tableWidget != null) collectNewSubTableNames(tableWidget.getJSONArray("childWidgetList"), tableNameSet); + } + return tableNameSet; + } + + private static void collectNewSubTableNames(JSONArray widgetList, Set tableNameSet) { + if (CollUtil.isEmpty(widgetList)) return; + for (int i = 0; i < widgetList.size(); i++) { + JSONObject widget = widgetList.getJSONObject(i); + if (widget == null) continue; + Integer widgetType = widget.getInteger("widgetType"); + if (widgetType != null && widgetType == 100) { + JSONObject bindData = widget.getJSONObject("bindData"); + if (bindData != null && StrUtil.isEmpty(bindData.getString("tableId"))) { + String tableName = bindData.getString("tableName"); + if (StrUtil.isNotEmpty(tableName)) tableNameSet.add(tableName); } + continue; } - }); - return map; + JSONArray childWidgetList = widget.getJSONArray("childWidgetList"); + if (CollUtil.isNotEmpty(childWidgetList)) collectNewSubTableNames(childWidgetList, tableNameSet); + } } /** - * 构建 fieldName → widget 的映射(用于主表已有字段匹配)。 - * key 优先取 bindData.columnFieldName,兼容 bindData.columnName。 + * 统一收集 pc/mobile 控件树中所有"表-字段"绑定关系。 + *

+ * 返回嵌套 Map:外层 key = 表标识(bindData.tableId;缺省用 bindData.tableName,用于尚未创建的新表), + * 内层 key = 字段名,value = 对应的控件对象(叶子字段控件 或 子表列对象)。 + *

+ * 收集规则: + *

    + *
  • widgetType=100(子表组件):取 props.tableColumnList 各列,字段名取 showFieldName(兜底 showName/columnId),跳过 fieldType=1 的序号/合计列;
  • + *
  • widgetType=102(移动端子表组件):跳过,由移动端子表专用逻辑处理,避免重复;
  • + *
  • 容器组件(childWidgetList 非空):递归收集;
  • + *
  • 叶子控件:取 bindData.columnName 与 bindData.tableId/tableName。
  • + *
+ * mobile 兼容两种结构:widgetList(新)与 老 tableWidget → childWidgetList。 * * @param widgetJson 在线表单的 widgetJson。 - * @return 字段名 → widget 的映射。 + * @return 表标识 → (字段名 → 控件对象) 的嵌套映射。 */ - public static Map buildFieldNameWidgetMap(JSONObject widgetJson) { - Map map = new HashMap<>(); - walkAllModes(widgetJson, widget -> { - JSONObject bindData = widget.getJSONObject("bindData"); - if (bindData == null) { - return; - } - String fieldName = bindData.getString("columnFieldName"); - if (StrUtil.isEmpty(fieldName)) { - fieldName = bindData.getString("columnName"); + public static Map> buildTableFieldMap(JSONObject widgetJson) { + Map> tableFieldMap = new HashMap<>(); + if (widgetJson == null) return tableFieldMap; + for (String mode : new String[]{"pc", "mobile"}) { + JSONObject modeObj = widgetJson.getJSONObject(mode); + if (modeObj == null) continue; + // 新结构:widgetList(递归收集) + collectTableFields(modeObj.getJSONArray("widgetList"), tableFieldMap); + // mobile 老结构兜底:tableWidget -> childWidgetList -> childWidgetList + JSONObject tableWidget = modeObj.getJSONObject("tableWidget"); + if (tableWidget != null) collectTableFields(tableWidget.getJSONArray("childWidgetList"), tableFieldMap); + } + return tableFieldMap; + } + + /** + * 递归收集控件树中的"表-字段"绑定关系。 + */ + private static void collectTableFields(JSONArray widgetList, Map> tableFieldMap) { + if (CollUtil.isEmpty(widgetList)) return; + for (int i = 0; i < widgetList.size(); i++) { + JSONObject widget = widgetList.getJSONObject(i); + if (widget == null) continue; + Integer widgetType = widget.getInteger("widgetType"); + if (widgetType != null && widgetType == 100) { + collectTableColumns(widget, tableFieldMap); + continue; } - if (StrUtil.isNotEmpty(fieldName)) { - map.put(fieldName, widget); + if (widgetType != null && widgetType == 102) continue; + JSONArray childWidgetList = widget.getJSONArray("childWidgetList"); + if (CollUtil.isNotEmpty(childWidgetList)) { + collectTableFields(childWidgetList, tableFieldMap); + continue; } - }); - return map; + JSONObject bindData = widget.getJSONObject("bindData"); + if (bindData == null) continue; + // 字段名优先取 bindData.columnName,兼容仅绑定 columnFieldName 的移动端字段 + String columnName = bindData.getString("columnName"); + if (StrUtil.isEmpty(columnName)) columnName = bindData.getString("columnFieldName"); + String tableKey = resolveTableKey(bindData); + if (StrUtil.isEmpty(columnName) || StrUtil.isEmpty(tableKey)) continue; + tableFieldMap.computeIfAbsent(tableKey, k -> new HashMap<>()).put(columnName, widget); + } } /** - * 构建子表控件 tableColumnList 的 columnId → 列对象 映射。 - * 过滤掉 fieldType=1 的列(如序号、合计等非真实字段)。 - * - * @param widgetJson 在线表单的 widgetJson。 - * @return columnId → 子表列对象 的映射。 + * 提取子表组件(widgetType=100) props.tableColumnList 中各列。 */ - public static Map buildSubTableColumnWidgetMap(JSONObject widgetJson) { - Map resultMap = new HashMap<>(); - walkAllModes(widgetJson, widget -> { - JSONArray widgetList = widget.getJSONArray("widgetList"); - if (widgetList == null) { - return; - } - for (int i = 0; i < widgetList.size(); i++) { - JSONObject childWidget = widgetList.getJSONObject(i); - // 只处理 widgetType=100 的子表控件 - Integer widgetType = childWidget.getInteger("widgetType"); - if (widgetType == null || widgetType != 100) { - continue; - } - JSONObject bindData = childWidget.getJSONObject("bindData"); - if (bindData == null) { - continue; - } - String tableId = bindData.getString("tableId"); - if (StrUtil.isBlank(tableId)) { - continue; - } - JSONObject props = childWidget.getJSONObject("props"); - if (props == null) { - continue; - } - JSONArray tableColumnList = props.getJSONArray("tableColumnList"); - for (int j = 0; j < tableColumnList.size(); j++) { - JSONObject tableColumn = tableColumnList.getJSONObject(j); - // 过滤掉 fieldType=1 的列(如序号、合计等非真实字段),避免被识别为需要新增的字段 - Integer fieldType = tableColumn.getInteger("fieldType"); - if (fieldType != null && fieldType == 1) { - continue; - } - resultMap.put(tableColumn.getString("columnId"), tableColumn); - } + private static void collectTableColumns(JSONObject tableWidget, Map> tableFieldMap) { + JSONObject bindData = tableWidget.getJSONObject("bindData"); + if (bindData == null) { + return; + } + String tableKey = resolveTableKey(bindData); + // 新增表(无 tableId、只有 tableName)下面不会有需要处理的字段,不注册进字段 map,由建表逻辑单独收集 + if (StrUtil.isEmpty(tableKey) || !tableKey.matches("\\d+")) { + return; + } + Map columnMap = tableFieldMap.computeIfAbsent(tableKey, k -> new HashMap<>()); + JSONObject props = tableWidget.getJSONObject("props"); + if (props == null) { + return; + } + JSONArray tableColumnList = props.getJSONArray("tableColumnList"); + if (CollUtil.isEmpty(tableColumnList)) { + return; + } + for (int i = 0; i < tableColumnList.size(); i++) { + JSONObject tableColumn = tableColumnList.getJSONObject(i); + Integer fieldType = tableColumn.getInteger("fieldType"); + if (fieldType != null && fieldType == 1) continue; + String columnIdStr = tableColumn.getString("columnId"); + String columnName; + if (StrUtil.isNotEmpty(columnIdStr) && !columnIdStr.matches("\\d+")) { + // 新增列:字段名存在 columnId 里(非数字),描述在 showName + columnName = columnIdStr; + } else { + // 已有列:字段名在 showFieldName,兜底 showName + columnName = tableColumn.getString("showFieldName"); + if (StrUtil.isEmpty(columnName)) columnName = tableColumn.getString("showName"); } - }); - return resultMap; + if (StrUtil.isEmpty(columnName)) continue; + columnMap.put(columnName, tableColumn); + } } /** - * 构建 mobile 端 columnName → widget 的映射。 - * 兼容两种结构: - * 1. mobile -> tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName - * 2. mobile -> widgetList -> childWidgetList -> bindData -> columnName (缺少 tableWidget 时) + * 解析控件所属表标识:优先 bindData.tableId,缺省用 bindData.tableName。 + */ + private static String resolveTableKey(JSONObject bindData) { + String tableId = bindData.getString("tableId"); + if (StrUtil.isNotEmpty(tableId)) { + return tableId; + } + return bindData.getString("tableName"); + } + + /** + * 回填新列 columnId 到 widgetJson 中所有归属该表且字段名匹配的控件。 + *

+ * 覆盖两类控件:叶子字段(bindData.columnName/columnFieldName == columnName)与 + * 子表列(widgetType=100 组件 props.tableColumnList 中 showFieldName/showName == columnName)。 + * 归属表用 tableId / tableName 双重校验,避免同名列误回填。 * - * @param widgetJson 在线表单的 widgetJson。 - * @return mobile 端字段名 → widget 的映射。 + * @param widgetJson widgetJson 根对象。 + * @param columnName 字段名。 + * @param columnId 新列 id。 + * @param tableId 所属表 id。 + * @param tableName 所属表名。 */ - public static Map buildMobileColumnNameWidgetMap(JSONObject widgetJson) { - Map map = new HashMap<>(); - JSONObject mobile = widgetJson.getJSONObject("mobile"); - if (mobile == null) { - return map; + public static void backfillColumnId(JSONObject widgetJson, String columnName, long columnId, Long tableId, String tableName) { + if (widgetJson == null) return; + for (String mode : new String[]{"pc", "mobile"}) { + JSONObject modeObj = widgetJson.getJSONObject(mode); + if (modeObj == null) continue; + backfillInWidgets(modeObj.getJSONArray("widgetList"), columnName, columnId, tableId, tableName); + JSONObject tableWidget = modeObj.getJSONObject("tableWidget"); + if (tableWidget != null) backfillInWidgets(tableWidget.getJSONArray("childWidgetList"), columnName, columnId, tableId, tableName); } - // 优先走原有结构: mobile.tableWidget - JSONObject tableWidget = mobile.getJSONObject("tableWidget"); - if (tableWidget != null) { - JSONArray cardList = tableWidget.getJSONArray("childWidgetList"); - if (cardList != null) { - for (int i = 0; i < cardList.size(); i++) { - JSONObject cardWidget = cardList.getJSONObject(i); - JSONArray fieldList = cardWidget.getJSONArray("childWidgetList"); - if (fieldList == null) { - continue; - } - for (int j = 0; j < fieldList.size(); j++) { - JSONObject fieldWidget = fieldList.getJSONObject(j); - JSONObject bindData = fieldWidget.getJSONObject("bindData"); - if (bindData != null && bindData.containsKey("columnName")) { - String name = bindData.getString("columnName"); - if (StrUtil.isNotEmpty(name)) { - map.put(name, fieldWidget); - } - } - } - } + } + + private static void backfillInWidgets(JSONArray widgetList, String columnName, long columnId, Long tableId, String tableName) { + if (CollUtil.isEmpty(widgetList)) return; + for (int i = 0; i < widgetList.size(); i++) { + JSONObject widget = widgetList.getJSONObject(i); + if (widget == null) continue; + Integer widgetType = widget.getInteger("widgetType"); + if (widgetType != null && widgetType == 100) { + backfillTableColumns(widget, columnName, columnId, tableId, tableName); + continue; } - return map; + JSONArray childWidgetList = widget.getJSONArray("childWidgetList"); + if (CollUtil.isNotEmpty(childWidgetList)) backfillInWidgets(childWidgetList, columnName, columnId, tableId, tableName); + JSONObject bindData = widget.getJSONObject("bindData"); + if (bindData == null) continue; + String boundColumnName = bindData.getString("columnName"); + if (StrUtil.isEmpty(boundColumnName)) boundColumnName = bindData.getString("columnFieldName"); + if (!columnName.equals(boundColumnName)) continue; + String widgetTableKey = resolveTableKey(bindData); + if (!tableMatched(widgetTableKey, tableId, tableName)) continue; + bindData.put("columnId", columnId); + bindData.put("tableId", tableId); + bindData.put("columnFieldName", columnName); } - // 兜底走新结构: mobile.widgetList -> childWidgetList -> bindData.columnName - JSONArray widgetList = mobile.getJSONArray("widgetList"); - if (widgetList != null) { - for (int i = 0; i < widgetList.size(); i++) { - JSONObject containerWidget = widgetList.getJSONObject(i); - JSONArray childWidgetList = containerWidget.getJSONArray("childWidgetList"); - if (childWidgetList == null) { - continue; - } - for (int j = 0; j < childWidgetList.size(); j++) { - JSONObject fieldWidget = childWidgetList.getJSONObject(j); - JSONObject bindData = fieldWidget.getJSONObject("bindData"); - if (bindData != null && bindData.containsKey("columnName")) { - String name = bindData.getString("columnName"); - if (StrUtil.isNotEmpty(name)) { - map.put(name, fieldWidget); - } - } - } + } + + private static void backfillTableColumns(JSONObject tableWidget, String columnName, long columnId, Long tableId, String tableName) { + JSONObject bindData = tableWidget.getJSONObject("bindData"); + JSONObject props = tableWidget.getJSONObject("props"); + if (bindData == null || props == null) return; + String widgetTableKey = resolveTableKey(bindData); + if (!tableMatched(widgetTableKey, tableId, tableName)) return; + JSONArray tableColumnList = props.getJSONArray("tableColumnList"); + if (CollUtil.isEmpty(tableColumnList)) return; + for (int i = 0; i < tableColumnList.size(); i++) { + JSONObject tableColumn = tableColumnList.getJSONObject(i); + String colIdStr = tableColumn.getString("columnId"); + String boundColumnName; + if (StrUtil.isNotEmpty(colIdStr) && !colIdStr.matches("\\d+")) { + // 新增列:字段名存在 columnId 里(非数字) + boundColumnName = colIdStr; + } else { + boundColumnName = tableColumn.getString("showFieldName"); + if (StrUtil.isEmpty(boundColumnName)) boundColumnName = tableColumn.getString("showName"); + } + if (columnName.equals(boundColumnName)) { + // 回写:columnId 写真实 id,原 columnId 值(字段名)移到 showFieldName + tableColumn.put("columnId", columnId); + tableColumn.put("showFieldName", columnName); } } - return map; + } + + private static boolean tableMatched(String widgetTableKey, Long tableId, String tableName) { + if (StrUtil.isEmpty(widgetTableKey)) return false; + if (widgetTableKey.equals(String.valueOf(tableId))) return true; + return StrUtil.isNotEmpty(tableName) && widgetTableKey.equalsIgnoreCase(tableName); } /**