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,68 @@
package tech.easyflow.admin.controller.datacenter;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.row.Row;
import org.springframework.web.bind.annotation.*;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.execution.model.DatacenterQueryRequest;
import tech.easyflow.datacenter.execution.model.DatacenterSchemaResponse;
import tech.easyflow.datacenter.execution.model.DatasetRef;
import tech.easyflow.datacenter.meta.model.DatacenterBatchRemoveRequest;
import tech.easyflow.datacenter.meta.model.DatacenterSaveDescriptionsRequest;
import tech.easyflow.datacenter.execution.service.DatacenterDatasetQueryService;
import tech.easyflow.datacenter.meta.service.DatacenterDatasetRegistryService;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.List;
@RestController
@RequestMapping("/api/v1/datacenterDataset")
public class DatacenterDatasetController {
@Resource
private DatacenterDatasetQueryService queryService;
@Resource
private DatacenterDatasetRegistryService registryService;
@PostMapping("/queryPage")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<Page<Row>> queryPage(@RequestBody DatacenterQueryRequest request) {
return Result.ok(queryService.queryPage(request));
}
@GetMapping("/schema")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<DatacenterSchemaResponse> schema(DatasetRef datasetRef) {
return Result.ok(queryService.getSchema(datasetRef));
}
@GetMapping("/managedTables")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<List<DatacenterTable>> managedTables(BigInteger sourceId, BigInteger catalogId) {
return Result.ok(registryService.listManagedTables(sourceId, catalogId));
}
@PostMapping("/removeBatch")
@SaCheckPermission("/api/v1/datacenterSource/save")
public Result<Integer> removeBatch(@RequestBody DatacenterBatchRemoveRequest request) {
return Result.ok(registryService.removeTables(request == null ? List.of() : request.getTableIds()));
}
@PostMapping("/saveDescriptions")
@SaCheckPermission("/api/v1/datacenterSource/save")
public Result<DatacenterSchemaResponse> saveDescriptions(@RequestBody DatacenterSaveDescriptionsRequest request) {
LoginAccount account = SaTokenUtil.getLoginAccount();
DatacenterTable table = registryService.saveDescriptions(
request == null ? null : request.getTableId(),
request == null ? null : request.getTableDesc(),
request == null ? List.of() : request.getFields(),
account
);
return Result.ok(queryService.getSchema(registryService.resolveDatasetRef(table.getId())));
}
}

View File

@@ -0,0 +1,108 @@
package tech.easyflow.admin.controller.datacenter;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.datacenter.excel.model.DatacenterExcelDeriveRequest;
import tech.easyflow.datacenter.excel.model.DatacenterExcelExportRequest;
import tech.easyflow.datacenter.excel.model.DatacenterExcelMergeRequest;
import tech.easyflow.datacenter.excel.model.DatacenterExcelSplitRequest;
import tech.easyflow.datacenter.excel.service.DatacenterExcelImportService;
import tech.easyflow.datacenter.meta.entity.DatacenterImportJob;
import java.io.File;
import java.io.FileInputStream;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.util.List;
@RestController
@RequestMapping("/api/v1/datacenterExcel")
public class DatacenterExcelController {
@Resource
private DatacenterExcelImportService excelImportService;
@PostMapping("/import")
@SaCheckPermission("/api/v1/datacenterSource/save")
public Result<DatacenterImportJob> importWorkbook(MultipartFile file) throws Exception {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(excelImportService.importWorkbook(file, account));
}
@PostMapping("/split")
@SaCheckPermission("/api/v1/datacenterSource/save")
public Result<DatacenterImportJob> split(@RequestBody DatacenterExcelSplitRequest request) {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(excelImportService.splitWorkbook(request, account));
}
@PostMapping("/merge")
@SaCheckPermission("/api/v1/datacenterSource/save")
public Result<DatacenterImportJob> merge(@RequestBody DatacenterExcelMergeRequest request) {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(excelImportService.mergeWorkbook(request, account));
}
@PostMapping("/derive")
@SaCheckPermission("/api/v1/datacenterSource/save")
public Result<DatacenterImportJob> derive(@RequestBody DatacenterExcelDeriveRequest request) {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(excelImportService.deriveWorkbook(request, account));
}
@PostMapping("/export")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<DatacenterImportJob> export(@RequestBody DatacenterExcelExportRequest request) throws Exception {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(excelImportService.exportWorkbook(request, account));
}
@GetMapping("/importJob/detail")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<DatacenterImportJob> jobDetail(BigInteger jobId) {
return Result.ok(excelImportService.getImportJobDetail(jobId));
}
@GetMapping("/job/detail")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<DatacenterImportJob> newJobDetail(BigInteger jobId) {
return Result.ok(excelImportService.getImportJobDetail(jobId));
}
@GetMapping("/job/list")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<List<DatacenterImportJob>> jobList(BigInteger sourceId, BigInteger tableId) {
return Result.ok(excelImportService.listJobs(sourceId, tableId));
}
@GetMapping("/download")
@SaCheckPermission("/api/v1/datacenterSource/query")
public void download(BigInteger jobId, HttpServletResponse response) throws Exception {
DatacenterImportJob job = excelImportService.getImportJobDetail(jobId);
if (job.getStoragePath() == null || job.getStoragePath().isBlank()) {
throw new IllegalStateException("导出文件不存在");
}
File file = new File(job.getStoragePath());
if (!file.exists()) {
throw new IllegalStateException("导出文件不存在");
}
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode(job.getFileName(), "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
try (FileInputStream inputStream = new FileInputStream(file)) {
inputStream.transferTo(response.getOutputStream());
response.flushBuffer();
}
}
}

View File

@@ -0,0 +1,86 @@
package tech.easyflow.admin.controller.datacenter;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
import org.springframework.web.bind.annotation.*;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
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.DatacenterRemoveSourceRequest;
import tech.easyflow.datacenter.meta.model.DatacenterTableDetailMeta;
import tech.easyflow.datacenter.meta.service.DatacenterSourceService;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.List;
@RestController
@RequestMapping("/api/v1/datacenterSource")
public class DatacenterSourceController {
@Resource
private DatacenterSourceService sourceService;
@PostMapping("/testConnection")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<DatacenterConnectionTestResult> testConnection(@RequestBody DatacenterSource source) {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(sourceService.testConnection(source, account));
}
@PostMapping("/save")
@SaCheckPermission("/api/v1/datacenterSource/save")
public Result<DatacenterSource> save(@RequestBody DatacenterSource source) {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(sourceService.saveSource(source, account));
}
@GetMapping("/page")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<Page<DatacenterSource>> page(Long pageNumber, Long pageSize) {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(sourceService.pageSources(pageNumber, pageSize, account));
}
@GetMapping("/catalogs")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<List<DatacenterCatalogMeta>> catalogs(BigInteger sourceId) {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(sourceService.listCatalogs(sourceId, account));
}
@GetMapping("/tables")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<List<DatacenterTable>> tables(BigInteger sourceId, String catalogName) {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(sourceService.listTables(sourceId, catalogName, account));
}
@GetMapping("/tableDetail")
@SaCheckPermission("/api/v1/datacenterSource/query")
public Result<DatacenterTableDetailMeta> tableDetail(BigInteger sourceId, String catalogName, String tableName,
@RequestParam(defaultValue = "false") boolean register) {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(sourceService.getTableDetail(sourceId, catalogName, tableName, register, account));
}
@PostMapping("/registerBatch")
@SaCheckPermission("/api/v1/datacenterSource/save")
public Result<List<DatacenterTable>> registerBatch(@RequestBody DatacenterBatchRegisterRequest request) {
LoginAccount account = SaTokenUtil.getLoginAccount();
return Result.ok(sourceService.batchRegisterTables(request, account));
}
@PostMapping("/remove")
@SaCheckPermission("/api/v1/datacenterSource/save")
public Result<Void> remove(@RequestBody DatacenterRemoveSourceRequest request) {
LoginAccount account = SaTokenUtil.getLoginAccount();
sourceService.removeSource(request == null ? null : request.getSourceId(), account);
return Result.ok();
}
}

View File

@@ -1,195 +0,0 @@
package tech.easyflow.admin.controller.datacenter;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.collection.CollectionUtil;
import cn.idev.excel.EasyExcel;
import cn.idev.excel.FastExcel;
import com.alibaba.fastjson2.JSONObject;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.row.Row;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.DatacenterQuery;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.entity.DatacenterTableField;
import tech.easyflow.datacenter.entity.vo.HeaderVo;
import tech.easyflow.datacenter.excel.ReadDataListener;
import tech.easyflow.datacenter.excel.ReadResVo;
import tech.easyflow.datacenter.service.DatacenterTableFieldService;
import tech.easyflow.datacenter.service.DatacenterTableService;
import javax.annotation.Resource;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 数据中枢表 控制层。
*
* @author ArkLight
* @since 2025-07-10
*/
@RestController
@RequestMapping("/api/v1/datacenterTable")
public class DatacenterTableController extends BaseCurdController<DatacenterTableService, DatacenterTable> {
@Resource
private DatacenterTableFieldService fieldsService;
public DatacenterTableController(DatacenterTableService service) {
super(service);
}
@Override
protected Result onSaveOrUpdateBefore(DatacenterTable entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave) {
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModifiedBy(loginUser.getId());
}
return super.onSaveOrUpdateBefore(entity, isSave);
}
@PostMapping("/saveTable")
@SaCheckPermission("/api/v1/datacenterTable/save")
public Result<Void> saveTable(@RequestBody DatacenterTable entity) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
List<DatacenterTableField> fields = entity.getFields();
if (CollectionUtil.isEmpty(fields)) {
return Result.fail(99, "字段不能为空");
}
BigInteger id = entity.getId();
if (id == null) {
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
service.saveTable(entity, loginUser);
return Result.ok();
}
@GetMapping("/detailInfo")
@SaCheckPermission("/api/v1/datacenterTable/query")
public Result<DatacenterTable> detailInfo(BigInteger tableId) {
DatacenterTable table = service.getById(tableId);
QueryWrapper wrapper = QueryWrapper.create();
wrapper.eq(DatacenterTableField::getTableId, tableId);
wrapper.orderBy("id");
List<DatacenterTableField> fields = fieldsService.list(wrapper);
table.setFields(fields);
return Result.ok(table);
}
@GetMapping("/removeTable")
@SaCheckPermission("/api/v1/datacenterTable/remove")
public Result<Void> removeTable(BigInteger tableId) {
service.removeTable(tableId);
return Result.ok();
}
@GetMapping("/getHeaders")
@SaCheckPermission("/api/v1/datacenterTable/query")
public Result<List<HeaderVo>> getHeaders(BigInteger tableId) {
List<HeaderVo> res = service.getHeaders(tableId);
return Result.ok(res);
}
@GetMapping("/getPageData")
@SaCheckPermission("/api/v1/datacenterTable/query")
public Result<Page<Row>> getPageData(DatacenterQuery where) {
Page<Row> res = service.getPageData(where);
return Result.ok(res);
}
@PostMapping("/saveValue")
@SaCheckPermission("/api/v1/datacenterTable/save")
public Result<Void> saveValue(@RequestParam Map<String, Object> map) {
JSONObject object = new JSONObject(map);
BigInteger tableId = object.getBigInteger("tableId");
LoginAccount account = SaTokenUtil.getLoginAccount();
if (tableId == null) {
return Result.fail(99, "参数错误");
}
service.saveValue(tableId, object, account);
return Result.ok();
}
@PostMapping("/removeValue")
@SaCheckPermission("/api/v1/datacenterTable/remove")
public Result<Void> removeValue(@RequestParam Map<String, Object> map) {
JSONObject object = new JSONObject(map);
BigInteger tableId = object.getBigInteger("tableId");
BigInteger id = object.getBigInteger("id");
if (tableId == null || id == null) {
return Result.fail(99, "参数错误");
}
LoginAccount account = SaTokenUtil.getLoginAccount();
service.removeValue(tableId, id, account);
return Result.ok();
}
/**
* 导入数据
*/
@PostMapping("/importData")
@SaCheckPermission("/api/v1/datacenterTable/save")
public Result<ReadResVo> importData(MultipartFile file, @RequestParam Map<String, Object> map) throws Exception {
Object tableId = map.get("tableId");
DatacenterTable record = service.getById(tableId.toString());
if (record == null) {
throw new RuntimeException("数据表不存在");
}
InputStream is = file.getInputStream();
List<DatacenterTableField> fields = service.getFields(record.getId());
LoginAccount account = SaTokenUtil.getLoginAccount();
ReadDataListener listener = new ReadDataListener(record.getId(), fields, account);
FastExcel.read(is, listener)
.sheet()
.doRead();
int totalCount = listener.getTotalCount();
int errorCount = listener.getErrorCount();
int successCount = listener.getSuccessCount();
List<JSONObject> errorRows = listener.getErrorRows();
return Result.ok(new ReadResVo(successCount, errorCount, totalCount, errorRows));
}
@GetMapping("/getTemplate")
public void getTemplate(BigInteger tableId, HttpServletResponse response) throws Exception {
List<DatacenterTableField> fields = service.getFields(tableId);
// 设置响应内容类型
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 设置文件名
String fileName = URLEncoder.encode("导入模板", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
// 动态表头数据
List<List<String>> headList = new ArrayList<>();
for (DatacenterTableField field : fields) {
List<String> head = new ArrayList<>();
head.add(field.getFieldName());
headList.add(head);
}
// 写入Excel
EasyExcel.write(response.getOutputStream())
.head(headList)
.sheet("模板")
.doWrite(new ArrayList<>()); // 写入空数据,只生成模板
}
}

View File

@@ -1,41 +0,0 @@
package tech.easyflow.admin.controller.datacenter;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.datacenter.entity.DatacenterTableField;
import tech.easyflow.datacenter.service.DatacenterTableFieldService;
import java.util.Date;
/**
* 控制层。
*
* @author ArkLight
* @since 2025-07-10
*/
@RestController
@RequestMapping("/api/v1/datacenterTableFields")
@UsePermission(moduleName = "/api/v1/datacenterTable")
public class DatacenterTableFieldsController extends BaseCurdController<DatacenterTableFieldService, DatacenterTableField> {
public DatacenterTableFieldsController(DatacenterTableFieldService service) {
super(service);
}
@Override
protected Result onSaveOrUpdateBefore(DatacenterTableField entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave) {
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
return null;
}
}