---初始化项目

This commit is contained in:
2025-09-19 16:14:08 +08:00
parent 902d3d7e3b
commit afee7c03ac
767 changed files with 75809 additions and 82 deletions

104
powerjob-client/pom.xml Normal file
View File

@ -0,0 +1,104 @@
<?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">
<parent>
<artifactId>powerjob</artifactId>
<groupId>tech.powerjob</groupId>
<version>5.1.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>powerjob-client</artifactId>
<name>powerjob-client</name>
<version>5.1.2</version>
<packaging>jar</packaging>
<properties>
<junit.version>5.9.1</junit.version>
<logback.version>1.2.13</logback.version>
<fastjson.version>1.2.83</fastjson.version>
<powerjob.common.version>5.1.2</powerjob.common.version>
<mvn.shade.plugin.version>3.2.4</mvn.shade.plugin.version>
</properties>
<dependencies>
<!-- fastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- oms-common -->
<dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-common</artifactId>
<version>${powerjob.common.version}</version>
</dependency>
<!-- Junit tests -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- log for test stage -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 暂时放弃 shade打 shade 包一定要非常干净,否则是更大的坑 -->
<!--
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${mvn.shade.plugin.version}</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>okhttp3</pattern>
<shadedPattern>shade.powerjob.okhttp3</shadedPattern>
</relocation>
<relocation>
<pattern>okio</pattern>
<shadedPattern>shade.powerjob.okio</shadedPattern>
</relocation>
<relocation>
<pattern>com.google</pattern>
<shadedPattern>shade.powerjob.com.google</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache</pattern>
<shadedPattern>shade.powerjob.org.apache</shadedPattern>
</relocation>
<relocation>
<pattern>com.alibaba</pattern>
<shadedPattern>shade.powerjob.com.alibaba</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
-->
</plugins>
</build>
</project>

View File

@ -0,0 +1,71 @@
package tech.powerjob.client;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import tech.powerjob.client.common.Protocol;
import tech.powerjob.client.extension.ClientExtension;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 客户端配置
*
* @author 程序帕鲁
* @since 2024/2/20
*/
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class ClientConfig implements Serializable {
/**
* 执行器 AppName
*/
private String appName;
/**
* 执行器密码
*/
private String password;
/**
* 地址列表,支持格式:
* - IP:Port, eg: 192.168.1.1:7700
* - 域名, eg: powerjob.apple-inc.com
*/
private List<String> addressList;
/**
* 客户端通讯协议
*/
private Protocol protocol = Protocol.HTTP;
/**
* 连接超时时间
*/
private Integer connectionTimeout;
/**
* 指定了等待服务器响应数据的最长时间。更具体地说这是从服务器开始返回响应数据包括HTTP头和数据客户端读取数据的超时时间
*/
private Integer readTimeout;
/**
* 指定了向服务器发送数据的最长时间。这是从客户端开始发送数据如POST请求的正文到数据完全发送出去的时间
*/
private Integer writeTimeout;
/**
* 默认携带的请求头
* 用于流量被基础设施识别
*/
private Map<String, String> defaultHeaders;
/**
* 客户端行为扩展
*/
private ClientExtension clientExtension;
}

View File

@ -0,0 +1,85 @@
package tech.powerjob.client;
import tech.powerjob.common.request.http.RunJobRequest;
import tech.powerjob.common.request.http.SaveJobInfoRequest;
import tech.powerjob.common.request.http.SaveWorkflowNodeRequest;
import tech.powerjob.common.request.http.SaveWorkflowRequest;
import tech.powerjob.common.request.query.InstancePageQuery;
import tech.powerjob.common.request.query.JobInfoQuery;
import tech.powerjob.common.response.*;
import java.util.List;
/**
* PowerJobClient, the client for OpenAPI.
*
* @author tjq
* @since 2023/3/5
*/
public interface IPowerJobClient {
/* ************* Job 区 ************* */
ResultDTO<SaveJobInfoRequest> exportJob(Long jobId);
ResultDTO<Long> saveJob(SaveJobInfoRequest request);
ResultDTO<Long> copyJob(Long jobId);
ResultDTO<JobInfoDTO> fetchJob(Long jobId);
ResultDTO<List<JobInfoDTO>> fetchAllJob();
ResultDTO<List<JobInfoDTO>> queryJob(JobInfoQuery powerQuery);
ResultDTO<Void> disableJob(Long jobId);
ResultDTO<Void> enableJob(Long jobId);
ResultDTO<Void> deleteJob(Long jobId);
ResultDTO<Long> runJob(Long jobId, String instanceParams, long delayMS);
PowerResultDTO<Long> runJob(RunJobRequest runJobRequest);
/* ************* Instance API list ************* */
ResultDTO<Void> stopInstance(Long instanceId);
ResultDTO<Void> cancelInstance(Long instanceId);
ResultDTO<Void> retryInstance(Long instanceId);
ResultDTO<Integer> fetchInstanceStatus(Long instanceId);
ResultDTO<InstanceInfoDTO> fetchInstanceInfo(Long instanceId);
ResultDTO<PageResult<InstanceInfoDTO>> queryInstanceInfo(InstancePageQuery instancePageQuery);
/* ************* Workflow API list ************* */
ResultDTO<Long> saveWorkflow(SaveWorkflowRequest request);
ResultDTO<Long> copyWorkflow(Long workflowId);
ResultDTO<List<WorkflowNodeInfoDTO>> saveWorkflowNode(List<SaveWorkflowNodeRequest> requestList);
ResultDTO<WorkflowInfoDTO> fetchWorkflow(Long workflowId);
ResultDTO<Void> disableWorkflow(Long workflowId);
ResultDTO<Void> enableWorkflow(Long workflowId);
ResultDTO<Void> deleteWorkflow(Long workflowId);
ResultDTO<Long> runWorkflow(Long workflowId, String initParams, long delayMS);
/* ************* Workflow Instance API list ************* */
ResultDTO<Void> stopWorkflowInstance(Long wfInstanceId);
ResultDTO<Void> retryWorkflowInstance(Long wfInstanceId);
ResultDTO<Void> markWorkflowNodeAsSuccess(Long wfInstanceId, Long nodeId);
ResultDTO<WorkflowInstanceInfoDTO> fetchWorkflowInstanceInfo(Long wfInstanceId);
}

View File

@ -0,0 +1,563 @@
package tech.powerjob.client;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import tech.powerjob.client.module.AppAuthRequest;
import tech.powerjob.client.module.AppAuthResult;
import tech.powerjob.client.service.PowerRequestBody;
import tech.powerjob.client.service.RequestService;
import tech.powerjob.client.service.impl.ClusterRequestServiceOkHttp3Impl;
import tech.powerjob.common.OpenAPIConstant;
import tech.powerjob.common.enums.EncryptType;
import tech.powerjob.common.enums.InstanceStatus;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.common.request.http.RunJobRequest;
import tech.powerjob.common.request.http.SaveJobInfoRequest;
import tech.powerjob.common.request.http.SaveWorkflowNodeRequest;
import tech.powerjob.common.request.http.SaveWorkflowRequest;
import tech.powerjob.common.request.query.InstancePageQuery;
import tech.powerjob.common.request.query.JobInfoQuery;
import tech.powerjob.common.response.*;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.common.utils.CommonUtils;
import tech.powerjob.common.utils.DigestUtils;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static tech.powerjob.client.TypeStore.*;
/**
* PowerJobClient, the client for OpenAPI.
*
* @author tjq
* @since 2020/4/15
*/
@Slf4j
public class PowerJobClient implements IPowerJobClient, Closeable {
private Long appId;
private final RequestService requestService;
public PowerJobClient(ClientConfig config) {
List<String> addressList = config.getAddressList();
String appName = config.getAppName();
CommonUtils.requireNonNull(addressList, "addressList can't be null!");
CommonUtils.requireNonNull(appName, "appName can't be null");
this.requestService = new ClusterRequestServiceOkHttp3Impl(config);
AppAuthRequest appAuthRequest = new AppAuthRequest();
appAuthRequest.setAppName(appName);
appAuthRequest.setEncryptedPassword(DigestUtils.md5(config.getPassword()));
appAuthRequest.setEncryptType(EncryptType.MD5.getCode());
String assertResponse = requestService.request(OpenAPIConstant.AUTH_APP, PowerRequestBody.newJsonRequestBody(appAuthRequest));
if (StringUtils.isNotEmpty(assertResponse)) {
ResultDTO<AppAuthResult> resultDTO = JSON.parseObject(assertResponse, APP_AUTH_RESULT_TYPE);
if (resultDTO.isSuccess()) {
appId = resultDTO.getData().getAppId();
} else {
throw new PowerJobException(resultDTO.getMessage());
}
}
if (appId == null) {
throw new PowerJobException("appId is null, please check your config");
}
log.info("[PowerJobClient] [INIT] {}'s PowerJobClient bootstrap successfully", appName);
}
/**
* Init PowerJobClient with domain, appName and password.
*
* @param domain like powerjob-server.apple-inc.com (Intranet Domain)
* @param appName name of the application
* @param password password of the application
*/
public PowerJobClient(String domain, String appName, String password) {
this(new ClientConfig().setAppName(appName).setPassword(password).setAddressList(Lists.newArrayList(domain)));
}
/**
* Init PowerJobClient with server address, appName and password.
*
* @param addressList IP:Port address list, like 192.168.1.1:7700
* @param appName name of the application
* @param password password of the application
*/
public PowerJobClient(List<String> addressList, String appName, String password) {
this(new ClientConfig().setAppName(appName).setPassword(password).setAddressList(addressList));
}
/* ************* Job 区 ************* */
/**
* Save one Job
* When an ID exists in SaveJobInfoRequest, it is an update operation. Otherwise, it is a crate operation.
*
* @param request Job meta info
* @return jobId
*/
@Override
public ResultDTO<Long> saveJob(SaveJobInfoRequest request) {
request.setAppId(appId);
String post = requestService.request(OpenAPIConstant.SAVE_JOB, PowerRequestBody.newJsonRequestBody(request));
return JSON.parseObject(post, LONG_RESULT_TYPE);
}
/**
* Copy one Job
*
* @param jobId Job id
* @return Id of job copy
*/
@Override
public ResultDTO<Long> copyJob(Long jobId) {
Map<String, String> param = Maps.newHashMap();
param.put("jobId", jobId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.COPY_JOB, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, LONG_RESULT_TYPE);
}
@Override
public ResultDTO<SaveJobInfoRequest> exportJob(Long jobId) {
Map<String, String> param = Maps.newHashMap();
param.put("jobId", jobId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.EXPORT_JOB, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, SAVE_JOB_INFO_REQUEST_RESULT_TYPE);
}
/**
* Query JobInfo by jobId
*
* @param jobId jobId
* @return Job meta info
*/
@Override
public ResultDTO<JobInfoDTO> fetchJob(Long jobId) {
Map<String, String> param = Maps.newHashMap();
param.put("jobId", jobId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.FETCH_JOB, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, JOB_RESULT_TYPE);
}
/**
* Query all JobInfo
*
* @return All JobInfo
*/
@Override
public ResultDTO<List<JobInfoDTO>> fetchAllJob() {
Map<String, String> param = Maps.newHashMap();
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.FETCH_ALL_JOB, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, LIST_JOB_RESULT_TYPE);
}
/**
* Query JobInfo by PowerQuery
*
* @param powerQuery JobQuery
* @return JobInfo
*/
@Override
public ResultDTO<List<JobInfoDTO>> queryJob(JobInfoQuery powerQuery) {
powerQuery.setAppIdEq(appId);
String post = requestService.request(OpenAPIConstant.QUERY_JOB, PowerRequestBody.newJsonRequestBody(powerQuery));
return JSON.parseObject(post, LIST_JOB_RESULT_TYPE);
}
/**
* Disable one Job by jobId
*
* @param jobId jobId
* @return Standard return object
*/
@Override
public ResultDTO<Void> disableJob(Long jobId) {
Map<String, String> param = Maps.newHashMap();
param.put("jobId", jobId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.DISABLE_JOB, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Enable one job by jobId
*
* @param jobId jobId
* @return Standard return object
*/
@Override
public ResultDTO<Void> enableJob(Long jobId) {
Map<String, String> param = Maps.newHashMap();
param.put("jobId", jobId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.ENABLE_JOB, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Delete one job by jobId
*
* @param jobId jobId
* @return Standard return object
*/
@Override
public ResultDTO<Void> deleteJob(Long jobId) {
Map<String, String> param = Maps.newHashMap();
param.put("jobId", jobId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.DELETE_JOB, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Run a job once
*
* @param jobId ID of the job to be run
* @param instanceParams Runtime parameters of the job (TaskContext#instanceParams)
* @param delayMS Delay timeMilliseconds
* @return instanceId
*/
@Override
public ResultDTO<Long> runJob(Long jobId, String instanceParams, long delayMS) {
RunJobRequest runJobRequest = new RunJobRequest().setJobId(jobId).setInstanceParams(instanceParams).setDelay(delayMS);
return runJob(runJobRequest);
}
public ResultDTO<Long> runJob(Long jobId) {
return runJob(jobId, null, 0);
}
@Override
public PowerResultDTO<Long> runJob(RunJobRequest runJobRequest) {
runJobRequest.setAppId(appId);
String post = requestService.request(OpenAPIConstant.RUN_JOB2, PowerRequestBody.newJsonRequestBody(runJobRequest));
return JSON.parseObject(post, LONG_POWER_RESULT_TYPE);
}
/* ************* Instance API list ************* */
/**
* Stop one job instance
*
* @param instanceId instanceId
* @return Standard return object
*/
@Override
public ResultDTO<Void> stopInstance(Long instanceId) {
Map<String, String> param = Maps.newHashMap();
param.put("instanceId", instanceId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.STOP_INSTANCE, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Cancel a job instance that is not yet running
* NoticeThere is a time interval between the call interface time and the expected execution time of the job instance to be cancelled, otherwise reliability is not guaranteed
*
* @param instanceId instanceId
* @return Standard return object
*/
@Override
public ResultDTO<Void> cancelInstance(Long instanceId) {
Map<String, String> param = Maps.newHashMap();
param.put("instanceId", instanceId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.CANCEL_INSTANCE, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Retry failed job instance
* Notice: Only job instance with completion status (success, failure, manually stopped, cancelled) can be retried, and retries of job instances within workflows are not supported yet.
*
* @param instanceId instanceId
* @return Standard return object
*/
@Override
public ResultDTO<Void> retryInstance(Long instanceId) {
Map<String, String> param = Maps.newHashMap();
param.put("instanceId", instanceId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.RETRY_INSTANCE, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Query status about a job instance
*
* @param instanceId instanceId
* @return {@link InstanceStatus}
*/
@Override
public ResultDTO<Integer> fetchInstanceStatus(Long instanceId) {
Map<String, String> param = Maps.newHashMap();
param.put("instanceId", instanceId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.FETCH_INSTANCE_STATUS, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, INTEGER_RESULT_TYPE);
}
/**
* Query detail about a job instance
*
* @param instanceId instanceId
* @return instance detail
*/
@Override
public ResultDTO<InstanceInfoDTO> fetchInstanceInfo(Long instanceId) {
Map<String, String> param = Maps.newHashMap();
param.put("instanceId", instanceId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.FETCH_INSTANCE_INFO, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, INSTANCE_RESULT_TYPE);
}
@Override
public ResultDTO<PageResult<InstanceInfoDTO>> queryInstanceInfo(InstancePageQuery instancePageQuery) {
instancePageQuery.setAppIdEq(appId);
String post = requestService.request(OpenAPIConstant.QUERY_INSTANCE, PowerRequestBody.newJsonRequestBody(instancePageQuery));
return JSON.parseObject(post, PAGE_INSTANCE_RESULT_TYPE);
}
/* ************* Workflow API list ************* */
/**
* Save one workflow
* When an ID exists in SaveWorkflowRequest, it is an update operation. Otherwise, it is a crate operation.
*
* @param request Workflow meta info
* @return workflowId
*/
@Override
public ResultDTO<Long> saveWorkflow(SaveWorkflowRequest request) {
request.setAppId(appId);
// 中坑记录:用 FastJSON 序列化会导致 Server 接收时 pEWorkflowDAG 为 null无语.jpg
String json = JsonUtils.toJSONStringUnsafe(request);
String post = requestService.request(OpenAPIConstant.SAVE_WORKFLOW, PowerRequestBody.newJsonRequestBody(json));
return JSON.parseObject(post, LONG_RESULT_TYPE);
}
/**
* Copy one workflow
*
* @param workflowId Workflow id
* @return Id of workflow copy
*/
@Override
public ResultDTO<Long> copyWorkflow(Long workflowId) {
Map<String, String> param = Maps.newHashMap();
param.put("workflowId", workflowId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.COPY_WORKFLOW, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, LONG_RESULT_TYPE);
}
/**
* 添加工作流节点
*
* @param requestList Node info list of Workflow
* @return Standard return object
*/
@Override
public ResultDTO<List<WorkflowNodeInfoDTO>> saveWorkflowNode(List<SaveWorkflowNodeRequest> requestList) {
for (SaveWorkflowNodeRequest saveWorkflowNodeRequest : requestList) {
saveWorkflowNodeRequest.setAppId(appId);
}
String json = JsonUtils.toJSONStringUnsafe(requestList);
String post = requestService.request(OpenAPIConstant.SAVE_WORKFLOW_NODE, PowerRequestBody.newJsonRequestBody(json));
return JSON.parseObject(post, WF_NODE_LIST_RESULT_TYPE);
}
/**
* Query Workflow by workflowId
*
* @param workflowId workflowId
* @return Workflow meta info
*/
@Override
public ResultDTO<WorkflowInfoDTO> fetchWorkflow(Long workflowId) {
Map<String, String> param = Maps.newHashMap();
param.put("workflowId", workflowId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.FETCH_WORKFLOW, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, WF_RESULT_TYPE);
}
/**
* Disable Workflow by workflowId
*
* @param workflowId workflowId
* @return Standard return object
*/
@Override
public ResultDTO<Void> disableWorkflow(Long workflowId) {
Map<String, String> param = Maps.newHashMap();
param.put("workflowId", workflowId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.DISABLE_WORKFLOW, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Enable Workflow by workflowId
*
* @param workflowId workflowId
* @return Standard return object
*/
@Override
public ResultDTO<Void> enableWorkflow(Long workflowId) {
Map<String, String> param = Maps.newHashMap();
param.put("workflowId", workflowId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.ENABLE_WORKFLOW, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Delete Workflow by workflowId
*
* @param workflowId workflowId
* @return Standard return object
*/
@Override
public ResultDTO<Void> deleteWorkflow(Long workflowId) {
Map<String, String> param = Maps.newHashMap();
param.put("workflowId", workflowId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.DELETE_WORKFLOW, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Run a workflow once
*
* @param workflowId workflowId
* @param initParams workflow startup parameters
* @param delayMS Delay timeMilliseconds
* @return workflow instanceId
*/
@Override
public ResultDTO<Long> runWorkflow(Long workflowId, String initParams, long delayMS) {
Map<String, String> param = Maps.newHashMap();
param.put("workflowId", workflowId.toString());
param.put("appId", appId.toString());
param.put("delay", String.valueOf(delayMS));
if (StringUtils.isNotEmpty(initParams)) {
param.put("initParams", initParams);
}
String post = requestService.request(OpenAPIConstant.RUN_WORKFLOW, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, LONG_RESULT_TYPE);
}
public ResultDTO<Long> runWorkflow(Long workflowId) {
return runWorkflow(workflowId, null, 0);
}
/* ************* Workflow Instance API list ************* */
/**
* Stop one workflow instance
*
* @param wfInstanceId workflow instanceId
* @return Standard return object
*/
@Override
public ResultDTO<Void> stopWorkflowInstance(Long wfInstanceId) {
Map<String, String> param = Maps.newHashMap();
param.put("wfInstanceId", wfInstanceId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.STOP_WORKFLOW_INSTANCE, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Retry one workflow instance
*
* @param wfInstanceId workflow instanceId
* @return Standard return object
*/
@Override
public ResultDTO<Void> retryWorkflowInstance(Long wfInstanceId) {
Map<String, String> param = Maps.newHashMap();
param.put("wfInstanceId", wfInstanceId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.RETRY_WORKFLOW_INSTANCE, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* mark the workflow node as success
*
* @param wfInstanceId workflow instanceId
* @param nodeId node id
* @return Standard return object
*/
@Override
public ResultDTO<Void> markWorkflowNodeAsSuccess(Long wfInstanceId, Long nodeId) {
Map<String, String> param = Maps.newHashMap();
param.put("wfInstanceId", wfInstanceId.toString());
param.put("appId", appId.toString());
param.put("nodeId", nodeId.toString());
String post = requestService.request(OpenAPIConstant.MARK_WORKFLOW_NODE_AS_SUCCESS, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, VOID_RESULT_TYPE);
}
/**
* Query detail about a workflow instance
*
* @param wfInstanceId workflow instanceId
* @return detail about a workflow
*/
@Override
public ResultDTO<WorkflowInstanceInfoDTO> fetchWorkflowInstanceInfo(Long wfInstanceId) {
Map<String, String> param = Maps.newHashMap();
param.put("wfInstanceId", wfInstanceId.toString());
param.put("appId", appId.toString());
String post = requestService.request(OpenAPIConstant.FETCH_WORKFLOW_INSTANCE_INFO, PowerRequestBody.newFormRequestBody(param));
return JSON.parseObject(post, WF_INSTANCE_RESULT_TYPE);
}
@Override
public void close() throws IOException {
requestService.close();
}
}

View File

@ -0,0 +1,44 @@
package tech.powerjob.client;
import com.alibaba.fastjson.TypeReference;
import tech.powerjob.client.module.AppAuthResult;
import tech.powerjob.common.request.http.SaveJobInfoRequest;
import tech.powerjob.common.response.*;
import java.util.List;
/**
* TypeReference store.
*
* @author tjq
* @since 11/7/20
*/
public class TypeStore {
public static final TypeReference<ResultDTO<AppAuthResult>> APP_AUTH_RESULT_TYPE = new TypeReference<ResultDTO<AppAuthResult>>(){};
public static final TypeReference<ResultDTO<Void>> VOID_RESULT_TYPE = new TypeReference<ResultDTO<Void>>(){};
public static final TypeReference<ResultDTO<Integer>> INTEGER_RESULT_TYPE = new TypeReference<ResultDTO<Integer>>(){};
public static final TypeReference<ResultDTO<Long>> LONG_RESULT_TYPE = new TypeReference<ResultDTO<Long>>(){};
public static final TypeReference<PowerResultDTO<Long>> LONG_POWER_RESULT_TYPE = new TypeReference<PowerResultDTO<Long>>(){};
public static final TypeReference<ResultDTO<JobInfoDTO>> JOB_RESULT_TYPE = new TypeReference<ResultDTO<JobInfoDTO>>(){};
public static final TypeReference<ResultDTO<SaveJobInfoRequest>> SAVE_JOB_INFO_REQUEST_RESULT_TYPE = new TypeReference<ResultDTO<SaveJobInfoRequest>>(){};
public static final TypeReference<ResultDTO<List<JobInfoDTO>>> LIST_JOB_RESULT_TYPE = new TypeReference<ResultDTO<List<JobInfoDTO>>>(){};
public static final TypeReference<ResultDTO<InstanceInfoDTO>> INSTANCE_RESULT_TYPE = new TypeReference<ResultDTO<InstanceInfoDTO>>() {};
public static final TypeReference<ResultDTO<List<InstanceInfoDTO>>> LIST_INSTANCE_RESULT_TYPE = new TypeReference<ResultDTO<List<InstanceInfoDTO>>>(){};
public static final TypeReference<ResultDTO<PageResult<InstanceInfoDTO>>> PAGE_INSTANCE_RESULT_TYPE = new TypeReference<ResultDTO<PageResult<InstanceInfoDTO>>>(){};
public static final TypeReference<ResultDTO<WorkflowInfoDTO>> WF_RESULT_TYPE = new TypeReference<ResultDTO<WorkflowInfoDTO>>() {};
public static final TypeReference<ResultDTO<WorkflowInstanceInfoDTO>> WF_INSTANCE_RESULT_TYPE = new TypeReference<ResultDTO<WorkflowInstanceInfoDTO>>() {};
public static final TypeReference<ResultDTO<List<WorkflowNodeInfoDTO>>> WF_NODE_LIST_RESULT_TYPE = new TypeReference<ResultDTO<List<WorkflowNodeInfoDTO>>> () {};
}

View File

@ -0,0 +1,28 @@
package tech.powerjob.client.common;
import lombok.Getter;
/**
* Protocol
*
* @author tjq
* @since 2024/2/20
*/
@Getter
public enum Protocol {
HTTP("http"),
HTTPS("https");
private final String protocol;
Protocol(String protocol) {
this.protocol = protocol;
}
@Override
public String toString() {
return protocol;
}
}

View File

@ -0,0 +1,19 @@
package tech.powerjob.client.extension;
import java.util.List;
/**
* 扩展服务
*
* @author tjq
* @since 2024/8/11
*/
public interface ClientExtension {
/**
* 动态提供地址,适用于 server 部署在动态集群上的场景
* @param context 上下文
* @return 地址,格式要求同 ClientConfig#addressList
*/
List<String> addressProvider(ExtensionContext context);
}

View File

@ -0,0 +1,10 @@
package tech.powerjob.client.extension;
/**
* 扩展上下文
*
* @author tjq
* @since 2024/8/11
*/
public class ExtensionContext {
}

View File

@ -0,0 +1,39 @@
package tech.powerjob.client.module;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.util.Map;
/**
* App 鉴权请求
*
* @author tjq
* @since 2024/2/19
*/
@Getter
@Setter
@ToString
public class AppAuthRequest implements Serializable {
/**
* 应用名称
*/
private String appName;
/**
* 加密后密码
*/
private String encryptedPassword;
/**
* 加密类型
*/
private String encryptType;
/**
* 额外参数,方便开发者传递其他参数
*/
private Map<String, Object> extra;
}

View File

@ -0,0 +1,30 @@
package tech.powerjob.client.module;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.util.Map;
/**
* App 鉴权响应
*
* @author tjq
* @since 2024/2/21
*/
@Getter
@Setter
@ToString
public class AppAuthResult implements Serializable {
private Long appId;
private String token;
/**
* 额外参数
* 有安全需求的开发者可执行扩展
*/
private Map<String, Object> extra;
}

View File

@ -0,0 +1,26 @@
package tech.powerjob.client.service;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Map;
/**
* HTTP 响应
*
* @author tjq
* @since 2024/8/10
*/
@Data
@Accessors(chain = true)
public class HttpResponse implements Serializable {
private boolean success;
private int code;
private String response;
private Map<String, String> headers;
}

View File

@ -0,0 +1,47 @@
package tech.powerjob.client.service;
import com.google.common.collect.Maps;
import lombok.Getter;
import tech.powerjob.common.enums.MIME;
import java.util.Map;
/**
* 请求体
*
* @author tjq
* @since 2024/8/10
*/
@Getter
public class PowerRequestBody {
private MIME mime;
private Object payload;
private final Map<String, String> headers = Maps.newHashMap();
private PowerRequestBody() {
}
public static PowerRequestBody newJsonRequestBody(Object data) {
PowerRequestBody powerRequestBody = new PowerRequestBody();
powerRequestBody.mime = MIME.APPLICATION_JSON;
powerRequestBody.payload = data;
return powerRequestBody;
}
public static PowerRequestBody newFormRequestBody(Map<String, String> form) {
PowerRequestBody powerRequestBody = new PowerRequestBody();
powerRequestBody.mime = MIME.APPLICATION_FORM;
powerRequestBody.payload = form;
return powerRequestBody;
}
public void addHeaders(Map<String, String> hs) {
if (hs == null || hs.isEmpty()) {
return;
}
this.headers.putAll(hs);
}
}

View File

@ -0,0 +1,15 @@
package tech.powerjob.client.service;
import java.io.Closeable;
/**
* 请求服务
*
* @author tjq
* @since 2024/2/20
*/
public interface RequestService extends Closeable {
String request(String path, PowerRequestBody powerRequestBody);
}

View File

@ -0,0 +1,113 @@
package tech.powerjob.client.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import tech.powerjob.client.ClientConfig;
import tech.powerjob.client.TypeStore;
import tech.powerjob.client.module.AppAuthRequest;
import tech.powerjob.client.module.AppAuthResult;
import tech.powerjob.client.service.HttpResponse;
import tech.powerjob.client.service.PowerRequestBody;
import tech.powerjob.common.OpenAPIConstant;
import tech.powerjob.common.enums.EncryptType;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.common.utils.DigestUtils;
import tech.powerjob.common.utils.MapUtils;
import java.util.Map;
/**
* 封装鉴权相关逻辑
*
* @author tjq
* @since 2024/2/21
*/
@Slf4j
abstract class AppAuthClusterRequestService extends ClusterRequestService {
protected AppAuthResult appAuthResult;
public AppAuthClusterRequestService(ClientConfig config) {
super(config);
}
@Override
public String request(String path, PowerRequestBody powerRequestBody) {
// 若不存在 appAuthResult则首先进行鉴权
if (appAuthResult == null) {
refreshAppAuthResult();
}
HttpResponse httpResponse = doRequest(path, powerRequestBody);
// 如果 auth 成功,则代表请求有效,直接返回
String authStatus = MapUtils.getString(httpResponse.getHeaders(), OpenAPIConstant.RESPONSE_HEADER_AUTH_STATUS);
// 继续尝试获取转为小写之后的 auth header (Nginx代理默认会将自定义header转为纯小写, 实现针对该情况的兼容)
if (StringUtils.isEmpty(authStatus)) {
authStatus = MapUtils.getString(httpResponse.getHeaders(),
OpenAPIConstant.RESPONSE_HEADER_AUTH_STATUS.toLowerCase());
}
if (Boolean.TRUE.toString().equalsIgnoreCase(authStatus)) {
return httpResponse.getResponse();
}
// 否则请求无效,刷新鉴权后重新请求
log.warn("[PowerJobClient] auth failed[authStatus: {}], try to refresh the auth info", authStatus);
refreshAppAuthResult();
httpResponse = doRequest(path, powerRequestBody);
// 只要请求不失败直接返回如果鉴权失败则返回鉴权错误信息server 保证 response 永远非空)
return httpResponse.getResponse();
}
private HttpResponse doRequest(String path, PowerRequestBody powerRequestBody) {
// 添加鉴权信息
Map<String, String> authHeaders = buildAuthHeader();
powerRequestBody.addHeaders(authHeaders);
HttpResponse httpResponse = clusterHaRequest(path, powerRequestBody);
// 任何请求不成功,都直接报错
if (!httpResponse.isSuccess()) {
throw new PowerJobException("REMOTE_SERVER_INNER_EXCEPTION");
}
return httpResponse;
}
private Map<String, String> buildAuthHeader() {
Map<String, String> authHeader = Maps.newHashMap();
authHeader.put(OpenAPIConstant.REQUEST_HEADER_APP_ID, String.valueOf(appAuthResult.getAppId()));
authHeader.put(OpenAPIConstant.REQUEST_HEADER_ACCESS_TOKEN, appAuthResult.getToken());
return authHeader;
}
@SneakyThrows
private void refreshAppAuthResult() {
AppAuthRequest appAuthRequest = buildAppAuthRequest();
HttpResponse httpResponse = clusterHaRequest(OpenAPIConstant.AUTH_APP, PowerRequestBody.newJsonRequestBody(appAuthRequest));
if (!httpResponse.isSuccess()) {
throw new PowerJobException("AUTH_APP_EXCEPTION!");
}
ResultDTO<AppAuthResult> authResultDTO = JSONObject.parseObject(httpResponse.getResponse(), TypeStore.APP_AUTH_RESULT_TYPE);
if (!authResultDTO.isSuccess()) {
throw new PowerJobException("AUTH_FAILED_" + authResultDTO.getMessage());
}
log.warn("[PowerJobClient] refresh auth info successfully!");
this.appAuthResult = authResultDTO.getData();
}
protected AppAuthRequest buildAppAuthRequest() {
AppAuthRequest appAuthRequest = new AppAuthRequest();
appAuthRequest.setAppName(config.getAppName());
appAuthRequest.setEncryptedPassword(DigestUtils.md5(config.getPassword()));
appAuthRequest.setEncryptType(EncryptType.MD5.getCode());
return appAuthRequest;
}
}

View File

@ -0,0 +1,140 @@
package tech.powerjob.client.service.impl;
import lombok.extern.slf4j.Slf4j;
import tech.powerjob.client.ClientConfig;
import tech.powerjob.client.extension.ClientExtension;
import tech.powerjob.client.extension.ExtensionContext;
import tech.powerjob.client.service.HttpResponse;
import tech.powerjob.client.service.PowerRequestBody;
import tech.powerjob.client.service.RequestService;
import tech.powerjob.common.OpenAPIConstant;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.common.utils.CollectionUtils;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Objects;
/**
* 集群请求服务
* 封装网络相关通用逻辑
*
* @author tjq
* @since 2024/2/21
*/
@Slf4j
abstract class ClusterRequestService implements RequestService {
protected final ClientConfig config;
/**
* 当前地址(上次请求成功的地址)
*/
protected String currentAddress;
/**
* 地址格式
* 协议://域名/OpenAPI/子路径
*/
protected static final String URL_PATTERN = "%s://%s%s%s";
/**
* 默认超时时间
*/
protected static final Integer DEFAULT_TIMEOUT_SECONDS = 2;
protected static final int HTTP_SUCCESS_CODE = 200;
public ClusterRequestService(ClientConfig config) {
this.config = config;
this.currentAddress = config.getAddressList().get(0);
}
/**
* 具体某一次 HTTP 请求的实现
* @param url 完整请求地址
* @param body 请求体
* @return 响应
* @throws IOException 异常
*/
protected abstract HttpResponse sendHttpRequest(String url, PowerRequestBody body) throws IOException;
/**
* 封装集群请求能力
* @param path 请求 PATH
* @param powerRequestBody 请求体
* @return 响应
*/
protected HttpResponse clusterHaRequest(String path, PowerRequestBody powerRequestBody) {
// 先尝试默认地址
String url = getUrl(path, currentAddress);
try {
return sendHttpRequest(url, powerRequestBody);
} catch (IOException e) {
log.warn("[ClusterRequestService] request url:{} failed, reason is {}.", url, e.toString());
}
List<String> addressList = fetchAddressList();
// 失败,开始重试
for (String addr : addressList) {
if (Objects.equals(addr, currentAddress)) {
continue;
}
url = getUrl(path, addr);
try {
HttpResponse res = sendHttpRequest(url, powerRequestBody);
log.warn("[ClusterRequestService] server change: from({}) -> to({}).", currentAddress, addr);
currentAddress = addr;
return res;
} catch (IOException e) {
log.warn("[ClusterRequestService] request url:{} failed, reason is {}.", url, e.toString());
}
}
log.error("[ClusterRequestService] do post for path: {} failed because of no server available in {}.", path, addressList);
throw new PowerJobException("no server available when send post request");
}
private List<String> fetchAddressList() {
ClientExtension clientExtension = config.getClientExtension();
if (clientExtension != null) {
List<String> addressList = clientExtension.addressProvider(new ExtensionContext());
if (!CollectionUtils.isEmpty(addressList)) {
return addressList;
}
}
return config.getAddressList();
}
/**
* 不验证证书
* X.509 是一个国际标准定义了公钥证书的格式。这个标准是由国际电信联盟ITU-T制定的用于公钥基础设施PKI中数字证书的创建和分发。X.509证书主要用于在公开网络上验证实体的身份如服务器或客户端的身份验证过程中确保通信双方是可信的。X.509证书广泛应用于多种安全协议中包括SSL/TLS它是实现HTTPS的基础。
*/
protected static class NoVerifyX509TrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) {
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) {
// 不验证
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
private String getUrl(String path, String address) {
String protocol = config.getProtocol().getProtocol();
return String.format(URL_PATTERN, protocol, address, OpenAPIConstant.WEB_PATH, path);
}
}

View File

@ -0,0 +1,148 @@
package tech.powerjob.client.service.impl;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import tech.powerjob.client.ClientConfig;
import tech.powerjob.client.common.Protocol;
import tech.powerjob.client.service.HttpResponse;
import tech.powerjob.client.service.PowerRequestBody;
import tech.powerjob.common.OmsConstant;
import tech.powerjob.common.serialize.JsonUtils;
import javax.net.ssl.*;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* desc
*
* @author tjq
* @since 2024/2/20
*/
@Slf4j
public class ClusterRequestServiceOkHttp3Impl extends AppAuthClusterRequestService {
private final OkHttpClient okHttpClient;
public ClusterRequestServiceOkHttp3Impl(ClientConfig config) {
super(config);
// 初始化 HTTP 客户端
if (Protocol.HTTPS.equals(config.getProtocol())) {
okHttpClient = initHttpsNoVerifyClient();
} else {
okHttpClient = initHttpClient();
}
}
@Override
protected HttpResponse sendHttpRequest(String url, PowerRequestBody powerRequestBody) throws IOException {
// 添加公共 header
powerRequestBody.addHeaders(config.getDefaultHeaders());
Object obj = powerRequestBody.getPayload();
RequestBody requestBody = null;
switch (powerRequestBody.getMime()) {
case APPLICATION_JSON:
MediaType jsonType = MediaType.parse(OmsConstant.JSON_MEDIA_TYPE);
String body = obj instanceof String ? (String) obj : JsonUtils.toJSONStringUnsafe(obj);
requestBody = RequestBody.create(jsonType, body);
break;
case APPLICATION_FORM:
FormBody.Builder formBuilder = new FormBody.Builder();
Map<String, String> formObj = (Map<String, String>) obj;
formObj.forEach(formBuilder::add);
requestBody = formBuilder.build();
}
Request request = new Request.Builder()
.post(requestBody)
.headers(Headers.of(powerRequestBody.getHeaders()))
.url(url)
.build();
try (Response response = okHttpClient.newCall(request).execute()) {
int code = response.code();
HttpResponse httpResponse = new HttpResponse()
.setCode(code)
.setSuccess(code == HTTP_SUCCESS_CODE);
ResponseBody body = response.body();
if (body != null) {
httpResponse.setResponse(body.string());
}
Headers respHeaders = response.headers();
Set<String> headerNames = respHeaders.names();
Map<String, String> respHeaderMap = Maps.newHashMap();
headerNames.forEach(hdKey -> respHeaderMap.put(hdKey, respHeaders.get(hdKey)));
httpResponse.setHeaders(respHeaderMap);
return httpResponse;
}
}
@SneakyThrows
private OkHttpClient initHttpClient() {
OkHttpClient.Builder okHttpBuilder = commonOkHttpBuilder();
return okHttpBuilder.build();
}
@SneakyThrows
private OkHttpClient initHttpsNoVerifyClient() {
X509TrustManager trustManager = new NoVerifyX509TrustManager();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder okHttpBuilder = commonOkHttpBuilder();
// 不需要校验证书
okHttpBuilder.sslSocketFactory(sslSocketFactory, trustManager);
// 不校验 url中的 hostname
okHttpBuilder.hostnameVerifier((String hostname, SSLSession session) -> true);
return okHttpBuilder.build();
}
private OkHttpClient.Builder commonOkHttpBuilder() {
return new OkHttpClient.Builder()
// 设置读取超时时间
.readTimeout(Optional.ofNullable(config.getReadTimeout()).orElse(DEFAULT_TIMEOUT_SECONDS), TimeUnit.SECONDS)
// 设置写的超时时间
.writeTimeout(Optional.ofNullable(config.getWriteTimeout()).orElse(DEFAULT_TIMEOUT_SECONDS), TimeUnit.SECONDS)
// 设置连接超时时间
.connectTimeout(Optional.ofNullable(config.getConnectionTimeout()).orElse(DEFAULT_TIMEOUT_SECONDS), TimeUnit.SECONDS)
.callTimeout(Optional.ofNullable(config.getConnectionTimeout()).orElse(DEFAULT_TIMEOUT_SECONDS), TimeUnit.SECONDS);
}
@Override
public void close() throws IOException {
// 关闭 Dispatcher
okHttpClient.dispatcher().executorService().shutdown();
// 清理连接池
okHttpClient.connectionPool().evictAll();
// 清理缓存(如果有使用)
Cache cache = okHttpClient.cache();
if (cache != null) {
cache.close();
}
}
}

View File

@ -0,0 +1,22 @@
package tech.powerjob.client.test;
import com.google.common.collect.Lists;
import org.junit.jupiter.api.BeforeAll;
import tech.powerjob.client.IPowerJobClient;
import tech.powerjob.client.PowerJobClient;
/**
* Initialize OhMyClient
*
* @author tjq
* @since 1/16/21
*/
public class ClientInitializer {
protected static IPowerJobClient powerJobClient;
@BeforeAll
public static void initClient() throws Exception {
powerJobClient = new PowerJobClient(Lists.newArrayList("127.0.0.1:7700", "127.0.0.1:7701"), "powerjob-worker-samples", "powerjob123");
}
}

View File

@ -0,0 +1,172 @@
package tech.powerjob.client.test;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import tech.powerjob.client.PowerJobClient;
import tech.powerjob.common.enums.ExecuteType;
import tech.powerjob.common.enums.ProcessorType;
import tech.powerjob.common.enums.TimeExpressionType;
import tech.powerjob.common.request.http.SaveJobInfoRequest;
import tech.powerjob.common.request.query.InstancePageQuery;
import tech.powerjob.common.response.InstanceInfoDTO;
import tech.powerjob.common.response.JobInfoDTO;
import tech.powerjob.common.response.ResultDTO;
/**
* Test cases for {@link PowerJobClient}
*
* @author tjq
* @author Echo009
* @since 2020/4/15
*/
@Slf4j
class TestClient extends ClientInitializer {
public static final long JOB_ID = 1L;
@Test
void testSaveJob() {
SaveJobInfoRequest newJobInfo = new SaveJobInfoRequest();
newJobInfo.setId(JOB_ID);
newJobInfo.setJobName("omsOpenAPIJobccccc" + System.currentTimeMillis());
newJobInfo.setJobDescription("test OpenAPI" + System.currentTimeMillis());
newJobInfo.setJobParams("{'aa':'bb'}");
newJobInfo.setTimeExpressionType(TimeExpressionType.CRON);
newJobInfo.setTimeExpression("0 0 * * * ? ");
newJobInfo.setExecuteType(ExecuteType.STANDALONE);
newJobInfo.setProcessorType(ProcessorType.BUILT_IN);
newJobInfo.setProcessorInfo("tech.powerjob.samples.processors.StandaloneProcessorDemo");
newJobInfo.setDesignatedWorkers("");
newJobInfo.setMinCpuCores(1.1);
newJobInfo.setMinMemorySpace(1.2);
newJobInfo.setMinDiskSpace(1.3);
log.info("[TestClient] [testSaveJob] SaveJobInfoRequest: {}", JSONObject.toJSONString(newJobInfo));
ResultDTO<Long> resultDTO = powerJobClient.saveJob(newJobInfo);
log.info("[TestClient] [testSaveJob] result: {}", JSONObject.toJSONString(resultDTO));
Assertions.assertNotNull(resultDTO);
}
@Test
void testCopyJob() {
ResultDTO<Long> copyJobRes = powerJobClient.copyJob(JOB_ID);
System.out.println(JSONObject.toJSONString(copyJobRes));
Assertions.assertNotNull(copyJobRes);
}
@Test
void testExportJob() {
ResultDTO<SaveJobInfoRequest> exportJobRes = powerJobClient.exportJob(JOB_ID);
System.out.println(JSONObject.toJSONString(exportJobRes));
}
@Test
void testFetchJob() {
ResultDTO<JobInfoDTO> fetchJob = powerJobClient.fetchJob(JOB_ID);
System.out.println(JSONObject.toJSONString(fetchJob));
Assertions.assertNotNull(fetchJob);
}
@Test
void testDisableJob() {
ResultDTO<Void> res = powerJobClient.disableJob(JOB_ID);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testEnableJob() {
ResultDTO<Void> res = powerJobClient.enableJob(JOB_ID);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testDeleteJob() {
ResultDTO<Void> res = powerJobClient.deleteJob(JOB_ID);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testRun() {
ResultDTO<Long> res = powerJobClient.runJob(JOB_ID, null, 0);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testRunJobDelay() {
ResultDTO<Long> res = powerJobClient.runJob(JOB_ID, "this is instanceParams", 60000);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testFetchInstanceInfo() {
ResultDTO<InstanceInfoDTO> res = powerJobClient.fetchInstanceInfo(702482902331424832L);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testQueryInstanceInfo() {
InstancePageQuery instancePageQuery = new InstancePageQuery();
instancePageQuery.setJobIdEq(11L);
instancePageQuery.setSortBy("actualTriggerTime");
instancePageQuery.setAsc(true);
instancePageQuery.setPageSize(3);
instancePageQuery.setStatusIn(Lists.newArrayList(1,2,5));
TestUtils.output(powerJobClient.queryInstanceInfo(instancePageQuery));
}
@Test
void testStopInstance() {
ResultDTO<Void> res = powerJobClient.stopInstance(702482902331424832L);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testFetchInstanceStatus() {
ResultDTO<Integer> res = powerJobClient.fetchInstanceStatus(702482902331424832L);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testCancelInstanceInTimeWheel() {
ResultDTO<Long> startRes = powerJobClient.runJob(JOB_ID, "start by OhMyClient", 20000);
System.out.println("runJob result: " + JSONObject.toJSONString(startRes));
ResultDTO<Void> cancelRes = powerJobClient.cancelInstance(startRes.getData());
System.out.println("cancelJob result: " + JSONObject.toJSONString(cancelRes));
Assertions.assertTrue(cancelRes.isSuccess());
}
// @Test
// @SneakyThrows
// void testCancelInstanceInDatabase() {
// ResultDTO<Long> startRes = powerJobClient.runJob(15L, "start by OhMyClient", 2000000);
// System.out.println("runJob result: " + JSONObject.toJSONString(startRes));
//
// // Restart server manually and clear all the data in time wheeler.
// TimeUnit.MINUTES.sleep(1);
//
// ResultDTO<Void> cancelRes = powerJobClient.cancelInstance(startRes.getData());
// System.out.println("cancelJob result: " + JSONObject.toJSONString(cancelRes));
// Assertions.assertTrue(cancelRes.isSuccess());
// }
@Test
void testRetryInstance() {
ResultDTO<Void> res = powerJobClient.retryInstance(169557545206153344L);
System.out.println(res);
Assertions.assertNotNull(res);
}
}

View File

@ -0,0 +1,35 @@
package tech.powerjob.client.test;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import tech.powerjob.common.response.JobInfoDTO;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.common.utils.CommonUtils;
/**
* 测试容灾能力
*
* @author tjq
* @since 2024/8/11
*/
@Slf4j
public class TestClusterHA extends ClientInitializer {
@Test
void testHa() {
// 人工让 server 启停
for (int i = 0; i < 1000000; i++) {
CommonUtils.easySleep(100);
ResultDTO<JobInfoDTO> jobInfoDTOResultDTO = powerJobClient.fetchJob(1L);
log.info("[TestClusterHA] response: {}", JSONObject.toJSONString(jobInfoDTOResultDTO));
if (!jobInfoDTOResultDTO.isSuccess()) {
throw new RuntimeException("request failed!");
}
}
}
}

View File

@ -0,0 +1,45 @@
package tech.powerjob.client.test;
import tech.powerjob.common.enums.ExecuteType;
import tech.powerjob.common.enums.ProcessorType;
import tech.powerjob.common.enums.TimeExpressionType;
import tech.powerjob.common.request.http.SaveJobInfoRequest;
import tech.powerjob.common.response.ResultDTO;
import org.junit.jupiter.api.Test;
import java.util.concurrent.ForkJoinPool;
/**
* TestConcurrencyControl
*
* @author tjq
* @since 1/16/21
*/
class TestConcurrencyControl extends ClientInitializer {
@Test
void testRunJobConcurrencyControl() {
SaveJobInfoRequest saveJobInfoRequest = new SaveJobInfoRequest();
saveJobInfoRequest.setJobName("test concurrency control job");
saveJobInfoRequest.setProcessorType(ProcessorType.SHELL);
saveJobInfoRequest.setProcessorInfo("pwd");
saveJobInfoRequest.setExecuteType(ExecuteType.STANDALONE);
saveJobInfoRequest.setTimeExpressionType(TimeExpressionType.API);
saveJobInfoRequest.setMaxInstanceNum(1);
Long jobId = powerJobClient.saveJob(saveJobInfoRequest).getData();
System.out.println("jobId: " + jobId);
ForkJoinPool pool = new ForkJoinPool(32);
for (int i = 0; i < 100; i++) {
String params = "index-" + i;
pool.execute(() -> {
ResultDTO<Long> res = powerJobClient.runJob(jobId, params, 0);
System.out.println(params + ": " + res);
});
}
}
}

View File

@ -0,0 +1,44 @@
package tech.powerjob.client.test;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import tech.powerjob.common.request.http.RunJobRequest;
import tech.powerjob.common.request.query.InstancePageQuery;
import tech.powerjob.common.response.InstanceInfoDTO;
import tech.powerjob.common.response.PageResult;
import tech.powerjob.common.response.PowerResultDTO;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.common.serialize.JsonUtils;
/**
* 测试任务实例
*
* @author tjq
* @since 2025/8/17
*/
@Slf4j
public class TestInstance extends ClientInitializer {
private static final Long jobId = 1L;
@Test
void testOuterKeyAndExtendValue() {
String outerKey = "ok_" + System.currentTimeMillis();
RunJobRequest runJobRequest = new RunJobRequest()
.setJobId(jobId)
.setOuterKey(outerKey).setExtendValue("TEST_EXT_VALUE")
.setInstanceParams("OpenAPI-Params")
.setDelay(1000L);
PowerResultDTO<Long> runJobResult = powerJobClient.runJob(runJobRequest);
log.info("[TestInstance] runJobResult: {}", runJobResult);
Long instanceId = runJobResult.getData();
InstancePageQuery instancePageQuery = new InstancePageQuery();
instancePageQuery.setOuterKeyEq(outerKey);
ResultDTO<PageResult<InstanceInfoDTO>> pageResultResultDTO = powerJobClient.queryInstanceInfo(instancePageQuery);
log.info("[TestInstance] queryInstanceInfo: {}", JsonUtils.toJSONString(pageResultResultDTO));
assert pageResultResultDTO.getData().getData().get(0).getInstanceId().equals(instanceId);
}
}

View File

@ -0,0 +1,48 @@
package tech.powerjob.client.test;
import com.alibaba.fastjson.JSON;
import tech.powerjob.common.request.query.JobInfoQuery;
import tech.powerjob.common.enums.ExecuteType;
import tech.powerjob.common.enums.ProcessorType;
import tech.powerjob.common.response.JobInfoDTO;
import tech.powerjob.common.response.ResultDTO;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.junit.jupiter.api.Test;
import java.util.Date;
import java.util.List;
/**
* Test the query method
*
* @author tjq
* @since 1/16/21
*/
@Slf4j
class TestQuery extends ClientInitializer {
@Test
void testFetchAllJob() {
ResultDTO<List<JobInfoDTO>> allJobRes = powerJobClient.fetchAllJob();
System.out.println(JSON.toJSONString(allJobRes));
}
@Test
void testQueryJob() {
JobInfoQuery jobInfoQuery = new JobInfoQuery()
.setIdGt(-1L)
.setIdLt(10086L)
.setJobNameLike("DAG")
.setGmtModifiedGt(DateUtils.addYears(new Date(), -10))
.setGmtCreateLt(DateUtils.addDays(new Date(), 10))
.setExecuteTypeIn(Lists.newArrayList(ExecuteType.STANDALONE.getV(), ExecuteType.BROADCAST.getV(), ExecuteType.MAP_REDUCE.getV()))
.setProcessorTypeIn(Lists.newArrayList(ProcessorType.BUILT_IN.getV(), ProcessorType.SHELL.getV(), ProcessorType.EXTERNAL.getV()))
.setProcessorInfoLike("tech.powerjob");
ResultDTO<List<JobInfoDTO>> jobQueryResult = powerJobClient.queryJob(jobInfoQuery);
System.out.println(JSON.toJSONString(jobQueryResult));
System.out.println(jobQueryResult.getData().size());
}
}

View File

@ -0,0 +1,17 @@
package tech.powerjob.client.test;
import com.alibaba.fastjson.JSONObject;
/**
* TestUtils
*
* @author tjq
* @since 2024/11/21
*/
public class TestUtils {
public static void output(Object v) {
String str = JSONObject.toJSONString(v);
System.out.println(str);
}
}

View File

@ -0,0 +1,191 @@
package tech.powerjob.client.test;
import com.alibaba.fastjson.JSONObject;
import tech.powerjob.client.PowerJobClient;
import tech.powerjob.common.enums.ExecuteType;
import tech.powerjob.common.enums.ProcessorType;
import tech.powerjob.common.enums.TimeExpressionType;
import tech.powerjob.common.enums.WorkflowNodeType;
import tech.powerjob.common.model.PEWorkflowDAG;
import tech.powerjob.common.request.http.SaveJobInfoRequest;
import tech.powerjob.common.request.http.SaveWorkflowNodeRequest;
import tech.powerjob.common.request.http.SaveWorkflowRequest;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.common.response.WorkflowInfoDTO;
import tech.powerjob.common.response.WorkflowInstanceInfoDTO;
import tech.powerjob.common.response.WorkflowNodeInfoDTO;
import com.google.common.collect.Lists;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List;
/**
* Test cases for {@link PowerJobClient} workflow.
*
* @author tjq
* @author Echo009
* @since 2020/6/2
*/
class TestWorkflow extends ClientInitializer {
private static final long WF_ID = 2;
@Test
void initTestData() {
SaveJobInfoRequest base = new SaveJobInfoRequest();
base.setJobName("DAG-Node-");
base.setTimeExpressionType(TimeExpressionType.WORKFLOW);
base.setExecuteType(ExecuteType.STANDALONE);
base.setProcessorType(ProcessorType.BUILT_IN);
base.setProcessorInfo("tech.powerjob.samples.workflow.WorkflowStandaloneProcessor");
for (int i = 0; i < 5; i++) {
SaveJobInfoRequest request = JSONObject.parseObject(JSONObject.toJSONBytes(base), SaveJobInfoRequest.class);
request.setJobName(request.getJobName() + i);
ResultDTO<Long> res = powerJobClient.saveJob(request);
System.out.println(res);
Assertions.assertNotNull(res);
}
}
@Test
void testSaveWorkflow() {
SaveWorkflowRequest req = new SaveWorkflowRequest();
req.setWfName("workflow-by-client");
req.setWfDescription("created by client");
req.setEnable(true);
req.setTimeExpressionType(TimeExpressionType.API);
System.out.println("req ->" + JSONObject.toJSON(req));
ResultDTO<Long> res = powerJobClient.saveWorkflow(req);
System.out.println(res);
Assertions.assertNotNull(res);
req.setId(res.getData());
// 创建节点
SaveWorkflowNodeRequest saveWorkflowNodeRequest1 = new SaveWorkflowNodeRequest();
saveWorkflowNodeRequest1.setJobId(1L);
saveWorkflowNodeRequest1.setNodeName("DAG-Node-1");
saveWorkflowNodeRequest1.setType(WorkflowNodeType.JOB.getCode());
SaveWorkflowNodeRequest saveWorkflowNodeRequest2 = new SaveWorkflowNodeRequest();
saveWorkflowNodeRequest2.setJobId(1L);
saveWorkflowNodeRequest2.setNodeName("DAG-Node-2");
saveWorkflowNodeRequest2.setType(WorkflowNodeType.JOB.getCode());
SaveWorkflowNodeRequest saveWorkflowNodeRequest3 = new SaveWorkflowNodeRequest();
saveWorkflowNodeRequest3.setJobId(1L);
saveWorkflowNodeRequest3.setNodeName("DAG-Node-3");
saveWorkflowNodeRequest3.setType(WorkflowNodeType.JOB.getCode());
List<WorkflowNodeInfoDTO> nodeList = powerJobClient.saveWorkflowNode(Lists.newArrayList(saveWorkflowNodeRequest1,saveWorkflowNodeRequest2,saveWorkflowNodeRequest3)).getData();
System.out.println(nodeList);
Assertions.assertNotNull(nodeList);
// DAG 图
List<PEWorkflowDAG.Node> nodes = Lists.newLinkedList();
List<PEWorkflowDAG.Edge> edges = Lists.newLinkedList();
nodes.add(new PEWorkflowDAG.Node(nodeList.get(0).getId()));
nodes.add(new PEWorkflowDAG.Node(nodeList.get(1).getId()));
nodes.add(new PEWorkflowDAG.Node(nodeList.get(2).getId()));
edges.add(new PEWorkflowDAG.Edge(nodeList.get(0).getId(), nodeList.get(1).getId()));
edges.add(new PEWorkflowDAG.Edge(nodeList.get(1).getId(), nodeList.get(2).getId()));
PEWorkflowDAG peWorkflowDAG = new PEWorkflowDAG(nodes, edges);
// 保存完整信息
req.setDag(peWorkflowDAG);
res = powerJobClient.saveWorkflow(req);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testCopyWorkflow() {
ResultDTO<Long> res = powerJobClient.copyWorkflow(WF_ID);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testDisableWorkflow() {
ResultDTO<Void> res = powerJobClient.disableWorkflow(WF_ID);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testDeleteWorkflow() {
ResultDTO<Void> res = powerJobClient.deleteWorkflow(WF_ID);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testEnableWorkflow() {
ResultDTO<Void> res = powerJobClient.enableWorkflow(WF_ID);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testFetchWorkflowInfo() {
ResultDTO<WorkflowInfoDTO> res = powerJobClient.fetchWorkflow(WF_ID);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testRunWorkflow() {
ResultDTO<Long> res = powerJobClient.runWorkflow(WF_ID, null, 0);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testStopWorkflowInstance() {
ResultDTO<Void> res = powerJobClient.stopWorkflowInstance(149962433421639744L);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testRetryWorkflowInstance() {
ResultDTO<Void> res = powerJobClient.retryWorkflowInstance(149962433421639744L);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testMarkWorkflowNodeAsSuccess() {
ResultDTO<Void> res = powerJobClient.markWorkflowNodeAsSuccess(149962433421639744L, 1L);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testFetchWfInstanceInfo() {
ResultDTO<WorkflowInstanceInfoDTO> res = powerJobClient.fetchWorkflowInstanceInfo(149962433421639744L);
System.out.println(res);
Assertions.assertNotNull(res);
}
@Test
void testRunWorkflowPlus() {
ResultDTO<Long> res = powerJobClient.runWorkflow(WF_ID, "this is init Params 2", 90000);
System.out.println(res);
Assertions.assertNotNull(res);
}
}