feat: 新增统一数据空间与联邦查询能力

- 提供数据连接、元数据、逻辑表与关联编排能力

- 接入联邦查询、执行分析、统计估算和 SQL 补全接口

- 增加查询预算、凭据保护、租户隔离和 V61 初始化迁移
This commit is contained in:
2026-08-25 01:04:45 +08:00
parent 9068d42f4d
commit c27e97bcc2
71 changed files with 7544 additions and 1 deletions

View File

@@ -60,6 +60,10 @@
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-module-skill</artifactId>
</dependency>
<dependency>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-module-dataspace</artifactId>
</dependency>
<dependency>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-module-auth</artifactId>

View File

@@ -4,8 +4,16 @@ import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.audit.AuditManager;
import com.mybatisflex.core.audit.ConsoleMessageCollector;
import com.mybatisflex.core.audit.MessageCollector;
import com.mybatisflex.core.tenant.TenantManager;
import com.mybatisflex.spring.boot.MyBatisFlexCustomizer;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.exception.NotWebContextException;
import cn.dev33.satoken.exception.SaTokenContextException;
import cn.dev33.satoken.context.SaHolder;
import java.math.BigInteger;
import org.springframework.context.annotation.Configuration;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
@Configuration
public class MybatisConfig implements MyBatisFlexCustomizer {
@@ -20,6 +28,9 @@ public class MybatisConfig implements MyBatisFlexCustomizer {
flexGlobalConfig.setNormalValueOfLogicDelete(0);
flexGlobalConfig.setDeletedValueOfLogicDelete(1);
// 登录请求自动追加 tenant_id 条件;系统任务没有登录上下文时保持显式全局语义。
TenantManager.setTenantFactory(this::currentTenantIds);
//取消控制台的 Banner 打印
flexGlobalConfig.setPrintBanner(false);
@@ -27,4 +38,24 @@ public class MybatisConfig implements MyBatisFlexCustomizer {
MessageCollector collector = new ConsoleMessageCollector();
AuditManager.setMessageCollector(collector);
}
/**
* 返回当前请求的租户条件;启动与调度线程没有 Sa-Token 上下文时保留系统级查询语义。
*
* @return 当前租户 ID 数组;没有请求登录上下文时返回 {@code null}
*/
private Object[] currentTenantIds() {
try {
if (!SaHolder.getContext().isValid() || !StpUtil.isLogin()) {
return null;
}
LoginAccount account = SaTokenUtil.getLoginAccount();
BigInteger tenantId = account == null || account.getTenantId() == null
? BigInteger.ZERO : account.getTenantId();
return new Object[]{tenantId};
} catch (NotWebContextException | SaTokenContextException ignored) {
// Bean 初始化与调度线程没有 Web 上下文,必须允许显式系统任务读取全局状态。
return null;
}
}
}

View File

@@ -0,0 +1,197 @@
SET NAMES utf8mb4;
CREATE TABLE `tb_dataspace_connection` (
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
`name` VARCHAR(100) NOT NULL COMMENT '连接名称',
`database_type` VARCHAR(32) NOT NULL COMMENT '数据库类型',
`host` VARCHAR(255) NOT NULL COMMENT '主机地址',
`port` INT NOT NULL COMMENT '端口',
`database_name` VARCHAR(128) NOT NULL COMMENT '数据库名',
`username` VARCHAR(128) NOT NULL COMMENT '用户名',
`credential_cipher` TEXT NOT NULL COMMENT '凭据密文',
`ssl_enabled` TINYINT NOT NULL DEFAULT 0 COMMENT '是否启用SSL',
`driver_class_name` VARCHAR(255) NOT NULL COMMENT 'JDBC Driver类名',
`options_json` JSON NULL COMMENT '连接扩展选项',
`definition_revision` BIGINT NOT NULL COMMENT 'Federation Definition revision',
`definition_checksum` CHAR(64) NULL COMMENT 'Definition SHA-256',
`status` VARCHAR(24) NOT NULL DEFAULT 'ENABLED' COMMENT '连接状态',
`last_test_status` VARCHAR(24) NULL COMMENT '最近测试状态',
`last_test_message` VARCHAR(500) NULL COMMENT '最近测试信息',
`database_product` VARCHAR(128) NULL COMMENT '数据库产品',
`database_version` VARCHAR(128) NULL COMMENT '数据库版本',
`driver_name` VARCHAR(255) NULL COMMENT '驱动名称',
`driver_version` VARCHAR(128) NULL COMMENT '驱动版本',
`last_tested_at` DATETIME NULL COMMENT '最近测试时间',
`metadata_refreshed_at` DATETIME NULL COMMENT '元数据刷新时间',
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (`id`),
KEY `idx_dataspace_connection_tenant_name` (`tenant_id`, `name`, `is_deleted`),
KEY `idx_dataspace_connection_tenant_status` (`tenant_id`, `status`, `is_deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间物理数据库连接';
CREATE TABLE `tb_dataspace_object` (
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
`connection_id` BIGINT UNSIGNED NOT NULL COMMENT '连接ID',
`catalog_name` VARCHAR(128) NULL COMMENT '物理Catalog',
`schema_name` VARCHAR(128) NULL COMMENT '物理Schema',
`object_name` VARCHAR(128) NOT NULL COMMENT '表或视图名称',
`object_type` VARCHAR(32) NOT NULL COMMENT '对象类型',
`remarks` VARCHAR(500) NULL COMMENT '对象说明',
`columns_json` JSON NOT NULL COMMENT '字段元数据快照',
`primary_keys_json` JSON NOT NULL COMMENT '主键字段快照',
`metadata_revision` BIGINT NOT NULL COMMENT '所属连接Definition revision',
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (`id`),
KEY `idx_dataspace_object_snapshot` (`tenant_id`, `connection_id`, `metadata_revision`, `schema_name`, `object_name`),
KEY `idx_dataspace_object_search` (`tenant_id`, `connection_id`, `object_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间版本化数据库对象元数据';
CREATE TABLE `tb_dataspace` (
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
`name` VARCHAR(100) NOT NULL COMMENT '数据空间名称',
`description` VARCHAR(500) NULL COMMENT '说明',
`current_revision` BIGINT NOT NULL DEFAULT 0 COMMENT '当前生效revision',
`status` VARCHAR(24) NOT NULL DEFAULT 'ENABLED' COMMENT '状态',
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (`id`),
KEY `idx_dataspace_tenant_name` (`tenant_id`, `name`, `is_deleted`),
KEY `idx_dataspace_tenant_status` (`tenant_id`, `status`, `is_deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Agent隔离查询使用的虚拟数据空间';
CREATE TABLE `tb_dataspace_revision` (
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
`dataspace_id` BIGINT UNSIGNED NOT NULL COMMENT '数据空间ID',
`revision_no` BIGINT NOT NULL COMMENT '不可变revision',
`snapshot_checksum` CHAR(64) NOT NULL COMMENT '快照SHA-256',
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_dataspace_revision` (`tenant_id`, `dataspace_id`, `revision_no`),
KEY `idx_dataspace_revision_lookup` (`tenant_id`, `dataspace_id`, `revision_no`, `id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间不可变revision';
CREATE TABLE `tb_dataspace_table_binding` (
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
`dataspace_id` BIGINT UNSIGNED NOT NULL COMMENT '数据空间ID',
`revision_id` BIGINT UNSIGNED NOT NULL COMMENT 'revision ID',
`connection_id` BIGINT UNSIGNED NOT NULL COMMENT '物理连接ID',
`source_revision` BIGINT NOT NULL COMMENT '绑定时物理源revision',
`object_id` BIGINT UNSIGNED NOT NULL COMMENT '版本化元数据对象ID',
`source_alias` VARCHAR(64) NOT NULL COMMENT 'Calcite数据源别名',
`schema_alias` VARCHAR(64) NOT NULL COMMENT 'Calcite Schema别名',
`table_alias` VARCHAR(128) NOT NULL COMMENT 'Calcite表名',
`position_x` INT NULL COMMENT '画布横坐标',
`position_y` INT NULL COMMENT '画布纵坐标',
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_dataspace_binding_object` (`tenant_id`, `revision_id`, `object_id`),
KEY `idx_dataspace_binding_revision` (`tenant_id`, `revision_id`, `id`),
KEY `idx_dataspace_binding_connection` (`tenant_id`, `connection_id`, `revision_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间revision表绑定';
CREATE TABLE `tb_dataspace_relation` (
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
`dataspace_id` BIGINT UNSIGNED NOT NULL COMMENT '数据空间ID',
`revision_id` BIGINT UNSIGNED NOT NULL COMMENT 'revision ID',
`left_binding_id` BIGINT UNSIGNED NOT NULL COMMENT '左表绑定ID',
`right_binding_id` BIGINT UNSIGNED NOT NULL COMMENT '右表绑定ID',
`join_type` VARCHAR(16) NOT NULL COMMENT 'Join类型',
`left_column` VARCHAR(128) NOT NULL COMMENT '左字段',
`right_column` VARCHAR(128) NOT NULL COMMENT '右字段',
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (`id`),
KEY `idx_dataspace_relation_revision` (`tenant_id`, `revision_id`, `id`),
KEY `idx_dataspace_relation_left` (`tenant_id`, `left_binding_id`),
KEY `idx_dataspace_relation_right` (`tenant_id`, `right_binding_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间revision表关联';
CREATE TABLE `tb_dataspace_query_audit` (
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
`dataspace_id` BIGINT UNSIGNED NOT NULL COMMENT '数据空间ID',
`revision_no` BIGINT NOT NULL COMMENT '查询使用的revision',
`query_id` VARCHAR(64) NOT NULL COMMENT '查询ID',
`query_mode` VARCHAR(32) NULL COMMENT '单源或联邦模式',
`sql_digest` CHAR(64) NOT NULL COMMENT 'SQL SHA-256',
`sql_text` MEDIUMTEXT NULL COMMENT '有界SQL文本',
`status` VARCHAR(24) NOT NULL COMMENT '查询终态',
`returned_rows` BIGINT NOT NULL DEFAULT 0 COMMENT '返回行数',
`intermediate_rows` BIGINT NOT NULL DEFAULT 0 COMMENT '中间结果行数',
`duration_millis` BIGINT NOT NULL DEFAULT 0 COMMENT '总耗时毫秒',
`error_code` VARCHAR(64) NULL COMMENT '错误码',
`error_message` VARCHAR(500) NULL COMMENT '脱敏错误信息',
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_dataspace_query_id` (`tenant_id`, `query_id`),
KEY `idx_dataspace_query_audit` (`tenant_id`, `dataspace_id`, `created`, `id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间查询审计';
INSERT INTO `tb_sys_menu` (
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
)
SELECT
368100000000000001, 0, 0, 'menus.ai.dataspace', '/dataspace', '/dataspace/DataspaceWorkspace', 'lucide:database-zap',
1, '', 54, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '统一数据空间与联邦SQL工作台'
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `tb_sys_menu` WHERE `id` = 368100000000000001);
INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES
(368100000000000011, 368100000000000001, 1, '连接查询', '', '', '', 0, '/api/v1/dataspaceConnection/query', 1, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间连接查询'),
(368100000000000012, 368100000000000001, 1, '连接保存', '', '', '', 0, '/api/v1/dataspaceConnection/save', 2, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间连接保存'),
(368100000000000013, 368100000000000001, 1, '连接删除', '', '', '', 0, '/api/v1/dataspaceConnection/remove', 3, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间连接删除'),
(368100000000000014, 368100000000000001, 1, '连接测试', '', '', '', 0, '/api/v1/dataspaceConnection/test', 4, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间连接测试'),
(368100000000000015, 368100000000000001, 1, '元数据刷新', '', '', '', 0, '/api/v1/dataspaceConnection/metadata', 5, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间元数据刷新'),
(368100000000000021, 368100000000000001, 1, '空间查询', '', '', '', 0, '/api/v1/dataspace/query', 11, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间列表查询'),
(368100000000000022, 368100000000000001, 1, '空间详情', '', '', '', 0, '/api/v1/dataspace/detail', 12, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间详情'),
(368100000000000023, 368100000000000001, 1, '空间保存', '', '', '', 0, '/api/v1/dataspace/save', 13, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间保存'),
(368100000000000024, 368100000000000001, 1, '空间删除', '', '', '', 0, '/api/v1/dataspace/remove', 14, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间删除'),
(368100000000000031, 368100000000000001, 1, 'SQL查询', '', '', '', 0, '/api/v1/dataspaceSql/query', 21, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间SQL查询'),
(368100000000000032, 368100000000000001, 1, 'SQL Explain', '', '', '', 0, '/api/v1/dataspaceSql/explain', 22, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间SQL Explain');
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES
(368100000000000101, 1, 368100000000000001),
(368100000000000111, 1, 368100000000000011),
(368100000000000112, 1, 368100000000000012),
(368100000000000113, 1, 368100000000000013),
(368100000000000114, 1, 368100000000000014),
(368100000000000115, 1, 368100000000000015),
(368100000000000121, 1, 368100000000000021),
(368100000000000122, 1, 368100000000000022),
(368100000000000123, 1, 368100000000000023),
(368100000000000124, 1, 368100000000000024),
(368100000000000131, 1, 368100000000000031),
(368100000000000132, 1, 368100000000000032);