feat: 重构数据中枢工作台与接入管理

- 新增统一的数据源、目录、纳管表与 Excel 处理后端能力

- 重建管理端数据中枢工作台并替换旧表管理页面

- 补充数据中枢迁移脚本、连接器底座与说明字段支持
This commit is contained in:
2026-04-02 18:55:31 +08:00
parent b6213d0933
commit 798effbd5b
117 changed files with 9739 additions and 1824 deletions

View File

@@ -0,0 +1,75 @@
package tech.easyflow.datacenter.meta.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.handler.FastjsonTypeHandler;
import tech.easyflow.common.entity.DateEntity;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Date;
import java.util.Map;
@Table(value = "tb_datacenter_catalog", comment = "数据中心逻辑库/命名空间")
public class DatacenterCatalog extends DateEntity implements Serializable {
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
private BigInteger id;
@Column(comment = "部门ID")
private BigInteger deptId;
@Column(tenantId = true, comment = "租户ID")
private BigInteger tenantId;
@Column(comment = "数据源ID")
private BigInteger sourceId;
@Column(comment = "目录名")
private String catalogName;
@Column(comment = "目录描述")
private String catalogDesc;
@Column(comment = "目录类型")
private String catalogType;
@Column(comment = "状态")
private Integer status;
@Column(typeHandler = FastjsonTypeHandler.class, comment = "扩展项")
private Map<String, Object> options;
@Column(comment = "创建时间")
private Date created;
@Column(comment = "创建人")
private BigInteger createdBy;
@Column(comment = "修改时间")
private Date modified;
@Column(comment = "修改人")
private BigInteger modifiedBy;
public BigInteger getId() { return id; }
public void setId(BigInteger id) { this.id = id; }
public BigInteger getDeptId() { return deptId; }
public void setDeptId(BigInteger deptId) { this.deptId = deptId; }
public BigInteger getTenantId() { return tenantId; }
public void setTenantId(BigInteger tenantId) { this.tenantId = tenantId; }
public BigInteger getSourceId() { return sourceId; }
public void setSourceId(BigInteger sourceId) { this.sourceId = sourceId; }
public String getCatalogName() { return catalogName; }
public void setCatalogName(String catalogName) { this.catalogName = catalogName; }
public String getCatalogDesc() { return catalogDesc; }
public void setCatalogDesc(String catalogDesc) { this.catalogDesc = catalogDesc; }
public String getCatalogType() { return catalogType; }
public void setCatalogType(String catalogType) { this.catalogType = catalogType; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public Map<String, Object> getOptions() { return options; }
public void setOptions(Map<String, Object> options) { this.options = options; }
@Override
public Date getCreated() { return created; }
@Override
public void setCreated(Date created) { this.created = created; }
public BigInteger getCreatedBy() { return createdBy; }
public void setCreatedBy(BigInteger createdBy) { this.createdBy = createdBy; }
@Override
public Date getModified() { return modified; }
@Override
public void setModified(Date modified) { this.modified = modified; }
public BigInteger getModifiedBy() { return modifiedBy; }
public void setModifiedBy(BigInteger modifiedBy) { this.modifiedBy = modifiedBy; }
}

View File

@@ -0,0 +1,70 @@
package tech.easyflow.datacenter.meta.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.handler.FastjsonTypeHandler;
import tech.easyflow.common.entity.DateEntity;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Date;
import java.util.Map;
@Table(value = "tb_datacenter_dataset_version", comment = "数据集版本")
public class DatacenterDatasetVersion extends DateEntity implements Serializable {
@Id(keyType = KeyType.Generator, value = "snowFlakeId")
private BigInteger id;
@Column(comment = "部门ID")
private BigInteger deptId;
@Column(tenantId = true, comment = "租户ID")
private BigInteger tenantId;
@Column(comment = "表ID")
private BigInteger tableId;
@Column(comment = "版本号")
private Integer versionNo;
@Column(comment = "版本标签")
private String versionLabel;
@Column(comment = "物化表名")
private String materializedTable;
@Column(typeHandler = FastjsonTypeHandler.class, comment = "版本快照")
private Map<String, Object> snapshotJson;
@Column(comment = "状态")
private Integer status;
@Column(comment = "创建时间")
private Date created;
@Column(comment = "创建人")
private BigInteger createdBy;
@Column(comment = "修改时间")
private Date modified;
@Column(comment = "修改人")
private BigInteger modifiedBy;
public BigInteger getId() { return id; }
public void setId(BigInteger id) { this.id = id; }
public BigInteger getDeptId() { return deptId; }
public void setDeptId(BigInteger deptId) { this.deptId = deptId; }
public BigInteger getTenantId() { return tenantId; }
public void setTenantId(BigInteger tenantId) { this.tenantId = tenantId; }
public BigInteger getTableId() { return tableId; }
public void setTableId(BigInteger tableId) { this.tableId = tableId; }
public Integer getVersionNo() { return versionNo; }
public void setVersionNo(Integer versionNo) { this.versionNo = versionNo; }
public String getVersionLabel() { return versionLabel; }
public void setVersionLabel(String versionLabel) { this.versionLabel = versionLabel; }
public String getMaterializedTable() { return materializedTable; }
public void setMaterializedTable(String materializedTable) { this.materializedTable = materializedTable; }
public Map<String, Object> getSnapshotJson() { return snapshotJson; }
public void setSnapshotJson(Map<String, Object> snapshotJson) { this.snapshotJson = snapshotJson; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
@Override public Date getCreated() { return created; }
@Override public void setCreated(Date created) { this.created = created; }
public BigInteger getCreatedBy() { return createdBy; }
public void setCreatedBy(BigInteger createdBy) { this.createdBy = createdBy; }
@Override public Date getModified() { return modified; }
@Override public void setModified(Date modified) { this.modified = modified; }
public BigInteger getModifiedBy() { return modifiedBy; }
public void setModifiedBy(BigInteger modifiedBy) { this.modifiedBy = modifiedBy; }
}

View File

@@ -0,0 +1,66 @@
package tech.easyflow.datacenter.meta.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.handler.FastjsonTypeHandler;
import tech.easyflow.common.entity.DateEntity;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Date;
import java.util.Map;
@Table(value = "tb_datacenter_derived_table", comment = "数据中心派生表关系")
public class DatacenterDerivedTable extends DateEntity implements Serializable {
@Id(keyType = KeyType.Generator, value = "snowFlakeId")
private BigInteger id;
@Column(comment = "部门ID")
private BigInteger deptId;
@Column(tenantId = true, comment = "租户ID")
private BigInteger tenantId;
@Column(comment = "源表ID")
private BigInteger sourceTableId;
@Column(comment = "派生表ID")
private BigInteger derivedTableId;
@Column(comment = "派生类型")
private String deriveType;
@Column(typeHandler = FastjsonTypeHandler.class, comment = "派生配置")
private Map<String, Object> deriveConfigJson;
@Column(comment = "状态")
private Integer status;
@Column(comment = "创建时间")
private Date created;
@Column(comment = "创建人")
private BigInteger createdBy;
@Column(comment = "修改时间")
private Date modified;
@Column(comment = "修改人")
private BigInteger modifiedBy;
public BigInteger getId() { return id; }
public void setId(BigInteger id) { this.id = id; }
public BigInteger getDeptId() { return deptId; }
public void setDeptId(BigInteger deptId) { this.deptId = deptId; }
public BigInteger getTenantId() { return tenantId; }
public void setTenantId(BigInteger tenantId) { this.tenantId = tenantId; }
public BigInteger getSourceTableId() { return sourceTableId; }
public void setSourceTableId(BigInteger sourceTableId) { this.sourceTableId = sourceTableId; }
public BigInteger getDerivedTableId() { return derivedTableId; }
public void setDerivedTableId(BigInteger derivedTableId) { this.derivedTableId = derivedTableId; }
public String getDeriveType() { return deriveType; }
public void setDeriveType(String deriveType) { this.deriveType = deriveType; }
public Map<String, Object> getDeriveConfigJson() { return deriveConfigJson; }
public void setDeriveConfigJson(Map<String, Object> deriveConfigJson) { this.deriveConfigJson = deriveConfigJson; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
@Override public Date getCreated() { return created; }
@Override public void setCreated(Date created) { this.created = created; }
public BigInteger getCreatedBy() { return createdBy; }
public void setCreatedBy(BigInteger createdBy) { this.createdBy = createdBy; }
@Override public Date getModified() { return modified; }
@Override public void setModified(Date modified) { this.modified = modified; }
public BigInteger getModifiedBy() { return modifiedBy; }
public void setModifiedBy(BigInteger modifiedBy) { this.modifiedBy = modifiedBy; }
}

View File

@@ -0,0 +1,102 @@
package tech.easyflow.datacenter.meta.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.handler.FastjsonTypeHandler;
import tech.easyflow.common.entity.DateEntity;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Date;
import java.util.Map;
@Table(value = "tb_datacenter_import_job", comment = "数据中心导入任务")
public class DatacenterImportJob extends DateEntity implements Serializable {
@Id(keyType = KeyType.Generator, value = "snowFlakeId")
private BigInteger id;
@Column(comment = "部门ID")
private BigInteger deptId;
@Column(tenantId = true, comment = "租户ID")
private BigInteger tenantId;
@Column(comment = "数据源ID")
private BigInteger sourceId;
@Column(comment = "目录ID")
private BigInteger catalogId;
@Column(comment = "表ID")
private BigInteger tableId;
@Column(comment = "任务类型")
private String jobType;
@Column(comment = "文件名")
private String fileName;
@Column(comment = "文件存储路径")
private String storagePath;
@Column(comment = "任务状态")
private String status;
@Column(comment = "总行数")
private Long totalRows;
@Column(comment = "成功行数")
private Long successRows;
@Column(comment = "失败行数")
private Long errorRows;
@Column(comment = "错误摘要")
private String errorSummary;
@Column(typeHandler = FastjsonTypeHandler.class, comment = "任务载荷")
private Map<String, Object> payloadJson;
@Column(comment = "开始时间")
private Date startedAt;
@Column(comment = "结束时间")
private Date finishedAt;
@Column(comment = "创建时间")
private Date created;
@Column(comment = "创建人")
private BigInteger createdBy;
@Column(comment = "修改时间")
private Date modified;
@Column(comment = "修改人")
private BigInteger modifiedBy;
public BigInteger getId() { return id; }
public void setId(BigInteger id) { this.id = id; }
public BigInteger getDeptId() { return deptId; }
public void setDeptId(BigInteger deptId) { this.deptId = deptId; }
public BigInteger getTenantId() { return tenantId; }
public void setTenantId(BigInteger tenantId) { this.tenantId = tenantId; }
public BigInteger getSourceId() { return sourceId; }
public void setSourceId(BigInteger sourceId) { this.sourceId = sourceId; }
public BigInteger getCatalogId() { return catalogId; }
public void setCatalogId(BigInteger catalogId) { this.catalogId = catalogId; }
public BigInteger getTableId() { return tableId; }
public void setTableId(BigInteger tableId) { this.tableId = tableId; }
public String getJobType() { return jobType; }
public void setJobType(String jobType) { this.jobType = jobType; }
public String getFileName() { return fileName; }
public void setFileName(String fileName) { this.fileName = fileName; }
public String getStoragePath() { return storagePath; }
public void setStoragePath(String storagePath) { this.storagePath = storagePath; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public Long getTotalRows() { return totalRows; }
public void setTotalRows(Long totalRows) { this.totalRows = totalRows; }
public Long getSuccessRows() { return successRows; }
public void setSuccessRows(Long successRows) { this.successRows = successRows; }
public Long getErrorRows() { return errorRows; }
public void setErrorRows(Long errorRows) { this.errorRows = errorRows; }
public String getErrorSummary() { return errorSummary; }
public void setErrorSummary(String errorSummary) { this.errorSummary = errorSummary; }
public Map<String, Object> getPayloadJson() { return payloadJson; }
public void setPayloadJson(Map<String, Object> payloadJson) { this.payloadJson = payloadJson; }
public Date getStartedAt() { return startedAt; }
public void setStartedAt(Date startedAt) { this.startedAt = startedAt; }
public Date getFinishedAt() { return finishedAt; }
public void setFinishedAt(Date finishedAt) { this.finishedAt = finishedAt; }
@Override public Date getCreated() { return created; }
@Override public void setCreated(Date created) { this.created = created; }
public BigInteger getCreatedBy() { return createdBy; }
public void setCreatedBy(BigInteger createdBy) { this.createdBy = createdBy; }
@Override public Date getModified() { return modified; }
@Override public void setModified(Date modified) { this.modified = modified; }
public BigInteger getModifiedBy() { return modifiedBy; }
public void setModifiedBy(BigInteger modifiedBy) { this.modifiedBy = modifiedBy; }
}

View File

@@ -0,0 +1,131 @@
package tech.easyflow.datacenter.meta.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.handler.FastjsonTypeHandler;
import tech.easyflow.common.entity.DateEntity;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Date;
import java.util.Map;
@Table(value = "tb_datacenter_source", comment = "数据中心数据源")
public class DatacenterSource extends DateEntity implements Serializable {
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
private BigInteger id;
@Column(comment = "部门ID")
private BigInteger deptId;
@Column(tenantId = true, comment = "租户ID")
private BigInteger tenantId;
@Column(comment = "数据源名称")
private String sourceName;
@Column(comment = "数据源编码")
private String sourceCode;
@Column(comment = "数据源类型")
private String sourceType;
@Column(comment = "访问模式")
private String accessMode;
@Column(comment = "是否内置")
private Integer builtinFlag;
@Column(comment = "驱动类名")
private String driverClassName;
@Column(comment = "JDBC URL")
private String jdbcUrl;
@Column(comment = "主机")
private String host;
@Column(comment = "端口")
private Integer port;
@Column(comment = "数据库名")
private String databaseName;
@Column(comment = "Schema名")
private String schemaName;
@Column(comment = "用户名")
private String username;
@Column(comment = "凭据密文")
private String credentialCipher;
@Column(typeHandler = FastjsonTypeHandler.class, comment = "连接配置")
private Map<String, Object> configJson;
@Column(typeHandler = FastjsonTypeHandler.class, comment = "能力声明")
private Map<String, Object> capabilitiesJson;
@Column(comment = "最近测试状态")
private String lastTestStatus;
@Column(comment = "最近测试信息")
private String lastTestMessage;
@Column(comment = "最近测试时间")
private Date lastTestedAt;
@Column(comment = "状态")
private Integer status;
@Column(typeHandler = FastjsonTypeHandler.class, comment = "扩展项")
private Map<String, Object> options;
@Column(comment = "创建时间")
private Date created;
@Column(comment = "创建人")
private BigInteger createdBy;
@Column(comment = "修改时间")
private Date modified;
@Column(comment = "修改人")
private BigInteger modifiedBy;
public BigInteger getId() { return id; }
public void setId(BigInteger id) { this.id = id; }
public BigInteger getDeptId() { return deptId; }
public void setDeptId(BigInteger deptId) { this.deptId = deptId; }
public BigInteger getTenantId() { return tenantId; }
public void setTenantId(BigInteger tenantId) { this.tenantId = tenantId; }
public String getSourceName() { return sourceName; }
public void setSourceName(String sourceName) { this.sourceName = sourceName; }
public String getSourceCode() { return sourceCode; }
public void setSourceCode(String sourceCode) { this.sourceCode = sourceCode; }
public String getSourceType() { return sourceType; }
public void setSourceType(String sourceType) { this.sourceType = sourceType; }
public String getAccessMode() { return accessMode; }
public void setAccessMode(String accessMode) { this.accessMode = accessMode; }
public Integer getBuiltinFlag() { return builtinFlag; }
public void setBuiltinFlag(Integer builtinFlag) { this.builtinFlag = builtinFlag; }
public String getDriverClassName() { return driverClassName; }
public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; }
public String getJdbcUrl() { return jdbcUrl; }
public void setJdbcUrl(String jdbcUrl) { this.jdbcUrl = jdbcUrl; }
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public Integer getPort() { return port; }
public void setPort(Integer port) { this.port = port; }
public String getDatabaseName() { return databaseName; }
public void setDatabaseName(String databaseName) { this.databaseName = databaseName; }
public String getSchemaName() { return schemaName; }
public void setSchemaName(String schemaName) { this.schemaName = schemaName; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getCredentialCipher() { return credentialCipher; }
public void setCredentialCipher(String credentialCipher) { this.credentialCipher = credentialCipher; }
public Map<String, Object> getConfigJson() { return configJson; }
public void setConfigJson(Map<String, Object> configJson) { this.configJson = configJson; }
public Map<String, Object> getCapabilitiesJson() { return capabilitiesJson; }
public void setCapabilitiesJson(Map<String, Object> capabilitiesJson) { this.capabilitiesJson = capabilitiesJson; }
public String getLastTestStatus() { return lastTestStatus; }
public void setLastTestStatus(String lastTestStatus) { this.lastTestStatus = lastTestStatus; }
public String getLastTestMessage() { return lastTestMessage; }
public void setLastTestMessage(String lastTestMessage) { this.lastTestMessage = lastTestMessage; }
public Date getLastTestedAt() { return lastTestedAt; }
public void setLastTestedAt(Date lastTestedAt) { this.lastTestedAt = lastTestedAt; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public Map<String, Object> getOptions() { return options; }
public void setOptions(Map<String, Object> options) { this.options = options; }
@Override
public Date getCreated() { return created; }
@Override
public void setCreated(Date created) { this.created = created; }
public BigInteger getCreatedBy() { return createdBy; }
public void setCreatedBy(BigInteger createdBy) { this.createdBy = createdBy; }
@Override
public Date getModified() { return modified; }
@Override
public void setModified(Date modified) { this.modified = modified; }
public BigInteger getModifiedBy() { return modifiedBy; }
public void setModifiedBy(BigInteger modifiedBy) { this.modifiedBy = modifiedBy; }
}

View File

@@ -0,0 +1,6 @@
package tech.easyflow.datacenter.meta.enums;
public enum DatacenterAccessMode {
READ_ONLY,
READ_WRITE
}

View File

@@ -0,0 +1,10 @@
package tech.easyflow.datacenter.meta.enums;
public enum DatacenterCapability {
TEST_CONNECTION,
BROWSE_METADATA,
READ_QUERY,
WRITE_MUTATION,
EXPORT,
MATERIALIZE
}

View File

@@ -0,0 +1,12 @@
package tech.easyflow.datacenter.meta.enums;
public enum DatacenterConnectionErrorCode {
INVALID_ARGUMENT,
DRIVER_NOT_FOUND,
NETWORK_UNREACHABLE,
AUTH_FAILED,
DATABASE_NOT_FOUND,
SCHEMA_NOT_FOUND,
PERMISSION_DENIED,
UNKNOWN_ERROR
}

View File

@@ -0,0 +1,9 @@
package tech.easyflow.datacenter.meta.enums;
public enum DatacenterImportStatus {
PENDING,
RUNNING,
SUCCESS,
FAILED,
NOT_IMPLEMENTED
}

View File

@@ -0,0 +1,13 @@
package tech.easyflow.datacenter.meta.enums;
public enum DatacenterSourceType {
PROJECT_MYSQL,
EXCEL,
EXCEL_MATERIALIZED,
MYSQL,
POSTGRESQL,
ORACLE,
GAUSSDB_NATIVE,
GBASE_8A,
GBASE_8S
}

View File

@@ -0,0 +1,10 @@
package tech.easyflow.datacenter.meta.enums;
public enum DatacenterTableKind {
PROJECT_MANAGED,
EXTERNAL_TABLE,
EXTERNAL_VIEW,
EXCEL_SHEET,
EXCEL_MATERIALIZED,
DERIVED_TABLE
}

View File

@@ -0,0 +1,36 @@
package tech.easyflow.datacenter.meta.model;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
public class DatacenterBatchRegisterRequest {
private BigInteger sourceId;
private String catalogName;
private List<String> tableNames = new ArrayList<>();
public BigInteger getSourceId() {
return sourceId;
}
public void setSourceId(BigInteger sourceId) {
this.sourceId = sourceId;
}
public String getCatalogName() {
return catalogName;
}
public void setCatalogName(String catalogName) {
this.catalogName = catalogName;
}
public List<String> getTableNames() {
return tableNames;
}
public void setTableNames(List<String> tableNames) {
this.tableNames = tableNames;
}
}

View File

@@ -0,0 +1,18 @@
package tech.easyflow.datacenter.meta.model;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
public class DatacenterBatchRemoveRequest {
private List<BigInteger> tableIds = new ArrayList<>();
public List<BigInteger> getTableIds() {
return tableIds;
}
public void setTableIds(List<BigInteger> tableIds) {
this.tableIds = tableIds;
}
}

View File

@@ -0,0 +1,22 @@
package tech.easyflow.datacenter.meta.model;
import java.math.BigInteger;
public class DatacenterCatalogMeta {
private BigInteger id;
private BigInteger sourceId;
private String catalogName;
private String catalogType;
private String catalogDesc;
public BigInteger getId() { return id; }
public void setId(BigInteger id) { this.id = id; }
public BigInteger getSourceId() { return sourceId; }
public void setSourceId(BigInteger sourceId) { this.sourceId = sourceId; }
public String getCatalogName() { return catalogName; }
public void setCatalogName(String catalogName) { this.catalogName = catalogName; }
public String getCatalogType() { return catalogType; }
public void setCatalogType(String catalogType) { this.catalogType = catalogType; }
public String getCatalogDesc() { return catalogDesc; }
public void setCatalogDesc(String catalogDesc) { this.catalogDesc = catalogDesc; }
}

View File

@@ -0,0 +1,25 @@
package tech.easyflow.datacenter.meta.model;
import java.math.BigInteger;
public class DatacenterFieldDescriptionUpdate {
private BigInteger fieldId;
private String fieldDesc;
public BigInteger getFieldId() {
return fieldId;
}
public void setFieldId(BigInteger fieldId) {
this.fieldId = fieldId;
}
public String getFieldDesc() {
return fieldDesc;
}
public void setFieldDesc(String fieldDesc) {
this.fieldDesc = fieldDesc;
}
}

View File

@@ -0,0 +1,16 @@
package tech.easyflow.datacenter.meta.model;
import java.math.BigInteger;
public class DatacenterRemoveSourceRequest {
private BigInteger sourceId;
public BigInteger getSourceId() {
return sourceId;
}
public void setSourceId(BigInteger sourceId) {
this.sourceId = sourceId;
}
}

View File

@@ -0,0 +1,36 @@
package tech.easyflow.datacenter.meta.model;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
public class DatacenterSaveDescriptionsRequest {
private BigInteger tableId;
private String tableDesc;
private List<DatacenterFieldDescriptionUpdate> fields = new ArrayList<>();
public BigInteger getTableId() {
return tableId;
}
public void setTableId(BigInteger tableId) {
this.tableId = tableId;
}
public String getTableDesc() {
return tableDesc;
}
public void setTableDesc(String tableDesc) {
this.tableDesc = tableDesc;
}
public List<DatacenterFieldDescriptionUpdate> getFields() {
return fields;
}
public void setFields(List<DatacenterFieldDescriptionUpdate> fields) {
this.fields = fields;
}
}

View File

@@ -0,0 +1,17 @@
package tech.easyflow.datacenter.meta.model;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.entity.DatacenterTableField;
import java.util.ArrayList;
import java.util.List;
public class DatacenterTableDetailMeta {
private DatacenterTable table;
private List<DatacenterTableField> fields = new ArrayList<>();
public DatacenterTable getTable() { return table; }
public void setTable(DatacenterTable table) { this.table = table; }
public List<DatacenterTableField> getFields() { return fields; }
public void setFields(List<DatacenterTableField> fields) { this.fields = fields; }
}

View File

@@ -0,0 +1,38 @@
package tech.easyflow.datacenter.meta.service;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.entity.DatacenterTableField;
import tech.easyflow.datacenter.execution.model.DatasetRef;
import tech.easyflow.datacenter.meta.entity.DatacenterCatalog;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.model.DatacenterFieldDescriptionUpdate;
import tech.easyflow.datacenter.meta.enums.DatacenterSourceType;
import tech.easyflow.datacenter.meta.model.DatacenterTableDetailMeta;
import java.math.BigInteger;
import java.util.List;
public interface DatacenterDatasetRegistryService {
DatacenterSource ensureBuiltinSource(DatacenterSourceType sourceType, LoginAccount account);
DatacenterCatalog ensureCatalog(DatacenterSource source, String catalogName, LoginAccount account);
DatacenterTable registerTable(DatacenterSource source, DatacenterCatalog catalog, DatacenterTableDetailMeta detail, LoginAccount account);
DatacenterTable getTableWithFields(BigInteger tableId);
List<DatacenterTableField> getFields(BigInteger tableId);
DatasetRef resolveDatasetRef(BigInteger tableId);
DatacenterSource getSourceRequired(BigInteger sourceId);
DatacenterCatalog getCatalogById(BigInteger catalogId);
List<DatacenterTable> listManagedTables(BigInteger sourceId, BigInteger catalogId);
int removeTables(List<BigInteger> tableIds);
DatacenterTable saveDescriptions(BigInteger tableId, String tableDesc, List<DatacenterFieldDescriptionUpdate> fields, LoginAccount account);
}

View File

@@ -0,0 +1,12 @@
package tech.easyflow.datacenter.meta.service;
import java.math.BigInteger;
public final class DatacenterMetaConstants {
private DatacenterMetaConstants() {
}
public static final BigInteger PROJECT_SOURCE_BASE = new BigInteger("9000000000002000000");
public static final BigInteger PROJECT_CATALOG_BASE = new BigInteger("9000000000003000000");
}

View File

@@ -0,0 +1,32 @@
package tech.easyflow.datacenter.meta.service;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.execution.model.DatacenterConnectionTestResult;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.model.DatacenterBatchRegisterRequest;
import tech.easyflow.datacenter.meta.model.DatacenterCatalogMeta;
import tech.easyflow.datacenter.meta.model.DatacenterTableDetailMeta;
import java.math.BigInteger;
import java.util.List;
public interface DatacenterSourceService extends IService<DatacenterSource> {
DatacenterSource saveSource(DatacenterSource source, LoginAccount account);
Page<DatacenterSource> pageSources(Long pageNumber, Long pageSize, LoginAccount account);
DatacenterConnectionTestResult testConnection(DatacenterSource source, LoginAccount account);
List<DatacenterCatalogMeta> listCatalogs(BigInteger sourceId, LoginAccount account);
List<DatacenterTable> listTables(BigInteger sourceId, String catalogName, LoginAccount account);
DatacenterTableDetailMeta getTableDetail(BigInteger sourceId, String catalogName, String tableName, boolean register, LoginAccount account);
List<DatacenterTable> batchRegisterTables(DatacenterBatchRegisterRequest request, LoginAccount account);
void removeSource(BigInteger sourceId, LoginAccount account);
}

View File

@@ -0,0 +1,405 @@
package tech.easyflow.datacenter.meta.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.datacenter.adapter.DbHandleManager;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.entity.DatacenterTableField;
import tech.easyflow.datacenter.execution.model.DatasetRef;
import tech.easyflow.datacenter.mapper.DatacenterCatalogMapper;
import tech.easyflow.datacenter.mapper.DatacenterDatasetVersionMapper;
import tech.easyflow.datacenter.mapper.DatacenterDerivedTableMapper;
import tech.easyflow.datacenter.mapper.DatacenterImportJobMapper;
import tech.easyflow.datacenter.mapper.DatacenterSourceMapper;
import tech.easyflow.datacenter.mapper.DatacenterTableFieldMapper;
import tech.easyflow.datacenter.mapper.DatacenterTableMapper;
import tech.easyflow.datacenter.meta.entity.DatacenterCatalog;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.model.DatacenterFieldDescriptionUpdate;
import tech.easyflow.datacenter.meta.enums.DatacenterSourceType;
import tech.easyflow.datacenter.meta.service.DatacenterDatasetRegistryService;
import tech.easyflow.datacenter.meta.service.DatacenterMetaConstants;
import tech.easyflow.datacenter.meta.model.DatacenterTableDetailMeta;
import tech.easyflow.datacenter.meta.enums.DatacenterTableKind;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Service
public class DatacenterDatasetRegistryServiceImpl implements DatacenterDatasetRegistryService {
@Resource
private DatacenterSourceMapper sourceMapper;
@Resource
private DatacenterCatalogMapper catalogMapper;
@Resource
private DatacenterTableMapper tableMapper;
@Resource
private DatacenterTableFieldMapper tableFieldMapper;
@Resource
private DatacenterDatasetVersionMapper datasetVersionMapper;
@Resource
private DatacenterDerivedTableMapper derivedTableMapper;
@Resource
private DatacenterImportJobMapper importJobMapper;
@Resource
private DbHandleManager dbHandleManager;
@Override
public DatacenterSource ensureBuiltinSource(DatacenterSourceType sourceType, LoginAccount account) {
BigInteger tenantId = account == null || account.getTenantId() == null ? BigInteger.ZERO : account.getTenantId();
BigInteger deptId = account == null || account.getDeptId() == null ? BigInteger.ZERO : account.getDeptId();
BigInteger id = builtinSourceId(sourceType, tenantId);
DatacenterSource source = sourceMapper.selectOneById(id);
if (source != null) {
return source;
}
source = new DatacenterSource();
source.setId(id);
source.setTenantId(tenantId);
source.setDeptId(deptId);
source.setSourceType(sourceType.name());
source.setBuiltinFlag(1);
source.setStatus(0);
source.setCreated(new Date());
source.setModified(new Date());
source.setCreatedBy(account == null ? BigInteger.ZERO : account.getId());
source.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
source.setCapabilitiesJson(Map.of("capabilities", builtinCapabilities(sourceType)));
if (sourceType == DatacenterSourceType.PROJECT_MYSQL) {
source.setSourceName("项目 MySQL");
source.setSourceCode("PROJECT_MYSQL_" + tenantId);
source.setAccessMode("READ_WRITE");
} else {
throw new BusinessException("不支持的内置数据源类型: " + sourceType);
}
sourceMapper.insert(source);
ensureCatalog(source, defaultCatalogName(sourceType), account);
return source;
}
@Override
public DatacenterCatalog ensureCatalog(DatacenterSource source, String catalogName, LoginAccount account) {
QueryWrapper wrapper = QueryWrapper.create();
wrapper.eq(DatacenterCatalog::getSourceId, source.getId());
wrapper.eq(DatacenterCatalog::getCatalogName, catalogName);
DatacenterCatalog catalog = catalogMapper.selectOneByQuery(wrapper);
if (catalog != null) {
return catalog;
}
catalog = new DatacenterCatalog();
BigInteger catalogId = builtinCatalogId(source, catalogName);
if (catalogId != null) {
catalog.setId(catalogId);
}
catalog.setSourceId(source.getId());
catalog.setTenantId(source.getTenantId());
catalog.setDeptId(source.getDeptId());
catalog.setCatalogName(catalogName);
catalog.setCatalogDesc(catalogName);
catalog.setCatalogType("DATABASE");
catalog.setStatus(0);
catalog.setCreated(new Date());
catalog.setModified(new Date());
catalog.setCreatedBy(account == null ? BigInteger.ZERO : account.getId());
catalog.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
catalogMapper.insert(catalog);
return catalog;
}
@Override
public DatacenterTable registerTable(DatacenterSource source, DatacenterCatalog catalog, DatacenterTableDetailMeta detail, LoginAccount account) {
DatacenterTable table = detail.getTable();
applyTableDefaults(table, source, detail);
QueryWrapper wrapper = QueryWrapper.create();
wrapper.eq(DatacenterTable::getSourceId, source.getId());
wrapper.eq(DatacenterTable::getCatalogId, catalog.getId());
wrapper.eq(DatacenterTable::getTableName, table.getTableName());
DatacenterTable existing = tableMapper.selectOneByQuery(wrapper);
Date now = new Date();
if (existing == null) {
table.setSourceId(source.getId());
table.setCatalogId(catalog.getId());
table.setTenantId(source.getTenantId());
table.setDeptId(source.getDeptId());
table.setStatus(0);
table.setCreated(now);
table.setModified(now);
table.setCreatedBy(account == null ? BigInteger.ZERO : account.getId());
table.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
tableMapper.insert(table);
} else {
if (!hasText(existing.getTableDesc())) {
existing.setTableDesc(normalizeDescription(table.getTableDesc()));
}
existing.setActualTable(table.getActualTable());
existing.setMaterializedTable(table.getMaterializedTable());
existing.setTableKind(table.getTableKind());
existing.setAccessMode(table.getAccessMode());
existing.setVersioningEnabled(table.getVersioningEnabled());
existing.setCapabilitiesJson(table.getCapabilitiesJson());
existing.setModified(now);
existing.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
tableMapper.update(existing);
table = existing;
}
Map<String, DatacenterTableField> existingFieldMap = getFields(table.getId()).stream()
.collect(LinkedHashMap::new, (map, field) -> map.put(field.getFieldName(), field), Map::putAll);
QueryWrapper deleteWrapper = QueryWrapper.create();
deleteWrapper.eq(DatacenterTableField::getTableId, table.getId());
tableFieldMapper.deleteByQuery(deleteWrapper);
for (DatacenterTableField field : detail.getFields()) {
DatacenterTableField existingField = existingFieldMap.get(field.getFieldName());
if (existingField != null && hasText(existingField.getFieldDesc())) {
field.setFieldDesc(existingField.getFieldDesc());
} else {
field.setFieldDesc(normalizeDescription(field.getFieldDesc()));
}
field.setId(null);
field.setTableId(table.getId());
field.setCreated(now);
field.setModified(now);
field.setCreatedBy(account == null ? BigInteger.ZERO : account.getId());
field.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
tableFieldMapper.insert(field);
}
table.setFields(getFields(table.getId()));
return table;
}
@Override
@Transactional(rollbackFor = Exception.class)
public DatacenterTable saveDescriptions(BigInteger tableId, String tableDesc, List<DatacenterFieldDescriptionUpdate> fields, LoginAccount account) {
if (tableId == null) {
throw new BusinessException("缺少表 ID");
}
DatacenterTable table = getTableWithFields(tableId);
Date now = new Date();
table.setTableDesc(normalizeDescription(tableDesc));
table.setModified(now);
table.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
tableMapper.update(table);
Map<BigInteger, DatacenterFieldDescriptionUpdate> fieldUpdateMap = new LinkedHashMap<>();
if (fields != null) {
for (DatacenterFieldDescriptionUpdate field : fields) {
if (field == null || field.getFieldId() == null) {
continue;
}
fieldUpdateMap.put(field.getFieldId(), field);
}
}
for (DatacenterTableField field : table.getFields()) {
DatacenterFieldDescriptionUpdate update = fieldUpdateMap.get(field.getId());
if (update == null) {
continue;
}
field.setFieldDesc(normalizeDescription(update.getFieldDesc()));
field.setModified(now);
field.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
tableFieldMapper.update(field);
}
return getTableWithFields(tableId);
}
@Override
public DatacenterTable getTableWithFields(BigInteger tableId) {
DatacenterTable table = tableMapper.selectOneById(tableId);
if (table == null) {
throw new BusinessException("数据集不存在: " + tableId);
}
table.setFields(getFields(tableId));
return table;
}
@Override
public List<DatacenterTableField> getFields(BigInteger tableId) {
QueryWrapper wrapper = QueryWrapper.create();
wrapper.eq(DatacenterTableField::getTableId, tableId);
wrapper.orderBy("id");
return tableFieldMapper.selectListByQuery(wrapper);
}
@Override
public DatasetRef resolveDatasetRef(BigInteger tableId) {
DatacenterTable table = getTableWithFields(tableId);
DatasetRef ref = new DatasetRef();
ref.setTableId(tableId);
ref.setSourceId(table.getSourceId());
ref.setCatalogId(table.getCatalogId());
ref.setTableName(table.getTableName());
DatacenterCatalog catalog = getCatalogById(table.getCatalogId());
if (catalog != null) {
ref.setCatalogName(catalog.getCatalogName());
}
return ref;
}
@Override
public DatacenterSource getSourceRequired(BigInteger sourceId) {
DatacenterSource source = sourceMapper.selectOneById(sourceId);
if (source == null) {
throw new BusinessException("连接不存在");
}
return source;
}
@Override
public DatacenterCatalog getCatalogById(BigInteger catalogId) {
if (catalogId == null) {
return null;
}
return catalogMapper.selectOneById(catalogId);
}
@Override
public List<DatacenterTable> listManagedTables(BigInteger sourceId, BigInteger catalogId) {
QueryWrapper wrapper = QueryWrapper.create();
wrapper.eq(DatacenterTable::getSourceId, sourceId);
if (catalogId != null) {
wrapper.eq(DatacenterTable::getCatalogId, catalogId);
}
wrapper.orderBy("created desc");
return tableMapper.selectListByQuery(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int removeTables(List<BigInteger> tableIds) {
List<BigInteger> ids = tableIds == null ? List.of() : tableIds.stream().filter(id -> id != null).distinct().toList();
if (ids.isEmpty()) {
return 0;
}
QueryWrapper physicalTableWrapper = QueryWrapper.create();
physicalTableWrapper.in(DatacenterTable::getId, ids);
List<DatacenterTable> tables = tableMapper.selectListByQuery(physicalTableWrapper);
tables.forEach(this::deletePhysicalTableIfNecessary);
QueryWrapper fieldWrapper = QueryWrapper.create();
fieldWrapper.in(DatacenterTableField::getTableId, ids);
tableFieldMapper.deleteByQuery(fieldWrapper);
QueryWrapper versionWrapper = QueryWrapper.create();
versionWrapper.in(tech.easyflow.datacenter.meta.entity.DatacenterDatasetVersion::getTableId, ids);
datasetVersionMapper.deleteByQuery(versionWrapper);
QueryWrapper importJobWrapper = QueryWrapper.create();
importJobWrapper.in(tech.easyflow.datacenter.meta.entity.DatacenterImportJob::getTableId, ids);
importJobMapper.deleteByQuery(importJobWrapper);
QueryWrapper upstreamWrapper = QueryWrapper.create();
upstreamWrapper.in(tech.easyflow.datacenter.meta.entity.DatacenterDerivedTable::getSourceTableId, ids);
derivedTableMapper.deleteByQuery(upstreamWrapper);
QueryWrapper downstreamWrapper = QueryWrapper.create();
downstreamWrapper.in(tech.easyflow.datacenter.meta.entity.DatacenterDerivedTable::getDerivedTableId, ids);
derivedTableMapper.deleteByQuery(downstreamWrapper);
QueryWrapper tableWrapper = QueryWrapper.create();
tableWrapper.in(DatacenterTable::getId, ids);
return tableMapper.deleteByQuery(tableWrapper);
}
private void deletePhysicalTableIfNecessary(DatacenterTable table) {
if (table == null || !shouldDropPhysicalTable(table)) {
return;
}
String physicalTableName = table.getActualTable();
if (physicalTableName == null || physicalTableName.isBlank()) {
physicalTableName = table.getMaterializedTable();
}
if (physicalTableName == null || physicalTableName.isBlank()) {
return;
}
DatacenterTable physicalTable = new DatacenterTable();
physicalTable.setActualTable(physicalTableName);
dbHandleManager.getDbHandler().deleteTable(physicalTable);
}
private boolean shouldDropPhysicalTable(DatacenterTable table) {
String tableKind = table.getTableKind();
if (tableKind == null || tableKind.isBlank()) {
return false;
}
try {
DatacenterTableKind kind = DatacenterTableKind.valueOf(tableKind);
return kind == DatacenterTableKind.PROJECT_MANAGED
|| kind == DatacenterTableKind.EXCEL_SHEET
|| kind == DatacenterTableKind.EXCEL_MATERIALIZED
|| kind == DatacenterTableKind.DERIVED_TABLE;
} catch (IllegalArgumentException ignored) {
return false;
}
}
private void applyTableDefaults(DatacenterTable table, DatacenterSource source, DatacenterTableDetailMeta detail) {
if (table.getTableKind() == null || table.getTableKind().isBlank()) {
table.setTableKind(DatacenterTableKind.EXTERNAL_TABLE.name());
}
if (table.getAccessMode() == null || table.getAccessMode().isBlank()) {
table.setAccessMode("READ_ONLY");
}
if (table.getVersioningEnabled() == null) {
table.setVersioningEnabled(0);
}
if (table.getCapabilitiesJson() == null || table.getCapabilitiesJson().isEmpty()) {
table.setCapabilitiesJson(Map.of(
"capabilities",
source.getCapabilitiesJson() == null
? List.of()
: source.getCapabilitiesJson().getOrDefault("capabilities", List.of())
));
}
if ((table.getActualTable() == null || table.getActualTable().isBlank()) && table.getTableName() != null) {
table.setActualTable(table.getTableName());
}
table.setTableDesc(normalizeDescription(table.getTableDesc()));
if (detail.getFields() == null) {
detail.setFields(List.of());
}
}
private String normalizeDescription(String description) {
if (description == null) {
return "";
}
return description.trim();
}
private boolean hasText(String value) {
return value != null && !value.trim().isEmpty();
}
private BigInteger builtinSourceId(DatacenterSourceType sourceType, BigInteger tenantId) {
if (sourceType == DatacenterSourceType.PROJECT_MYSQL) {
return DatacenterMetaConstants.PROJECT_SOURCE_BASE.add(tenantId);
}
throw new BusinessException("不支持的内置源 ID 计算");
}
private BigInteger builtinCatalogId(DatacenterSource source, String catalogName) {
if (source.getId().compareTo(DatacenterMetaConstants.PROJECT_SOURCE_BASE) >= 0
&& defaultCatalogName(DatacenterSourceType.PROJECT_MYSQL).equals(catalogName)) {
return DatacenterMetaConstants.PROJECT_CATALOG_BASE.add(source.getTenantId());
}
return null;
}
private String defaultCatalogName(DatacenterSourceType sourceType) {
if (sourceType == DatacenterSourceType.PROJECT_MYSQL) {
return "project_mysql";
}
return sourceType.name().toLowerCase();
}
private List<String> builtinCapabilities(DatacenterSourceType sourceType) {
return List.of("TEST_CONNECTION", "BROWSE_METADATA", "READ_QUERY", "WRITE_MUTATION");
}
}

View File

@@ -0,0 +1,311 @@
package tech.easyflow.datacenter.meta.service.impl;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.datacenter.connector.DatacenterConnector;
import tech.easyflow.datacenter.connector.DatacenterConnectorRegistry;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.mapper.DatacenterCatalogMapper;
import tech.easyflow.datacenter.mapper.DatacenterSourceMapper;
import tech.easyflow.datacenter.meta.entity.DatacenterCatalog;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.enums.DatacenterSourceType;
import tech.easyflow.datacenter.meta.model.DatacenterBatchRegisterRequest;
import tech.easyflow.datacenter.meta.model.DatacenterCatalogMeta;
import tech.easyflow.datacenter.meta.model.DatacenterTableDetailMeta;
import tech.easyflow.datacenter.meta.service.DatacenterDatasetRegistryService;
import tech.easyflow.datacenter.meta.service.DatacenterSourceService;
import tech.easyflow.datacenter.meta.support.DatacenterSourceConnectionDefaults;
import tech.easyflow.datacenter.execution.model.DatacenterConnectionTestResult;
import tech.easyflow.datacenter.security.DatacenterCredentialCipher;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class DatacenterSourceServiceImpl extends ServiceImpl<DatacenterSourceMapper, DatacenterSource> implements DatacenterSourceService {
@Resource
private DatacenterConnectorRegistry connectorRegistry;
@Resource
private DatacenterDatasetRegistryService registryService;
@Resource
private DatacenterCredentialCipher credentialCipher;
@Resource
private DatacenterCatalogMapper catalogMapper;
@Resource
private DatacenterSourceConnectionDefaults connectionDefaults;
@Override
public DatacenterSource saveSource(DatacenterSource source, LoginAccount account) {
if (source == null || source.getSourceType() == null || source.getSourceType().isBlank()) {
throw new BusinessException("数据源类型不能为空");
}
DatacenterSource existing = source.getId() == null ? null : getById(source.getId());
DatacenterSource normalized = mergeWithExisting(existing, source);
DatacenterConnector connector = connectorRegistry.getConnector(normalized.getSourceType());
applyCredentialCipher(normalized, existing);
normalized.setConfigJson(connectionDefaults.sanitizeConfig(normalized.getConfigJson()));
connectionDefaults.normalize(normalized);
normalized.setCapabilitiesJson(Map.of("capabilities", connector.getCapabilities().stream().map(Enum::name).toList()));
Date now = new Date();
if (normalized.getId() == null) {
normalized.setCreated(now);
normalized.setCreatedBy(account == null ? BigInteger.ZERO : account.getId());
normalized.setTenantId(account == null ? BigInteger.ZERO : account.getTenantId());
normalized.setDeptId(account == null ? BigInteger.ZERO : account.getDeptId());
normalized.setStatus(normalized.getStatus() == null ? 0 : normalized.getStatus());
normalized.setModified(now);
normalized.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
save(normalized);
} else {
if (existing == null) {
throw new BusinessException("连接不存在");
}
normalized.setModified(now);
normalized.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
updateById(normalized);
}
return getById(normalized.getId());
}
@Override
public Page<DatacenterSource> pageSources(Long pageNumber, Long pageSize, LoginAccount account) {
registryService.ensureBuiltinSource(DatacenterSourceType.PROJECT_MYSQL, account);
return page(new Page<>(pageNumber == null ? 1L : pageNumber, pageSize == null ? 10L : pageSize), QueryWrapper.create());
}
@Override
public DatacenterConnectionTestResult testConnection(DatacenterSource source, LoginAccount account) {
DatacenterSource existing = source != null && source.getId() != null ? getById(source.getId()) : null;
if (source != null && source.getId() != null && existing == null) {
throw new BusinessException("连接不存在");
}
DatacenterSource actual = mergeWithExisting(existing, source);
if (DatacenterSourceType.PROJECT_MYSQL.name().equals(actual.getSourceType())) {
actual = registryService.ensureBuiltinSource(DatacenterSourceType.PROJECT_MYSQL, account);
}
applyCredentialCipher(actual, existing);
actual.setConfigJson(connectionDefaults.sanitizeConfig(actual.getConfigJson()));
connectionDefaults.normalize(actual);
DatacenterConnector connector = connectorRegistry.getConnector(actual.getSourceType());
DatacenterConnectionTestResult result = connector.testConnection(actual);
Map<String, Object> details = new LinkedHashMap<>();
if (result.getDetails() != null) {
details.putAll(result.getDetails());
}
details.put("effectiveDriverClassName", actual.getDriverClassName());
details.put("effectiveJdbcUrl", actual.getJdbcUrl());
details.put("effectivePort", actual.getPort());
result.setDetails(details);
if (actual.getId() != null) {
DatacenterSource persisted = new DatacenterSource();
persisted.setId(actual.getId());
persisted.setLastTestStatus(result.isSuccess() ? "SUCCESS" : "FAILED");
persisted.setLastTestMessage(result.getMessage());
persisted.setLastTestedAt(new Date());
updateById(persisted);
}
return result;
}
@Override
public List<DatacenterCatalogMeta> listCatalogs(BigInteger sourceId, LoginAccount account) {
DatacenterSource source = registryService.getSourceRequired(sourceId);
if (isManagedOnly(source.getSourceType())) {
QueryWrapper wrapper = QueryWrapper.create();
wrapper.eq(DatacenterCatalog::getSourceId, sourceId);
return catalogMapper.selectListByQuery(wrapper).stream().map(this::toCatalogMeta).collect(Collectors.toList());
}
return connectorRegistry.getConnector(source.getSourceType()).listCatalogs(source);
}
@Override
public List<DatacenterTable> listTables(BigInteger sourceId, String catalogName, LoginAccount account) {
DatacenterSource source = registryService.getSourceRequired(sourceId);
if (isManagedOnly(source.getSourceType())) {
BigInteger catalogId = resolveCatalogId(sourceId, catalogName);
return registryService.listManagedTables(sourceId, catalogId);
}
return connectorRegistry.getConnector(source.getSourceType()).listTables(source, catalogName);
}
@Override
public DatacenterTableDetailMeta getTableDetail(BigInteger sourceId, String catalogName, String tableName, boolean register, LoginAccount account) {
DatacenterSource source = registryService.getSourceRequired(sourceId);
if (isManagedOnly(source.getSourceType())) {
BigInteger catalogId = resolveCatalogId(sourceId, catalogName);
List<DatacenterTable> tables = registryService.listManagedTables(sourceId, catalogId);
DatacenterTable target = tables.stream().filter(item -> tableName.equals(item.getTableName())).findFirst()
.orElseThrow(() -> new BusinessException("数据集不存在: " + tableName));
DatacenterTableDetailMeta detail = new DatacenterTableDetailMeta();
detail.setTable(registryService.getTableWithFields(target.getId()));
detail.setFields(detail.getTable().getFields());
return detail;
}
DatacenterConnector connector = connectorRegistry.getConnector(source.getSourceType());
DatacenterTableDetailMeta detail = connector.getTableDetail(source, catalogName, tableName);
if (register) {
DatacenterCatalog catalog = registryService.ensureCatalog(source, catalogName, account);
DatacenterTable table = registryService.registerTable(source, catalog, detail, account);
detail.setTable(table);
detail.setFields(table.getFields());
}
return detail;
}
@Override
public List<DatacenterTable> batchRegisterTables(DatacenterBatchRegisterRequest request, LoginAccount account) {
if (request == null || request.getSourceId() == null) {
throw new BusinessException("数据连接不能为空");
}
if (request.getCatalogName() == null || request.getCatalogName().isBlank()) {
throw new BusinessException("库不能为空");
}
List<String> tableNames = request.getTableNames() == null
? List.of()
: request.getTableNames().stream().filter(name -> name != null && !name.isBlank()).distinct().toList();
if (tableNames.isEmpty()) {
throw new BusinessException("至少选择一张表");
}
DatacenterSource source = registryService.getSourceRequired(request.getSourceId());
if (isManagedOnly(source.getSourceType())) {
throw new BusinessException("当前数据连接不支持批量接入");
}
DatacenterConnector connector = connectorRegistry.getConnector(source.getSourceType());
DatacenterCatalog catalog = registryService.ensureCatalog(source, request.getCatalogName(), account);
return tableNames.stream().map(tableName -> {
DatacenterTableDetailMeta detail = connector.getTableDetail(source, request.getCatalogName(), tableName);
return registryService.registerTable(source, catalog, detail, account);
}).collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void removeSource(BigInteger sourceId, LoginAccount account) {
if (sourceId == null) {
throw new BusinessException("数据连接不能为空");
}
DatacenterSource source = getById(sourceId);
if (source == null) {
throw new BusinessException("连接不存在");
}
if (Boolean.TRUE.equals(source.getBuiltinFlag())) {
throw new BusinessException("内置数据连接不支持删除");
}
List<BigInteger> tableIds = registryService.listManagedTables(sourceId, null).stream()
.map(DatacenterTable::getId)
.filter(id -> id != null)
.toList();
registryService.removeTables(tableIds);
QueryWrapper catalogWrapper = QueryWrapper.create();
catalogWrapper.eq(DatacenterCatalog::getSourceId, sourceId);
catalogMapper.deleteByQuery(catalogWrapper);
removeById(sourceId);
}
private boolean isManagedOnly(String sourceType) {
return DatacenterSourceType.EXCEL.name().equals(sourceType)
|| DatacenterSourceType.EXCEL_MATERIALIZED.name().equals(sourceType);
}
private DatacenterCatalogMeta toCatalogMeta(DatacenterCatalog catalog) {
DatacenterCatalogMeta meta = new DatacenterCatalogMeta();
meta.setId(catalog.getId());
meta.setSourceId(catalog.getSourceId());
meta.setCatalogName(catalog.getCatalogName());
meta.setCatalogDesc(catalog.getCatalogDesc());
meta.setCatalogType(catalog.getCatalogType());
return meta;
}
private BigInteger resolveCatalogId(BigInteger sourceId, String catalogName) {
if (catalogName == null || catalogName.isBlank()) {
return null;
}
QueryWrapper wrapper = QueryWrapper.create();
wrapper.eq(DatacenterCatalog::getSourceId, sourceId);
wrapper.eq(DatacenterCatalog::getCatalogName, catalogName);
DatacenterCatalog catalog = catalogMapper.selectOneByQuery(wrapper);
if (catalog == null) {
throw new BusinessException("目录不存在: " + catalogName);
}
return catalog.getId();
}
private void applyCredentialCipher(DatacenterSource target, DatacenterSource existing) {
String password = extractPassword(target.getConfigJson());
if (password != null) {
target.setCredentialCipher(credentialCipher.encrypt(password));
return;
}
if ((target.getCredentialCipher() == null || target.getCredentialCipher().isBlank()) && existing != null) {
target.setCredentialCipher(existing.getCredentialCipher());
}
}
private String extractPassword(Map<String, Object> configJson) {
if (configJson == null) {
return null;
}
Object password = configJson.get("password");
if (password == null) {
return null;
}
String value = String.valueOf(password);
return value.isBlank() ? null : value;
}
private DatacenterSource mergeWithExisting(DatacenterSource existing, DatacenterSource incoming) {
if (incoming == null) {
return existing;
}
if (existing == null) {
return incoming;
}
DatacenterSource merged = new DatacenterSource();
merged.setId(existing.getId());
merged.setCreated(existing.getCreated());
merged.setCreatedBy(existing.getCreatedBy());
merged.setTenantId(existing.getTenantId());
merged.setDeptId(existing.getDeptId());
merged.setBuiltinFlag(existing.getBuiltinFlag());
merged.setStatus(existing.getStatus());
merged.setLastTestStatus(existing.getLastTestStatus());
merged.setLastTestMessage(existing.getLastTestMessage());
merged.setLastTestedAt(existing.getLastTestedAt());
merged.setOptions(existing.getOptions());
merged.setSourceName(valueOrExisting(incoming.getSourceName(), existing.getSourceName()));
merged.setSourceCode(valueOrExisting(incoming.getSourceCode(), existing.getSourceCode()));
merged.setSourceType(valueOrExisting(incoming.getSourceType(), existing.getSourceType()));
merged.setAccessMode(valueOrExisting(incoming.getAccessMode(), existing.getAccessMode()));
merged.setDriverClassName(valueOrExisting(incoming.getDriverClassName(), existing.getDriverClassName()));
merged.setJdbcUrl(valueOrExisting(incoming.getJdbcUrl(), existing.getJdbcUrl()));
merged.setHost(valueOrExisting(incoming.getHost(), existing.getHost()));
merged.setPort(incoming.getPort() != null ? incoming.getPort() : existing.getPort());
merged.setDatabaseName(valueOrExisting(incoming.getDatabaseName(), existing.getDatabaseName()));
merged.setSchemaName(valueOrExisting(incoming.getSchemaName(), existing.getSchemaName()));
merged.setUsername(valueOrExisting(incoming.getUsername(), existing.getUsername()));
merged.setCredentialCipher(valueOrExisting(incoming.getCredentialCipher(), existing.getCredentialCipher()));
merged.setConfigJson(incoming.getConfigJson() != null ? incoming.getConfigJson() : existing.getConfigJson());
merged.setCapabilitiesJson(existing.getCapabilitiesJson());
return merged;
}
private String valueOrExisting(String incoming, String existing) {
return incoming != null ? incoming : existing;
}
}

View File

@@ -0,0 +1,153 @@
package tech.easyflow.datacenter.meta.support;
import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Component;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.enums.DatacenterSourceType;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
public class DatacenterSourceConnectionDefaults {
public boolean supportsExternalConnection(String sourceType) {
if (StrUtil.isBlank(sourceType)) {
return false;
}
DatacenterSourceType resolved = resolveSourceType(sourceType);
if (resolved == null) {
return false;
}
return switch (resolved) {
case MYSQL, POSTGRESQL, ORACLE, GAUSSDB_NATIVE, GBASE_8A, GBASE_8S -> true;
default -> false;
};
}
public DatacenterSource normalize(DatacenterSource source) {
if (source == null || !supportsExternalConnection(source.getSourceType())) {
return source;
}
if (source.getPort() == null || source.getPort() <= 0) {
source.setPort(defaultPort(source.getSourceType()));
}
if (StrUtil.isBlank(source.getDriverClassName())) {
source.setDriverClassName(defaultDriverClassName(source.getSourceType()));
}
if (StrUtil.isBlank(source.getJdbcUrl())) {
String jdbcUrl = buildJdbcUrl(source);
if (StrUtil.isNotBlank(jdbcUrl)) {
source.setJdbcUrl(jdbcUrl);
}
}
return source;
}
public Map<String, Object> sanitizeConfig(Map<String, Object> configJson) {
Map<String, Object> sanitized = new LinkedHashMap<>();
if (configJson == null || configJson.isEmpty()) {
return sanitized;
}
sanitized.putAll(configJson);
sanitized.remove("password");
return sanitized;
}
public Integer defaultPort(String sourceType) {
DatacenterSourceType resolved = requireSourceType(sourceType);
return switch (resolved) {
case MYSQL -> 3306;
case POSTGRESQL, GAUSSDB_NATIVE -> 5432;
case ORACLE -> 1521;
case GBASE_8A -> 5258;
case GBASE_8S -> 9088;
default -> null;
};
}
public String defaultDriverClassName(String sourceType) {
DatacenterSourceType resolved = requireSourceType(sourceType);
return switch (resolved) {
case MYSQL -> "com.mysql.cj.jdbc.Driver";
case POSTGRESQL, GAUSSDB_NATIVE -> "org.postgresql.Driver";
case ORACLE -> "oracle.jdbc.OracleDriver";
case GBASE_8A -> "com.gbase.jdbc.Driver";
case GBASE_8S -> "com.gbasedbt.jdbc.Driver";
default -> "";
};
}
public String buildJdbcUrl(DatacenterSource source) {
if (source == null || !supportsExternalConnection(source.getSourceType())) {
return null;
}
String host = StrUtil.trimToEmpty(source.getHost());
Integer port = source.getPort() == null || source.getPort() <= 0 ? defaultPort(source.getSourceType()) : source.getPort();
String databaseName = resolveDatabaseName(source);
if (StrUtil.isBlank(host) || port == null || StrUtil.isBlank(databaseName)) {
return null;
}
DatacenterSourceType resolved = requireSourceType(source.getSourceType());
return switch (resolved) {
case MYSQL -> String.format(
"jdbc:mysql://%s:%d/%s?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false",
host, port, databaseName
);
case POSTGRESQL -> String.format("jdbc:postgresql://%s:%d/%s", host, port, databaseName);
case ORACLE -> String.format("jdbc:oracle:thin:@//%s:%d/%s", host, port, databaseName);
case GAUSSDB_NATIVE -> String.format("jdbc:postgresql://%s:%d/%s", host, port, databaseName);
case GBASE_8A -> String.format("jdbc:gbase://%s:%d/%s", host, port, databaseName);
case GBASE_8S -> {
String informixServer = resolveInformixServer(source);
if (StrUtil.isBlank(informixServer)) {
yield null;
}
yield String.format("jdbc:gbasedbt-sqli://%s:%d/%s:INFORMIXSERVER=%s", host, port, databaseName, informixServer);
}
default -> null;
};
}
private DatacenterSourceType requireSourceType(String sourceType) {
DatacenterSourceType resolved = resolveSourceType(sourceType);
if (resolved == null) {
throw new IllegalArgumentException("Unsupported datacenter source type: " + sourceType);
}
return resolved;
}
private DatacenterSourceType resolveSourceType(String sourceType) {
try {
return DatacenterSourceType.valueOf(sourceType);
} catch (IllegalArgumentException ex) {
return null;
}
}
private String resolveDatabaseName(DatacenterSource source) {
if (StrUtil.isNotBlank(source.getDatabaseName())) {
return source.getDatabaseName().trim();
}
if (source.getConfigJson() == null) {
return null;
}
Object serviceName = source.getConfigJson().get("serviceName");
if (serviceName != null && StrUtil.isNotBlank(String.valueOf(serviceName))) {
return String.valueOf(serviceName).trim();
}
return null;
}
private String resolveInformixServer(DatacenterSource source) {
if (source.getConfigJson() == null) {
return null;
}
Object informixServer = source.getConfigJson().get("informixServer");
if (informixServer == null) {
return null;
}
String value = String.valueOf(informixServer).trim();
return value.isEmpty() ? null : value;
}
}