全部產品
Search
文件中心

DataWorks:最佳實務:資料開發、提交與運行OpenAPI基礎實踐

更新時間:Jun 19, 2024

DataWorks提供了豐富的OpenAPI,您可以根據需要使用DataWorks的OpenAPI等開放能力實現各種業務情境。本文以資料開發為例,為您樣本如何使用OpenAPI快速進行資料開發、提交與運行。

背景資訊

本實踐將滿足以下業務情境的需求,建議您先學習瞭解不同業務情境涉及的核心能力與概念。

  • 查詢與管理工作空間列表、商務程序列表、節點檔案夾與節點列表;提交發布節點任務。主要需要應用資料開發的OpenAPI,如CreateBusinessListBusiness等。

  • 進行煙霧測試 (Smoke Test)並查看過程日誌。主要需要應用營運中心的部分OpenAPI,如RunSmokeTest等。

以下為您分布為您介紹本實踐的主要操作流程與核心程式碼範例。

  1. 後端代碼開發

  2. 前端代碼開發

  3. 本地部署運行

如果您想查看或下載全量原始碼樣本,可參考下文的參考:全量程式碼範例與原始碼下載

後端代碼開發

步驟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來執行煙霧測試 (Smoke Test)。

  • 定義一個getInstanceLog函數,調用GetInstanceLog來擷取煙霧測試 (Smoke Test)過程日誌內容。

這些方法將在介面中被調用,分別用來建立檔案、拉取檔案清單、儲存檔案、提交與執行使用。

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);
    }


}

前端代碼開發

  1. 初始化編輯器、分類樹與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>
      );
    };
                            
  2. 擷取樣本商務程序與檔案,並展示分類樹。

    整體流程和範例程式碼如下。流程圖1

    /**
     * 擷取分類樹資料來源
     * @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 }];
    }
  3. 當使用者編輯檔案並儲存時,需要將編輯的內容傳回後端並更新資料。

    程式碼範例如下。

    /**
     * 儲存檔案,當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');
    }
  4. 當使用者在terminal終端中輸入dw run ...時,會將檔案提交到調度系統,並執行煙霧測試 (Smoke Test)運行。

    處理流程和程式碼範例如下。流程2

    /**
     * 處理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;
      }
      // 執行煙霧測試 (Smoke Test),運行調度任務
      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;
  }
  // 執行煙霧測試 (Smoke Test),運行調度任務
  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;