Browse Source

feat(online): 优化在线表单公式计算和字段同步功能

- 将值改变事件重命名为值更新事件以提高语义清晰度
- 实现公式引用表单字段的解析和计算功能,支持表.表.字段格式引用
- 替换VALUE_CHANGE为DATA_VALUE_CHANGE属性枚举以统一数据变更处理
- 添加resolveFormulaField方法解析关联字段和分录字段的实际值
- 重构syncNewFieldsAndSubTables方法优化字段和子表同步逻辑
- 移除废弃的关联控件校验代码并简化数据验证流程
- 实现移动端子表组件的递归遍历和字段同步功能
- 添加逻辑删除标记和时间戳字段到表单字段创建过程
feature/2026-07-ccc-dev
chenchuchuan 1 day ago
parent
commit
b15ec4aba9
  1. 45
      common/common-online/src/main/java/apelet/common/online/abstractplugin/MyInterfaceRulesExecute.java
  2. 675
      common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java
  3. 478
      common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java

45
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) { public void change(String widgetVariableName, ObjectValue objectValue) {
Map values = objectValue.getValues(); Map values = objectValue.getValues();
List<Map<String, Object>> conditional = (List) values.get("conditional"); List<Map<String, Object>> conditional = (List) values.get("conditional");
conditional("值改变", widgetVariableName, conditional, objectValue); conditional("值更新", widgetVariableName, conditional, objectValue);
List<Map<String, Object>> business = (List) values.get("business"); List<Map<String, Object>> business = (List) values.get("business");
conditional("值改变", widgetVariableName, business, objectValue); conditional("值更新", widgetVariableName, business, objectValue);
} }
void conditional(String type, String widgetVariableName, List<Map<String, Object>> isEstablish, ObjectValue objectValue) { void conditional(String type, String widgetVariableName, List<Map<String, Object>> isEstablish, ObjectValue objectValue) {
@ -86,13 +86,20 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent {
String tag = split.get(0); String tag = split.get(0);
String src = split.get(1); String src = split.get(1);
if (src.contains(objectValue.getTableName())) { if (src.contains(objectValue.getTableName())) {
List<String> list = Arrays.asList(tag.split(" ")); // 公式引用表单字段(如 amount = price * qty):把 表.表.字段 引用替换为实际值后求值,再赋给目标字段
list.forEach(f -> { Map<String, Object> 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<String> list = Arrays.asList(tag.split("\\."));
super.setWidgetAttribute(list.get(list.size() - 1).trim(), AttributeEnum.DATA_VALUE_CHANGE, computed);
} else { } else {
List<String> list = Arrays.asList(tag.split("\\.")); List<String> 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 { } else {
fun = jexl3Util.getMySplit("\\$(.*?)\\$", fun); fun = jexl3Util.getMySplit("\\$(.*?)\\$", fun);
@ -100,7 +107,7 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent {
Object[] objects = jexl3Util.getFunParam(funParam, objectValue); Object[] objects = jexl3Util.getFunParam(funParam, objectValue);
Object o = onlineFormService.executeFunction(obj, fun, objects); Object o = onlineFormService.executeFunction(obj, fun, objects);
List<String> list = Arrays.asList(split.get(0).split("\\.")); List<String> 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; break;
} }
@ -279,4 +286,26 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent {
// 使用正则表达式替换所有非字母数字字符为空字符串 // 使用正则表达式替换所有非字母数字字符为空字符串
return input.replaceAll("_", "").toLowerCase(); 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;
}
} }

675
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.cache.CacheKey;
import apelet.common.core.constant.AppDeviceType; import apelet.common.core.constant.AppDeviceType;
import apelet.common.core.constant.ErrorCodeEnum; import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.constant.GlobalDeletedFlag;
import apelet.common.core.object.*; import apelet.common.core.object.*;
import apelet.common.core.util.MyCommonUtil; import apelet.common.core.util.MyCommonUtil;
import apelet.common.core.util.MyModelUtil; 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.util.WidgetJsonUtil;
import apelet.common.online.vo.OnlineFormVo; import apelet.common.online.vo.OnlineFormVo;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
@ -57,6 +57,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.groups.Default; import javax.validation.groups.Default;
import java.util.*; import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -229,116 +230,6 @@ public class OnlineFormController {
.eq(OnlineDatasourceRelation::getRelationType,0) .eq(OnlineDatasourceRelation::getRelationType,0)
.eq(OnlineDatasourceRelation::getDatasourceId,onlineFormDto.getDatasourceIdList().get(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<OnlineDatasourceRelation>()
// .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<OnlineDatasourceRelation>()
// .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()); onlineForm.setWidgetJson(jsonObject.toJSONString());
} }
@ -707,256 +598,98 @@ public class OnlineFormController {
* @return 修改后的 widgetJson (含回填的 columnId/relationId)无变更则返回 null * @return 修改后的 widgetJson (含回填的 columnId/relationId)无变更则返回 null
*/ */
private JSONObject syncNewFieldsAndSubTables(OnlineForm form, OnlineFormDto dto, JSONObject widgetJson) { private JSONObject syncNewFieldsAndSubTables(OnlineForm form, OnlineFormDto dto, JSONObject widgetJson) {
// 1. 获取数据源 → OnlineTable → OnlFormHead // 1. 获取数据源 → 主表 → 主表 head
OnlineDatasource datasource = onlineDatasourceService.getById(dto.getDatasourceIdList().get(0)); OnlineDatasource datasource = onlineDatasourceService.getById(dto.getDatasourceIdList().get(0));
OnlineTable onlineTable = onlineTableService.getById(datasource.getMasterTableId()); OnlineTable masterTable = onlineTableService.getById(datasource.getMasterTableId());
OnlFormHead masterHead = onlFormHeadService.getOne(new LambdaQueryWrapper<OnlFormHead>().eq(OnlFormHead::getTableName, masterTable.getTableName()));
OnlFormHead onlFormHead = onlFormHeadService.getOne( if (masterHead == null) return null;
new LambdaQueryWrapper<OnlFormHead>()
.eq(OnlFormHead::getTableName, onlineTable.getTableName()));
if (onlFormHead == null) {
return null;
}
boolean changed = false; boolean changed = false;
// === Diff 新增字段 === // === 统一取值:pc/mobile 所有"表-字段"绑定关系(表标识 → 字段名 → 控件) ===
// 收集 pc/mobile 端所有绑定的字段列名 Map<String, Map<String, JSONObject>> tableFieldMap = WidgetJsonUtil.buildTableFieldMap(widgetJson);
Set<String> widgetColumnNames = WidgetJsonUtil.collectBindColumnNames(widgetJson);
// === 新增表:widgetType=100 且无 tableId(只有 tableName)的表统一先建表 ===
List<OnlFormField> existingFields = onlFormFieldService.list(new LambdaQueryWrapper<OnlFormField>().eq(OnlFormField::getHeadId, onlFormHead.getId())); Set<String> newTableNames = WidgetJsonUtil.collectNewSubTableNames(widgetJson);
Set<String> existingFieldNames = existingFields.stream() if (!newTableNames.isEmpty()) changed |= createNewSubTables(widgetJson, masterHead, datasource, newTableNames);
.map(OnlFormField::getFieldName).collect(Collectors.toSet());
Set<String> newColumnNames = new HashSet<>(widgetColumnNames); // === 逐表 diff 新增字段 ===
newColumnNames.removeAll(existingFieldNames); for (Map.Entry<String, Map<String, JSONObject>> entry : tableFieldMap.entrySet()) {
// 主表/单表已有字段:如果绑定了从表(slaveTableId),同步更新 refPropert String tableKey = entry.getKey();
Map<String, JSONObject> fieldNameToWidget = WidgetJsonUtil.buildFieldNameWidgetMap(widgetJson); Map<String, JSONObject> columnWidgetMap = entry.getValue();
for (OnlFormField existingField : existingFields) { if (columnWidgetMap.isEmpty()) continue;
JSONObject widget = fieldNameToWidget.get(existingField.getFieldName()); // 解析表:纯数字 → 已有表(主表/单表/附表统一处理);字符串 → 刚创建的新表
if (widget == null) { OnlineTable table = tableKey.matches("\\d+") ? onlineTableService.getById(Long.valueOf(tableKey)) : onlineTableService.getOne(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, tableKey.toLowerCase()));
continue; if (table == null) continue;
} OnlFormHead head = onlFormHeadService.getOne(new LambdaQueryWrapper<OnlFormHead>().eq(OnlFormHead::getTableName, table.getTableName()));
JSONObject bindData = widget.getJSONObject("bindData"); if (head == null) continue;
if (bindData == null) {
continue; List<OnlFormField> existingFields = onlFormFieldService.list(new LambdaQueryWrapper<OnlFormField>().eq(OnlFormField::getHeadId, head.getId()));
} Map<String, OnlFormField> fieldByColumnName = existingFields.stream().collect(Collectors.toMap(OnlFormField::getFieldName, Function.identity(), (a, b) -> a));
String slaveTableId = bindData.getString("slaveTableId");
if (StrUtil.isNotEmpty(slaveTableId)) { List<String> newColumnNames = new ArrayList<>();
existingField.setRefPropert(Long.valueOf(slaveTableId)); for (Map.Entry<String, JSONObject> columnEntry : columnWidgetMap.entrySet()) {
onlFormFieldService.updateById(existingField); String columnName = columnEntry.getKey();
} JSONObject widget = columnEntry.getValue();
} OnlFormField existingField = fieldByColumnName.get(columnName);
if (!newColumnNames.isEmpty()) { if (existingField != null) {
Map<String, JSONObject> nameToWidget = WidgetJsonUtil.buildColumnNameWidgetMap(widgetJson); // 已有字段:带 slaveTableId 则更新 refPropert(子表列仅关联列 comType=402 生效)
// 收集 mobile 端的 widget 映射(遍历 childWidgetList 路径) boolean isTableColumn = !widget.containsKey("bindData");
Map<String, JSONObject> mobileNameToWidget = WidgetJsonUtil.buildMobileColumnNameWidgetMap(widgetJson); Integer comType = widget.getInteger("comType");
OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId()); boolean isAssociation = !isTableColumn || (comType != null && comType == 402);
String slaveTableId = extractSlaveTableId(widget);
// 预校验:过滤掉 widgetType 不在映射中的控件,避免 getFieldTypeId 抛异常导致部分数据已入库 if (isAssociation && StrUtil.isNotEmpty(slaveTableId)) {
Set<String> validColumnNames = new LinkedHashSet<>(); existingField.setRefPropert(Long.valueOf(slaveTableId));
for (String fieldName : newColumnNames) { onlFormFieldService.updateById(existingField);
JSONObject widget = nameToWidget.get(fieldName); changed = true;
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<SqlTableColumn> allColumns = onlineDblinkService.getDblinkTableColumnList(
dblink, onlineTable.getTableName());
Map<String, SqlTableColumn> 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);
}
} }
continue;
} }
} // 新增字段:识别"字段控件"还是"子表列",非数据控件跳过
redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineTableKey(onlineTable.getTableId())).delete(); boolean isTableColumn = !widget.containsKey("bindData");
redissonClient.getBucket(CacheKey.makeTableKey(onlineTable.getTableName())).delete(); Integer widgetType = widget.getInteger("widgetType");
redissonClient.getBucket(CacheKey.makeChildFieldListKey(onlFormHead.getId())).delete(); if (!isTableColumn && (widgetType == null || !WidgetFieldTypeMapping.isDataWidget(widgetType))) continue;
metaCacheService.removeData(onlineTable.getTableName()); if (isTableColumn) {
changed = true; // 子表列:字段类型沿用现状,硬编码 750
} this.saveField(750, widget.getString("showName"), null, head, columnName, extractSlaveTableId(widget));
// === Diff 新增子表 ===
// 取 widgetList->bindData -> tableId 在去 zz_online_table 查询,这样就可以判断哪些是新增,哪些是 已有的表
// 如果存在附表
Map<String, JSONObject> tableIdAndPropMap = WidgetJsonUtil.collectSubTableProps(widgetJson);
if (MapUtil.isNotEmpty(tableIdAndPropMap)) {
//搜集的所有tableId, 要么是 tableId(已存在),要么是 tableName(不存在)
Set<String> allTableIdSet = tableIdAndPropMap.keySet();
Set<String> newSubTableNames = new HashSet<>();
Set<Long> existingSubTableIdSet = new HashSet<>();
for (String tableId : allTableIdSet) {
if (tableId.matches("\\d+")) {
existingSubTableIdSet.add(Long.parseLong(tableId));
} else { } 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);
} }
} newColumnNames.add(columnName);
//分两步, changed = true;
//step1 : 看看存在的附表,是否存在不存在的字段 }
if (!existingSubTableIdSet.isEmpty()) {
// 拿到tableId 下面的所有字段,先查询 zz_online_table ,在根据 tableName 查询 onl_form_head 表 if (!newColumnNames.isEmpty()) {
List<OnlineTable> tableList = onlineTableService.listByIds(existingSubTableIdSet); // ALTER TABLE ADD COLUMN
Map<Long, String> tableIdAndNameMap = tableList.stream().collect(Collectors.toMap(OnlineTable::getTableId, OnlineTable::getTableName)); onlFormHeadService.syncDB(head, 0L);
List<String> tableNames = tableList.stream().map(OnlineTable::getTableName).collect(Collectors.toList()); // 批量获取列信息,避免 N+1
List<OnlFormHead> onlFormHeadList = onlFormHeadService.list(new LambdaQueryWrapper<OnlFormHead>().in(OnlFormHead::getTableName, tableNames)); OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId());
Map<String, OnlFormHead> tableNameAndHeadIdMap = onlFormHeadList.stream().collect(Collectors.toMap(OnlFormHead::getTableName, Function.identity())); List<SqlTableColumn> allColumns = onlineDblinkService.getDblinkTableColumnList(dblink, table.getTableName());
//因为这里是zz_online_column.column_id 的id和 列名的混合,我只要列名 Map<String, SqlTableColumn> columnMap = new HashMap<>();
Map<String, JSONObject> nameToWidget = WidgetJsonUtil.buildSubTableColumnWidgetMap(widgetJson); if (CollUtil.isNotEmpty(allColumns)) {
Set<String> newSubColumnNameSet = nameToWidget.keySet().stream().filter(s -> !s.matches("\\d+")).collect(Collectors.toSet()); for (SqlTableColumn col : allColumns) {
//这里要走 新增逻辑 if (col.getColumnName() != null) columnMap.put(col.getColumnName().toLowerCase(), col);
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);
} }
onlFormHeadService.syncDB(entryHead, 0L);
//step 2 :在保存 OnlineColumn 表
OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId());
List<SqlTableColumn> allColumns = onlineDblinkService.getDblinkTableColumnList(dblink, tableName);
Map<String, SqlTableColumn> 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;
} }
// 增量创建 OnlineColumn + 回填 columnId 到所有匹配控件
// 附表已有字段:检查 tableColumnList 中 comType=402 的列是否有 slaveTableId,同步更新 refPropert for (String columnName : newColumnNames) {
for (Long tableId : existingSubTableIdSet) { SqlTableColumn sqlCol = columnMap.get(columnName.toLowerCase());
String tableName = tableIdAndNameMap.get(tableId); if (sqlCol == null) continue;
if (tableName == null) { long columnId = onlineColumnService.saveBySqlTable(sqlCol, table.getTableId(), columnWidgetMap.get(columnName).getString("showName"));
continue; WidgetJsonUtil.backfillColumnId(widgetJson, columnName, columnId, table.getTableId(), table.getTableName());
}
OnlFormHead subHead = tableNameAndHeadIdMap.get(tableName);
if (subHead == null) {
continue;
}
List<OnlFormField> subFields = onlFormFieldService.list(
new LambdaQueryWrapper<OnlFormField>().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;
}
}
} }
} }
//step2 : 不存在的附表,新增 // 删除缓存
changed |= createNewSubTables(widgetJson, onlFormHead, datasource, newSubTableNames); redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineTableKey(table.getTableId())).delete();
} else { redissonClient.getBucket(CacheKey.makeTableKey(table.getTableName())).delete();
// tableIdAndPropMap 为空,说明走 tableId 取值没取到,兜底通过 tableName 收集附表并创建 redissonClient.getBucket(CacheKey.makeChildFieldListKey(head.getId())).delete();
changed |= createNewSubTables(widgetJson, onlFormHead, datasource, new HashSet<>()); metaCacheService.removeData(table.getTableName());
} }
// === Diff mobile 端子表 ===
// 路径: mobile -> widgetList -> [widgetType=102] -> bindData -> tableName/tableId // === mobile 端子表(widgetType=102)专用同步,保持原有逻辑 ===
changed |= syncMobileSubTables(widgetJson, onlFormHead, datasource); changed |= syncMobileSubTables(widgetJson, masterHead, datasource);
return changed ? widgetJson : null; return changed ? widgetJson : null;
} }
@ -1009,6 +742,21 @@ public class OnlineFormController {
} }
/**
* 提取控件/子表列绑定的从表 idslaveTableId
* 字段控件在 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) { private void saveField(Integer widgetType, String columnComment, String showName, OnlFormHead onlFormHead, String fieldName, String refPropert) {
OnlFormField field = new OnlFormField(); OnlFormField field = new OnlFormField();
field.setHeadId(onlFormHead.getId()); field.setHeadId(onlFormHead.getId());
@ -1025,6 +773,12 @@ public class OnlineFormController {
field.setIsDefault(0); field.setIsDefault(0);
field.setRowDisabled(0); field.setRowDisabled(0);
field.setSyncFlag(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 设置字段备注 // 从 widget 的 bindData.columnComment 设置字段备注
field.setFieldRemark(columnComment); field.setFieldRemark(columnComment);
@ -1037,31 +791,41 @@ public class OnlineFormController {
// ============ mobile 端子表同步相关方法 ============ // ============ mobile 端子表同步相关方法 ============
/** /**
* 递归遍历 mobile 控件树childWidgetList收集所有 widgetType=102 的子表组件
* 102 可嵌套在分组/卡片等容器内
*/
private void walkMobileSubTableWidgets(JSONArray widgetList, Consumer<JSONObject> 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 端的子表(新增子表 + 已有子表的新增字段) * 同步 mobile 端的子表(新增子表 + 已有子表的新增字段)
* 规则bindData tableId 已有表向下找字段只有 tableName 新增表不找字段建表后回写 tableId
* 子表: mobile -> widgetList -> [widgetType=102] -> bindData -> tableName/tableId * 子表: mobile -> widgetList -> [widgetType=102] -> bindData -> tableName/tableId
* 字段: mobile -> widgetList -> childWidgetList -> childWidgetList -> bindData -> columnName * 字段: mobile -> widgetList -> childWidgetList -> childWidgetList -> bindData -> columnName
*/ */
private boolean syncMobileSubTables(JSONObject widgetJson, OnlFormHead onlFormHead, OnlineDatasource datasource) { private boolean syncMobileSubTables(JSONObject widgetJson, OnlFormHead onlFormHead, OnlineDatasource datasource) {
JSONObject mobile = widgetJson.getJSONObject("mobile"); JSONObject mobile = widgetJson.getJSONObject("mobile");
if (mobile == null) { if (mobile == null) return false;
return false;
}
JSONArray mobileWidgetList = mobile.getJSONArray("widgetList"); JSONArray mobileWidgetList = mobile.getJSONArray("widgetList");
if (mobileWidgetList == null) { if (mobileWidgetList == null) return false;
return false;
}
boolean changed = false; boolean changed = false;
Set<String> newSubTableNames = new LinkedHashSet<>(); Set<String> newSubTableNames = new LinkedHashSet<>();
Set<Long> existingSubTableIds = new LinkedHashSet<>(); Set<Long> existingSubTableIds = new LinkedHashSet<>();
for (int i = 0; i < mobileWidgetList.size(); i++) { walkMobileSubTableWidgets(mobileWidgetList, widget -> {
JSONObject widget = mobileWidgetList.getJSONObject(i);
if (widget.getInteger("widgetType") == null || widget.getInteger("widgetType") != 102) {
continue;
}
JSONObject bindData = widget.getJSONObject("bindData"); JSONObject bindData = widget.getJSONObject("bindData");
if (bindData == null) { if (bindData == null) return;
continue;
}
String tableName = bindData.getString("tableName"); String tableName = bindData.getString("tableName");
String tableIdStr = bindData.getString("tableId"); String tableIdStr = bindData.getString("tableId");
if (StrUtil.isNotEmpty(tableName) && StrUtil.isEmpty(tableIdStr)) { if (StrUtil.isNotEmpty(tableName) && StrUtil.isEmpty(tableIdStr)) {
@ -1069,7 +833,7 @@ public class OnlineFormController {
} else if (StrUtil.isNotEmpty(tableIdStr)) { } else if (StrUtil.isNotEmpty(tableIdStr)) {
existingSubTableIds.add(Long.valueOf(tableIdStr)); existingSubTableIds.add(Long.valueOf(tableIdStr));
} }
} });
// 处理新子表: 创建后回写 tableId + masterColumnName // 处理新子表: 创建后回写 tableId + masterColumnName
if (!newSubTableNames.isEmpty()) { if (!newSubTableNames.isEmpty()) {
changed |= createNewSubTables(widgetJson, onlFormHead, datasource, newSubTableNames); changed |= createNewSubTables(widgetJson, onlFormHead, datasource, newSubTableNames);
@ -1088,35 +852,21 @@ public class OnlineFormController {
*/ */
private void backfillMobileSubTableId(JSONObject widgetJson, Set<String> newSubTableNames) { private void backfillMobileSubTableId(JSONObject widgetJson, Set<String> newSubTableNames) {
JSONObject mobile = widgetJson.getJSONObject("mobile"); JSONObject mobile = widgetJson.getJSONObject("mobile");
if (mobile == null) { if (mobile == null) return;
return;
}
JSONArray mobileWidgetList = mobile.getJSONArray("widgetList"); JSONArray mobileWidgetList = mobile.getJSONArray("widgetList");
if (mobileWidgetList == null) { if (mobileWidgetList == null) return;
return; walkMobileSubTableWidgets(mobileWidgetList, widget -> {
}
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"); JSONObject bindData = widget.getJSONObject("bindData");
if (bindData == null) { if (bindData == null) return;
continue;
}
String tableName = bindData.getString("tableName"); String tableName = bindData.getString("tableName");
if (StrUtil.isEmpty(tableName) || !newSubTableNames.contains(tableName)) { if (StrUtil.isEmpty(tableName) || !newSubTableNames.contains(tableName)) return;
continue; // createNewSubTables 只回写了 100 组件的 tableId,102 组件的在这里补
}
// createNewSubTables 已通过 WidgetJsonUtil.backfillTableId 回写了 tableId,这里只需补 masterColumnName
if (!bindData.containsKey("tableId")) { if (!bindData.containsKey("tableId")) {
OnlineTable subTable = onlineTableService.getOne(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, tableName.toLowerCase())); OnlineTable subTable = onlineTableService.getOne(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, tableName.toLowerCase()));
if (subTable != null) { if (subTable != null) bindData.put("tableId", subTable.getTableId());
bindData.put("tableId", subTable.getTableId());
}
} }
bindData.put("masterColumnName", "id"); bindData.put("masterColumnName", "id");
} });
} }
/** /**
@ -1124,105 +874,92 @@ public class OnlineFormController {
* 字段路径: mobile -> widgetList -> [widgetType=102] -> childWidgetList -> childWidgetList -> bindData -> columnName * 字段路径: mobile -> widgetList -> [widgetType=102] -> childWidgetList -> childWidgetList -> bindData -> columnName
*/ */
private boolean syncMobileSubTableNewFields(JSONObject widgetJson, OnlineDatasource datasource, Set<Long> existingSubTableIds) { private boolean syncMobileSubTableNewFields(JSONObject widgetJson, OnlineDatasource datasource, Set<Long> existingSubTableIds) {
// 查询已有子表信息
List<OnlineTable> subTables = onlineTableService.listByIds(existingSubTableIds); List<OnlineTable> subTables = onlineTableService.listByIds(existingSubTableIds);
if (CollUtil.isEmpty(subTables)) { if (CollUtil.isEmpty(subTables)) return false;
return false;
}
Map<Long, String> tableIdToName = subTables.stream().collect(Collectors.toMap(OnlineTable::getTableId, OnlineTable::getTableName)); Map<Long, String> tableIdToName = subTables.stream().collect(Collectors.toMap(OnlineTable::getTableId, OnlineTable::getTableName));
List<String> tableNames = subTables.stream().map(OnlineTable::getTableName).collect(Collectors.toList()); List<String> tableNames = subTables.stream().map(OnlineTable::getTableName).collect(Collectors.toList());
List<OnlFormHead> subHeads = onlFormHeadService.list(new LambdaQueryWrapper<OnlFormHead>().in(OnlFormHead::getTableName, tableNames)); List<OnlFormHead> subHeads = onlFormHeadService.list(new LambdaQueryWrapper<OnlFormHead>().in(OnlFormHead::getTableName, tableNames));
Map<String, OnlFormHead> tableNameToHead = subHeads.stream().collect(Collectors.toMap(OnlFormHead::getTableName, Function.identity())); Map<String, OnlFormHead> tableNameToHead = subHeads.stream().collect(Collectors.toMap(OnlFormHead::getTableName, Function.identity()));
OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId()); OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId());
// 遍历 mobile 端已有子表 widget, 搜集新增字段
JSONObject mobile = widgetJson.getJSONObject("mobile"); JSONObject mobile = widgetJson.getJSONObject("mobile");
if (mobile == null) return false;
JSONArray mobileWidgetList = mobile.getJSONArray("widgetList"); JSONArray mobileWidgetList = mobile.getJSONArray("widgetList");
boolean changed = false; if (mobileWidgetList == null) return false;
for (int i = 0; i < mobileWidgetList.size(); i++) { boolean[] changed = {false};
JSONObject widget = mobileWidgetList.getJSONObject(i); walkMobileSubTableWidgets(mobileWidgetList, widget -> {
if (widget.getInteger("widgetType") == null || widget.getInteger("widgetType") != 102) { if (syncMobileSubTableNewFieldForWidget(widget, tableIdToName, tableNameToHead, dblink)) changed[0] = true;
continue; });
} return changed[0];
JSONObject bindData = widget.getJSONObject("bindData"); }
if (bindData == null) {
continue; /**
} * 处理单个已有子表widgetType=102下的新增字段返回是否有变更
String tableIdStr = bindData.getString("tableId"); */
if (StrUtil.isEmpty(tableIdStr)) { private boolean syncMobileSubTableNewFieldForWidget(JSONObject widget, Map<Long, String> tableIdToName, Map<String, OnlFormHead> tableNameToHead, OnlineDblink dblink) {
continue; JSONObject bindData = widget.getJSONObject("bindData");
} if (bindData == null) return false;
Long tableId = Long.valueOf(tableIdStr); String tableIdStr = bindData.getString("tableId");
String tableName = tableIdToName.get(tableId); if (StrUtil.isEmpty(tableIdStr)) return false;
if (tableName == null) { Long tableId = Long.valueOf(tableIdStr);
continue; String tableName = tableIdToName.get(tableId);
} if (tableName == null) return false;
OnlFormHead subHead = tableNameToHead.get(tableName); OnlFormHead subHead = tableNameToHead.get(tableName);
if (subHead == null) { if (subHead == null) return false;
continue; // 搜集该子表下无 columnId 的字段
} List<JSONObject> newFieldWidgets = new ArrayList<>();
// 搜集该子表下无 columnId 的字段 JSONArray cardList = widget.getJSONArray("childWidgetList");
List<JSONObject> newFieldWidgets = new ArrayList<>(); if (cardList != null) {
JSONArray cardList = widget.getJSONArray("childWidgetList"); for (int j = 0; j < cardList.size(); j++) {
if (cardList != null) { JSONObject card = cardList.getJSONObject(j);
for (int j = 0; j < cardList.size(); j++) { JSONArray fieldList = card.getJSONArray("childWidgetList");
JSONObject card = cardList.getJSONObject(j); if (fieldList == null) continue;
JSONArray fieldList = card.getJSONArray("childWidgetList"); for (int k = 0; k < fieldList.size(); k++) {
if (fieldList == null) { JSONObject field = fieldList.getJSONObject(k);
continue; JSONObject fieldBd = field.getJSONObject("bindData");
} if (fieldBd != null && fieldBd.containsKey("columnName") && !fieldBd.containsKey("columnId")) {
for (int k = 0; k < fieldList.size(); k++) { newFieldWidgets.add(field);
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; if (newFieldWidgets.isEmpty()) return false;
} // 创建字段记录
// 创建字段记录 for (JSONObject fieldWidget : newFieldWidgets) {
for (JSONObject fieldWidget : newFieldWidgets) { JSONObject fieldBd = fieldWidget.getJSONObject("bindData");
JSONObject fieldBd = fieldWidget.getJSONObject("bindData"); String columnName = fieldBd.getString("columnName");
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<SqlTableColumn> allColumns = onlineDblinkService.getDblinkTableColumnList(dblink, tableName);
Map<String, SqlTableColumn> 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"); String showName = fieldWidget.getString("showName");
this.saveField(fieldWidget.getInteger("widgetType"), fieldBd.getString("columnComment"), showName, subHead, columnName, null); long columnId = onlineColumnService.saveBySqlTable(sqlCol, tableId, showName);
} fieldBd.put("columnId", columnId);
// ALTER TABLE ADD COLUMN fieldBd.put("tableId", tableId);
onlFormHeadService.syncDB(subHead, 0L); fieldBd.put("columnFieldName", columnName);
// 批量获取列信息
List<SqlTableColumn> allColumns = onlineDblinkService.getDblinkTableColumnList(dblink, tableName);
Map<String, SqlTableColumn> 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);
}
} }
// 删除缓存
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;
} }
/** /**

478
common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java

@ -75,152 +75,6 @@ public class WidgetJsonUtil {
} }
/** /**
* 遍历 pc/mobile 控件树收集所有控件 bindData.columnName
* <p>
* 每个读取源用"设备端 + 字段步骤链"描述步骤支持对象字段 obj() 和数组字段 arr()
* <pre>
* pc: widgetList -> bindData -> columnName
* mobile: tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName
* mobile: widgetList -> childWidgetList -> bindData -> columnName
* </pre>
* 后续新增读取源 otherWidgetList只需在下方追加一行
* {@code collectColumnNamesByPath(widgetJson, names, "other", arr("otherWidgetList"))}
*
* @param widgetJson 在线表单的 widgetJson
* @return 绑定的字段名列集合
*/
public static Set<String> collectBindColumnNames(JSONObject widgetJson) {
Set<String> 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<String> 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<String> 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<String> 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<String, JSONObject> collectSubTableProps(JSONObject widgetJson) {
Map<String, JSONObject> 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 的名称集合 * 收集子表控件(widgetType=100) bindData.tableName 的名称集合
* *
* @param widgetJson 在线表单的 widgetJson * @param widgetJson 在线表单的 widgetJson
@ -253,160 +107,240 @@ public class WidgetJsonUtil {
} }
/** /**
* 构建 columnName 控件 widget 的映射 * 收集需要新增的子表名称widgetType=100 bindData 只有 tableName tableId的表
* 新增表下面通常不存在需要处理的字段故不注册进字段 map collectTableColumns
* 仅在此单独收集用于建表递归遍历 childWidgetList buildTableFieldMap 取值保持一致
* *
* @param widgetJson 在线表单的 widgetJson * @param widgetJson 在线表单的 widgetJson
* @return 字段名 widget 的映射 * @return 新增子表名称集合
*/ */
public static Map<String, JSONObject> buildColumnNameWidgetMap(JSONObject widgetJson) { public static Set<String> collectNewSubTableNames(JSONObject widgetJson) {
Map<String, JSONObject> map = new HashMap<>(); Set<String> tableNameSet = new HashSet<>();
walkAllModes(widgetJson, widget -> { if (widgetJson == null) return tableNameSet;
JSONObject bindData = widget.getJSONObject("bindData"); for (String mode : new String[]{"pc", "mobile"}) {
if (bindData != null && bindData.containsKey("columnName")) { JSONObject modeObj = widgetJson.getJSONObject(mode);
String name = bindData.getString("columnName"); if (modeObj == null) continue;
if (StrUtil.isNotEmpty(name)) { collectNewSubTableNames(modeObj.getJSONArray("widgetList"), tableNameSet);
map.put(name, widget); JSONObject tableWidget = modeObj.getJSONObject("tableWidget");
if (tableWidget != null) collectNewSubTableNames(tableWidget.getJSONArray("childWidgetList"), tableNameSet);
}
return tableNameSet;
}
private static void collectNewSubTableNames(JSONArray widgetList, Set<String> 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;
} }
}); JSONArray childWidgetList = widget.getJSONArray("childWidgetList");
return map; if (CollUtil.isNotEmpty(childWidgetList)) collectNewSubTableNames(childWidgetList, tableNameSet);
}
} }
/** /**
* 构建 fieldName widget 的映射用于主表已有字段匹配 * 统一收集 pc/mobile 控件树中所有"表-字段"绑定关系
* key 优先取 bindData.columnFieldName兼容 bindData.columnName * <p>
* 返回嵌套 Map外层 key = 表标识bindData.tableId缺省用 bindData.tableName用于尚未创建的新表
* 内层 key = 字段名value = 对应的控件对象叶子字段控件 子表列对象
* <p>
* 收集规则
* <ul>
* <li>widgetType=100子表组件 props.tableColumnList 各列字段名取 showFieldName兜底 showName/columnId跳过 fieldType=1 的序号/合计列</li>
* <li>widgetType=102移动端子表组件跳过由移动端子表专用逻辑处理避免重复</li>
* <li>容器组件childWidgetList 非空递归收集</li>
* <li>叶子控件 bindData.columnName bindData.tableId/tableName</li>
* </ul>
* mobile 兼容两种结构widgetList tableWidget childWidgetList
* *
* @param widgetJson 在线表单的 widgetJson * @param widgetJson 在线表单的 widgetJson
* @return 字段名 widget 的映射 * @return 表标识 (字段名 控件对象) 的嵌套映射
*/ */
public static Map<String, JSONObject> buildFieldNameWidgetMap(JSONObject widgetJson) { public static Map<String, Map<String, JSONObject>> buildTableFieldMap(JSONObject widgetJson) {
Map<String, JSONObject> map = new HashMap<>(); Map<String, Map<String, JSONObject>> tableFieldMap = new HashMap<>();
walkAllModes(widgetJson, widget -> { if (widgetJson == null) return tableFieldMap;
JSONObject bindData = widget.getJSONObject("bindData"); for (String mode : new String[]{"pc", "mobile"}) {
if (bindData == null) { JSONObject modeObj = widgetJson.getJSONObject(mode);
return; if (modeObj == null) continue;
} // 新结构:widgetList(递归收集)
String fieldName = bindData.getString("columnFieldName"); collectTableFields(modeObj.getJSONArray("widgetList"), tableFieldMap);
if (StrUtil.isEmpty(fieldName)) { // mobile 老结构兜底:tableWidget -> childWidgetList -> childWidgetList
fieldName = bindData.getString("columnName"); JSONObject tableWidget = modeObj.getJSONObject("tableWidget");
if (tableWidget != null) collectTableFields(tableWidget.getJSONArray("childWidgetList"), tableFieldMap);
}
return tableFieldMap;
}
/**
* 递归收集控件树中的"表-字段"绑定关系
*/
private static void collectTableFields(JSONArray widgetList, Map<String, Map<String, JSONObject>> 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)) { if (widgetType != null && widgetType == 102) continue;
map.put(fieldName, widget); JSONArray childWidgetList = widget.getJSONArray("childWidgetList");
if (CollUtil.isNotEmpty(childWidgetList)) {
collectTableFields(childWidgetList, tableFieldMap);
continue;
} }
}); JSONObject bindData = widget.getJSONObject("bindData");
return map; 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 列对象 映射 * 提取子表组件(widgetType=100) props.tableColumnList 中各列
* 过滤掉 fieldType=1 的列如序号合计等非真实字段
*
* @param widgetJson 在线表单的 widgetJson
* @return columnId 子表列对象 的映射
*/ */
public static Map<String, JSONObject> buildSubTableColumnWidgetMap(JSONObject widgetJson) { private static void collectTableColumns(JSONObject tableWidget, Map<String, Map<String, JSONObject>> tableFieldMap) {
Map<String, JSONObject> resultMap = new HashMap<>(); JSONObject bindData = tableWidget.getJSONObject("bindData");
walkAllModes(widgetJson, widget -> { if (bindData == null) {
JSONArray widgetList = widget.getJSONArray("widgetList"); return;
if (widgetList == null) { }
return; String tableKey = resolveTableKey(bindData);
} // 新增表(无 tableId、只有 tableName)下面不会有需要处理的字段,不注册进字段 map,由建表逻辑单独收集
for (int i = 0; i < widgetList.size(); i++) { if (StrUtil.isEmpty(tableKey) || !tableKey.matches("\\d+")) {
JSONObject childWidget = widgetList.getJSONObject(i); return;
// 只处理 widgetType=100 的子表控件 }
Integer widgetType = childWidget.getInteger("widgetType"); Map<String, JSONObject> columnMap = tableFieldMap.computeIfAbsent(tableKey, k -> new HashMap<>());
if (widgetType == null || widgetType != 100) { JSONObject props = tableWidget.getJSONObject("props");
continue; if (props == null) {
} return;
JSONObject bindData = childWidget.getJSONObject("bindData"); }
if (bindData == null) { JSONArray tableColumnList = props.getJSONArray("tableColumnList");
continue; if (CollUtil.isEmpty(tableColumnList)) {
} return;
String tableId = bindData.getString("tableId"); }
if (StrUtil.isBlank(tableId)) { for (int i = 0; i < tableColumnList.size(); i++) {
continue; JSONObject tableColumn = tableColumnList.getJSONObject(i);
} Integer fieldType = tableColumn.getInteger("fieldType");
JSONObject props = childWidget.getJSONObject("props"); if (fieldType != null && fieldType == 1) continue;
if (props == null) { String columnIdStr = tableColumn.getString("columnId");
continue; String columnName;
} if (StrUtil.isNotEmpty(columnIdStr) && !columnIdStr.matches("\\d+")) {
JSONArray tableColumnList = props.getJSONArray("tableColumnList"); // 新增列:字段名存在 columnId 里(非数字),描述在 showName
for (int j = 0; j < tableColumnList.size(); j++) { columnName = columnIdStr;
JSONObject tableColumn = tableColumnList.getJSONObject(j); } else {
// 过滤掉 fieldType=1 的列(如序号、合计等非真实字段),避免被识别为需要新增的字段 // 已有列:字段名在 showFieldName,兜底 showName
Integer fieldType = tableColumn.getInteger("fieldType"); columnName = tableColumn.getString("showFieldName");
if (fieldType != null && fieldType == 1) { if (StrUtil.isEmpty(columnName)) columnName = tableColumn.getString("showName");
continue;
}
resultMap.put(tableColumn.getString("columnId"), tableColumn);
}
} }
}); if (StrUtil.isEmpty(columnName)) continue;
return resultMap; columnMap.put(columnName, tableColumn);
}
} }
/** /**
* 构建 mobile columnName widget 的映射 * 解析控件所属表标识优先 bindData.tableId缺省用 bindData.tableName
* 兼容两种结构: */
* 1. mobile -> tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName private static String resolveTableKey(JSONObject bindData) {
* 2. mobile -> widgetList -> childWidgetList -> bindData -> columnName (缺少 tableWidget ) String tableId = bindData.getString("tableId");
if (StrUtil.isNotEmpty(tableId)) {
return tableId;
}
return bindData.getString("tableName");
}
/**
* 回填新列 columnId widgetJson 中所有归属该表且字段名匹配的控件
* <p>
* 覆盖两类控件叶子字段bindData.columnName/columnFieldName == columnName
* 子表列widgetType=100 组件 props.tableColumnList showFieldName/showName == columnName
* 归属表用 tableId / tableName 双重校验避免同名列误回填
* *
* @param widgetJson 在线表单的 widgetJson * @param widgetJson widgetJson 根对象
* @return mobile 端字段名 widget 的映射 * @param columnName 字段名
* @param columnId 新列 id
* @param tableId 所属表 id
* @param tableName 所属表名
*/ */
public static Map<String, JSONObject> buildMobileColumnNameWidgetMap(JSONObject widgetJson) { public static void backfillColumnId(JSONObject widgetJson, String columnName, long columnId, Long tableId, String tableName) {
Map<String, JSONObject> map = new HashMap<>(); if (widgetJson == null) return;
JSONObject mobile = widgetJson.getJSONObject("mobile"); for (String mode : new String[]{"pc", "mobile"}) {
if (mobile == null) { JSONObject modeObj = widgetJson.getJSONObject(mode);
return map; 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) { private static void backfillInWidgets(JSONArray widgetList, String columnName, long columnId, Long tableId, String tableName) {
JSONArray cardList = tableWidget.getJSONArray("childWidgetList"); if (CollUtil.isEmpty(widgetList)) return;
if (cardList != null) { for (int i = 0; i < widgetList.size(); i++) {
for (int i = 0; i < cardList.size(); i++) { JSONObject widget = widgetList.getJSONObject(i);
JSONObject cardWidget = cardList.getJSONObject(i); if (widget == null) continue;
JSONArray fieldList = cardWidget.getJSONArray("childWidgetList"); Integer widgetType = widget.getInteger("widgetType");
if (fieldList == null) { if (widgetType != null && widgetType == 100) {
continue; backfillTableColumns(widget, columnName, columnId, tableId, tableName);
} 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);
}
}
}
}
} }
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) { private static void backfillTableColumns(JSONObject tableWidget, String columnName, long columnId, Long tableId, String tableName) {
for (int i = 0; i < widgetList.size(); i++) { JSONObject bindData = tableWidget.getJSONObject("bindData");
JSONObject containerWidget = widgetList.getJSONObject(i); JSONObject props = tableWidget.getJSONObject("props");
JSONArray childWidgetList = containerWidget.getJSONArray("childWidgetList"); if (bindData == null || props == null) return;
if (childWidgetList == null) { String widgetTableKey = resolveTableKey(bindData);
continue; if (!tableMatched(widgetTableKey, tableId, tableName)) return;
} JSONArray tableColumnList = props.getJSONArray("tableColumnList");
for (int j = 0; j < childWidgetList.size(); j++) { if (CollUtil.isEmpty(tableColumnList)) return;
JSONObject fieldWidget = childWidgetList.getJSONObject(j); for (int i = 0; i < tableColumnList.size(); i++) {
JSONObject bindData = fieldWidget.getJSONObject("bindData"); JSONObject tableColumn = tableColumnList.getJSONObject(i);
if (bindData != null && bindData.containsKey("columnName")) { String colIdStr = tableColumn.getString("columnId");
String name = bindData.getString("columnName"); String boundColumnName;
if (StrUtil.isNotEmpty(name)) { if (StrUtil.isNotEmpty(colIdStr) && !colIdStr.matches("\\d+")) {
map.put(name, fieldWidget); // 新增列:字段名存在 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);
} }
/** /**

Loading…
Cancel
Save