@ -3,6 +3,7 @@ package apelet.common.online.controller;
@@ -3,6 +3,7 @@ package apelet.common.online.controller;
import apelet.common.core.annotation.MyRequestBody ;
import apelet.common.core.annotation.NoAuthInterface ;
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.object.* ;
@ -12,16 +13,21 @@ import apelet.common.core.util.MyPageUtil;
@@ -12,16 +13,21 @@ import apelet.common.core.util.MyPageUtil;
import apelet.common.core.validator.UpdateGroup ;
import apelet.common.dbutil.object.SqlTable ;
import apelet.common.dbutil.object.SqlTableColumn ;
import apelet.common.generator.model.OnlFormField ;
import apelet.common.generator.model.OnlFormHead ;
import apelet.common.generator.service.IOnlFormFieldService ;
import apelet.common.generator.service.IOnlFormHeadService ;
import apelet.common.generator.utils.CustomUtil ;
import apelet.common.generator.utils.cache.MetaCacheService ;
import apelet.common.log.annotation.OperationLog ;
import apelet.common.log.model.constant.SysOperationLogType ;
import apelet.common.online.config.OnlineProperties ;
import apelet.common.online.dto.* ;
import apelet.common.online.dto.OnlineEventPluginExecuteDto ;
import apelet.common.online.dto.OnlineFormDto ;
import apelet.common.online.model.* ;
import apelet.common.online.service.* ;
import apelet.common.online.util.OnlineRedisKeyUtil ;
import apelet.common.online.util.WidgetFieldTypeMapping ;
import apelet.common.online.vo.OnlineFormVo ;
import cn.hutool.core.collection.CollUtil ;
import cn.hutool.core.map.MapUtil ;
@ -47,8 +53,9 @@ import org.springframework.web.bind.annotation.*;
@@ -47,8 +53,9 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource ;
import javax.validation.groups.Default ;
import java.lang.reflect.Method ;
import java.util.* ;
import java.util.function.Consumer ;
import java.util.function.Function ;
import java.util.stream.Collectors ;
/ * *
@ -83,12 +90,17 @@ public class OnlineFormController {
@@ -83,12 +90,17 @@ public class OnlineFormController {
@Autowired
IOnlFormHeadService onlFormHeadService ;
@Autowired
IOnlFormFieldService onlFormFieldService ;
@Autowired
OnlinePageService onlinePageService ;
@Autowired
private OnlineProperties properties ;
@Resource ( name = "caffeineCacheManager" )
private CacheManager cacheManager ;
@Autowired
private MetaCacheService metaCacheService ;
/ * *
* 新增在线表单数据 。
*
@ -326,6 +338,13 @@ public class OnlineFormController {
@@ -326,6 +338,13 @@ public class OnlineFormController {
onlineForm . setWidgetJson ( jsonObject . toJSONString ( ) ) ;
}
// ★ 新增: 处理新增字段和子表
JSONObject updatedJson = syncNewFieldsAndSubTables ( onlineForm , onlineFormDto , JSON . parseObject ( onlineForm . getWidgetJson ( ) ) ) ;
if ( updatedJson ! = null ) {
onlineForm . setWidgetJson ( updatedJson . toJSONString ( ) ) ;
onlineFormDto . setWidgetJson ( updatedJson . toJSONString ( ) ) ;
}
if ( ! onlineFormService . update ( onlineForm , originalOnlineForm , datasourceIdSet ) ) {
redissonClient . getBucket ( OnlineRedisKeyUtil . makeOnlineFormKey ( onlineForm . getFormId ( ) ) ) . delete ( ) ;
return ResponseResult . error ( ErrorCodeEnum . DATA_NOT_EXIST ) ;
@ -685,4 +704,404 @@ public class OnlineFormController {
@@ -685,4 +704,404 @@ public class OnlineFormController {
}
return ResponseResult . success ( onlineFormList ) ;
}
// ============ 字段diff+同步 相关私有方法 ============
/ * *
* 对比 widgetJson 与现有 OnlFormField / OnlFormHead 识别新增字段和子表 ,
* 创建 OnlFormField 记录 、 同步数据库 、 创建 OnlineColumn 、 回填 columnId / relationId 。
*
* @return 修改后的 widgetJson ( 含回填的 columnId / relationId ) , 无变更则返回 null
* /
private JSONObject syncNewFieldsAndSubTables ( OnlineForm form , OnlineFormDto dto , JSONObject widgetJson ) {
// 1. 获取数据源 → OnlineTable → OnlFormHead
OnlineDatasource datasource = onlineDatasourceService . getById ( dto . getDatasourceIdList ( ) . get ( 0 ) ) ;
OnlineTable onlineTable = onlineTableService . getById ( datasource . getMasterTableId ( ) ) ;
OnlFormHead onlFormHead = onlFormHeadService . getOne (
new LambdaQueryWrapper < OnlFormHead > ( )
. eq ( OnlFormHead : : getTableName , onlineTable . getTableName ( ) ) ) ;
if ( onlFormHead = = null ) {
return null ;
}
boolean changed = false ;
// === Diff 新增字段 ===
Set < String > widgetColumnNames = collectAllBindColumnNames ( widgetJson ) ;
List < OnlFormField > existingFields = onlFormFieldService . list (
new LambdaQueryWrapper < OnlFormField > ( ) . eq ( OnlFormField : : getHeadId , onlFormHead . getId ( ) ) ) ;
Set < String > existingFieldNames = existingFields . stream ( )
. map ( OnlFormField : : getFieldName ) . collect ( Collectors . toSet ( ) ) ;
Set < String > newColumnNames = new HashSet < > ( widgetColumnNames ) ;
newColumnNames . removeAll ( existingFieldNames ) ;
if ( ! newColumnNames . isEmpty ( ) ) {
Map < String , JSONObject > nameToWidget = mapColumnNameToWidget ( widgetJson ) ;
OnlineDblink dblink = onlineDblinkService . getById ( datasource . getDblinkId ( ) ) ;
// 预校验:过滤掉 widgetType 不在映射中的控件,避免 getFieldTypeId 抛异常导致部分数据已入库
Set < String > validColumnNames = new LinkedHashSet < > ( ) ;
for ( String fieldName : newColumnNames ) {
JSONObject widget = nameToWidget . 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 ) ;
// 从 widget 的 bindData.columnComment 设置字段备注
JSONObject bindData = widget . getJSONObject ( "bindData" ) ;
this . saveField ( widget . getInteger ( "widgetType" ) , bindData . getString ( "columnComment" ) , widget . getString ( "showName" ) , onlFormHead , fieldName ) ;
}
// 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 ) {
// 回填 columnId 到 widgetJson
JSONObject widget = nameToWidget . get ( fieldName ) ;
String showName = widget = = null ? null : widget . getString ( "showName" ) ;
long columnId = onlineColumnService . saveBySqlTable ( sqlCol , onlineTable . getTableId ( ) , showName ) ;
if ( widget ! = null ) {
JSONObject bindData2 = widget . getJSONObject ( "bindData" ) ;
if ( bindData2 ! = null ) {
bindData2 . put ( "columnId" , columnId ) ;
bindData2 . put ( "tableId" , onlineTable . getTableId ( ) ) ;
}
}
}
}
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 < String , JSONObject > tableIdAndPropMap = collectSubTableAndPropMap ( 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 {
newSubTableNames . add ( tableId ) ;
}
}
//分两步,
//step1 : 看看存在的附表,是否存在不存在的字段
if ( ! existingSubTableIdSet . isEmpty ( ) ) {
// 拿到tableId 下面的所有字段,先查询 zz_online_table ,在根据 tableName 查询 onl_form_head 表
List < OnlineTable > tableList = onlineTableService . listByIds ( existingSubTableIdSet ) ;
Map < Long , String > tableIdAndNameMap = tableList . stream ( ) . collect ( Collectors . toMap ( OnlineTable : : getTableId , OnlineTable : : getTableName ) ) ;
List < String > tableNames = tableList . stream ( ) . map ( OnlineTable : : getTableName ) . collect ( Collectors . toList ( ) ) ;
List < OnlFormHead > onlFormHeadList = onlFormHeadService . list ( new LambdaQueryWrapper < OnlFormHead > ( ) . in ( OnlFormHead : : getTableName , tableNames ) ) ;
Map < String , OnlFormHead > tableNameAndHeadIdMap = onlFormHeadList . stream ( ) . collect ( Collectors . toMap ( OnlFormHead : : getTableName , Function . identity ( ) ) ) ;
//因为这里是zz_online_column.column_id 的id和 列名的混合,我只要列名
Map < String , JSONObject > nameToWidget = mapColumnIdAndWidget ( widgetJson ) ;
Set < String > 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" ) ;
// 默认是字符串类型
this . saveField ( 750 , showName , null , entryHead , newSubColumnName ) ;
}
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 . toLowerCase ( ) ) ;
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 ;
}
}
//step2 : 不存在的附表,新增
if ( newSubTableNames . isEmpty ( ) ) {
//如果他为空,说明我们走tableId取值没取到,我们就去一下 tableName;
newSubTableNames = this . collectSubTableName ( widgetJson ) ;
}
if ( ! newSubTableNames . isEmpty ( ) ) {
for ( String subTableName : newSubTableNames ) {
OnlFormHead subHead = onlFormHeadService . createMetaAndSync ( subTableName , onlFormHead . getTableTxt ( ) + "附表" , CustomUtil . MetaType_Entry , onlFormHead . getTableName ( ) ) ;
// 获取子表的 OnlineTable
OnlineTable subOnlineTable = onlineTableService . getOne ( new LambdaQueryWrapper < OnlineTable > ( ) . eq ( OnlineTable : : getTableName , subTableName . toLowerCase ( ) ) ) ;
if ( subOnlineTable = = null ) {
// 从DB获取表结构并创建 OnlineTable
SqlTable subSqlTable = onlineDblinkService . getDblinkTable ( onlineDblinkService . getById ( datasource . getDblinkId ( ) ) , subTableName ) ;
if ( subSqlTable ! = null ) {
subOnlineTable = onlineTableService . saveNewFromSqlTable ( subSqlTable ) ;
}
}
OnlineDblink dblink = onlineDblinkService . getById ( datasource . getDblinkId ( ) ) ;
onlineDatasourceRelationService . addOnlineDatasourceRelation ( onlFormHead , datasource , null , dblink ) ;
if ( subOnlineTable ! = null ) {
backfillRelationId ( widgetJson , subTableName , subOnlineTable . getTableId ( ) ) ;
}
}
changed = true ;
}
}
return changed ? widgetJson : null ;
}
private void saveField ( Integer widgetType , String columnComment , String showName , OnlFormHead onlFormHead , String fieldName ) {
OnlFormField field = new OnlFormField ( ) ;
field . setHeadId ( onlFormHead . getId ( ) ) ;
field . setFieldName ( fieldName ) ;
field . setFieldType ( WidgetFieldTypeMapping . getFieldTypeId ( widgetType ) ) ;
Integer length = WidgetFieldTypeMapping . getDefaultLength ( widgetType ) ;
if ( length ! = null ) {
field . setFieldLength ( length ) ;
}
field . setIsEmpty ( 1 ) ;
field . setIsDefault ( 0 ) ;
field . setRowDisabled ( 0 ) ;
field . setSyncFlag ( 0 ) ;
// 从 widget 的 bindData.columnComment 设置字段备注
field . setFieldRemark ( columnComment ) ;
if ( StringUtils . isNotBlank ( showName ) & & ( StringUtils . isBlank ( field . getFieldRemark ( ) ) | | StringUtils . equals ( "null" , field . getFieldRemark ( ) ) ) ) {
field . setFieldRemark ( showName ) ;
}
onlFormFieldService . save ( field ) ;
}
/ * *
* 遍历 pc / mobile 的 widget 树 , 收集所有 bindData . columnName 值 。
* /
private Set < String > collectAllBindColumnNames ( JSONObject widgetJson ) {
Set < String > names = new HashSet < > ( ) ;
walkAllModes ( widgetJson , widget - > {
JSONObject bindData = widget . getJSONObject ( "bindData" ) ;
if ( bindData ! = null & & bindData . containsKey ( "columnName" ) ) {
String name = bindData . getString ( "columnName" ) ;
if ( StrUtil . isNotEmpty ( name ) ) {
names . add ( name ) ;
}
}
} ) ;
return names ;
}
/ * *
* 遍历 pc / mobile 的 widget 树 , 收集 widgetType = 100 ( TableContainer ) 的 bindData 下的 tableId
* /
private Map < String , JSONObject > collectSubTableAndPropMap ( JSONObject widgetJson ) {
Map < String , JSONObject > resultMap = new HashMap < > ( ) ;
walkAllModes ( widgetJson , widget - > {
JSONArray widgetList = widget . getJSONArray ( "widgetList" ) ;
if ( widgetList ! = null ) {
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 ;
}
private Set < String > collectSubTableName ( JSONObject widgetJson ) {
Set < String > tableNameSet = new HashSet < > ( ) ;
walkAllModes ( widgetJson , widget - > {
JSONArray widgetList = widget . getJSONArray ( "widgetList" ) ;
if ( widgetList ! = null ) {
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 ;
}
if ( bindData . containsKey ( "tableName" ) ) {
tableNameSet . add ( bindData . getString ( "tableName" ) ) ;
}
}
}
} ) ;
return tableNameSet ;
}
/ * *
* 构建 columnName → widget 的映射 。
* /
private Map < String , JSONObject > mapColumnNameToWidget ( JSONObject widgetJson ) {
Map < String , JSONObject > 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 ) ;
}
}
} ) ;
return map ;
}
private Map < String , JSONObject > mapColumnIdAndWidget ( JSONObject widgetJson ) {
Map < String , JSONObject > resultMap = new HashMap < > ( ) ;
walkAllModes ( widgetJson , widget - > {
JSONArray widgetList = widget . getJSONArray ( "widgetList" ) ;
if ( widgetList ! = null ) {
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 ) ;
resultMap . put ( tableColumn . getString ( "columnId" ) , tableColumn ) ;
}
}
}
} ) ;
return resultMap ;
}
/ * *
* 在 widgetJson 中查找匹配 tableName 的 TableContainer 控件 , 回填 tableId 。
* /
private void backfillRelationId ( JSONObject widgetJson , String tableName , Long tableId ) {
walkAllModes ( widgetJson , widget - > {
if ( widget . getInteger ( "widgetType" ) ! = null & & 100 = = widget . getInteger ( "widgetType" ) ) {
JSONObject props = widget . getJSONObject ( "bindData" ) ;
if ( props ! = null & & tableName . equals ( props . getString ( "tableName" ) ) ) {
props . put ( "tableId" , tableId ) ;
}
}
} ) ;
}
/ * *
* 遍历 "pc" 和 "mobile" 两个模式的控件树 。
* /
private void walkAllModes ( JSONObject widgetJson , Consumer < JSONObject > visitor ) {
for ( String mode : new String [ ] { "pc" , "mobile" } ) {
JSONObject modeObj = widgetJson . getJSONObject ( mode ) ;
if ( modeObj ! = null ) {
walkWidgets ( modeObj , visitor ) ;
}
}
}
/ * *
* 递归遍历控件树 , 访问每个控件及其子控件 ( widgetList ) 。
* /
private void walkWidgets ( JSONObject node , Consumer < JSONObject > visitor ) {
if ( node = = null ) {
return ;
}
visitor . accept ( node ) ;
JSONArray children = node . getJSONArray ( "widgetList" ) ;
if ( children ! = null ) {
for ( int i = 0 ; i < children . size ( ) ; i + + ) {
walkWidgets ( children . getJSONObject ( i ) , visitor ) ;
}
}
}
}