You've already forked DataMate
init datamate
This commit is contained in:
229
backend/services/data-collection-service/README.md
Normal file
229
backend/services/data-collection-service/README.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# 数据归集服务 (Data Collection Service)
|
||||
|
||||
基于DataX的数据归集和同步服务,提供多数据源之间的数据同步功能。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🔗 **多数据源支持**: 支持MySQL、PostgreSQL、Oracle、SQL Server等主流数据库
|
||||
- 📊 **任务管理**: 创建、配置、执行和监控数据同步任务
|
||||
- ⏰ **定时调度**: 支持Cron表达式的定时任务
|
||||
- 📈 **实时监控**: 任务执行进度、状态和性能指标监控
|
||||
- 📝 **执行日志**: 详细的任务执行日志记录
|
||||
- 🔌 **插件化**: DataX Reader/Writer插件化集成
|
||||
|
||||
## 技术架构
|
||||
|
||||
- **框架**: Spring Boot 3.x
|
||||
- **数据库**: MySQL + MyBatis
|
||||
- **同步引擎**: DataX
|
||||
- **API**: OpenAPI 3.0 自动生成
|
||||
- **架构模式**: DDD (领域驱动设计)
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
src/main/java/com/datamate/collection/
|
||||
├── DataCollectionApplication.java # 应用启动类
|
||||
├── domain/ # 领域层
|
||||
│ ├── model/ # 领域模型
|
||||
│ │ ├── DataSource.java # 数据源实体
|
||||
│ │ ├── CollectionTask.java # 归集任务实体
|
||||
│ │ ├── TaskExecution.java # 任务执行记录
|
||||
│ │ └── ExecutionLog.java # 执行日志
|
||||
│ └── service/ # 领域服务
|
||||
│ ├── DataSourceService.java
|
||||
│ ├── CollectionTaskService.java
|
||||
│ ├── TaskExecutionService.java
|
||||
│ └── impl/ # 服务实现
|
||||
├── infrastructure/ # 基础设施层
|
||||
│ ├── config/ # 配置类
|
||||
│ ├── datax/ # DataX执行引擎
|
||||
│ │ └── DataXExecutionEngine.java
|
||||
│ └── persistence/ # 持久化
|
||||
│ ├── mapper/ # MyBatis Mapper
|
||||
│ └── typehandler/ # 类型处理器
|
||||
└── interfaces/ # 接口层
|
||||
├── api/ # OpenAPI生成的接口
|
||||
├── dto/ # OpenAPI生成的DTO
|
||||
└── rest/ # REST控制器
|
||||
├── DataSourceController.java
|
||||
├── CollectionTaskController.java
|
||||
├── TaskExecutionController.java
|
||||
└── exception/ # 异常处理
|
||||
|
||||
src/main/resources/
|
||||
├── mappers/ # MyBatis XML映射文件
|
||||
├── application.properties # 应用配置
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Java 17+
|
||||
- Maven 3.6+
|
||||
- MySQL 8.0+
|
||||
- DataX 3.0+
|
||||
- Redis (可选,用于缓存)
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 应用配置 (application.properties)
|
||||
|
||||
```properties
|
||||
# 服务端口
|
||||
server.port=8090
|
||||
|
||||
# 数据库配置
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/knowledge_base
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=123456
|
||||
|
||||
# DataX配置
|
||||
datax.home=/runtime/datax
|
||||
datax.python.path=/runtime/datax/bin/datax.py
|
||||
datax.job.timeout=7200
|
||||
datax.job.memory=2g
|
||||
```
|
||||
|
||||
### DataX配置
|
||||
|
||||
确保DataX已正确安装并配置:
|
||||
|
||||
1. 下载DataX到 `/runtime/datax` 目录
|
||||
2. 配置相关Reader/Writer插件
|
||||
3. 确保Python环境可用
|
||||
|
||||
## 数据库初始化
|
||||
|
||||
执行数据库初始化脚本:
|
||||
|
||||
```bash
|
||||
mysql -u root -p knowledge_base < scripts/db/data-collection-init.sql
|
||||
```
|
||||
|
||||
## 构建和运行
|
||||
|
||||
### 1. 编译项目
|
||||
|
||||
```bash
|
||||
cd backend/services/data-collection-service
|
||||
mvn clean compile
|
||||
```
|
||||
|
||||
这将触发OpenAPI代码生成。
|
||||
|
||||
### 2. 打包
|
||||
|
||||
```bash
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
### 3. 运行
|
||||
|
||||
作为独立服务运行:
|
||||
```bash
|
||||
java -jar target/data-collection-service-1.0.0-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
或通过main-application统一启动:
|
||||
```bash
|
||||
cd backend/services/main-application
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
## API文档
|
||||
|
||||
服务启动后,可通过以下地址访问API文档:
|
||||
|
||||
- Swagger UI: http://localhost:8090/swagger-ui.html
|
||||
- OpenAPI JSON: http://localhost:8090/v3/api-docs
|
||||
|
||||
## 主要API端点
|
||||
|
||||
### 数据源管理
|
||||
|
||||
- `GET /api/v1/collection/datasources` - 获取数据源列表
|
||||
- `POST /api/v1/collection/datasources` - 创建数据源
|
||||
- `GET /api/v1/collection/datasources/{id}` - 获取数据源详情
|
||||
- `PUT /api/v1/collection/datasources/{id}` - 更新数据源
|
||||
- `DELETE /api/v1/collection/datasources/{id}` - 删除数据源
|
||||
- `POST /api/v1/collection/datasources/{id}/test` - 测试连接
|
||||
|
||||
### 归集任务管理
|
||||
|
||||
- `GET /api/v1/collection/tasks` - 获取任务列表
|
||||
- `POST /api/v1/collection/tasks` - 创建任务
|
||||
- `GET /api/v1/collection/tasks/{id}` - 获取任务详情
|
||||
- `PUT /api/v1/collection/tasks/{id}` - 更新任务
|
||||
- `DELETE /api/v1/collection/tasks/{id}` - 删除任务
|
||||
|
||||
### 任务执行管理
|
||||
|
||||
- `POST /api/v1/collection/tasks/{id}/execute` - 执行任务
|
||||
- `POST /api/v1/collection/tasks/{id}/stop` - 停止任务
|
||||
- `GET /api/v1/collection/executions` - 获取执行历史
|
||||
- `GET /api/v1/collection/executions/{executionId}` - 获取执行详情
|
||||
- `GET /api/v1/collection/executions/{executionId}/logs` - 获取执行日志
|
||||
|
||||
### 监控统计
|
||||
|
||||
- `GET /api/v1/collection/monitor/statistics` - 获取统计信息
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 添加新的数据源类型
|
||||
|
||||
1. 在 `DataSource.DataSourceType` 枚举中添加新类型
|
||||
2. 在 `DataXExecutionEngine` 中添加对应的Reader/Writer映射
|
||||
3. 更新数据库表结构和初始化数据
|
||||
|
||||
### 自定义DataX插件
|
||||
|
||||
1. 将插件放置在 `/runtime/datax/plugin` 目录下
|
||||
2. 在 `DataXExecutionEngine` 中配置插件映射关系
|
||||
3. 根据插件要求调整配置模板
|
||||
|
||||
### 扩展监控指标
|
||||
|
||||
1. 在 `StatisticsService` 中添加新的统计逻辑
|
||||
2. 更新 `CollectionStatistics` DTO
|
||||
3. 在数据库中添加相应的统计表或字段
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **DataX执行失败**
|
||||
- 检查DataX安装路径和Python环境
|
||||
- 确认数据源连接配置正确
|
||||
- 查看执行日志获取详细错误信息
|
||||
|
||||
2. **数据库连接失败**
|
||||
- 检查数据库配置和网络连通性
|
||||
- 确认数据库用户权限
|
||||
|
||||
3. **API调用失败**
|
||||
- 检查请求参数格式
|
||||
- 查看应用日志获取详细错误信息
|
||||
|
||||
### 日志查看
|
||||
|
||||
```bash
|
||||
# 应用日志
|
||||
tail -f logs/data-collection-service.log
|
||||
|
||||
# 任务执行日志
|
||||
curl http://localhost:8090/api/v1/collection/executions/{executionId}/logs
|
||||
```
|
||||
|
||||
## 贡献指南
|
||||
|
||||
1. Fork项目
|
||||
2. 创建特性分支: `git checkout -b feature/new-feature`
|
||||
3. 提交更改: `git commit -am 'Add new feature'`
|
||||
4. 推送分支: `git push origin feature/new-feature`
|
||||
5. 提交Pull Request
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
BIN
backend/services/data-collection-service/image.png
Normal file
BIN
backend/services/data-collection-service/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
BIN
backend/services/data-collection-service/image1.png
Normal file
BIN
backend/services/data-collection-service/image1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
backend/services/data-collection-service/image2.png
Normal file
BIN
backend/services/data-collection-service/image2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
BIN
backend/services/data-collection-service/image3.png
Normal file
BIN
backend/services/data-collection-service/image3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
200
backend/services/data-collection-service/pom.xml
Normal file
200
backend/services/data-collection-service/pom.xml
Normal file
@@ -0,0 +1,200 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>data-mate-platform</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>data-collection-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Data Collection Service</name>
|
||||
<description>DataX-based data collection and aggregation service</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Dependencies -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Database -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DataX Dependencies (集成DataX插件) -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-exec</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Connection Pool -->
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Oracle JDBC Driver -->
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
<version>21.5.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- PostgreSQL JDBC Driver -->
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON Processing -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Shared Domain -->
|
||||
<dependency>
|
||||
<groupId>com.datamate</groupId>
|
||||
<artifactId>domain-common</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- OpenAPI Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>jackson-databind-nullable</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Test Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.16.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- OpenAPI Generator Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>openapi-generator-maven-plugin</artifactId>
|
||||
<version>6.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<inputSpec>${project.basedir}/../../openapi/specs/data-collection.yaml</inputSpec>
|
||||
<generatorName>spring</generatorName>
|
||||
<output>${project.build.directory}/generated-sources/openapi</output>
|
||||
<apiPackage>com.datamate.collection.interfaces.api</apiPackage>
|
||||
<modelPackage>com.datamate.collection.interfaces.dto</modelPackage>
|
||||
<configOptions>
|
||||
<interfaceOnly>true</interfaceOnly>
|
||||
<useTags>true</useTags>
|
||||
<useSpringBoot3>true</useSpringBoot3>
|
||||
<documentationProvider>springdoc</documentationProvider>
|
||||
<dateLibrary>java8-localdatetime</dateLibrary>
|
||||
<java8>true</java8>
|
||||
</configOptions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
<classifier>exec</classifier>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
<annotationProcessorPaths>
|
||||
<!-- 顺序很重要 -->
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||
<version>${lombok-mapstruct-binding.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
<arg>-Amapstruct.defaultComponentModel=spring</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.datamate.collection;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* 数据归集服务配置类
|
||||
*
|
||||
* 基于DataX的数据归集和同步服务,支持多种数据源的数据采集和归集
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
@EnableTransactionManagement
|
||||
@ComponentScan(basePackages = {
|
||||
"com.datamate.collection",
|
||||
"com.datamate.shared"
|
||||
})
|
||||
public class DataCollectionServiceConfiguration {
|
||||
// Configuration class for JAR packaging - no main method needed
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.datamate.collection.application.scheduler;
|
||||
|
||||
import com.datamate.collection.application.service.DataxExecutionService;
|
||||
import com.datamate.collection.domain.model.CollectionTask;
|
||||
import com.datamate.collection.domain.model.TaskStatus;
|
||||
import com.datamate.collection.domain.model.TaskExecution;
|
||||
import com.datamate.collection.infrastructure.persistence.mapper.CollectionTaskMapper;
|
||||
import com.datamate.collection.infrastructure.persistence.mapper.TaskExecutionMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.scheduling.support.CronExpression;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class TaskSchedulerInitializer {
|
||||
|
||||
private final CollectionTaskMapper taskMapper;
|
||||
private final TaskExecutionMapper executionMapper;
|
||||
private final DataxExecutionService dataxExecutionService;
|
||||
|
||||
// 定期扫描激活的采集任务,根据 Cron 判断是否到期执行
|
||||
@Scheduled(fixedDelayString = "${datamate.data-collection.scheduler.scan-interval-ms:10000}")
|
||||
public void scanAndTrigger() {
|
||||
List<CollectionTask> tasks = taskMapper.selectActiveTasks();
|
||||
if (tasks == null || tasks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (CollectionTask task : tasks) {
|
||||
String cronExpr = task.getScheduleExpression();
|
||||
if (!StringUtils.hasText(cronExpr)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
// 如果最近一次执行仍在运行,则跳过
|
||||
TaskExecution latest = executionMapper.selectLatestByTaskId(task.getId());
|
||||
if (latest != null && latest.getStatus() == TaskStatus.RUNNING) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CronExpression cron = CronExpression.parse(cronExpr);
|
||||
LocalDateTime base = latest != null && latest.getStartedAt() != null
|
||||
? latest.getStartedAt()
|
||||
: now.minusYears(1); // 没有历史记录时,拉长基准时间确保到期判定
|
||||
LocalDateTime nextTime = cron.next(base);
|
||||
|
||||
if (nextTime != null && !nextTime.isAfter(now)) {
|
||||
// 到期,触发一次执行
|
||||
TaskExecution exec = dataxExecutionService.createExecution(task);
|
||||
int timeout = task.getTimeoutSeconds() == null ? 3600 : task.getTimeoutSeconds();
|
||||
dataxExecutionService.runAsync(task, exec.getId(), timeout);
|
||||
log.info("Triggered DataX execution for task {} at {}, execId={}", task.getId(), now, exec.getId());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.warn("Skip task {} due to invalid cron or scheduling error: {}", task.getId(), ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.datamate.collection.application.service;
|
||||
|
||||
import com.datamate.collection.domain.model.CollectionTask;
|
||||
import com.datamate.collection.domain.model.TaskExecution;
|
||||
import com.datamate.collection.domain.model.TaskStatus;
|
||||
import com.datamate.collection.domain.model.DataxTemplate;
|
||||
import com.datamate.collection.infrastructure.persistence.mapper.CollectionTaskMapper;
|
||||
import com.datamate.collection.infrastructure.persistence.mapper.TaskExecutionMapper;
|
||||
import com.datamate.collection.interfaces.dto.SyncMode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CollectionTaskService {
|
||||
private final CollectionTaskMapper taskMapper;
|
||||
private final TaskExecutionMapper executionMapper;
|
||||
private final DataxExecutionService dataxExecutionService;
|
||||
|
||||
@Transactional
|
||||
public CollectionTask create(CollectionTask task) {
|
||||
task.setStatus(TaskStatus.READY);
|
||||
task.setCreatedAt(LocalDateTime.now());
|
||||
task.setUpdatedAt(LocalDateTime.now());
|
||||
taskMapper.insert(task);
|
||||
executeTaskNow(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
private void executeTaskNow(CollectionTask task) {
|
||||
if (Objects.equals(task.getSyncMode(), SyncMode.ONCE.getValue())) {
|
||||
TaskExecution exec = dataxExecutionService.createExecution(task);
|
||||
int timeout = task.getTimeoutSeconds() == null ? 3600 : task.getTimeoutSeconds();
|
||||
dataxExecutionService.runAsync(task, exec.getId(), timeout);
|
||||
log.info("Triggered DataX execution for task {} at {}, execId={}", task.getId(), LocalDateTime.now(), exec.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CollectionTask update(CollectionTask task) {
|
||||
task.setUpdatedAt(LocalDateTime.now());
|
||||
taskMapper.update(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(String id) { taskMapper.deleteById(id); }
|
||||
|
||||
public CollectionTask get(String id) { return taskMapper.selectById(id); }
|
||||
|
||||
public List<CollectionTask> list(Integer page, Integer size, String status, String name) {
|
||||
Map<String, Object> p = new HashMap<>();
|
||||
p.put("status", status);
|
||||
p.put("name", name);
|
||||
if (page != null && size != null) {
|
||||
p.put("offset", page * size);
|
||||
p.put("limit", size);
|
||||
}
|
||||
return taskMapper.selectAll(p);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public TaskExecution startExecution(CollectionTask task) {
|
||||
return dataxExecutionService.createExecution(task);
|
||||
}
|
||||
|
||||
// ---- Template related merged methods ----
|
||||
public List<DataxTemplate> listTemplates(String sourceType, String targetType, int page, int size) {
|
||||
int offset = page * size;
|
||||
return taskMapper.selectList(sourceType, targetType, offset, size);
|
||||
}
|
||||
|
||||
public int countTemplates(String sourceType, String targetType) {
|
||||
return taskMapper.countTemplates(sourceType, targetType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.datamate.collection.application.service;
|
||||
|
||||
import com.datamate.collection.domain.model.CollectionTask;
|
||||
import com.datamate.collection.domain.model.TaskExecution;
|
||||
import com.datamate.collection.domain.model.TaskStatus;
|
||||
import com.datamate.collection.infrastructure.persistence.mapper.CollectionTaskMapper;
|
||||
import com.datamate.collection.infrastructure.persistence.mapper.TaskExecutionMapper;
|
||||
import com.datamate.collection.infrastructure.runtime.datax.DataxJobBuilder;
|
||||
import com.datamate.collection.infrastructure.runtime.datax.DataxProcessRunner;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DataxExecutionService {
|
||||
|
||||
private final DataxJobBuilder jobBuilder;
|
||||
private final DataxProcessRunner processRunner;
|
||||
private final TaskExecutionMapper executionMapper;
|
||||
private final CollectionTaskMapper taskMapper;
|
||||
|
||||
|
||||
@Transactional
|
||||
public TaskExecution createExecution(CollectionTask task) {
|
||||
TaskExecution exec = TaskExecution.initTaskExecution();
|
||||
exec.setTaskId(task.getId());
|
||||
exec.setTaskName(task.getName());
|
||||
executionMapper.insert(exec);
|
||||
taskMapper.updateLastExecution(task.getId(), exec.getId());
|
||||
taskMapper.updateStatus(task.getId(), TaskStatus.RUNNING.name());
|
||||
return exec;
|
||||
}
|
||||
|
||||
@Async
|
||||
public void runAsync(CollectionTask task, String executionId, int timeoutSeconds) {
|
||||
try {
|
||||
Path job = jobBuilder.buildJobFile(task);
|
||||
|
||||
int code = processRunner.runJob(job.toFile(), executionId, Duration.ofSeconds(timeoutSeconds));
|
||||
log.info("DataX finished with code {} for execution {}", code, executionId);
|
||||
// 简化:成功即完成
|
||||
executionMapper.completeExecution(executionId, TaskStatus.SUCCESS.name(), LocalDateTime.now(),
|
||||
0, 0L, 0L, 0L, null, null);
|
||||
taskMapper.updateStatus(task.getId(), TaskStatus.SUCCESS.name());
|
||||
} catch (Exception e) {
|
||||
log.error("DataX execution failed", e);
|
||||
executionMapper.completeExecution(executionId, TaskStatus.FAILED.name(), LocalDateTime.now(),
|
||||
0, 0L, 0L, 0L, e.getMessage(), null);
|
||||
taskMapper.updateStatus(task.getId(), TaskStatus.FAILED.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.datamate.collection.application.service;
|
||||
|
||||
import com.datamate.collection.domain.model.CollectionTask;
|
||||
import com.datamate.collection.domain.model.TaskExecution;
|
||||
import com.datamate.collection.domain.model.TaskStatus;
|
||||
import com.datamate.collection.infrastructure.persistence.mapper.CollectionTaskMapper;
|
||||
import com.datamate.collection.infrastructure.persistence.mapper.TaskExecutionMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TaskExecutionService {
|
||||
private final TaskExecutionMapper executionMapper;
|
||||
private final CollectionTaskMapper taskMapper;
|
||||
|
||||
public List<TaskExecution> list(String taskId, String status, LocalDateTime startDate,
|
||||
LocalDateTime endDate, Integer page, Integer size) {
|
||||
Map<String, Object> p = new HashMap<>();
|
||||
p.put("taskId", taskId);
|
||||
p.put("status", status);
|
||||
p.put("startDate", startDate);
|
||||
p.put("endDate", endDate);
|
||||
if (page != null && size != null) {
|
||||
p.put("offset", page * size);
|
||||
p.put("limit", size);
|
||||
}
|
||||
return executionMapper.selectAll(p);
|
||||
}
|
||||
|
||||
public long count(String taskId, String status, LocalDateTime startDate, LocalDateTime endDate) {
|
||||
Map<String, Object> p = new HashMap<>();
|
||||
p.put("taskId", taskId);
|
||||
p.put("status", status);
|
||||
p.put("startDate", startDate);
|
||||
p.put("endDate", endDate);
|
||||
return executionMapper.count(p);
|
||||
}
|
||||
|
||||
// --- Added convenience methods ---
|
||||
public TaskExecution get(String id) { return executionMapper.selectById(id); }
|
||||
public TaskExecution getLatestByTaskId(String taskId) { return executionMapper.selectLatestByTaskId(taskId); }
|
||||
|
||||
@Transactional
|
||||
public void complete(String executionId, boolean success, long successCount, long failedCount,
|
||||
long dataSizeBytes, String errorMessage, String resultJson) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
TaskExecution exec = executionMapper.selectById(executionId);
|
||||
if (exec == null) { return; }
|
||||
int duration = (int) Duration.between(exec.getStartedAt(), now).getSeconds();
|
||||
executionMapper.completeExecution(executionId, success ? TaskStatus.SUCCESS.name() : TaskStatus.FAILED.name(),
|
||||
now, duration, successCount, failedCount, dataSizeBytes, errorMessage, resultJson);
|
||||
CollectionTask task = taskMapper.selectById(exec.getTaskId());
|
||||
if (task != null) {
|
||||
taskMapper.updateStatus(task.getId(), success ? TaskStatus.SUCCESS.name() : TaskStatus.FAILED.name());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void stop(String executionId) {
|
||||
TaskExecution exec = executionMapper.selectById(executionId);
|
||||
if (exec == null || exec.getStatus() != TaskStatus.RUNNING) { return; }
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int duration = (int) Duration.between(exec.getStartedAt(), now).getSeconds();
|
||||
// Reuse completeExecution to persist STOPPED status and timing info
|
||||
executionMapper.completeExecution(exec.getId(), TaskStatus.STOPPED.name(), now, duration,
|
||||
exec.getRecordsSuccess(), exec.getRecordsFailed(), exec.getDataSizeBytes(), null, exec.getResult());
|
||||
taskMapper.updateStatus(exec.getTaskId(), TaskStatus.STOPPED.name());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void stopLatestByTaskId(String taskId) {
|
||||
TaskExecution latest = executionMapper.selectLatestByTaskId(taskId);
|
||||
if (latest != null) { stop(latest.getId()); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.datamate.collection.domain.model;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class CollectionTask {
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
private String config; // DataX JSON 配置,包含源端和目标端配置信息
|
||||
private TaskStatus status;
|
||||
private String syncMode; // ONCE / SCHEDULED
|
||||
private String scheduleExpression;
|
||||
private Integer retryCount;
|
||||
private Integer timeoutSeconds;
|
||||
private Long maxRecords;
|
||||
private String sortField;
|
||||
private String lastExecutionId;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
private String createdBy;
|
||||
private String updatedBy;
|
||||
|
||||
public void addPath() {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
Map<String, Object> parameter = objectMapper.readValue(
|
||||
config,
|
||||
new TypeReference<>() {}
|
||||
);
|
||||
parameter.put("destPath", "/dataset/local/" + id);
|
||||
parameter.put("filePaths", Collections.singletonList(parameter.get("destPath")));
|
||||
config = objectMapper.writeValueAsString(parameter);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.datamate.collection.domain.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class DataxTemplate {
|
||||
|
||||
/**
|
||||
* 模板ID(UUID)
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 源数据源类型
|
||||
*/
|
||||
private String sourceType;
|
||||
|
||||
/**
|
||||
* 目标数据源类型
|
||||
*/
|
||||
private String targetType;
|
||||
|
||||
/**
|
||||
* 模板内容(JSON格式)
|
||||
*/
|
||||
private String templateContent;
|
||||
|
||||
/**
|
||||
* 模板描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 是否为系统模板
|
||||
*/
|
||||
private Boolean isSystem;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private String updatedBy;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.datamate.collection.domain.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
public class TaskExecution {
|
||||
private String id;
|
||||
private String taskId;
|
||||
private String taskName;
|
||||
private TaskStatus status;
|
||||
private Double progress;
|
||||
private Long recordsTotal;
|
||||
private Long recordsProcessed;
|
||||
private Long recordsSuccess;
|
||||
private Long recordsFailed;
|
||||
private Double throughput;
|
||||
private Long dataSizeBytes;
|
||||
private LocalDateTime startedAt;
|
||||
private LocalDateTime completedAt;
|
||||
private Integer durationSeconds;
|
||||
private String errorMessage;
|
||||
private String dataxJobId;
|
||||
private String config;
|
||||
private String result;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public static TaskExecution initTaskExecution() {
|
||||
TaskExecution exec = new TaskExecution();
|
||||
exec.setId(UUID.randomUUID().toString());
|
||||
exec.setStatus(TaskStatus.RUNNING);
|
||||
exec.setProgress(0.0);
|
||||
exec.setStartedAt(LocalDateTime.now());
|
||||
exec.setCreatedAt(LocalDateTime.now());
|
||||
return exec;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.datamate.collection.domain.model;
|
||||
|
||||
/**
|
||||
* 统一的任务和执行状态枚举
|
||||
*
|
||||
* @author Data Mate Platform Team
|
||||
*/
|
||||
public enum TaskStatus {
|
||||
/** 草稿状态 */
|
||||
DRAFT,
|
||||
/** 就绪状态 */
|
||||
READY,
|
||||
/** 运行中 */
|
||||
RUNNING,
|
||||
/** 执行成功(对应原来的COMPLETED) */
|
||||
SUCCESS,
|
||||
/** 执行失败 */
|
||||
FAILED,
|
||||
/** 已停止 */
|
||||
STOPPED
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.datamate.collection.infrastructure.persistence.mapper;
|
||||
|
||||
import com.datamate.collection.domain.model.CollectionTask;
|
||||
import com.datamate.collection.domain.model.DataxTemplate;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface CollectionTaskMapper {
|
||||
int insert(CollectionTask entity);
|
||||
int update(CollectionTask entity);
|
||||
int deleteById(@Param("id") String id);
|
||||
CollectionTask selectById(@Param("id") String id);
|
||||
CollectionTask selectByName(@Param("name") String name);
|
||||
List<CollectionTask> selectByStatus(@Param("status") String status);
|
||||
List<CollectionTask> selectAll(Map<String, Object> params);
|
||||
int updateStatus(@Param("id") String id, @Param("status") String status);
|
||||
int updateLastExecution(@Param("id") String id, @Param("lastExecutionId") String lastExecutionId);
|
||||
List<CollectionTask> selectActiveTasks();
|
||||
|
||||
/**
|
||||
* 查询模板列表
|
||||
*
|
||||
* @param sourceType 源数据源类型(可选)
|
||||
* @param targetType 目标数据源类型(可选)
|
||||
* @param offset 偏移量
|
||||
* @param limit 限制数量
|
||||
* @return 模板列表
|
||||
*/
|
||||
List<DataxTemplate> selectList(@Param("sourceType") String sourceType,
|
||||
@Param("targetType") String targetType,
|
||||
@Param("offset") int offset,
|
||||
@Param("limit") int limit);
|
||||
|
||||
/**
|
||||
* 统计模板数量
|
||||
*
|
||||
* @param sourceType 源数据源类型(可选)
|
||||
* @param targetType 目标数据源类型(可选)
|
||||
* @return 模板总数
|
||||
*/
|
||||
int countTemplates(@Param("sourceType") String sourceType,
|
||||
@Param("targetType") String targetType);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.datamate.collection.infrastructure.persistence.mapper;
|
||||
|
||||
import com.datamate.collection.domain.model.TaskExecution;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface TaskExecutionMapper {
|
||||
int insert(TaskExecution entity);
|
||||
int update(TaskExecution entity);
|
||||
int deleteById(@Param("id") String id);
|
||||
TaskExecution selectById(@Param("id") String id);
|
||||
List<TaskExecution> selectByTaskId(@Param("taskId") String taskId, @Param("limit") Integer limit);
|
||||
List<TaskExecution> selectByStatus(@Param("status") String status);
|
||||
List<TaskExecution> selectAll(Map<String, Object> params);
|
||||
long count(Map<String, Object> params);
|
||||
int updateProgress(@Param("id") String id,
|
||||
@Param("status") String status,
|
||||
@Param("progress") Double progress,
|
||||
@Param("recordsProcessed") Long recordsProcessed,
|
||||
@Param("throughput") Double throughput);
|
||||
int completeExecution(@Param("id") String id,
|
||||
@Param("status") String status,
|
||||
@Param("completedAt") LocalDateTime completedAt,
|
||||
@Param("durationSeconds") Integer durationSeconds,
|
||||
@Param("recordsSuccess") Long recordsSuccess,
|
||||
@Param("recordsFailed") Long recordsFailed,
|
||||
@Param("dataSizeBytes") Long dataSizeBytes,
|
||||
@Param("errorMessage") String errorMessage,
|
||||
@Param("result") String result);
|
||||
List<TaskExecution> selectRunningExecutions();
|
||||
TaskExecution selectLatestByTaskId(@Param("taskId") String taskId);
|
||||
int deleteOldExecutions(@Param("beforeDate") LocalDateTime beforeDate);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.datamate.collection.infrastructure.runtime.datax;
|
||||
|
||||
import com.datamate.collection.domain.model.CollectionTask;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 根据任务配置拼装 DataX 作业 JSON 文件
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DataxJobBuilder {
|
||||
|
||||
private final DataxProperties props;
|
||||
|
||||
public Path buildJobFile(CollectionTask task) throws IOException {
|
||||
Files.createDirectories(Paths.get(props.getJobConfigPath()));
|
||||
String fileName = String.format("datax-job-%s.json", task.getId());
|
||||
Path path = Paths.get(props.getJobConfigPath(), fileName);
|
||||
// 简化:直接将任务中的 config 字段作为 DataX 作业 JSON
|
||||
try (FileWriter fw = new FileWriter(path.toFile())) {
|
||||
String json = task.getConfig() == null || task.getConfig().isEmpty() ?
|
||||
defaultJobJson() : task.getConfig();
|
||||
if (StringUtils.isNotBlank(task.getConfig())) {
|
||||
json = getJobConfig(task);
|
||||
}
|
||||
log.info("Job config: {}", json);
|
||||
fw.write(json);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private String getJobConfig(CollectionTask task) {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
Map<String, Object> parameter = objectMapper.readValue(
|
||||
task.getConfig(),
|
||||
new TypeReference<>() {}
|
||||
);
|
||||
Map<String, Object> job = new HashMap<>();
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
Map<String, Object> reader = new HashMap<>();
|
||||
reader.put("name", "nfsreader");
|
||||
reader.put("parameter", parameter);
|
||||
content.put("reader", reader);
|
||||
Map<String, Object> writer = new HashMap<>();
|
||||
writer.put("name", "nfswriter");
|
||||
writer.put("parameter", parameter);
|
||||
content.put("writer", writer);
|
||||
job.put("content", List.of(content));
|
||||
Map<String, Object> setting = new HashMap<>();
|
||||
Map<String, Object> channel = new HashMap<>();
|
||||
channel.put("channel", 2);
|
||||
setting.put("speed", channel);
|
||||
job.put("setting", setting);
|
||||
Map<String, Object> jobConfig = new HashMap<>();
|
||||
jobConfig.put("job", job);
|
||||
return objectMapper.writeValueAsString(jobConfig);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse task config", e);
|
||||
throw new RuntimeException("Failed to parse task config", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String defaultJobJson() {
|
||||
// 提供一个最小可运行的空 job,实际会被具体任务覆盖
|
||||
return "{\n \"job\": {\n \"setting\": {\n \"speed\": {\n \"channel\": 1\n }\n },\n \"content\": []\n }\n}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.datamate.collection.infrastructure.runtime.datax;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.exec.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DataxProcessRunner {
|
||||
|
||||
private final DataxProperties props;
|
||||
|
||||
public int runJob(File jobFile, String executionId, Duration timeout) throws Exception {
|
||||
File logFile = new File(props.getLogPath(), String.format("datax-%s.log", executionId));
|
||||
String python = props.getPythonPath();
|
||||
String dataxPy = props.getHomePath() + File.separator + "bin" + File.separator + "datax.py";
|
||||
String cmd = String.format("%s %s %s", python, dataxPy, jobFile.getAbsolutePath());
|
||||
|
||||
log.info("Execute DataX: {}", cmd);
|
||||
|
||||
CommandLine cl = CommandLine.parse(cmd);
|
||||
DefaultExecutor executor = new DefaultExecutor();
|
||||
|
||||
// 将日志追加输出到文件
|
||||
File parent = logFile.getParentFile();
|
||||
if (!parent.exists()) parent.mkdirs();
|
||||
|
||||
ExecuteStreamHandler streamHandler = new PumpStreamHandler(
|
||||
new org.apache.commons.io.output.TeeOutputStream(
|
||||
new java.io.FileOutputStream(logFile, true), System.out),
|
||||
new org.apache.commons.io.output.TeeOutputStream(
|
||||
new java.io.FileOutputStream(logFile, true), System.err)
|
||||
);
|
||||
executor.setStreamHandler(streamHandler);
|
||||
|
||||
ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout.toMillis());
|
||||
executor.setWatchdog(watchdog);
|
||||
|
||||
return executor.execute(cl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.datamate.collection.infrastructure.runtime.datax;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "datamate.data-collection.datax")
|
||||
public class DataxProperties {
|
||||
private String homePath; // DATAX_HOME
|
||||
private String pythonPath; // python 可执行文件
|
||||
private String jobConfigPath; // 生成的作业文件目录
|
||||
private String logPath; // 运行日志目录
|
||||
private Integer maxMemory = 2048;
|
||||
private Integer channelCount = 5;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.datamate.collection.interfaces.converter;
|
||||
|
||||
import com.datamate.collection.domain.model.CollectionTask;
|
||||
import com.datamate.collection.domain.model.DataxTemplate;
|
||||
import com.datamate.collection.interfaces.dto.*;
|
||||
import com.datamate.common.infrastructure.exception.BusinessException;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface CollectionTaskConverter {
|
||||
CollectionTaskConverter INSTANCE = Mappers.getMapper(CollectionTaskConverter.class);
|
||||
|
||||
@Mapping(source = "config", target = "config", qualifiedByName = "parseJsonToMap")
|
||||
CollectionTaskResponse toResponse(CollectionTask task);
|
||||
|
||||
CollectionTaskSummary toSummary(CollectionTask task);
|
||||
|
||||
DataxTemplateSummary toTemplateSummary(DataxTemplate template);
|
||||
|
||||
@Mapping(source = "config", target = "config", qualifiedByName = "mapToJsonString")
|
||||
CollectionTask toCollectionTask(CreateCollectionTaskRequest request);
|
||||
|
||||
@Mapping(source = "config", target = "config", qualifiedByName = "mapToJsonString")
|
||||
CollectionTask toCollectionTask(UpdateCollectionTaskRequest request);
|
||||
|
||||
@Named("parseJsonToMap")
|
||||
default Map<String, Object> parseJsonToMap(String json) {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return objectMapper.readValue(json, Map.class);
|
||||
} catch (Exception e) {
|
||||
throw BusinessException.of(SystemErrorCode.INVALID_PARAMETER);
|
||||
}
|
||||
}
|
||||
|
||||
@Named("mapToJsonString")
|
||||
default String mapToJsonString(Map<String, Object> map) {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return objectMapper.writeValueAsString(map != null ? map : Map.of());
|
||||
} catch (Exception e) {
|
||||
throw BusinessException.of(SystemErrorCode.INVALID_PARAMETER);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.datamate.collection.interfaces.rest;
|
||||
|
||||
import com.datamate.collection.application.service.CollectionTaskService;
|
||||
import com.datamate.collection.domain.model.CollectionTask;
|
||||
import com.datamate.collection.domain.model.DataxTemplate;
|
||||
import com.datamate.collection.interfaces.api.CollectionTaskApi;
|
||||
import com.datamate.collection.interfaces.converter.CollectionTaskConverter;
|
||||
import com.datamate.collection.interfaces.dto.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class CollectionTaskController implements CollectionTaskApi {
|
||||
|
||||
private final CollectionTaskService taskService;
|
||||
|
||||
@Override
|
||||
public ResponseEntity<CollectionTaskResponse> createTask(CreateCollectionTaskRequest request) {
|
||||
CollectionTask task = CollectionTaskConverter.INSTANCE.toCollectionTask(request);
|
||||
task.setId(UUID.randomUUID().toString());
|
||||
task.addPath();
|
||||
return ResponseEntity.ok().body(CollectionTaskConverter.INSTANCE.toResponse(taskService.create(task)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<CollectionTaskResponse> updateTask(String id, UpdateCollectionTaskRequest request) {
|
||||
if (taskService.get(id) == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
CollectionTask task = CollectionTaskConverter.INSTANCE.toCollectionTask(request);
|
||||
task.setId(id);
|
||||
return ResponseEntity.ok(CollectionTaskConverter.INSTANCE.toResponse(taskService.update(task)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> deleteTask(String id) {
|
||||
taskService.delete(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<CollectionTaskResponse> getTaskDetail(String id) {
|
||||
CollectionTask task = taskService.get(id);
|
||||
return task == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(CollectionTaskConverter.INSTANCE.toResponse(task));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<PagedCollectionTaskSummary> getTasks(Integer page, Integer size, TaskStatus status, String name) {
|
||||
var list = taskService.list(page, size, status == null ? null : status.getValue(), name);
|
||||
PagedCollectionTaskSummary response = new PagedCollectionTaskSummary();
|
||||
response.setContent(list.stream().map(CollectionTaskConverter.INSTANCE::toSummary).collect(Collectors.toList()));
|
||||
response.setNumber(page);
|
||||
response.setSize(size);
|
||||
response.setTotalElements(list.size()); // 简化处理,实际项目中应该有单独的count查询
|
||||
response.setTotalPages(size == null || size == 0 ? 1 : (int) Math.ceil(list.size() * 1.0 / size));
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<PagedDataxTemplates> templatesGet(String sourceType, String targetType,
|
||||
Integer page, Integer size) {
|
||||
int pageNum = page != null ? page : 0;
|
||||
int pageSize = size != null ? size : 20;
|
||||
List<DataxTemplate> templates = taskService.listTemplates(sourceType, targetType, pageNum, pageSize);
|
||||
int totalElements = taskService.countTemplates(sourceType, targetType);
|
||||
PagedDataxTemplates response = new PagedDataxTemplates();
|
||||
response.setContent(templates.stream().map(CollectionTaskConverter.INSTANCE::toTemplateSummary).collect(Collectors.toList()));
|
||||
response.setNumber(pageNum);
|
||||
response.setSize(pageSize);
|
||||
response.setTotalElements(totalElements);
|
||||
response.setTotalPages(pageSize > 0 ? (int) Math.ceil(totalElements * 1.0 / pageSize) : 1);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.datamate.collection.interfaces.rest;
|
||||
|
||||
import com.datamate.collection.application.service.CollectionTaskService;
|
||||
import com.datamate.collection.application.service.TaskExecutionService;
|
||||
import com.datamate.collection.domain.model.TaskExecution;
|
||||
import com.datamate.collection.interfaces.api.TaskExecutionApi;
|
||||
import com.datamate.collection.interfaces.dto.PagedTaskExecutions;
|
||||
import com.datamate.collection.interfaces.dto.TaskExecutionDetail;
|
||||
import com.datamate.collection.interfaces.dto.TaskExecutionResponse;
|
||||
import com.datamate.collection.interfaces.dto.TaskStatus; // DTO enum
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class TaskExecutionController implements TaskExecutionApi {
|
||||
|
||||
private final TaskExecutionService executionService;
|
||||
private final CollectionTaskService taskService;
|
||||
|
||||
private TaskExecutionDetail toDetail(TaskExecution e) {
|
||||
TaskExecutionDetail d = new TaskExecutionDetail();
|
||||
d.setId(e.getId());
|
||||
d.setTaskId(e.getTaskId());
|
||||
d.setTaskName(e.getTaskName());
|
||||
if (e.getStatus() != null) { d.setStatus(TaskStatus.fromValue(e.getStatus().name())); }
|
||||
d.setProgress(e.getProgress());
|
||||
d.setRecordsTotal(e.getRecordsTotal() != null ? e.getRecordsTotal().intValue() : null);
|
||||
d.setRecordsProcessed(e.getRecordsProcessed() != null ? e.getRecordsProcessed().intValue() : null);
|
||||
d.setRecordsSuccess(e.getRecordsSuccess() != null ? e.getRecordsSuccess().intValue() : null);
|
||||
d.setRecordsFailed(e.getRecordsFailed() != null ? e.getRecordsFailed().intValue() : null);
|
||||
d.setThroughput(e.getThroughput());
|
||||
d.setDataSizeBytes(e.getDataSizeBytes() != null ? e.getDataSizeBytes().intValue() : null);
|
||||
d.setStartedAt(e.getStartedAt());
|
||||
d.setCompletedAt(e.getCompletedAt());
|
||||
d.setDurationSeconds(e.getDurationSeconds());
|
||||
d.setErrorMessage(e.getErrorMessage());
|
||||
return d;
|
||||
}
|
||||
|
||||
// GET /executions/{id}
|
||||
@Override
|
||||
public ResponseEntity<TaskExecutionDetail> executionsIdGet(String id) {
|
||||
var exec = executionService.get(id);
|
||||
return exec == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(toDetail(exec));
|
||||
}
|
||||
|
||||
// DELETE /executions/{id}
|
||||
@Override
|
||||
public ResponseEntity<Void> executionsIdDelete(String id) {
|
||||
executionService.stop(id); // 幂等处理,在service内部判断状态
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// POST /tasks/{id}/execute -> 201
|
||||
@Override
|
||||
public ResponseEntity<TaskExecutionResponse> tasksIdExecutePost(String id) {
|
||||
var task = taskService.get(id);
|
||||
if (task == null) { return ResponseEntity.notFound().build(); }
|
||||
var latestExec = executionService.getLatestByTaskId(id);
|
||||
if (latestExec != null && latestExec.getStatus() == com.datamate.collection.domain.model.TaskStatus.RUNNING) {
|
||||
TaskExecutionResponse r = new TaskExecutionResponse();
|
||||
r.setId(latestExec.getId());
|
||||
r.setTaskId(latestExec.getTaskId());
|
||||
r.setTaskName(latestExec.getTaskName());
|
||||
r.setStatus(TaskStatus.fromValue(latestExec.getStatus().name()));
|
||||
r.setStartedAt(latestExec.getStartedAt());
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(r); // 返回已有运行实例
|
||||
}
|
||||
var exec = taskService.startExecution(task);
|
||||
TaskExecutionResponse r = new TaskExecutionResponse();
|
||||
r.setId(exec.getId());
|
||||
r.setTaskId(exec.getTaskId());
|
||||
r.setTaskName(exec.getTaskName());
|
||||
r.setStatus(TaskStatus.fromValue(exec.getStatus().name()));
|
||||
r.setStartedAt(exec.getStartedAt());
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(r);
|
||||
}
|
||||
|
||||
// GET /tasks/{id}/executions -> 分页
|
||||
@Override
|
||||
public ResponseEntity<PagedTaskExecutions> tasksIdExecutionsGet(String id, Integer page, Integer size) {
|
||||
if (page == null || page < 0) { page = 0; }
|
||||
if (size == null || size <= 0) { size = 20; }
|
||||
var list = executionService.list(id, null, null, null, page, size);
|
||||
long total = executionService.count(id, null, null, null);
|
||||
PagedTaskExecutions p = new PagedTaskExecutions();
|
||||
p.setContent(list.stream().map(this::toDetail).collect(Collectors.toList()));
|
||||
p.setNumber(page);
|
||||
p.setSize(size);
|
||||
p.setTotalElements((int) total);
|
||||
p.setTotalPages(size == 0 ? 1 : (int) Math.ceil(total * 1.0 / size));
|
||||
return ResponseEntity.ok(p);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
datamate:
|
||||
data-collection:
|
||||
# DataX配置
|
||||
datax:
|
||||
home-path: ${DATAX_HOME:D:/datax}
|
||||
python-path: ${DATAX_PYTHON_PATH:python3}
|
||||
job-config-path: ${DATAX_JOB_PATH:./data/temp/datax/jobs}
|
||||
log-path: ${DATAX_LOG_PATH:./logs/datax}
|
||||
max-memory: ${DATAX_MAX_MEMORY:2048}
|
||||
channel-count: ${DATAX_CHANNEL_COUNT:5}
|
||||
|
||||
# 执行配置
|
||||
execution:
|
||||
max-concurrent-tasks: ${DATA_COLLECTION_MAX_CONCURRENT_TASKS:10}
|
||||
task-timeout-minutes: ${DATA_COLLECTION_TASK_TIMEOUT:120}
|
||||
retry-count: ${DATA_COLLECTION_RETRY_COUNT:3}
|
||||
retry-interval-seconds: ${DATA_COLLECTION_RETRY_INTERVAL:30}
|
||||
|
||||
# 监控配置
|
||||
monitoring:
|
||||
status-check-interval-seconds: ${DATA_COLLECTION_STATUS_CHECK_INTERVAL:30}
|
||||
log-retention-days: ${DATA_COLLECTION_LOG_RETENTION:30}
|
||||
enable-metrics: ${DATA_COLLECTION_ENABLE_METRICS:true}
|
||||
@@ -0,0 +1,188 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.datamate.collection.infrastructure.persistence.mapper.CollectionTaskMapper">
|
||||
|
||||
<!-- Result Map -->
|
||||
<resultMap id="CollectionTaskResultMap" type="com.datamate.collection.domain.model.CollectionTask">
|
||||
<id property="id" column="id"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="description" column="description"/>
|
||||
<result property="config" column="config"/>
|
||||
<result property="status" column="status" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
|
||||
<result property="syncMode" column="sync_mode"/>
|
||||
<result property="scheduleExpression" column="schedule_expression"/>
|
||||
<result property="retryCount" column="retry_count"/>
|
||||
<result property="timeoutSeconds" column="timeout_seconds"/>
|
||||
<result property="maxRecords" column="max_records"/>
|
||||
<result property="sortField" column="sort_field"/>
|
||||
<result property="lastExecutionId" column="last_execution_id"/>
|
||||
<result property="createdAt" column="created_at"/>
|
||||
<result property="updatedAt" column="updated_at"/>
|
||||
<result property="createdBy" column="created_by"/>
|
||||
<result property="updatedBy" column="updated_by"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 结果映射 (模板) -->
|
||||
<resultMap id="DataxTemplateResultMap" type="com.datamate.collection.domain.model.DataxTemplate">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="name" property="name" jdbcType="VARCHAR"/>
|
||||
<result column="source_type" property="sourceType" jdbcType="VARCHAR"/>
|
||||
<result column="target_type" property="targetType" jdbcType="VARCHAR"/>
|
||||
<result column="template_content" property="templateContent" jdbcType="VARCHAR"/>
|
||||
<result column="description" property="description" jdbcType="VARCHAR"/>
|
||||
<result column="version" property="version" jdbcType="VARCHAR"/>
|
||||
<result column="is_system" property="isSystem" jdbcType="BOOLEAN"/>
|
||||
<result column="created_at" property="createdAt" jdbcType="TIMESTAMP"/>
|
||||
<result column="updated_at" property="updatedAt" jdbcType="TIMESTAMP"/>
|
||||
<result column="created_by" property="createdBy" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- Base Column List (tasks) -->
|
||||
<sql id="Base_Column_List">
|
||||
id,
|
||||
name, description, config, status, sync_mode,
|
||||
schedule_expression, retry_count, timeout_seconds, max_records, sort_field,
|
||||
last_execution_id, created_at, updated_at, created_by, updated_by
|
||||
</sql>
|
||||
|
||||
<!-- Template Column List -->
|
||||
<sql id="Template_Column_List">
|
||||
id, name, source_type, target_type, template_content, description, version, is_system, created_at, updated_at, created_by
|
||||
</sql>
|
||||
|
||||
<!-- Insert -->
|
||||
<insert id="insert" parameterType="com.datamate.collection.domain.model.CollectionTask">
|
||||
INSERT INTO t_dc_collection_tasks (id, name, description, config, status, sync_mode,
|
||||
schedule_expression, retry_count, timeout_seconds, max_records, sort_field,
|
||||
last_execution_id, created_at, updated_at, created_by, updated_by)
|
||||
VALUES (#{id}, #{name}, #{description}, #{config}, #{status}, #{syncMode},
|
||||
#{scheduleExpression}, #{retryCount}, #{timeoutSeconds}, #{maxRecords}, #{sortField},
|
||||
#{lastExecutionId}, #{createdAt}, #{updatedAt}, #{createdBy}, #{updatedBy})
|
||||
</insert>
|
||||
|
||||
<!-- Update -->
|
||||
<update id="update" parameterType="com.datamate.collection.domain.model.CollectionTask">
|
||||
UPDATE t_dc_collection_tasks
|
||||
SET name = #{name},
|
||||
description = #{description},
|
||||
config = #{config},
|
||||
status = #{status},
|
||||
sync_mode = #{syncMode},
|
||||
schedule_expression = #{scheduleExpression},
|
||||
retry_count = #{retryCount},
|
||||
timeout_seconds = #{timeoutSeconds},
|
||||
max_records = #{maxRecords},
|
||||
sort_field = #{sortField},
|
||||
last_execution_id = #{lastExecutionId},
|
||||
updated_at = #{updatedAt},
|
||||
updated_by = #{updatedBy}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<!-- Delete by ID -->
|
||||
<delete id="deleteById" parameterType="java.lang.String">
|
||||
DELETE FROM t_dc_collection_tasks WHERE id = #{id}
|
||||
</delete>
|
||||
|
||||
<!-- Select by ID -->
|
||||
<select id="selectById" parameterType="java.lang.String" resultMap="CollectionTaskResultMap">
|
||||
SELECT <include refid="Base_Column_List"/> FROM t_dc_collection_tasks WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<!-- Select by Name -->
|
||||
<select id="selectByName" parameterType="java.lang.String" resultMap="CollectionTaskResultMap">
|
||||
SELECT <include refid="Base_Column_List"/> FROM t_dc_collection_tasks WHERE name = #{name}
|
||||
</select>
|
||||
|
||||
<!-- Select by Status -->
|
||||
<select id="selectByStatus" parameterType="java.lang.String" resultMap="CollectionTaskResultMap">
|
||||
SELECT <include refid="Base_Column_List"/> FROM t_dc_collection_tasks WHERE status = #{status} ORDER BY created_at DESC
|
||||
</select>
|
||||
|
||||
<!-- Select All with Pagination -->
|
||||
<select id="selectAll" resultMap="CollectionTaskResultMap">
|
||||
SELECT <include refid="Base_Column_List"/> FROM t_dc_collection_tasks
|
||||
<where>
|
||||
<if test="status != null and status != ''">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
<if test="name != null and name != ''">
|
||||
AND name LIKE CONCAT('%', #{name}, '%')
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY created_at DESC
|
||||
<if test="offset != null and limit != null">
|
||||
LIMIT #{offset}, #{limit}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- Count Total -->
|
||||
<select id="count" resultType="java.lang.Long">
|
||||
SELECT COUNT(*) FROM t_dc_collection_tasks
|
||||
<where>
|
||||
<if test="status != null and status != ''">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
<if test="name != null and name != ''">
|
||||
AND name LIKE CONCAT('%', #{name}, '%')
|
||||
</if>
|
||||
<if test="sourceDataSourceId != null and sourceDataSourceId != ''">
|
||||
AND source_datasource_id = #{sourceDataSourceId}
|
||||
</if>
|
||||
<if test="targetDataSourceId != null and targetDataSourceId != ''">
|
||||
AND target_datasource_id = #{targetDataSourceId}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!-- Update Status -->
|
||||
<update id="updateStatus">
|
||||
UPDATE t_dc_collection_tasks SET status = #{status}, updated_at = NOW() WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<!-- Update Last Execution -->
|
||||
<update id="updateLastExecution">
|
||||
UPDATE t_dc_collection_tasks SET last_execution_id = #{lastExecutionId}, updated_at = NOW() WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<!-- Select Active Tasks for Scheduling -->
|
||||
<select id="selectActiveTasks" resultMap="CollectionTaskResultMap">
|
||||
SELECT <include refid="Base_Column_List"/> FROM t_dc_collection_tasks
|
||||
WHERE status IN ('READY', 'RUNNING')
|
||||
AND schedule_expression IS NOT NULL
|
||||
ORDER BY created_at DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询模板列表 -->
|
||||
<select id="selectList" resultMap="DataxTemplateResultMap">
|
||||
SELECT <include refid="Template_Column_List"/> FROM t_dc_datax_templates
|
||||
<where>
|
||||
<if test="sourceType != null and sourceType != ''">
|
||||
AND source_type = #{sourceType}
|
||||
</if>
|
||||
<if test="targetType != null and targetType != ''">
|
||||
AND target_type = #{targetType}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY is_system DESC, created_at DESC
|
||||
<if test="limit > 0">
|
||||
LIMIT #{offset}, #{limit}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 统计模板数量 -->
|
||||
<select id="countTemplates" resultType="java.lang.Integer">
|
||||
SELECT COUNT(1) FROM t_dc_datax_templates
|
||||
<where>
|
||||
<if test="sourceType != null and sourceType != ''">
|
||||
AND source_type = #{sourceType}
|
||||
</if>
|
||||
<if test="targetType != null and targetType != ''">
|
||||
AND target_type = #{targetType}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,191 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.datamate.collection.infrastructure.persistence.mapper.TaskExecutionMapper">
|
||||
|
||||
<!-- Result Map -->
|
||||
<resultMap id="TaskExecutionResultMap" type="com.datamate.collection.domain.model.TaskExecution">
|
||||
<id property="id" column="id"/>
|
||||
<result property="taskId" column="task_id"/>
|
||||
<result property="taskName" column="task_name"/>
|
||||
<result property="status" column="status" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
|
||||
<result property="progress" column="progress"/>
|
||||
<result property="recordsTotal" column="records_total"/>
|
||||
<result property="recordsProcessed" column="records_processed"/>
|
||||
<result property="recordsSuccess" column="records_success"/>
|
||||
<result property="recordsFailed" column="records_failed"/>
|
||||
<result property="throughput" column="throughput"/>
|
||||
<result property="dataSizeBytes" column="data_size_bytes"/>
|
||||
<result property="startedAt" column="started_at"/>
|
||||
<result property="completedAt" column="completed_at"/>
|
||||
<result property="durationSeconds" column="duration_seconds"/>
|
||||
<result property="errorMessage" column="error_message"/>
|
||||
<result property="dataxJobId" column="datax_job_id"/>
|
||||
<result property="config" column="config"/>
|
||||
<result property="result" column="result"/>
|
||||
<result property="createdAt" column="created_at"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- Base Column List -->
|
||||
<sql id="Base_Column_List">
|
||||
id, task_id, task_name, status, progress, records_total, records_processed,
|
||||
records_success, records_failed, throughput, data_size_bytes, started_at,
|
||||
completed_at, duration_seconds, error_message, datax_job_id, config, result, created_at
|
||||
</sql>
|
||||
|
||||
<!-- Insert -->
|
||||
<insert id="insert" parameterType="com.datamate.collection.domain.model.TaskExecution">
|
||||
INSERT INTO t_dc_task_executions (
|
||||
id, task_id, task_name, status, progress, records_total, records_processed,
|
||||
records_success, records_failed, throughput, data_size_bytes, started_at,
|
||||
completed_at, duration_seconds, error_message, datax_job_id, config, result, created_at
|
||||
) VALUES (
|
||||
#{id}, #{taskId}, #{taskName}, #{status}, #{progress}, #{recordsTotal}, #{recordsProcessed},
|
||||
#{recordsSuccess}, #{recordsFailed}, #{throughput}, #{dataSizeBytes}, #{startedAt},
|
||||
#{completedAt}, #{durationSeconds}, #{errorMessage}, #{dataxJobId}, #{config}, #{result}, #{createdAt}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- Update -->
|
||||
<update id="update" parameterType="com.datamate.collection.domain.model.TaskExecution">
|
||||
UPDATE t_dc_task_executions
|
||||
SET status = #{status},
|
||||
progress = #{progress},
|
||||
records_total = #{recordsTotal},
|
||||
records_processed = #{recordsProcessed},
|
||||
records_success = #{recordsSuccess},
|
||||
records_failed = #{recordsFailed},
|
||||
throughput = #{throughput},
|
||||
data_size_bytes = #{dataSizeBytes},
|
||||
completed_at = #{completedAt},
|
||||
duration_seconds = #{durationSeconds},
|
||||
error_message = #{errorMessage},
|
||||
result = #{result}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<!-- Delete by ID -->
|
||||
<delete id="deleteById" parameterType="java.lang.String">
|
||||
DELETE FROM t_dc_task_executions WHERE id = #{id}
|
||||
</delete>
|
||||
|
||||
<!-- Select by ID -->
|
||||
<select id="selectById" parameterType="java.lang.String" resultMap="TaskExecutionResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM t_dc_task_executions
|
||||
WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<!-- Select by Task ID -->
|
||||
<select id="selectByTaskId" resultMap="TaskExecutionResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM t_dc_task_executions
|
||||
WHERE task_id = #{taskId}
|
||||
ORDER BY started_at DESC
|
||||
<if test="limit != null">
|
||||
LIMIT #{limit}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- Select by Status -->
|
||||
<select id="selectByStatus" parameterType="java.lang.String" resultMap="TaskExecutionResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM t_dc_task_executions
|
||||
WHERE status = #{status}
|
||||
ORDER BY started_at DESC
|
||||
</select>
|
||||
|
||||
<!-- Select All with Pagination -->
|
||||
<select id="selectAll" resultMap="TaskExecutionResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM t_dc_task_executions
|
||||
<where>
|
||||
<if test="taskId != null and taskId != ''">
|
||||
AND task_id = #{taskId}
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
<if test="startDate != null">
|
||||
AND started_at >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null">
|
||||
AND started_at <= #{endDate}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY started_at DESC
|
||||
<if test="offset != null and limit != null">
|
||||
LIMIT #{offset}, #{limit}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- Count Total -->
|
||||
<select id="count" resultType="java.lang.Long">
|
||||
SELECT COUNT(*)
|
||||
FROM t_dc_task_executions
|
||||
<where>
|
||||
<if test="taskId != null and taskId != ''">
|
||||
AND task_id = #{taskId}
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
<if test="startDate != null">
|
||||
AND started_at >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null">
|
||||
AND started_at <= #{endDate}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!-- Update Status and Progress -->
|
||||
<update id="updateProgress">
|
||||
UPDATE t_dc_task_executions
|
||||
SET status = #{status},
|
||||
progress = #{progress},
|
||||
records_processed = #{recordsProcessed},
|
||||
throughput = #{throughput}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<!-- Complete Execution -->
|
||||
<update id="completeExecution">
|
||||
UPDATE t_dc_task_executions
|
||||
SET status = #{status},
|
||||
progress = 100.00,
|
||||
completed_at = #{completedAt},
|
||||
duration_seconds = #{durationSeconds},
|
||||
records_success = #{recordsSuccess},
|
||||
records_failed = #{recordsFailed},
|
||||
data_size_bytes = #{dataSizeBytes},
|
||||
error_message = #{errorMessage},
|
||||
result = #{result}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<!-- Select Running Executions -->
|
||||
<select id="selectRunningExecutions" resultMap="TaskExecutionResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM t_dc_task_executions
|
||||
WHERE status = 'RUNNING'
|
||||
ORDER BY started_at ASC
|
||||
</select>
|
||||
|
||||
<!-- Select Latest Execution by Task -->
|
||||
<select id="selectLatestByTaskId" parameterType="java.lang.String" resultMap="TaskExecutionResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM t_dc_task_executions
|
||||
WHERE task_id = #{taskId}
|
||||
ORDER BY started_at DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- Delete Old Executions -->
|
||||
<delete id="deleteOldExecutions">
|
||||
DELETE FROM t_dc_task_executions
|
||||
WHERE started_at < #{beforeDate}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user