DataWorks提供了豐富的OpenAPI,您可以根據需要使用DataWorks的OpenAPI等開放能力實現各種業務場景。本文以數據開發為例,為您示例如何使用OpenAPI快速進行數據開發、提交與運行。
背景信息
本實踐將滿足以下業務場景的需求,建議您先學習了解不同業務場景涉及的核心能力與概念。
查詢與管理工作空間列表、業務流程列表、節點文件夾與節點列表;提交發布節點任務。主要需要應用數據開發的OpenAPI,如CreateBusiness、ListBusiness等。
進行冒煙測試并查看過程日志。主要需要應用運維中心的部分OpenAPI,如RunSmokeTest等。
完成本實踐后,您可實現如下的一個可進行數據開發、提交、運行的本地環境。
后端代碼開發
步驟1:開發ProjectService類,獲取工作空間
您需要開發一個ProjectService類,類中定義了ListProjects函數來調用ListProjects來獲取工作空間列表,這個函數會返回工作空間列表供前端調用以獲取界面中需要的工作空間下拉列表內容。
package com.aliyun.dataworks.services;
import com.aliyuncs.dataworks_public.model.v20200518.ListProjectsRequest;
import com.aliyuncs.dataworks_public.model.v20200518.ListProjectsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProjectService {
@Autowired
private DataWorksOpenApiClient dataWorksOpenApiClient;
/**
* @param pageNumber
* @param pageSize
* @return
*/
public ListProjectsResponse.PageResult listProjects(Integer pageNumber, Integer pageSize) {
try {
ListProjectsRequest listProjectsRequest = new ListProjectsRequest();
listProjectsRequest.setPageNumber(pageNumber);
listProjectsRequest.setPageSize(pageSize);
ListProjectsResponse listProjectsResponse = dataWorksOpenApiClient.createClient().getAcsResponse(listProjectsRequest);
System.out.println(listProjectsResponse.getRequestId());
return listProjectsResponse.getPageResult();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return null;
}
}
步驟2:開發BusinessService類,處理業務流程
您需要開發一個BusinessService類,類中定義如下函數。
定義了CreateBusiness函數,調用CreateBusiness來創建業務流程。
定義了ListBusiness函數,調用ListBusiness來獲取業務流程列表。
這兩個函數會在前端界面中被使用,分別用來創建示例業務流程與拉取列表。
您也可以定義一個FolderService的函數,用于展示目錄樹(目錄樹由業務流程、節點文件夾與節點組成),下面的例省略了節點文件夾的相關步驟,只示例了核心流程,文件夾的操作相關的FolderService函數,您可在Github中的全量示例代碼中獲取,獲取地址為Github代碼示例。
package com.aliyun.dataworks.services;
import com.aliyun.dataworks.dto.CreateBusinessDTO;
import com.aliyun.dataworks.dto.DeleteBusinessDTO;
import com.aliyun.dataworks.dto.ListBusinessesDTO;
import com.aliyun.dataworks.dto.UpdateBusinessDTO;
import com.aliyuncs.dataworks_public.model.v20200518.*;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* @author dataworks demo
*/
@Service
public class BusinessService {
@Autowired
private DataWorksOpenApiClient dataWorksOpenApiClient;
/**
* create a business
*
* @param createBusinessDTO
*/
public Long createBusiness(CreateBusinessDTO createBusinessDTO) {
try {
CreateBusinessRequest createBusinessRequest = new CreateBusinessRequest();
//業務流程的名稱
createBusinessRequest.setBusinessName(createBusinessDTO.getBusinessName());
createBusinessRequest.setDescription(createBusinessDTO.getDescription());
createBusinessRequest.setOwner(createBusinessDTO.getOwner());
createBusinessRequest.setProjectId(createBusinessDTO.getProjectId());
//業務流程所屬的功能模塊 NORMAL(數據開發) MANUAL_BIZ(手動業務流程)
createBusinessRequest.setUseType(createBusinessDTO.getUseType());
CreateBusinessResponse createBusinessResponse = dataWorksOpenApiClient.createClient().getAcsResponse(createBusinessRequest);
System.out.println("create business requestId:" + createBusinessResponse.getRequestId());
System.out.println("create business successful,the businessId:" + createBusinessResponse.getBusinessId());
return createBusinessResponse.getBusinessId();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return null;
}
/**
* @param listBusinessesDTO
* @return
*/
public List<ListBusinessResponse.Data.BusinessItem> listBusiness(ListBusinessesDTO listBusinessesDTO) {
try {
ListBusinessRequest listBusinessRequest = new ListBusinessRequest();
listBusinessRequest.setKeyword(listBusinessesDTO.getKeyword());
listBusinessRequest.setPageNumber(listBusinessesDTO.getPageNumber() < 1 ? 1 : listBusinessesDTO.getPageNumber());
listBusinessRequest.setPageSize(listBusinessesDTO.getPageSize() < 10 ? 10 : listBusinessesDTO.getPageSize());
listBusinessRequest.setProjectId(listBusinessesDTO.getProjectId());
ListBusinessResponse listBusinessResponse = dataWorksOpenApiClient.createClient().getAcsResponse(listBusinessRequest);
System.out.println("list business requestId:" + listBusinessResponse.getRequestId());
ListBusinessResponse.Data data = listBusinessResponse.getData();
System.out.println("total count:" + data.getTotalCount());
if (!CollectionUtils.isEmpty(data.getBusiness())) {
for (ListBusinessResponse.Data.BusinessItem businessItem : data.getBusiness()) {
System.out.println(businessItem.getBusinessId() + "," + businessItem.getBusinessName() + "," + businessItem.getUseType());
}
}
return data.getBusiness();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return null;
}
/**
* update a business
* @param updateBusinessDTO
* @return
*/
public Boolean updateBusiness(UpdateBusinessDTO updateBusinessDTO) {
try {
UpdateBusinessRequest updateBusinessRequest = new UpdateBusinessRequest();
updateBusinessRequest.setBusinessId(updateBusinessDTO.getBusinessId());
updateBusinessRequest.setBusinessName(updateBusinessDTO.getBusinessName());
updateBusinessRequest.setDescription(updateBusinessDTO.getDescription());
updateBusinessRequest.setOwner(updateBusinessDTO.getOwner());
updateBusinessRequest.setProjectId(updateBusinessDTO.getProjectId());
UpdateBusinessResponse updateBusinessResponse = dataWorksOpenApiClient.createClient().getAcsResponse(updateBusinessRequest);
System.out.println(updateBusinessResponse.getRequestId());
System.out.println(updateBusinessResponse.getSuccess());
return updateBusinessResponse.getSuccess();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return false;
}
/**
* delete a business
* @param deleteBusinessDTO
*/
public boolean deleteBusiness(DeleteBusinessDTO deleteBusinessDTO) {
try {
DeleteBusinessRequest deleteBusinessRequest = new DeleteBusinessRequest();
deleteBusinessRequest.setBusinessId(deleteBusinessDTO.getBusinessId());
deleteBusinessRequest.setProjectId(deleteBusinessDTO.getProjectId());
DeleteBusinessResponse deleteBusinessResponse = dataWorksOpenApiClient.createClient().getAcsResponse(deleteBusinessRequest);
System.out.println("delete business:" + deleteBusinessResponse.getRequestId());
System.out.println("delete business" + deleteBusinessResponse.getSuccess());
return deleteBusinessResponse.getSuccess();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return false;
}
}
步驟3:開發FileService類,處理文件事務
您需要開發一個FileService類,這個類中包含了所有與文件事務相關的操作函數:
定義一個listFile函數,調用ListFiles來獲取文件列表。
定義一個createFile函數,調用CreateFile來創建文件列表。
定義一個updateFile函數,調用UpdateFile來更新文件列表。
定義一個deployFile函數,調用DeployFile來發布文件列表。
定義一個runSmokeTest函數,調用RunSmokeTest來執行冒煙測試。
定義一個getInstanceLog函數,調用GetInstanceLog來獲取冒煙測試過程日志內容。
這些方法將在界面中被調用,分別用來創建文件、拉取文件列表、保存文件、提交與執行使用。
package com.aliyun.dataworks.services;
import com.aliyun.dataworks.dto.*;
import com.aliyuncs.dataworks_public.model.v20200518.*;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* the ide files manager service
*
* @author dataworks demo
*/
@Service
public class FileService {
@Autowired
private DataWorksOpenApiClient dataWorksOpenApiClient;
public static final int CYCLE_NUM = 10;
/**
* 分頁查詢
* @param listFilesDTO
* @return
*/
public List<ListFilesResponse.Data.File> listFiles(ListFilesDTO listFilesDTO) {
try {
ListFilesRequest listFilesRequest = new ListFilesRequest();
// 文件路徑: “業務流程/” + 目標業務流程名 + 目錄名 + 最新文件夾名
// 業務流程/我的第一個業務流程/MaxCompute/ods層,不要加"數據開發"
listFilesRequest.setFileFolderPath(listFilesDTO.getFileFolderPath());
// 文件的代碼類型。支持多個,以逗號分隔,例子:10,23
listFilesRequest.setFileTypes(listFilesDTO.getFileTypes());
// 文件名稱的關鍵字。支持模糊匹配
listFilesRequest.setKeyword(listFilesDTO.getKeyword());
// 調度節點的ID
listFilesRequest.setNodeId(listFilesDTO.getNodeId());
// 文件責任人
listFilesRequest.setOwner(listFilesDTO.getOwner());
// 請求的數據頁數
listFilesRequest.setPageNumber(listFilesDTO.getPageNumber() <= 0 ? 1 : listFilesDTO.getPageNumber());
// 每頁顯示的條數
listFilesRequest.setPageSize(listFilesDTO.getPageSize() <= 10 ? 10 : listFilesDTO.getPageSize());
// DataWorks工作空間的ID
listFilesRequest.setProjectId(listFilesDTO.getProjectId());
// 文件所屬的功能模塊
listFilesRequest.setUseType(listFilesDTO.getUseType());
ListFilesResponse listFilesResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(listFilesRequest);
ListFilesResponse.Data fileData = listFilesResponse.getData();
if (fileData.getFiles() != null && !fileData.getFiles().isEmpty()) {
for (ListFilesResponse.Data.File file : fileData.getFiles()) {
// 業務流程ID
System.out.println(file.getBusinessId());
// 文件ID
System.out.println(file.getFileId());
// 文件名稱
System.out.println(file.getFileName());
// 文件類型 10
System.out.println(file.getFileType());
// 節點ID
System.out.println(file.getNodeId());
// 文件夾ID
System.out.println(file.getFileFolderId());
}
}
return fileData.getFiles();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return null;
}
/**
* 新增文件
* @param createFileDTO
*/
public Long createFile(CreateFileDTO createFileDTO) {
try {
CreateFileRequest createFileRequest = new CreateFileRequest();
// 任務的高級配置
createFileRequest.setAdvancedSettings(createFileDTO.getAdvancedSettings());
// 文件是否開啟自動解析功能 必填
createFileRequest.setAutoParsing(createFileDTO.getAutoParsing());
// 出錯自動重跑時間間隔,單位為毫秒。最大為1800000毫秒(30分鐘)
createFileRequest.setAutoRerunIntervalMillis(createFileDTO.getAutoRerunIntervalMillis());
// 自動重試次數
createFileRequest.setAutoRerunTimes(createFileDTO.getAutoRerunTimes());
// 文件發布成任務后,任務執行時連接的數據源。 必填
createFileRequest.setConnectionName(createFileDTO.getConnectionName());
// 文件代碼內容 必填
createFileRequest.setContent(createFileDTO.getContent());
// 周期調度的cron表達式 必填
createFileRequest.setCronExpress(createFileDTO.getCronExpress());
// 調度周期的類型 必填
createFileRequest.setCycleType(createFileDTO.getCycleType());
// 依賴上一周期的節點列表
createFileRequest.setDependentNodeIdList(createFileDTO.getDependentNodeIdList());
// 依賴上一周期的方式 必填
createFileRequest.setDependentType(createFileDTO.getDependentType());
// 停止自動調度的時間戳,單位為毫秒。
createFileRequest.setEndEffectDate(createFileDTO.getEndEffectDate());
// 文件描述
createFileRequest.setFileDescription(createFileDTO.getFileDescription());
// 文件的路徑 必填
createFileRequest.setFileFolderPath(createFileDTO.getFileFolderPath());
// 文件的名稱 必填
createFileRequest.setFileName(createFileDTO.getFileName());
// 文件的代碼類型 必填
createFileRequest.setFileType(createFileDTO.getFileType());
// 文件依賴的上游文件的輸出名稱,多個輸出使用英文逗號(,)分隔。必填
createFileRequest.setInputList(createFileDTO.getInputList());
// 文件責任人的阿里云用戶ID。如果該參數為空,則默認使用調用者的阿里云用戶ID。 必填
createFileRequest.setOwner(createFileDTO.getOwner());
// 調度參數。
createFileRequest.setParaValue(createFileDTO.getParaValue());
// 項目空間ID 必填
createFileRequest.setProjectId(createFileDTO.getProjectId());
// 重跑屬性
createFileRequest.setRerunMode(createFileDTO.getRerunMode());
// 任務的資源組 必填
createFileRequest.setResourceGroupIdentifier(createFileDTO.getResourceGroupIdentifier());
// 調度的類型
createFileRequest.setSchedulerType(createFileDTO.getSchedulerType());
// 開始自動調度的毫秒時間戳
createFileRequest.setStartEffectDate(createFileDTO.getStartEffectDate());
// 是否暫停調度
createFileRequest.setStop(createFileDTO.getStop());
CreateFileResponse createFileResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(createFileRequest);
// requestId
System.out.println(createFileResponse.getRequestId());
// fileId
System.out.println(createFileResponse.getData());
return createFileResponse.getData();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return null;
}
/**
* 更改文件
*
* @param updateFileDTO
*/
public boolean updateFile(UpdateFileDTO updateFileDTO) {
try {
UpdateFileRequest updateFileRequest = new UpdateFileRequest();
// 任務的高級配置,具體格式參考文檔
updateFileRequest.setAdvancedSettings(updateFileDTO.getAdvancedSettings());
// 文件是否開啟自動解析功能
updateFileRequest.setAutoParsing(updateFileDTO.getAutoParsing());
// 出錯自動重跑時間間隔,單位為毫秒。最大為1800000毫秒(30分鐘)。
updateFileRequest.setAutoRerunIntervalMillis(updateFileDTO.getAutoRerunIntervalMillis());
// 文件出錯后,自動重跑的次數
updateFileRequest.setAutoRerunTimes(updateFileDTO.getAutoRerunTimes());
// 文件對應任務執行時,任務使用的數據源標識符
updateFileRequest.setConnectionName(updateFileDTO.getConnectionName());
// 文件代碼內容
updateFileRequest.setContent(updateFileDTO.getContent());
// 周期調度的cron表達式,
updateFileRequest.setCronExpress(updateFileDTO.getCronExpress());
// 調度周期的類型,包括NOT_DAY(分鐘、小時)和DAY(日、周、月)
updateFileRequest.setCycleType(updateFileDTO.getCycleType());
// 當DependentType參數配置為USER_DEFINE時,用于設置當前文件具體依賴的節點ID。依賴多個節點時,使用英文逗號(,)分隔
updateFileRequest.setDependentNodeIdList(updateFileDTO.getDependentNodeIdList());
// 依賴上一周期的方式
updateFileRequest.setDependentType(updateFileDTO.getDependentType());
// 停止自動調度的時間戳,單位為毫秒。
updateFileRequest.setEndEffectDate(updateFileDTO.getEndEffectDate());
// 文件的描述
updateFileRequest.setFileDescription(updateFileDTO.getFileDescription());
// 文件所在的路徑
updateFileRequest.setFileFolderPath(updateFileDTO.getFileFolderPath());
// 文件的ID
updateFileRequest.setFileId(updateFileDTO.getFileId());
// 文件的名稱
updateFileRequest.setFileName(updateFileDTO.getFileName());
// 文件依賴的上游文件的輸出名稱。依賴多個輸出時,使用英文逗號(,)分隔
updateFileRequest.setInputList(updateFileDTO.getInputList());
// 文件的輸出
updateFileRequest.setOutputList(updateFileDTO.getOutputList());
// 文件所有者的用戶ID
updateFileRequest.setOwner(updateFileDTO.getOwner());
// 調度參數
updateFileRequest.setParaValue(updateFileDTO.getParaValue());
// DataWorks工作空間的ID
updateFileRequest.setProjectId(updateFileDTO.getProjectId());
// 重跑屬性 ALL_ALLOWED
updateFileRequest.setRerunMode(updateFileDTO.getRerunMode());
// 文件發布成任務后,任務執行時對應的資源組
updateFileRequest.setResourceGroupIdentifier(updateFileDTO.getResourceGroupIdentifier());
// 調度的類型 NORMAL
updateFileRequest.setSchedulerType(updateFileDTO.getSchedulerType());
// 開始自動調度的毫秒時間戳
updateFileRequest.setStartEffectDate(updateFileDTO.getStartEffectDate());
// 發布后是否立即啟動
updateFileRequest.setStartImmediately(updateFileDTO.getStartImmediately());
// 是否暫停調度
updateFileRequest.setStop(updateFileDTO.getStop());
UpdateFileResponse updateFileResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(updateFileRequest);
// requestId
System.out.println(updateFileResponse.getRequestId());
// 成功與否
System.out.println(updateFileResponse.getSuccess());
return updateFileResponse.getSuccess();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return false;
}
/**
* 刪除一個文件
* @param deleteFileDTO
* @return
* @throws InterruptedException
*/
public boolean deleteFile(DeleteFileDTO deleteFileDTO) throws InterruptedException {
try {
DeleteFileRequest deleteFileRequest = new DeleteFileRequest();
deleteFileRequest.setFileId(deleteFileDTO.getFileId());
deleteFileRequest.setProjectId(deleteFileDTO.getProjectId());
DeleteFileResponse deleteFileResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(deleteFileRequest);
System.out.println(deleteFileResponse.getRequestId());
System.out.println(deleteFileResponse.getDeploymentId());
GetDeploymentRequest getDeploymentRequest = new GetDeploymentRequest();
getDeploymentRequest.setProjectId(deleteFileDTO.getProjectId());
getDeploymentRequest.setDeploymentId(deleteFileResponse.getDeploymentId());
for (int i = 0; i < CYCLE_NUM; i++) {
GetDeploymentResponse getDeploymentResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(getDeploymentRequest);
// 發布包當前的狀態,包括0(就緒)、1(成功)和2(失敗)。
Integer deleteStatus = getDeploymentResponse.getData().getDeployment().getStatus();
// 此處可以循環判斷刪除狀態
if (deleteStatus == 1) {
System.out.println("成功刪除文件。");
break;
} else {
System.out.println("正在刪除文件");
Thread.sleep(1000L);
}
}
GetProjectRequest getProjectRequest = new GetProjectRequest();
getProjectRequest.setProjectId(deleteFileDTO.getProjectId());
GetProjectResponse getProjectResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(getProjectRequest);
// 標準模式有DEV和PROD,簡單模式只有PROD
Boolean standardMode = getProjectResponse.getData().getEnvTypes().size() == 2;
if (standardMode) {
// 標準模式需要把刪除發布到線上
DeployFileRequest deployFileRequest = new DeployFileRequest();
deployFileRequest.setProjectId(deleteFileDTO.getProjectId());
deployFileRequest.setFileId(deleteFileDTO.getFileId());
DeployFileResponse deployFileResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(deployFileRequest);
getDeploymentRequest.setDeploymentId(deployFileResponse.getData());
for (int i = 0; i < CYCLE_NUM; i++) {
GetDeploymentResponse getDeploymentResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(getDeploymentRequest);
// 發布包當前的狀態,包括0(就緒)、1(成功)和2(失敗)。
Integer deleteStatus = getDeploymentResponse.getData().getDeployment().getStatus();
// 此處可以循環判斷刪除狀態
if (deleteStatus == 1) {
System.out.println("成功刪除文件。");
break;
} else {
System.out.println("正在刪除文件");
Thread.sleep(1000L);
}
}
}
return true;
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return false;
}
/**
* 查詢文件
* @param getFileDTO
*/
public GetFileResponse.Data.File getFile(GetFileDTO getFileDTO) {
try {
GetFileRequest getFileRequest = new GetFileRequest();
getFileRequest.setFileId(getFileDTO.getFileId());
getFileRequest.setProjectId(getFileDTO.getProjectId());
getFileRequest.setNodeId(getFileDTO.getNodeId());
GetFileResponse getFileResponse = dataWorksOpenApiClient.createClient().getAcsResponse(getFileRequest);
System.out.println(getFileResponse.getRequestId());
GetFileResponse.Data.File file = getFileResponse.getData().getFile();
System.out.println(file.getFileName());
System.out.println(file.getFileType());
System.out.println(file.getNodeId());
System.out.println(file.getCreateUser());
return file;
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return null;
}
/**
* @param deployFileDTO
* @return
* @throws InterruptedException
*/
public Boolean deployFile(DeployFileDTO deployFileDTO) throws InterruptedException {
try {
GetProjectRequest getProjectRequest = new GetProjectRequest();
getProjectRequest.setProjectId(deployFileDTO.getProjectId());
GetProjectResponse getProjectResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(getProjectRequest);
// 標準模式有DEV和PROD,簡單模式只有PROD
Boolean standardMode = getProjectResponse.getData().getEnvTypes().size() == 2;
if (standardMode) {
SubmitFileRequest submitFileRequest = new SubmitFileRequest();
submitFileRequest.setFileId(deployFileDTO.getFileId());
submitFileRequest.setProjectId(deployFileDTO.getProjectId());
SubmitFileResponse submitFileResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(submitFileRequest);
System.out.println("submit file requestId:" + submitFileResponse.getRequestId());
System.out.println("submit file deploymentId:" + submitFileResponse.getData());
for (int i = 0; i < CYCLE_NUM; i++) {
GetDeploymentRequest getDeploymentRequest = new GetDeploymentRequest();
getDeploymentRequest.setProjectId(deployFileDTO.getProjectId());
getDeploymentRequest.setDeploymentId(submitFileResponse.getData());
GetDeploymentResponse getDeploymentResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(getDeploymentRequest);
// 發布包當前的狀態,包括0(就緒)、1(成功)和2(失敗)。
Integer deleteStatus = getDeploymentResponse.getData().getDeployment().getStatus();
// 此處可以循環判斷刪除狀態
if (deleteStatus == 1) {
System.out.println("成功提交文件。");
break;
} else {
System.out.println("正在提交文件...");
Thread.sleep(1000L);
}
}
}
DeployFileRequest deployFileRequest = new DeployFileRequest();
deployFileRequest.setFileId(deployFileDTO.getFileId());
deployFileRequest.setProjectId(deployFileDTO.getProjectId());
DeployFileResponse deployFileResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(deployFileRequest);
System.out.println("deploy file requestId:" + deployFileResponse.getRequestId());
System.out.println("deploy file deploymentId:" + deployFileResponse.getData());
for (int i = 0; i < CYCLE_NUM; i++) {
GetDeploymentRequest getDeploymentRequest = new GetDeploymentRequest();
getDeploymentRequest.setProjectId(deployFileDTO.getProjectId());
getDeploymentRequest.setDeploymentId(deployFileResponse.getData());
GetDeploymentResponse getDeploymentResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(getDeploymentRequest);
// 發布包當前的狀態,包括0(就緒)、1(成功)和2(失敗)。
Integer deleteStatus = getDeploymentResponse.getData().getDeployment().getStatus();
// 此處可以循環判斷刪除狀態
if (deleteStatus == 1) {
System.out.println("成功發布文件。");
break;
} else {
System.out.println("正在發布文件...");
Thread.sleep(1000L);
}
}
return true;
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return false;
}
public List<ListInstancesResponse.Data.Instance> runSmokeTest(RunSmokeTestDTO runSmokeTestDTO) {
try {
RunSmokeTestRequest runSmokeTestRequest = new RunSmokeTestRequest();
runSmokeTestRequest.setBizdate(runSmokeTestDTO.getBizdate());
runSmokeTestRequest.setNodeId(runSmokeTestDTO.getNodeId());
runSmokeTestRequest.setNodeParams(runSmokeTestDTO.getNodeParams());
runSmokeTestRequest.setName(runSmokeTestDTO.getName());
runSmokeTestRequest.setProjectEnv(runSmokeTestDTO.getProjectEnv());
RunSmokeTestResponse runSmokeTestResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(runSmokeTestRequest);
System.out.println(runSmokeTestResponse.getRequestId());
// DAGID
System.out.println(runSmokeTestResponse.getData());
ListInstancesRequest listInstancesRequest = new ListInstancesRequest();
listInstancesRequest.setDagId(runSmokeTestResponse.getData());
listInstancesRequest.setProjectId(runSmokeTestDTO.getProjectId());
listInstancesRequest.setProjectEnv(runSmokeTestDTO.getProjectEnv());
listInstancesRequest.setNodeId(runSmokeTestDTO.getNodeId());
listInstancesRequest.setPageNumber(1);
listInstancesRequest.setPageSize(10);
ListInstancesResponse listInstancesResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(listInstancesRequest);
System.out.println(listInstancesResponse.getRequestId());
List<ListInstancesResponse.Data.Instance> instances = listInstancesResponse.getData().getInstances();
if (CollectionUtils.isEmpty(instances)) {
return null;
}
return instances;
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return null;
}
public InstanceDetail getInstanceLog(Long instanceId, String projectEnv) {
try {
GetInstanceLogRequest getInstanceLogRequest = new GetInstanceLogRequest();
getInstanceLogRequest.setInstanceId(instanceId);
getInstanceLogRequest.setProjectEnv(projectEnv);
GetInstanceLogResponse getInstanceLogResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(getInstanceLogRequest);
System.out.println(getInstanceLogResponse.getRequestId());
GetInstanceRequest getInstanceRequest = new GetInstanceRequest();
getInstanceRequest.setInstanceId(instanceId);
getInstanceRequest.setProjectEnv(projectEnv);
GetInstanceResponse getInstanceResponse = dataWorksOpenApiClient.createClient()
.getAcsResponse(getInstanceRequest);
System.out.println(getInstanceResponse.getRequestId());
System.out.println(getInstanceResponse.getData());
InstanceDetail instanceDetail = new InstanceDetail();
instanceDetail.setInstance(getInstanceResponse.getData());
instanceDetail.setInstanceLog(getInstanceLogResponse.getData());
return instanceDetail;
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
// 請求ID
System.out.println(e.getRequestId());
// 錯誤碼
System.out.println(e.getErrCode());
// 錯誤信息
System.out.println(e.getErrMsg());
}
return null;
}
}
步驟4:開發一個IdeController
您需要定義一個IdeController,提供前端調用的路由接口。
package com.aliyun.dataworks.demo;
import com.aliyun.dataworks.dto.*;
import com.aliyun.dataworks.services.BusinessService;
import com.aliyun.dataworks.services.FileService;
import com.aliyun.dataworks.services.FolderService;
import com.aliyun.dataworks.services.ProjectService;
import com.aliyuncs.dataworks_public.model.v20200518.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author dataworks demo
*/
@RestController
@RequestMapping("/ide")
public class IdeController {
@Autowired
private FileService fileService;
@Autowired
private FolderService folderService;
@Autowired
private BusinessService businessService;
@Autowired
private ProjectService projectService;
/**
* for list those files
*
* @param listFilesDTO
* @return ListFilesResponse.Data.File
*/
@CrossOrigin(origins = "http://localhost:8080")
@GetMapping("/listFiles")
public List<ListFilesResponse.Data.File> listFiles(ListFilesDTO listFilesDTO) {
return fileService.listFiles(listFilesDTO);
}
/**
* for list those folders
*
* @param listFoldersDTO
* @return ListFoldersResponse.Data.FoldersItem
*/
@CrossOrigin(origins = "http://localhost:8080")
@GetMapping("/listFolders")
public List<ListFoldersResponse.Data.FoldersItem> listFolders(ListFoldersDTO listFoldersDTO) {
return folderService.listFolders(listFoldersDTO);
}
/**
* for create the folder
*
* @param createFolderDTO
* @return boolean
*/
@CrossOrigin(origins = "http://localhost:8080")
@PostMapping("/createFolder")
public boolean createFolder(@RequestBody CreateFolderDTO createFolderDTO) {
return folderService.createFolder(createFolderDTO);
}
/**
* for update the folder
*
* @param updateFolderDTO
* @return boolean
*/
@CrossOrigin(origins = "http://localhost:8080")
@PostMapping("/updateFolder")
public boolean updateFolder(@RequestBody UpdateFolderDTO updateFolderDTO) {
return folderService.updateFolder(updateFolderDTO);
}
/**
* for get the file
*
* @param getFileDTO
* @return GetFileResponse.Data.File
*/
@CrossOrigin(origins = "http://localhost:8080")
@GetMapping("/getFile")
public GetFileResponse.Data.File getFile(GetFileDTO getFileDTO) {
return fileService.getFile(getFileDTO);
}
/**
* for create the file
*
* @param createFileDTO
* @return fileId
*/
@CrossOrigin(origins = "http://localhost:8080")
@PostMapping("/createFile")
public Long createFile(@RequestBody CreateFileDTO createFileDTO) {
return fileService.createFile(createFileDTO);
}
/**
* for update the file
*
* @param updateFileDTO
* @return boolean
*/
@CrossOrigin(origins = "http://localhost:8080")
@PostMapping("/updateFile")
public boolean updateFile(@RequestBody UpdateFileDTO updateFileDTO) {
return fileService.updateFile(updateFileDTO);
}
/**
* for deploy the file
*
* @param deployFileDTO
* @return boolean
*/
@CrossOrigin(origins = "http://localhost:8080")
@PostMapping("/deployFile")
public boolean deployFile(@RequestBody DeployFileDTO deployFileDTO) {
try {
return fileService.deployFile(deployFileDTO);
} catch (Exception e) {
System.out.println(e);
}
return false;
}
/**
* for delete the file
*
* @param deleteFileDTO
* @return
*/
@CrossOrigin(origins = "http://localhost:8080")
@DeleteMapping("/deleteFile")
public boolean deleteFile(DeleteFileDTO deleteFileDTO) {
try {
return fileService.deleteFile(deleteFileDTO);
} catch (Exception e) {
System.out.println(e);
}
return false;
}
/**
* for delete the folder
*
* @param deleteFolderDTO
* @return
*/
@CrossOrigin(origins = "http://localhost:8080")
@DeleteMapping("/deleteFolder")
public boolean deleteFolder(DeleteFolderDTO deleteFolderDTO) {
return folderService.deleteFolder(deleteFolderDTO);
}
/**
* list businesses
*
* @param listBusinessesDTO
* @return
*/
@CrossOrigin(origins = "http://localhost:8080")
@GetMapping("/listBusinesses")
public List<ListBusinessResponse.Data.BusinessItem> listBusiness(ListBusinessesDTO listBusinessesDTO) {
return businessService.listBusiness(listBusinessesDTO);
}
/**
* create a business
*
* @param createBusinessDTO
* @return
*/
@CrossOrigin(origins = "http://localhost:8080")
@PostMapping("/createBusiness")
public Long createBusiness(@RequestBody CreateBusinessDTO createBusinessDTO) {
return businessService.createBusiness(createBusinessDTO);
}
/**
* update a business
*
* @param updateBusinessDTO
* @return
*/
@CrossOrigin(origins = "http://localhost:8080")
@PostMapping("/updateBusiness")
public boolean updateBusiness(@RequestBody UpdateBusinessDTO updateBusinessDTO) {
return businessService.updateBusiness(updateBusinessDTO);
}
/**
* delete a business
*
* @param deleteBusinessDTO
* @return
*/
@CrossOrigin(origins = "http://localhost:8080")
@PostMapping("/deleteBusiness")
public boolean deleteBusiness(@RequestBody DeleteBusinessDTO deleteBusinessDTO) {
return businessService.deleteBusiness(deleteBusinessDTO);
}
/**
* @param pageNumber
* @param pageSize
* @return
*/
@CrossOrigin(origins = "http://localhost:8080")
@GetMapping("/listProjects")
public ListProjectsResponse.PageResult listProjects(Integer pageNumber, Integer pageSize) {
return projectService.listProjects(pageNumber, pageSize);
}
/**
* @param runSmokeTestDTO
* @return
*/
@CrossOrigin(origins = "http://localhost:8080")
@PutMapping("/runSmokeTest")
public List<ListInstancesResponse.Data.Instance> runSmokeTest(@RequestBody RunSmokeTestDTO runSmokeTestDTO) {
return fileService.runSmokeTest(runSmokeTestDTO);
}
/**
* @param instanceId
* @param projectEnv
* @return
*/
@CrossOrigin(origins = "http://localhost:8080")
@GetMapping("/getLog")
public InstanceDetail getLog(@RequestParam Long instanceId, @RequestParam String projectEnv) {
return fileService.getInstanceLog(instanceId, projectEnv);
}
}
前端代碼開發
初始化編輯器、目錄樹與terminal終端。
示例代碼如下。
const App: FunctionComponent<Props> = () => { const editorRef = useRef<HTMLDivElement>(null); const termianlRef = useRef<HTMLDivElement>(null); const [terminal, setTerminal] = useState<NextTerminal>(); const [editor, setEditor] = useState<monaco.editor.IStandaloneCodeEditor>(); const [expnadedKeys, setExpandedKeys] = useState<any[]>(); const [workspace, setWorkspace] = useState<number>(); const [workspaces, setWorkspaces] = useState<{ label: string, value: number }[]>([]); const [dataSource, setDataSource] = useState<any[]>(); const [selectedFile, setSelectedFile] = useState<number>(); const [loading, setLoading] = useState<boolean>(false); // 創建編輯器實例 useEffect(() => { if (editorRef.current) { const nextEditor = monaco.editor.create(editorRef.current, editorOptions); setEditor(nextEditor); return () => { nextEditor.dispose(); }; } }, [editorRef.current]); // 添加保存文件按鍵事件 useEffect(() => { editor?.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { if (!workspace) { showTips('Please select workspace first'); return; } saveFile(workspace, editor, selectedFile); }); }, [editor, workspace, selectedFile]); // 創建terminal實例 useEffect(() => { if (termianlRef.current) { const term: NextTerminal = new Terminal(terminalOptions) as any; term.pointer = -1; term.stack = []; setTerminal(term); const fitAddon = new FitAddon(); term.loadAddon(fitAddon); term.open(termianlRef.current); fitAddon.fit(); term.write('$ '); return () => { term.dispose(); }; } }, [termianlRef.current]); // 注冊terminal輸入事件 useEffect(() => { const event = terminal?.onKey(e => onTerminalKeyChange(e, terminal, dataSource, workspace)); return () => { event?.dispose(); }; }, [terminal, dataSource, workspace]); // 獲取目錄樹數據源 useEffect(() => { workspace && (async () => { setLoading(true); const nextDataSource = await getTreeDataSource(workspace, workspaces); const defaultKey = nextDataSource?.[0]?.key; defaultKey && setExpandedKeys([defaultKey]); setDataSource(nextDataSource); setLoading(false); })(); }, [workspace]); // 當目錄樹文件被點擊時,獲取文件詳情并展示代碼 useEffect(() => { workspace && selectedFile && (async () => { setLoading(true); const file = await getFileInfo(workspace, selectedFile); editor?.setValue(file.content); editor?.getAction('editor.action.formatDocument').run(); setLoading(false); })(); }, [selectedFile]); // 獲取工作空間列表 useEffect(() => { (async () => { const list = await getWorkspaceList(); setWorkspaces(list); })(); }, []); const onExapnd = useCallback((keys: number[]) => { setExpandedKeys(keys); }, []); const onWorkspaceChange = useCallback((value: number) => { setWorkspace(value) }, []); const onTreeNodeSelect = useCallback((key: number[]) => { key[0] && setSelectedFile(key[0]) }, []); return ( <div className={cn(classes.appWrapper)}> <div className={cn(classes.leftArea)}> <div className={cn(classes.workspaceWrapper)}> Workspace: <Select value={workspace} dataSource={workspaces} onChange={onWorkspaceChange} autoWidth={false} showSearch /> </div> <div className={cn(classes.treeWrapper)}> <Tree dataSource={dataSource} isNodeBlock={{ defaultPaddingLeft: 20 }} expandedKeys={expnadedKeys} selectedKeys={[selectedFile]} onExpand={onExapnd} onSelect={onTreeNodeSelect} defaultExpandAll /> </div> </div> <div className={cn(classes.rightArea)}> <div className={cn(classes.monacoEditorWrapper)} ref={editorRef} /> <div className={cn(classes.panelWrapper)} ref={termianlRef} /> </div> <div className={cn(classes.loaderLine)} style={{ display: loading ? 'block' : 'none' }} /> </div> ); };
獲取示例業務流程與文件,并展示目錄樹。
整體流程和示例代碼如下。
/** * 獲取目錄樹數據源 * @param workspace 工作空間id * @param dataSource 工作空間列表 */ async function getTreeDataSource(workspace: number, dataSource: { label: string, value: number }[]) { try { const businesses = await services.ide.getBusinessList(workspace, openPlatformBusinessName); businesses.length === 0 && await services.ide.createBusiness(workspace, openPlatformBusinessName); } catch (e) { showError('You have no permission to access this workspace.'); return; } const fileFolderPath = `業務流程/${openPlatformBusinessName}/MaxCompute`; const files = await services.ide.getFileList(workspace, fileFolderPath); let children: { key: number, label: string }[] = []; if (files.length === 0) { try { const currentWorkspace = dataSource.find(i => i.value === workspace); const file1 = await services.ide.createFile(workspace, currentWorkspace!.label, fileFolderPath, 'simpleSQL.mc.sql', 'SELECT 1'); const file2 = await services.ide.createFile(workspace, currentWorkspace!.label, fileFolderPath, 'createTable.mc.sql', 'CREATE TABLE IF NOT EXISTS _qcc_mysql1_odps_source_20220113100903_done_ (\ncol string\n)\nCOMMENT \'全量數據同步完成標DONE表\'\nPARTITIONED BY\n(\nstatus STRING COMMENT \'標DONE分區\'\n)\nLIFECYCLE 36500;'); children = children.concat([ { key: file1, label: 'simpleSQL.mc.sql' }, { key: file2, label: 'createTable.mc.sql' }, ]); } catch (e) { showError('Create file failed. The datasource odps_source does not exist.'); return; } } else { children = files.map((i) => ({ key: i.fileId, label: i.fileName })); } return [{ key: 1, label: openPlatformBusinessName, children }]; }
當用戶編輯文件并保存時,需要將編輯的內容傳回后端并更新數據。
代碼示例如下。
/** * 保存文件,當Ctrl+S時觸發 * @param workspace 工作空間id * @param editor 編輯器實例 * @param selectedFile 已選擇的文件 */ async function saveFile(workspace: number, editor: monaco.editor.IStandaloneCodeEditor, selectedFile?: number) { if (!selectedFile) { showTips('Please select a file.'); return; } const content = editor.getValue(); const result = await services.ide.updateFile(workspace, selectedFile, { content }); result ? showTips('Saved file') : showError('Failed to save file'); }
當用戶在terminal終端中輸入
dw run ...
時,會將文件提交到調度系統,并執行冒煙測試運行。處理流程和代碼示例如下。
/** * 處理Terminal鍵盤事件 * @param e 事件對象 * @param term terminal實例 * @param dataSource 目錄樹數據源 * @param workspace 工作空間id */ function onTerminalKeyChange(e: { key: string; domEvent: KeyboardEvent; }, term: NextTerminal, dataSource: any, workspace?: number) { const ev = e.domEvent; const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey; term.inputText = typeof term.inputText === 'string' ? term.inputText : ''; switch (ev.key) { case 'ArrowUp': term.pointer = term.pointer < (term.stack.length - 1) ? term.pointer + 1 : term.pointer; term.inputText = term.stack[term.pointer]; term.write(`\x1b[2K\r$ ${term.inputText}`); break; case 'ArrowDown': term.pointer = term.pointer > -1 ? term.pointer - 1 : -1; term.inputText = term.pointer === -1 ? '' : term.stack[term.pointer]; term.write(`\x1b[2K\r$ ${term.inputText}`); break; case 'ArrowLeft': (term as any)._core.buffer.x > 2 && printable && term.write(e.key); break; case 'ArrowRight': (term as any)._core.buffer.x <= (term.inputText.length + 1) && printable && term.write(e.key); break; case 'Enter': commandHandler(term, dataSource, workspace); break; case 'Backspace': if ((term as any)._core.buffer.x > 2) { term.inputText = term.inputText.slice(0, -1); term.write('\b \b'); } break; default: if (printable) { term.inputText += e.key; term.write(e.key); } } } /** * 處理提交任務的方法,當terminal中輸入dw run ...時觸發 * @param term terminal實例 * @param dataSource 目錄樹數據源 * @param workspace 工作空間id */ async function commandHandler(term: NextTerminal, dataSource: any, workspace?: number) { term.write('\r\n$ '); const input = term.inputText; term.inputText = ''; if (['', undefined].includes(input)) { return; } term.stack = [input!, ...term.stack]; term.pointer = -1; if (!workspace) { term.write(highlight.text('[ERROR] You should select workspace first.\r\n$ ', brush)); return; } // 這里簡單的處理了下輸入的命令行解析,如果命令開頭為dw,且執行命令為run就繼續往下處理,否則報錯 const words = input?.split(' '); const tag = words?.[0].toLowerCase(); const command = words?.[1]?.toLowerCase(); const fileName = words?.[2]; if (tag !== 'dw' || !validCommands.includes(command!)) { term.write(highlight.text('[ERROR] Invalid command.\r\n$ ', brush)); return; } // 獲取輸入文件 const source = dataSource?.[0]?.children.find((i: any) => i.label === fileName); const file = await services.ide.getFile(workspace, source.key); if (!file) { term.write(highlight.text('[ERROR] File name does not exist.\r\n$ ', brush)); return; } term.write(highlight.text('[INFO] Submiting file.\r\n$ ', brush)); // 調用部署文件接口,將文件發布到調度系統中 const response = await services.ide.deployFile(workspace, source.key); if (response) { term.write(highlight.text('[INFO] Submit file success.\r\n$ ', brush)); } else { term.write(highlight.text('[ERROR] Submit file failed.\r\n$ ', brush)); return; } // 執行冒煙測試,運行調度任務 let dag: services.ide.Dag; try { term.write(highlight.text('[INFO] Start to run task.\r\n$ ', brush)); dag = (await services.ide.runSmoke(workspace, file.nodeId, openPlatformBusinessName))[0]; term.write(highlight.text('[INFO] Trigger sql task success.\r\n$ ', brush)); } catch (e) { term.write(highlight.text('[ERROR] Trigger sql task failed.\r\n$ ', brush)); return; } // 輪詢獲取任務日志 const event = setInterval(async () => { try { const logInfo = await services.ide.getLog(dag.instanceId, 'DEV'); let log: string; switch (logInfo.instance.status) { case 'WAIT_TIME': log = '等待定時時間到來'; break; case 'WAIT_RESOURCE': log = '等待資源...'; break; default: log = logInfo.instanceLog; } term.write(`${highlight.text(log, brush).replace(/\n/g, '\r\n')}\r\n$ `); const finished = ['SUCCESS', 'FAILURE', 'NOT_RUN'].includes(logInfo.instance.status); finished && clearInterval(event); } catch (e) { term.write(highlight.text('[ERROR] SQL Task run failed.\r\n$ ', brush)); return; } }, 3000); }
本地部署運行
您需要按照Github代碼示例中的指示信息完成環境準備工作,包含依賴的環境:java8 及以上、maven 構建工具、node 環境、pnpm 工具。并執行初始化安裝。
pnpm install
您還需要在根路徑下修改AK、SK相關信息。最后,在工程根目錄下執行以下命令,運行示例實踐代碼。
npm run example:ide
完成后,您可以在瀏覽器中訪問以下頁面驗證結果。
https://localhost:8080
參考:全量代碼示例與源代碼下載
您可在Github中下載全量示例源代碼,獲取地址為Github代碼示例。全部流程的代碼示例匯總如下。
import { useEffect, useRef, useState, useCallback } from 'react';
import type { FunctionComponent } from 'react';
import cn from 'classnames';
import * as monaco from 'monaco-editor';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { Tree, Select, Message } from '@alifd/next';
import * as highlight from '../helpers/highlight';
import * as services from '../services';
import classes from '../styles/app.module.css';
export interface Props {}
export interface NextTerminal extends Terminal {
inputText?: string;
stack: string[];
pointer: number;
}
const brush = {
rules: [
{ regex: /\bERROR\b/gmi, theme: 'red' },
{ regex: /\bWARN\b/gmi, theme: 'yellow' },
{ regex: /\bINFO\b/gmi, theme: 'green' },
{ regex: /^FAILED:.*$/gmi, theme: 'red' },
],
};
// 示例業務流程名稱
const openPlatformBusinessName = '開放平臺示例業務流程';
// 編輯器創建參數
const editorOptions = {
content: '',
language: 'sql',
theme: 'vs-dark',
automaticLayout: true,
fontSize: 16,
};
// Terminal創建參數
const terminalOptions = {
cursorBlink: true,
cursorStyle: 'underline' as const,
fontSize: 16,
};
const validCommands = [
'run',
];
/**
* 展示錯誤信息彈窗的方法
* @param message 錯誤信息
*/
function showError(message: string) {
Message.error({ title: 'Error Message', content: message });
}
/**
* 展示提示信息彈窗的方法
* @param message 提示信息
*/
function showTips(message: string) {
Message.show({ title: 'Tips', content: message });
}
/**
* 處理提交任務的方法,當terminal中輸入dw run ...時觸發
* @param term terminal實例
* @param dataSource 目錄樹數據源
* @param workspace 工作空間id
*/
async function commandHandler(term: NextTerminal, dataSource: any, workspace?: number) {
term.write('\r\n$ ');
const input = term.inputText;
term.inputText = '';
if (['', undefined].includes(input)) {
return;
}
term.stack = [input!, ...term.stack];
term.pointer = -1;
if (!workspace) {
term.write(highlight.text('[ERROR] You should select workspace first.\r\n$ ', brush));
return;
}
// 這里簡單的處理了下輸入的命令行解析,如果命令開頭為dw,且執行命令為run就繼續往下處理,否則報錯
const words = input?.split(' ');
const tag = words?.[0].toLowerCase();
const command = words?.[1]?.toLowerCase();
const fileName = words?.[2];
if (tag !== 'dw' || !validCommands.includes(command!)) {
term.write(highlight.text('[ERROR] Invalid command.\r\n$ ', brush));
return;
}
// 獲取輸入文件
const source = dataSource?.[0]?.children.find((i: any) => i.label === fileName);
const file = await services.ide.getFile(workspace, source.key);
if (!file) {
term.write(highlight.text('[ERROR] File name does not exist.\r\n$ ', brush));
return;
}
term.write(highlight.text('[INFO] Submiting file.\r\n$ ', brush));
// 調用部署文件接口,將文件發布到調度系統中
const response = await services.ide.deployFile(workspace, source.key);
if (response) {
term.write(highlight.text('[INFO] Submit file success.\r\n$ ', brush));
} else {
term.write(highlight.text('[ERROR] Submit file failed.\r\n$ ', brush));
return;
}
// 執行冒煙測試,運行調度任務
let dag: services.ide.Dag;
try {
term.write(highlight.text('[INFO] Start to run task.\r\n$ ', brush));
dag = (await services.ide.runSmoke(workspace, file.nodeId, openPlatformBusinessName))[0];
term.write(highlight.text('[INFO] Trigger sql task success.\r\n$ ', brush));
} catch (e) {
term.write(highlight.text('[ERROR] Trigger sql task failed.\r\n$ ', brush));
return;
}
// 輪詢獲取任務日志
const event = setInterval(async () => {
try {
const logInfo = await services.ide.getLog(dag.instanceId, 'DEV');
let log: string;
switch (logInfo.instance.status) {
case 'WAIT_TIME':
log = '等待定時時間到來';
break;
case 'WAIT_RESOURCE':
log = '等待資源...';
break;
default:
log = logInfo.instanceLog;
}
term.write(`${highlight.text(log, brush).replace(/\n/g, '\r\n')}\r\n$ `);
const finished = ['SUCCESS', 'FAILURE', 'NOT_RUN'].includes(logInfo.instance.status);
finished && clearInterval(event);
} catch (e) {
term.write(highlight.text('[ERROR] SQL Task run failed.\r\n$ ', brush));
return;
}
}, 3000);
}
/**
* 處理Terminal鍵盤事件
* @param e 事件對象
* @param term terminal實例
* @param dataSource 目錄樹數據源
* @param workspace 工作空間id
*/
function onTerminalKeyChange(e: { key: string; domEvent: KeyboardEvent; }, term: NextTerminal, dataSource: any, workspace?: number) {
const ev = e.domEvent;
const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey;
term.inputText = typeof term.inputText === 'string' ? term.inputText : '';
switch (ev.key) {
case 'ArrowUp':
term.pointer = term.pointer < (term.stack.length - 1) ? term.pointer + 1 : term.pointer;
term.inputText = term.stack[term.pointer];
term.write(`\x1b[2K\r$ ${term.inputText}`);
break;
case 'ArrowDown':
term.pointer = term.pointer > -1 ? term.pointer - 1 : -1;
term.inputText = term.pointer === -1 ? '' : term.stack[term.pointer];
term.write(`\x1b[2K\r$ ${term.inputText}`);
break;
case 'ArrowLeft':
(term as any)._core.buffer.x > 2 && printable && term.write(e.key);
break;
case 'ArrowRight':
(term as any)._core.buffer.x <= (term.inputText.length + 1) && printable && term.write(e.key);
break;
case 'Enter':
commandHandler(term, dataSource, workspace);
break;
case 'Backspace':
if ((term as any)._core.buffer.x > 2) {
term.inputText = term.inputText.slice(0, -1);
term.write('\b \b');
}
break;
default:
if (printable) {
term.inputText += e.key;
term.write(e.key);
}
}
}
/**
* 獲取工作空間列表
*/
async function getWorkspaceList() {
const response = await services.tenant.getProjectList();
const list = response.projectList.filter(i => i.projectStatusCode === 'AVAILABLE').map(i => (
{ label: i.projectName, value: i.projectId }
));
return list;
}
/**
* 獲取目錄樹數據源
* @param workspace 工作空間id
* @param dataSource 工作空間列表
*/
async function getTreeDataSource(workspace: number, dataSource: { label: string, value: number }[]) {
try {
const businesses = await services.ide.getBusinessList(workspace, openPlatformBusinessName);
businesses.length === 0 && await services.ide.createBusiness(workspace, openPlatformBusinessName);
} catch (e) {
showError('You have no permission to access this workspace.');
return;
}
const fileFolderPath = `業務流程/${openPlatformBusinessName}/MaxCompute`;
const files = await services.ide.getFileList(workspace, fileFolderPath);
let children: { key: number, label: string }[] = [];
if (files.length === 0) {
try {
const currentWorkspace = dataSource.find(i => i.value === workspace);
const file1 = await services.ide.createFile(workspace, currentWorkspace!.label, fileFolderPath, 'simpleSQL.mc.sql', 'SELECT 1');
const file2 = await services.ide.createFile(workspace, currentWorkspace!.label, fileFolderPath, 'createTable.mc.sql', 'CREATE TABLE IF NOT EXISTS _qcc_mysql1_odps_source_20220113100903_done_ (\ncol string\n)\nCOMMENT \'全量數據同步完成標DONE表\'\nPARTITIONED BY\n(\nstatus STRING COMMENT \'標DONE分區\'\n)\nLIFECYCLE 36500;');
children = children.concat([
{ key: file1, label: 'simpleSQL.mc.sql' },
{ key: file2, label: 'createTable.mc.sql' },
]);
} catch (e) {
showError('Create file failed. The datasource odps_source does not exist.');
return;
}
} else {
children = files.map((i) => ({ key: i.fileId, label: i.fileName }));
}
return [{ key: 1, label: openPlatformBusinessName, children }];
}
/**
* 獲取文件詳細信息
* @param workspace 工作空間id
* @param fileId 文件id
*/
async function getFileInfo(workspace: number, fileId: number) {
const response = await services.ide.getFile(workspace, fileId);
return response;
}
/**
* 保存文件,當Ctrl+S時觸發
* @param workspace 工作空間id
* @param editor 編輯器實例
* @param selectedFile 已選擇的文件
*/
async function saveFile(workspace: number, editor: monaco.editor.IStandaloneCodeEditor, selectedFile?: number) {
if (!selectedFile) {
showTips('Please select a file.');
return;
}
const content = editor.getValue();
const result = await services.ide.updateFile(workspace, selectedFile, { content });
result ? showTips('Saved file') : showError('Failed to save file');
}
const App: FunctionComponent<Props> = () => {
const editorRef = useRef<HTMLDivElement>(null);
const termianlRef = useRef<HTMLDivElement>(null);
const [terminal, setTerminal] = useState<NextTerminal>();
const [editor, setEditor] = useState<monaco.editor.IStandaloneCodeEditor>();
const [expnadedKeys, setExpandedKeys] = useState<any[]>();
const [workspace, setWorkspace] = useState<number>();
const [workspaces, setWorkspaces] = useState<{ label: string, value: number }[]>([]);
const [dataSource, setDataSource] = useState<any[]>();
const [selectedFile, setSelectedFile] = useState<number>();
const [loading, setLoading] = useState<boolean>(false);
// 創建編輯器實例
useEffect(() => {
if (editorRef.current) {
const nextEditor = monaco.editor.create(editorRef.current, editorOptions);
setEditor(nextEditor);
return () => { nextEditor.dispose(); };
}
}, [editorRef.current]);
// 添加保存文件按鍵事件
useEffect(() => {
editor?.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
if (!workspace) {
showTips('Please select workspace first');
return;
}
saveFile(workspace, editor, selectedFile);
});
}, [editor, workspace, selectedFile]);
// 創建terminal實例
useEffect(() => {
if (termianlRef.current) {
const term: NextTerminal = new Terminal(terminalOptions) as any;
term.pointer = -1;
term.stack = [];
setTerminal(term);
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(termianlRef.current);
fitAddon.fit();
term.write('$ ');
return () => { term.dispose(); };
}
}, [termianlRef.current]);
// 注冊terminal輸入事件
useEffect(() => {
const event = terminal?.onKey(e => onTerminalKeyChange(e, terminal, dataSource, workspace));
return () => {
event?.dispose();
};
}, [terminal, dataSource, workspace]);
// 獲取目錄樹數據源
useEffect(() => {
workspace && (async () => {
setLoading(true);
const nextDataSource = await getTreeDataSource(workspace, workspaces);
const defaultKey = nextDataSource?.[0]?.key;
defaultKey && setExpandedKeys([defaultKey]);
setDataSource(nextDataSource);
setLoading(false);
})();
}, [workspace]);
// 當目錄樹文件被點擊時,獲取文件詳情并展示代碼
useEffect(() => {
workspace && selectedFile && (async () => {
setLoading(true);
const file = await getFileInfo(workspace, selectedFile);
editor?.setValue(file.content);
editor?.getAction('editor.action.formatDocument').run();
setLoading(false);
})();
}, [selectedFile]);
// 獲取工作空間列表
useEffect(() => {
(async () => {
const list = await getWorkspaceList();
setWorkspaces(list);
})();
}, []);
const onExapnd = useCallback((keys: number[]) => { setExpandedKeys(keys); }, []);
const onWorkspaceChange = useCallback((value: number) => { setWorkspace(value) }, []);
const onTreeNodeSelect = useCallback((key: number[]) => { key[0] && setSelectedFile(key[0]) }, []);
return (
<div className={cn(classes.appWrapper)}>
<div className={cn(classes.leftArea)}>
<div className={cn(classes.workspaceWrapper)}>
Workspace:
<Select
value={workspace}
dataSource={workspaces}
onChange={onWorkspaceChange}
autoWidth={false}
showSearch
/>
</div>
<div className={cn(classes.treeWrapper)}>
<Tree
dataSource={dataSource}
isNodeBlock={{ defaultPaddingLeft: 20 }}
expandedKeys={expnadedKeys}
selectedKeys={[selectedFile]}
onExpand={onExapnd}
onSelect={onTreeNodeSelect}
defaultExpandAll
/>
</div>
</div>
<div className={cn(classes.rightArea)}>
<div
className={cn(classes.monacoEditorWrapper)}
ref={editorRef}
/>
<div
className={cn(classes.panelWrapper)}
ref={termianlRef}
/>
</div>
<div className={cn(classes.loaderLine)} style={{ display: loading ? 'block' : 'none' }} />
</div>
);
};
export default App;