Browse Source

feature-online-form-direct-creation:收到请求后自动创建数据库物理表 + 元数据记录 + 数据源配置

online-form-direct-creation
sunquan 2 weeks ago
parent
commit
610a4d10ed
  1. 11
      common/common-generator/src/main/java/apelet/common/generator/service/IOnlFormHeadService.java
  2. 38
      common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java
  3. 293
      common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java
  4. 71
      common/common-online/src/main/java/apelet/common/online/controller/OnlinePageController.java
  5. 14
      common/common-online/src/main/java/apelet/common/online/dto/OnlinePageDto.java

11
common/common-generator/src/main/java/apelet/common/generator/service/IOnlFormHeadService.java

@ -119,4 +119,15 @@ public interface IOnlFormHeadService extends IService<OnlFormHead> { @@ -119,4 +119,15 @@ public interface IOnlFormHeadService extends IService<OnlFormHead> {
* @return
*/
OnlFormHead getHeadTableNameCache(String tableName);
/**
* 创建元数据含默认字段并同步数据库. 返回创建后的 OnlFormHead.
*
* @param tableName 表名
* @param tableTxt 表描述
* @param metaType 表类型: "0"=附表(分录), "1"=主表(业务单据), "2"=单表(基础资料)
* @param sortFiled 关联字段附表时传主表表名
* @return OnlFormHead
*/
OnlFormHead createMetaAndSync(String tableName, String tableTxt, String metaType, String sortFiled);
}

38
common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java

@ -1660,4 +1660,42 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -1660,4 +1660,42 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
public void saveEasDict(List<OnlineFromDict> list) {
onlFormHeadMapper.saveEasDict(list);
}
@Override
@Transactional(rollbackFor = Exception.class)
public OnlFormHead createMetaAndSync(String tableName, String tableTxt, String metaType, String sortFiled) {
OnlFormHead head = new OnlFormHead();
head.setId(idGenerator.nextLongId());
head.setTableName(tableName);
head.setTableTxt(tableTxt);
head.setTableType(metaType);
head.setStatus("0"); // 未同步状态
head.setLine(CustomUtil.MetaType_Entry.equals(metaType) ? 1 : 0);
if (StringUtils.isNotEmpty(sortFiled)) {
head.setSort(sortFiled);
}
// 初始化审计字段(createUserId/updateUserId/createTime/updateTime/deletedFlag)
addOrUpdateBaseData(head, head.getId());
// Build DTO
OnlFormHeadDto dto = new OnlFormHeadDto();
BeanUtil.copyProperties(head, dto);
// Generate default fields
List<OnlFormField> defaultFields = new ArrayList<>();
defaultData(defaultFields, null, null, metaType);
dto.setOnlFormFieldList(defaultFields);
// Save
this.addAll(dto);
// Reload to get full info
OnlFormHead saved = this.getById(head.getId());
// Sync database (type=0: CREATE TABLE or ALTER TABLE ADD COLUMN)
this.syncDB(saved, 0L);
return saved;
}
}

293
common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java

@ -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);
}
}
}
}

71
common/common-online/src/main/java/apelet/common/online/controller/OnlinePageController.java

@ -36,6 +36,7 @@ import lombok.extern.slf4j.Slf4j; @@ -36,6 +36,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.validation.groups.Default;
@ -82,41 +83,84 @@ public class OnlinePageController { @@ -82,41 +83,84 @@ public class OnlinePageController {
*/
@ApiOperationSupport(ignoreParameters = {"onlinePageDto.pageId"})
@OperationLog(type = SysOperationLogType.ADD)
@Transactional(rollbackFor = Exception.class)
@PostMapping("/add")
public ResponseResult<Long> add(@MyRequestBody OnlinePageDto onlinePageDto) {
String errorMessage = MyCommonUtil.getModelValidationError(onlinePageDto);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
// Validate based on metaSource
String metaSource = onlinePageDto.getMetaSource();
if ("new".equals(metaSource)) {
// New metadata flow: metaType and pageCode are required
if (StrUtil.isEmpty(onlinePageDto.getMetaType())) {
errorMessage = "数据验证失败,新建元数据时表类型(metaType)不能为空!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
if (StrUtil.isEmpty(onlinePageDto.getPageCode())) {
errorMessage = "数据验证失败,新建元数据时页面编码不能为空!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
} else {
// Existing metadata flow: masterTableId is required
if (StrUtil.isEmpty(onlinePageDto.getMasterTableId())) {
errorMessage = "数据验证失败,表单元数据不能为空!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
}
OnlinePage onlinePage = MyModelUtil.copyTo(onlinePageDto, OnlinePage.class);
if (onlinePageService.existByPageCode(onlinePage.getPageCode())) {
errorMessage = "数据验证失败,页面编码已经存在!";
return ResponseResult.error(ErrorCodeEnum.DUPLICATED_UNIQUE_KEY, errorMessage);
}
Long masterTableId;
OnlFormHead onlFormHead;
if ("new".equals(metaSource)) {
// === New branch: create fresh metadata ===
String metaType = onlinePageDto.getMetaType();
String tableName = onlinePageDto.getPageCode();
String tableTxt = onlinePageDto.getPageName();
onlFormHead = iOnlFormHeadService.createMetaAndSync(tableName, tableTxt, metaType, null);
masterTableId = onlFormHead.getId();
onlinePage.setMasterTableId(String.valueOf(masterTableId));
onlinePage.setMasterTableName(onlFormHead.getTableName());
// For business documents (metaType="1"): auto-create sub-table
if (CustomUtil.MetaType_Main.equals(metaType)) {
String entryTableName = tableName + "_entry";
iOnlFormHeadService.createMetaAndSync(entryTableName, tableTxt + "附表", CustomUtil.MetaType_Entry, tableName);
}
} else {
// === Existing branch: use existing metadata ===
String formMetadata = onlinePageDto.getMasterTableId();
onlFormHead = iOnlFormHeadService.getById(formMetadata);
masterTableId = Long.valueOf(formMetadata);
}
try {
onlinePage = onlinePageService.saveNew(onlinePage);
} catch (DuplicateKeyException e) {
errorMessage = "数据验证失败,当前应用的页面编码已经存在!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
//根据所传表单元数据id查出对应表单信息
String formMetadata = onlinePageDto.getMasterTableId();
//在线表单主表对象
OnlFormHead onlFormHead = iOnlFormHeadService.getById(formMetadata);
//准备创建数据模型所需的数据
// 准备创建数据模型所需的数据
OnlineDatasourceDto onlineDatasourceDto = new OnlineDatasourceDto();
//数据源标识-取表名驼峰
// 数据源标识-取表名驼峰
onlineDatasourceDto.setVariableName(CustomUtil.UnderlineToCamel(onlFormHead.getTableName()));
//数据主表标识
// 数据主表标识
onlineDatasourceDto.setMasterTableName(onlFormHead.getTableName());
//数据源名称-取表名
// 数据源名称-取表名
onlineDatasourceDto.setDatasourceName((onlFormHead.getTableName()));
//目前系统只有一个数据源,所以默认1740180645717610505
// 目前系统只有一个数据源,所以默认1740180645717610505
OnlineDblink onlineDblink = onlineDblinkService.getById(1740180645717610505L);
onlineDatasourceDto.setDblinkId(1740180645717610505L);
//创建数据模型
// 创建数据模型
SqlTable sqlTable = onlineDblinkService.getDblinkTable(onlineDblink, onlineDatasourceDto.getMasterTableName());
if (sqlTable == null) {
errorMessage = "数据验证失败,指定的数据表名不存在!";
@ -130,14 +174,11 @@ public class OnlinePageController { @@ -130,14 +174,11 @@ public class OnlinePageController {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
/**
*创建数据表关联关系 先查出改元数据表的所有附表关系
* 创建数据表关联关系 先查出改元数据表的所有附表关系
* 一对多
*
*/
addOnlineDatasourceRelation(onlFormHead, onlineDatasource, errorMessage, onlineDblink);
return ResponseResult.success(onlinePage.getPageId());
}

14
common/common-online/src/main/java/apelet/common/online/dto/OnlinePageDto.java

@ -60,13 +60,23 @@ public class OnlinePageDto { @@ -60,13 +60,23 @@ public class OnlinePageDto {
* 表单元数据
*/
@Schema(description = "表单元数据")
@NotNull(message = "数据验证失败,表单元数据不能为空!")
private String masterTableId;
/**
* 表单元数据
*/
@Schema(description = "表单元数据表名")
// @NotNull(message = "数据验证失败,表单元数据不能为空!")
private String masterTableName;
/**
* 数据来源: "new"=新建元数据, "existing"=使用已有元数据
*/
@Schema(description = "数据来源: new=新建元数据, existing=使用已有元数据")
private String metaSource;
/**
* 表类型: "2"=基础资料(单表), "1"=业务单据(主表+附表)仅在metaSource="new"时有效
*/
@Schema(description = "表类型: 2=基础资料(单表), 1=业务单据(主表+附表)。仅在metaSource=new时有效")
private String metaType;
}

Loading…
Cancel
Save