diff --git a/bin/build-command.sh b/bin/build-command.sh
new file mode 100755
index 0000000..5969316
--- /dev/null
+++ b/bin/build-command.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+mvn -pl service-dependencies,service-configuration,service-forest clean deploy -D skipTests -P local -s ~/.m2/settings-development.xml
+mvn -pl service-command clean package spring-boot:repackage -D skipTests -s ~/.m2/settings-development.xml
+ytp-transfer2 /Users/lanyuanxiaoyao/Project/IdeaProjects/hudi-service/service-command/target/service-command-1.0.0-SNAPSHOT.jar
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 48e000f..7e8e5b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,7 @@
service-api
service-scheduler
service-launcher
+ service-command
diff --git a/service-cli/service-cli-runner/src/main/resources/template/command/cli.ftl b/service-cli/service-cli-runner/src/main/resources/template/command/cli.ftl
index bf72b9c..5c9c40c 100644
--- a/service-cli/service-cli-runner/src/main/resources/template/command/cli.ftl
+++ b/service-cli/service-cli-runner/src/main/resources/template/command/cli.ftl
@@ -1,10 +1,4 @@
#!/bin/bash
-
-datetime=`date +%Y%m%d%H%M%S`
-log_path='${runtime.logPath}'
-loki_url='${runtime.loki.servicePushUrl}'
-
mkdir -p ${runtime.jarPath}
-
export JASYPT_ENCRYPTOR_PASSWORD='r#(R,P\"Dp^A47>WSn:Wn].gs/+\"v:q_Q*An~zF*g-@j@jtSTv5H/,S-3:R?r9R}.'
-${runtime.jdkPath} <#noparse>-Ddatetime=${datetime} -Dhostname=${HOSTNAME} -Dlogging.parent=${log_path} -Dloki.url=${loki_url}#noparse> -Dspring.profiles.include=default,b12 -jar ${runtime.jarPath}/service-command.jar<#if directly> $@#if>
+${runtime.jdkPath} <#noparse>-Ddatetime=$(date +%Y%m%d%H%M%S) -Dhostname=$(ssh $host 'hostname')#noparse> -Dlogging.parent=${runtime.logPath} -Dloki.url=${runtime.loki.servicePushUrl} -Dspring.cloud.zookeeper.connect-string=${runtime.zkUrl} -Dyarn-cluster.sync-clusters=${runtime.yarn.syncClusters} -Dyarn-cluster.compaction-clusters=${runtime.yarn.compactionClusters} -Dspring.profiles.include=default,b12 -jar ${runtime.jarPath}/service-command.jar<#if directly> $@#if>
diff --git a/service-cli/service-cli-runner/src/main/resources/template/command/update.ftl b/service-cli/service-cli-runner/src/main/resources/template/command/update.ftl
index 40ab693..62abfb9 100644
--- a/service-cli/service-cli-runner/src/main/resources/template/command/update.ftl
+++ b/service-cli/service-cli-runner/src/main/resources/template/command/update.ftl
@@ -1,3 +1,3 @@
#!/bin/bash
-curl ftp://yyy:QeY\!68\)4nH1@132.121.122.15:2222/command-1.0.0-SNAPSHOT.jar -o ${runtime.jarPath}/service-command.jar
\ No newline at end of file
+curl ftp://yyy:QeY\!68\)4nH1@132.121.122.15:2222/service-command-1.0.0-SNAPSHOT.jar -o ${runtime.jarPath}/service-command.jar
\ No newline at end of file
diff --git a/service-command/pom.xml b/service-command/pom.xml
new file mode 100644
index 0000000..e183cf4
--- /dev/null
+++ b/service-command/pom.xml
@@ -0,0 +1,54 @@
+
+
+ 4.0.0
+
+ com.lanyuanxiaoyao
+ hudi-service
+ 1.0.0-SNAPSHOT
+
+
+ service-command
+
+
+
+ com.lanyuanxiaoyao
+ service-forest
+ 1.0.0-SNAPSHOT
+
+
+ org.springframework.cloud
+ spring-cloud-starter-loadbalancer
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ org.springframework.shell
+ spring-shell-starter
+ 2.1.0-M3
+
+
+ me.tongfei
+ progressbar
+ 0.9.3
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/service-command/src/main/java/com/lanyuanxiaoyao/service/command/CommandApplication.java b/service-command/src/main/java/com/lanyuanxiaoyao/service/command/CommandApplication.java
new file mode 100644
index 0000000..a03f045
--- /dev/null
+++ b/service-command/src/main/java/com/lanyuanxiaoyao/service/command/CommandApplication.java
@@ -0,0 +1,43 @@
+package com.lanyuanxiaoyao.service.command;
+
+import com.lanyuanxiaoyao.service.configuration.SecurityConfig;
+import com.lanyuanxiaoyao.service.forest.configuration.SpringCloudDiscoveryInterceptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.FilterType;
+
+/**
+ * 命令行工具入口
+ *
+ * @author ZhangJiacheng
+ * @date 2022-03-24
+ */
+@SpringBootApplication
+@EnableConfigurationProperties
+@ComponentScan(
+ basePackages = {"com.lanyuanxiaoyao.service"},
+ excludeFilters = {
+ @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {
+ SpringCloudDiscoveryInterceptor.class,
+ SecurityConfig.class
+ }),
+ }
+)
+public class CommandApplication implements ApplicationRunner {
+ private static final Logger logger = LoggerFactory.getLogger(CommandApplication.class);
+
+ public static void main(String[] args) {
+ SpringApplication.run(CommandApplication.class, args);
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ // Test
+ }
+}
diff --git a/service-command/src/main/java/com/lanyuanxiaoyao/service/command/commands/AbstractUtilShellComponent.java b/service-command/src/main/java/com/lanyuanxiaoyao/service/command/commands/AbstractUtilShellComponent.java
new file mode 100644
index 0000000..c26e291
--- /dev/null
+++ b/service-command/src/main/java/com/lanyuanxiaoyao/service/command/commands/AbstractUtilShellComponent.java
@@ -0,0 +1,28 @@
+package com.lanyuanxiaoyao.service.command.commands;
+
+import org.springframework.shell.component.ConfirmationInput;
+import org.springframework.shell.standard.AbstractShellComponent;
+
+public abstract class AbstractUtilShellComponent extends AbstractShellComponent {
+ protected Boolean doubleCheck(String message, Boolean ignore) {
+ return ignore || doubleCheck(message);
+ }
+
+ protected Boolean doubleCheck(String message) {
+ ConfirmationInput confirmationInput = new ConfirmationInput(getTerminal(), message, false);
+ confirmationInput.setResourceLoader(getResourceLoader());
+ confirmationInput.setTemplateExecutor(getTemplateExecutor());
+ ConfirmationInput.ConfirmationInputContext context = confirmationInput.run(ConfirmationInput.ConfirmationInputContext.empty());
+ return context.getResultValue();
+ }
+
+ protected void doubleCheck(String message, T throwable) throws T {
+ if (!doubleCheck(message)) {
+ throw throwable;
+ }
+ }
+
+ protected void doubleCheckQuiet(String message, T throwable) {
+ doubleCheck(message, new RuntimeException(throwable));
+ }
+}
diff --git a/service-command/src/main/java/com/lanyuanxiaoyao/service/command/commands/CheckCommand.java b/service-command/src/main/java/com/lanyuanxiaoyao/service/command/commands/CheckCommand.java
new file mode 100644
index 0000000..9ddf9ee
--- /dev/null
+++ b/service-command/src/main/java/com/lanyuanxiaoyao/service/command/commands/CheckCommand.java
@@ -0,0 +1,61 @@
+package com.lanyuanxiaoyao.service.command.commands;
+
+import cn.hutool.core.util.StrUtil;
+import com.eshore.odcp.hudi.connector.entity.TableMeta;
+import com.lanyuanxiaoyao.service.configuration.ExecutorProvider;
+import com.lanyuanxiaoyao.service.forest.service.InfoService;
+import com.lanyuanxiaoyao.service.forest.service.PulsarService;
+import org.eclipse.collections.api.factory.Lists;
+import org.eclipse.collections.api.list.MutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.shell.standard.ShellComponent;
+import org.springframework.shell.standard.ShellMethod;
+
+/**
+ * @author ZhangJiacheng
+ * @date 2024-01-30
+ */
+@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
+@ShellComponent("检查相关操作")
+public class CheckCommand {
+ private static final Logger logger = LoggerFactory.getLogger(CheckCommand.class);
+
+ private final InfoService infoService;
+ private final PulsarService pulsarService;
+
+ public CheckCommand(InfoService infoService, PulsarService pulsarService) {
+ this.infoService = infoService;
+ this.pulsarService = pulsarService;
+ }
+
+ @ShellMethod("检查表topic是否对应")
+ public void checkPulsarTopicExists() {
+ MutableList results = Lists.mutable.empty().asSynchronized();
+ infoService.tableMetaList()
+ .asParallel(ExecutorProvider.EXECUTORS, 10)
+ .forEach(meta -> {
+ logger.info("{} pulsar check...", meta.getAlias());
+ if (StrUtil.isBlank(meta.getPulsarAddress())) {
+ results.add(StrUtil.format("{} pulsar url is blank", meta.getAlias()));
+ }
+ if (StrUtil.isBlank(meta.getTopic())) {
+ results.add(StrUtil.format("{} pulsar topic is blank", meta.getAlias()));
+ }
+ if (!pulsarService.existsTopic(meta.getPulsarAddress(), meta.getTopic())) {
+ results.add(StrUtil.format("{} pulsar url {} not contain topic {}", meta.getAlias(), meta.getPulsarAddress(), meta.getTopic()));
+ }
+ });
+ results.toSortedList().forEach(logger::info);
+ }
+
+ @ShellMethod("杂项工具")
+ public String getAllFieldType() {
+ return infoService.tableMetaList()
+ .flatCollect(TableMeta::getFields)
+ .collect(TableMeta.FieldMeta::getType)
+ .distinct()
+ .toSortedList()
+ .makeString("\n");
+ }
+}
diff --git a/service-command/src/main/java/com/lanyuanxiaoyao/service/command/commands/JobCommand.java b/service-command/src/main/java/com/lanyuanxiaoyao/service/command/commands/JobCommand.java
new file mode 100644
index 0000000..853c02c
--- /dev/null
+++ b/service-command/src/main/java/com/lanyuanxiaoyao/service/command/commands/JobCommand.java
@@ -0,0 +1,218 @@
+package com.lanyuanxiaoyao.service.command.commands;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.eshore.odcp.hudi.connector.entity.FlinkJob;
+import com.eshore.odcp.hudi.connector.entity.TableMeta;
+import com.lanyuanxiaoyao.service.command.commands.jobs.*;
+import com.lanyuanxiaoyao.service.command.provider.SearchTypeValueProvider;
+import com.lanyuanxiaoyao.service.command.utils.SearchUtils;
+import com.lanyuanxiaoyao.service.configuration.entity.info.JobAndMetas;
+import com.lanyuanxiaoyao.service.forest.service.InfoService;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.eclipse.collections.api.list.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.shell.CompletionContext;
+import org.springframework.shell.CompletionProposal;
+import org.springframework.shell.standard.ShellComponent;
+import org.springframework.shell.standard.ShellMethod;
+import org.springframework.shell.standard.ShellOption;
+import org.springframework.shell.standard.ValueProviderSupport;
+import org.springframework.shell.table.BorderStyle;
+import org.springframework.shell.table.TableBuilder;
+import org.springframework.shell.table.TableModelBuilder;
+
+/**
+ * 任务操作
+ *
+ * @author ZhangJiacheng
+ * @date 2022-03-24
+ */
+@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
+@ShellComponent("Config job")
+public class JobCommand {
+ private static final Logger logger = LoggerFactory.getLogger(JobCommand.class);
+
+ private final InfoService infoService;
+
+ public JobCommand(InfoService infoService) {
+ this.infoService = infoService;
+ }
+
+ @ShellMethod("显示 Flink job 配置信息")
+ public String jobAll(
+ @ShellOption(help = "搜索模式",
+ defaultValue = "CONTAINS",
+ valueProvider = SearchTypeValueProvider.class) SearchUtils.Type type,
+ @ShellOption(help = "搜索值 (除了 REGEX 模式, 其余可使用英文逗号分隔多个值)",
+ defaultValue = "") String pattern
+ ) {
+ ImmutableList jobAndMetas = infoService.allFlinkJobId()
+ .collect(id -> {
+ FlinkJob flinkJob = infoService.flinkJobDetail(id);
+ ImmutableList metas = infoService.tableMetaList(id)
+ .select(meta -> SearchUtils.search(type, pattern, meta.getSchema() + meta.getAlias()));
+ return new JobAndMetas(flinkJob, metas);
+ })
+ .reject(job -> job.getMetas().isEmpty());
+ TableModelBuilder