@ -12,8 +12,12 @@ import apelet.common.core.util.MyPageUtil;
@@ -12,8 +12,12 @@ 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.online.util.WidgetFieldTypeMapping ;
import apelet.common.log.annotation.OperationLog ;
import apelet.common.log.model.constant.SysOperationLogType ;
import apelet.common.online.config.OnlineProperties ;
@ -49,6 +53,7 @@ import javax.annotation.Resource;
@@ -49,6 +53,7 @@ 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.stream.Collectors ;
/ * *
@ -83,6 +88,8 @@ public class OnlineFormController {
@@ -83,6 +88,8 @@ public class OnlineFormController {
@Autowired
IOnlFormHeadService onlFormHeadService ;
@Autowired
IOnlFormFieldService onlFormFieldService ;
@Autowired
OnlinePageService onlinePageService ;
@Autowired
private OnlineProperties properties ;
@ -326,6 +333,13 @@ public class OnlineFormController {
@@ -326,6 +333,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 +699,283 @@ public class OnlineFormController {
@@ -685,4 +699,283 @@ 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 ) ;
Integer widgetType = widget . getInteger ( "widgetType" ) ;
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 设置字段备注
JSONObject bindData = widget . getJSONObject ( "bindData" ) ;
if ( bindData ! = null & & bindData . containsKey ( "columnComment" ) ) {
field . setFieldRemark ( bindData . getString ( "columnComment" ) ) ;
}
onlFormFieldService . save ( field ) ;
}
// 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 ) {
OnlineColumn newCol = new OnlineColumn ( ) ;
newCol . setColumnName ( sqlCol . getColumnName ( ) ) ;
newCol . setColumnType ( sqlCol . getColumnType ( ) ) ;
newCol . setFullColumnType ( sqlCol . getFullColumnType ( ) ) ;
if ( sqlCol . getColumnDefault ( ) ! = null ) {
newCol . setColumnDefault ( sqlCol . getColumnDefault ( ) . toString ( ) ) ;
}
newCol . setNullable ( sqlCol . getNullable ( ) ) ;
newCol . setTableId ( onlineTable . getTableId ( ) ) ;
onlineColumnService . save ( newCol ) ;
// 回填 columnId 到 widgetJson
JSONObject widget = nameToWidget . get ( fieldName ) ;
if ( widget ! = null ) {
JSONObject bindData2 = widget . getJSONObject ( "bindData" ) ;
if ( bindData2 ! = null ) {
bindData2 . put ( "columnId" , newCol . getColumnId ( ) ) ;
bindData2 . put ( "tableId" , onlineTable . getTableId ( ) ) ;
}
}
}
}
changed = true ;
}
// === Diff 新增子表 ===
Set < String > widgetSubTables = collectSubTableNames ( widgetJson ) ;
List < OnlFormHead > existingSubTables = onlFormHeadService . list (
new LambdaQueryWrapper < OnlFormHead > ( )
. eq ( OnlFormHead : : getSort , onlFormHead . getTableName ( ) )
. eq ( OnlFormHead : : getLine , 1 ) ) ;
Set < String > existingSubTableNames = existingSubTables . stream ( )
. map ( OnlFormHead : : getTableName ) . collect ( Collectors . toSet ( ) ) ;
Set < String > newSubTableNames = new HashSet < > ( widgetSubTables ) ;
newSubTableNames . removeAll ( existingSubTableNames ) ;
if ( ! newSubTableNames . isEmpty ( ) ) {
// 获取主表的主键字段(id)作为关联字段
OnlineColumn masterPkColumn = onlineColumnService . getOne (
new LambdaQueryWrapper < OnlineColumn > ( )
. eq ( OnlineColumn : : getTableId , onlineTable . getTableId ( ) )
. eq ( OnlineColumn : : getColumnName , "id" ) ) ;
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 ) ;
}
}
// 创建子表数据源
OnlineDatasource subDatasource = new OnlineDatasource ( ) ;
subDatasource . setDblinkId ( datasource . getDblinkId ( ) ) ;
if ( subOnlineTable ! = null ) {
subDatasource . setMasterTableId ( subOnlineTable . getTableId ( ) ) ;
}
onlineDatasourceService . save ( subDatasource ) ;
// 创建 1:N 关系
OnlineDatasourceRelation relation = new OnlineDatasourceRelation ( ) ;
relation . setDatasourceId ( datasource . getDatasourceId ( ) ) ;
relation . setRelationType ( 1 ) ;
if ( subOnlineTable ! = null ) {
relation . setSlaveTableId ( subOnlineTable . getTableId ( ) ) ;
}
if ( masterPkColumn ! = null ) {
relation . setMasterColumnId ( masterPkColumn . getColumnId ( ) ) ;
}
onlineDatasourceRelationService . save ( relation ) ;
backfillRelationId ( widgetJson , subTableName , relation . getRelationId ( ) ) ;
}
changed = true ;
}
return changed ? widgetJson : null ;
}
/ * *
* 遍历 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 = 403 ( TableContainer ) 的 props . tableName 值 。
* /
private Set < String > collectSubTableNames ( JSONObject widgetJson ) {
Set < String > names = new HashSet < > ( ) ;
walkAllModes ( widgetJson , widget - > {
if ( widget . getInteger ( "widgetType" ) ! = null & & 403 = = widget . getInteger ( "widgetType" ) ) {
JSONObject props = widget . getJSONObject ( "props" ) ;
if ( props ! = null & & props . containsKey ( "tableName" ) ) {
String tn = props . getString ( "tableName" ) ;
if ( StrUtil . isNotEmpty ( tn ) ) {
names . add ( tn ) ;
}
}
}
} ) ;
return names ;
}
/ * *
* 构建 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 ;
}
/ * *
* 在 widgetJson 中查找匹配 tableName 的 TableContainer 控件 , 回填 relationId 。
* /
private void backfillRelationId ( JSONObject widgetJson , String tableName , Long relationId ) {
walkAllModes ( widgetJson , widget - > {
if ( widget . getInteger ( "widgetType" ) ! = null & & 403 = = widget . getInteger ( "widgetType" ) ) {
JSONObject props = widget . getJSONObject ( "props" ) ;
if ( props ! = null & & tableName . equals ( props . getString ( "tableName" ) ) ) {
props . put ( "relationId" , relationId ) ;
}
}
} ) ;
}
/ * *
* 遍历 "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 ) ;
}
}
}
}