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

@@ -0,0 +1,161 @@
package tech.easyflow.admin.controller.dataspace;
import cn.dev33.satoken.annotation.SaCheckPermission;
import java.math.BigInteger;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.dataspace.model.ConnectionDefinition;
import tech.easyflow.dataspace.model.ConnectionView;
import tech.easyflow.dataspace.model.ObjectView;
import tech.easyflow.dataspace.provider.DataspaceProbe;
import tech.easyflow.dataspace.service.DataspaceConnectionService;
/**
* 数据空间物理连接管理端 API。
*/
@RestController
@RequestMapping("/api/v1/dataspaceConnection")
public class DataspaceConnectionController {
private final DataspaceConnectionService connectionService;
/**
* 创建连接控制器。
*
* @param connectionService 连接服务
*/
public DataspaceConnectionController(DataspaceConnectionService connectionService) {
this.connectionService = connectionService;
}
/**
* 查询当前租户连接列表。
*
* @param keyword 搜索关键词
* @return 连接列表
*/
@GetMapping("/list")
@SaCheckPermission("/api/v1/dataspaceConnection/query")
public Result<List<ConnectionView>> list(String keyword) {
return Result.ok(connectionService.list(keyword));
}
/**
* 获取连接详情。
*
* @param id 连接 ID
* @return 连接详情
*/
@GetMapping("/detail")
@SaCheckPermission("/api/v1/dataspaceConnection/query")
public Result<ConnectionView> detail(BigInteger id) {
return Result.ok(connectionService.detail(id));
}
/**
* 测试候选或已保存连接。
*
* @param definition 连接定义
* @return 测试结果
*/
@PostMapping("/test")
@SaCheckPermission("/api/v1/dataspaceConnection/test")
public Result<DataspaceProbe> test(
@JsonBody(required = true, skipConvertError = false) ConnectionDefinition definition) {
return Result.ok(connectionService.test(definition));
}
/**
* 创建或更新连接。
*
* @param definition 连接定义
* @return 保存后的连接
*/
@PostMapping("/save")
@SaCheckPermission("/api/v1/dataspaceConnection/save")
public Result<ConnectionView> save(
@JsonBody(required = true, skipConvertError = false) ConnectionDefinition definition) {
return Result.ok(connectionService.save(definition));
}
/**
* 启用或禁用连接。
*
* @param request 状态变更请求
* @return 变更后的连接
*/
@PostMapping("/status")
@SaCheckPermission("/api/v1/dataspaceConnection/save")
public Result<ConnectionView> status(
@JsonBody(required = true, skipConvertError = false) StatusRequest request) {
if (request == null || request.enabled() == null) {
throw new BusinessException("连接状态不能为空");
}
return Result.ok(connectionService.setEnabled(request.id(), request.enabled()));
}
/**
* 查询当前连接的对象树数据。
*
* @param connectionId 连接 ID
* @param keyword Schema 或表名关键词
* @return 对象列表
*/
@GetMapping("/objects")
@SaCheckPermission("/api/v1/dataspaceConnection/query")
public Result<List<ObjectView>> objects(BigInteger connectionId, String keyword) {
return Result.ok(connectionService.objects(connectionId, keyword));
}
/**
* 刷新连接元数据。
*
* @param request 刷新请求
* @return 新 revision 对象列表
*/
@PostMapping("/refreshMetadata")
@SaCheckPermission("/api/v1/dataspaceConnection/metadata")
public Result<List<ObjectView>> refreshMetadata(
@JsonBody(required = true, skipConvertError = false) RefreshRequest request) {
return Result.ok(connectionService.refreshMetadata(
request.connectionId(), request.expectedRevision()));
}
/**
* 删除未被引用的连接。
*
* @param id 连接 ID
* @return 成功结果
*/
@PostMapping("/remove")
@SaCheckPermission("/api/v1/dataspaceConnection/remove")
public Result<Void> remove(
@JsonBody(value = "id", required = true, skipConvertError = false) BigInteger id) {
connectionService.remove(id);
return Result.ok();
}
/**
* 元数据刷新请求。
*
* @param connectionId 连接 ID
* @param expectedRevision 期望 revision
*/
public record RefreshRequest(BigInteger connectionId, long expectedRevision) {
}
/**
* 连接状态变更请求。
*
* @param id 连接 ID
* @param enabled 是否启用
*/
public record StatusRequest(BigInteger id, Boolean enabled) {
}
}

View File

@@ -0,0 +1,113 @@
package tech.easyflow.admin.controller.dataspace;
import cn.dev33.satoken.annotation.SaCheckPermission;
import java.math.BigInteger;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.dataspace.model.DataspaceDefinition;
import tech.easyflow.dataspace.model.DataspaceSummary;
import tech.easyflow.dataspace.model.DataspaceView;
import tech.easyflow.dataspace.service.DataspaceService;
/**
* 虚拟数据空间管理端 API。
*/
@RestController
@RequestMapping("/api/v1/dataspace")
public class DataspaceController {
private final DataspaceService dataspaceService;
/**
* 创建数据空间控制器。
*
* @param dataspaceService 数据空间服务
*/
public DataspaceController(DataspaceService dataspaceService) {
this.dataspaceService = dataspaceService;
}
/**
* 查询数据空间列表。
*
* @param keyword 搜索关键词
* @return 数据空间摘要
*/
@GetMapping("/list")
@SaCheckPermission("/api/v1/dataspace/query")
public Result<List<DataspaceSummary>> list(String keyword) {
return Result.ok(dataspaceService.list(keyword));
}
/**
* 获取数据空间当前 revision 详情。
*
* @param id 数据空间 ID
* @return 数据空间详情
*/
@GetMapping("/detail")
@SaCheckPermission("/api/v1/dataspace/detail")
public Result<DataspaceView> detail(BigInteger id) {
return Result.ok(dataspaceService.detail(id));
}
/**
* 保存数据空间并生成新 revision。
*
* @param definition 数据空间定义
* @return 保存后的详情
*/
@PostMapping("/save")
@SaCheckPermission("/api/v1/dataspace/save")
public Result<DataspaceView> save(
@RequestBody DataspaceDefinition definition) {
return Result.ok(dataspaceService.save(definition));
}
/**
* 启用或禁用数据空间。
*
* @param request 状态变更请求
* @return 成功结果
*/
@PostMapping("/status")
@SaCheckPermission("/api/v1/dataspace/save")
public Result<Void> status(
@JsonBody(required = true, skipConvertError = false) StatusRequest request) {
if (request == null || request.enabled() == null) {
throw new BusinessException("数据空间状态不能为空");
}
dataspaceService.setEnabled(request.id(), request.enabled());
return Result.ok();
}
/**
* 逻辑删除数据空间。
*
* @param id 数据空间 ID
* @return 成功结果
*/
@PostMapping("/remove")
@SaCheckPermission("/api/v1/dataspace/remove")
public Result<Void> remove(
@JsonBody(value = "id", required = true, skipConvertError = false) BigInteger id) {
dataspaceService.remove(id);
return Result.ok();
}
/**
* 数据空间状态变更请求。
*
* @param id 数据空间 ID
* @param enabled 是否启用
*/
public record StatusRequest(BigInteger id, Boolean enabled) {
}
}

View File

@@ -0,0 +1,86 @@
package tech.easyflow.admin.controller.dataspace;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.dataspace.model.DataspaceExplainResult;
import tech.easyflow.dataspace.model.DataspaceQueryRequest;
import tech.easyflow.dataspace.model.DataspaceQueryResult;
import tech.easyflow.dataspace.model.DataspaceSqlCompletionRequest;
import tech.easyflow.dataspace.model.DataspaceSqlCompletionResult;
import tech.easyflow.dataspace.service.DataspaceQueryService;
/**
* 数据空间 SQL 工作台 Query、Explain、Complete 与 Cancel API。
*/
@RestController
@RequestMapping("/api/v1/dataspaceSql")
public class DataspaceSqlController {
private final DataspaceQueryService queryService;
/**
* 创建 SQL 控制器。
*
* @param queryService 查询服务
*/
public DataspaceSqlController(DataspaceQueryService queryService) {
this.queryService = queryService;
}
/**
* 执行只读 SQL。
*
* @param request 查询请求
* @return 查询结果与指标
*/
@PostMapping("/query")
@SaCheckPermission("/api/v1/dataspaceSql/query")
public Result<DataspaceQueryResult> query(
@JsonBody(required = true, skipConvertError = false) DataspaceQueryRequest request) {
return Result.ok(queryService.query(request));
}
/**
* 显式执行非 ANALYZE Explain。
*
* @param request Explain 请求
* @return Explain 与索引信息
*/
@PostMapping("/explain")
@SaCheckPermission("/api/v1/dataspaceSql/explain")
public Result<DataspaceExplainResult> explain(
@JsonBody(required = true, skipConvertError = false) DataspaceQueryRequest request) {
return Result.ok(queryService.explain(request));
}
/**
* 返回当前数据空间内的 Calcite SQL 补全候选。
*
* @param request 补全请求
* @return 补全替换区间与候选
*/
@PostMapping("/complete")
@SaCheckPermission("/api/v1/dataspaceSql/query")
public Result<DataspaceSqlCompletionResult> complete(
@JsonBody(required = true, skipConvertError = false)
DataspaceSqlCompletionRequest request) {
return Result.ok(queryService.complete(request));
}
/**
* 尝试取消当前节点查询。
*
* @param queryId 查询 ID
* @return 是否找到并发起取消
*/
@PostMapping("/cancel")
@SaCheckPermission("/api/v1/dataspaceSql/query")
public Result<Boolean> cancel(
@JsonBody(value = "queryId", required = true, skipConvertError = false) String queryId) {
return Result.ok(queryService.cancel(queryId));
}
}