全部產品
Search
文件中心

DataWorks:最佳實務:表管理OpenAPI基礎實踐

更新時間:Jun 19, 2024

DataWorks提供了豐富的OpenAPI,您可以根據需要使用DataWorks的OpenAPI等開放能力實現各種業務情境。本文以中繼資料表管理為例,為您介紹如何串聯中繼資料的OpenAPI來達成查詢列表、查詢表詳情、血緣圖展示與建立表等操作。

背景資訊

在進行本實踐之前,建議您先參考以下連結,瞭解DataWorks的OpenAPI的基本能力和概念:

下文為您提供了表管理的多個細分情境的實踐:

實踐1:查詢表列表

以下實踐將介紹如何使用OpenAPI來實現查詢表列表,獲得MaxCompute專案下的所有表的列表,並做分頁查詢,實踐的主要流程如下。

  1. 在MetaServiceProxy中編寫一個GetMetaDBTableList方法,處理前端參數並發送請求到OpenAPI GetMetaDBTableList中,以擷取中繼資料表的列表資訊。

    package com.aliyun.dataworks.services;
    
    import com.aliyun.dataworks.dto.*;
    import com.aliyuncs.IAcsClient;
    import com.aliyuncs.dataworks_public.model.v20200518.*;
    import com.aliyuncs.exceptions.ClientException;
    import com.aliyuncs.exceptions.ServerException;
    import com.aliyuncs.utils.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.util.CollectionUtils;
    
    /**
     * @author dataworks demo
     */
    @Service
    public class MetaServiceProxy {
      @Autowired
      private DataWorksOpenApiClient dataWorksOpenApiClient;
      /**
       * DataWorks OpenAPI : GetMetaDBTableList
       * Link : https://www.alibabacloud.com/help/doc-detail/173916.html
       *
       * @param listTablesDto
       */
      public GetMetaDBTableListResponse.Data getMetaDBTableList(ListTablesDto listTablesDto) {
        try {
          IAcsClient client = dataWorksOpenApiClient.createClient();
          GetMetaDBTableListRequest getMetaDBTableListRequest = new GetMetaDBTableListRequest();
          if (StringUtils.isEmpty(listTablesDto.getDataSourceType())) {
            // 資料類型,目前僅支援odps和emr。
            getMetaDBTableListRequest.setDataSourceType("odps");
          }
          // 專案的唯一標識,格式為odps.{projectName}。僅當資料類型為odps時,需要配置該參數。
          getMetaDBTableListRequest.setAppGuid(listTablesDto.getAppGuid());
          // 請求的資料頁數,用於翻頁。
          getMetaDBTableListRequest.setPageNumber(listTablesDto.getPageNumber());
          // 每頁顯示的條數,預設為10條,最大為100條。
          getMetaDBTableListRequest.setPageSize(listTablesDto.getPageSize());
          // 資料庫的名稱。
          getMetaDBTableListRequest.setDatabaseName(listTablesDto.getDatabaseName());
          GetMetaDBTableListResponse acsResponse = client.getAcsResponse(getMetaDBTableListRequest);
          // 計算引擎的總數
          System.out.println(acsResponse.getData().getTotalCount());
          for (GetMetaDBTableListResponse.Data.TableEntityListItem tableEntityListItem : acsResponse.getData()
            .getTableEntityList()) {
            // 表的唯一標識
            System.out.println(tableEntityListItem.getTableGuid());
            // 表的名稱
            System.out.println(tableEntityListItem.getTableName());
            // 資料庫的名稱
            System.out.println(tableEntityListItem.getDatabaseName());
          }
          return acsResponse.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;
      }
    }

  2. 在MetaRestController中添加一個listMetaDBTable方法作為入口供前端訪問。

    package com.aliyun.dataworks.demo;
    
    import com.aliyun.dataworks.dto.*;
    import com.aliyun.dataworks.services.MetaServiceProxy;
    import com.aliyuncs.dataworks_public.model.v20200518.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.bind.annotation.CrossOrigin;
    
    
    /**
     * 示範如何通過DataWorks OpenAPI構建自訂中繼資料平台
     *
     * @author dataworks demo
     */
    @RestController
    @RequestMapping("/meta")
    public class MetaRestController {
      @Autowired
      private MetaServiceProxy metaService;
      /**
       * 分頁查詢表資料
       *
       * @param listTablesDto
       * @return {@link GetMetaDBTableListResponse.Data}
       */
      @GetMapping("/listTables")
      public GetMetaDBTableListResponse.Data listMetaDBTable(ListTablesDto listTablesDto) {
          return metaService.getMetaDBTableList(listTablesDto);
      }
    }
  3. 在前端實現一個簡易的可互動介面。

    說明

    下方代碼簡化了部分非表查詢流程,只展示表查詢的最小實現。

    import React from 'react';
    import { Table, Form, Field, Input, Button, Pagination, Dialog, Card } from '@alifd/next';
    
    function getTableList(params: TableListInput): Promise<TableListOutput> {
      const url = helpers.createFetchUrl(METHOD_URL.GET_TABLE_LIST, params);
      const response = await fetch(url);
      const result = await response.json();
      return result;
    }
    
    const { Column } = Table;
    const { Item } = Form;
    const { Header, Content } = Card;
    const App: React.FunctionComponent<Props> = () => {
      const field = Field.useField();
      const [datasource, setDatasource] = React.useState<TableItem[]>([]);
      const [loading, setLoading] = React.useState<boolean>(false);
      const [total, setTotal] = React.useState<number>(0);
      React.useEffect(() => field.setValue('dataSourceType', 'odps'), []);
      const onSubmit = React.useCallback((pageNumber: number = 1) => {
        field.validate(async (errors, values) => {
          if (errors) return;
          setLoading(true);
          try {
            const response = await getTableList({ pageNumber, ...values } as TableListInput);
            setDatasource(response.tableEntityList);
            setTotal(response.totalCount);
          } catch (e) {
            throw e;
          } finally {
            setLoading(false);
          }
        });
      }, [field]);
      return (
        <div>
          <Card free hasBorder={false}>
            <Header title="中繼資料表管理情境 Demo" />
            <Content style={{ marginTop: 24 }}>
              <Form field={field} colon fullWidth>
                <Item label="MaxCompute專案ID" name="appGuid" required>
                  <Input />
                </Item>
                <Item label="資料庫名稱" name="databaseName">
                  <Input />
                </Item>
                <div>
                  <Button type="primary" onClick={() => onSubmit()}>查詢</Button>
                  <Button type="primary">建立表</Button>
                </div>
              </Form>
              <div>
                <Table dataSource={datasource} loading={loading}>
                  <Column title="資料庫名稱" dataIndex="databaseName" />
                  <Column title="表GUID" dataIndex="tableGuid" />
                  <Column title="表名稱" dataIndex="tableName" />
                  <Column title="操作" width={150} cell={(value, index, record) => (
                    <div>
                      <Button type="primary" text>查看詳情</Button>
                      <Button type="primary" text>查看錶血緣</Button>
                    </div>
                  )} />
                </Table>
                <Pagination
                  total={total}
                  onChange={onSubmit}
                  showJump={false}
                />
              </div>
            </Content>
          </Card>
        </div>
      );
    };
    
    export default App;
                            
  4. 完成上述代碼開發後,您可在本地部署並運行工程代碼。部署並啟動並執行操作請參見通用操作:本地部署運行

實踐2:查詢表詳情

以下實踐將結合中繼資料中GetMetaTableBasicInfoGetMetaTableColumnGetMetaTablePartition三個OpenAPI來實現查詢表詳情,實踐操作流程如下。

  1. 在MetaServiceProxy中構建getMetaTableBasicInfo、getMetaTableColumn與getMetaTablePartition方法來調用OpenAPI服務擷取表的基礎資訊、表的欄位資訊與表的分區資訊。

    package com.aliyun.dataworks.services;
    
    import com.aliyun.dataworks.dto.*;
    import com.aliyuncs.IAcsClient;
    import com.aliyuncs.dataworks_public.model.v20200518.*;
    import com.aliyuncs.exceptions.ClientException;
    import com.aliyuncs.exceptions.ServerException;
    import com.aliyuncs.utils.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.util.CollectionUtils;
    
    /**
     * @author dataworks demo
     */
    @Service
    public class MetaServiceProxy {
      @Autowired
      private DataWorksOpenApiClient dataWorksOpenApiClient;
      /**
       * DataWorks OpenAPI : GetMetaTableBasicInfo
       * Link : https://help.aliyun.com/document_detail/173920.htmlhttps://www.alibabacloud.com/help/doc-detail/173920.html
       *
       * @param getMetaTableBasicInfoDto
       */
      public GetMetaTableBasicInfoResponse.Data getMetaTableBasicInfo(GetMetaTableBasicInfoDto getMetaTableBasicInfoDto) {
        try {
          IAcsClient client = dataWorksOpenApiClient.createClient();
          GetMetaTableBasicInfoRequest getMetaTableBasicInfoRequest = new GetMetaTableBasicInfoRequest();
          // MaxCompute表的唯一標識。格式為odps.projectName.tableName。
          getMetaTableBasicInfoRequest.setTableGuid(getMetaTableBasicInfoDto.getTableGuid());
          // 資料庫名稱
          getMetaTableBasicInfoRequest.setDatabaseName(getMetaTableBasicInfoDto.getDatabaseName());
          // EMR的表名稱
          getMetaTableBasicInfoRequest.setTableName(getMetaTableBasicInfoDto.getTableName());
          // 資料類型,包括odps和emr。
          getMetaTableBasicInfoRequest.setDataSourceType("odps");
          // 是否包含擴充欄位。擴充欄位包含讀取次數、收藏次數、瀏覽次數等。僅當資料類型為ODPS時,該參數生效。
          getMetaTableBasicInfoRequest.setExtension(true);
          GetMetaTableBasicInfoResponse acsResponse = client.getAcsResponse(getMetaTableBasicInfoRequest);
          // 表的名稱。
          System.out.println(acsResponse.getData().getTableName());
          // 表的收藏次數。僅當Extension參數取值為true時才會返回該參數,並且該參數僅對odps資料類型生效。
          System.out.println(acsResponse.getData().getFavoriteCount());
          // 表的描述。
          System.out.println(acsResponse.getData().getComment());
          // 欄位的個數。
          System.out.println(acsResponse.getData().getColumnCount());
          // 建立表的時間。
          System.out.println(acsResponse.getData().getCreateTime());
          // 工作空間的ID。
          System.out.println(acsResponse.getData().getProjectId());
          // 表所有者的ID。
          System.out.println(acsResponse.getData().getOwnerId());
          // 環境類型,取值如下:
          // 0表示開發表。
          // 1表示生產表。
          System.out.println(acsResponse.getData().getEnvType());
          // 資料庫的名稱。
          System.out.println(acsResponse.getData().getDatabaseName());
          // 表的可見度:0表示目標表對工作空間成員可見。
          // 1表示目標表對租戶內成員可見。
          // 2表示目標表對租戶間成員均可見。
          // 3表示目標表僅對責任人可見。
          System.out.println(acsResponse.getData().getIsVisible());
          // 表的唯一標識。
          System.out.println(acsResponse.getData().getTableGuid());
          // 表的讀取次數。僅當Extension參數取值為true時才會返回該參數,並且該參數僅對odps資料類型生效。
          System.out.println(acsResponse.getData().getReadCount());
          // EMR叢集的ID。
          System.out.println(acsResponse.getData().getClusterId());
          // 是否為分區表,取值如下:
          // true:是分區表。
          // false:不是分區表。
          System.out.println(acsResponse.getData().getIsPartitionTable());
          // 是否為視圖,取值如下:
          // true:是視圖。
          // false:不是視圖。
          System.out.println(acsResponse.getData().getIsView());
          // 表的生命週期。單位為天。
          System.out.println(acsResponse.getData().getLifeCycle());
          // 工作空間的名稱。
          System.out.println(acsResponse.getData().getProjectName());
          // 表的瀏覽次數。僅當Extension參數取值為true時才會返回該參數,並且該參數僅對odps資料類型生效。
          System.out.println(acsResponse.getData().getViewCount());
          // 最近一次訪問表的時間。
          System.out.println(acsResponse.getData().getLastAccessTime());
          // 表佔用的儲存空間。單位為Byte。
          System.out.println(acsResponse.getData().getDataSize());
          // 最近一次更新表的時間。
          System.out.println(acsResponse.getData().getLastModifyTime());
          // 最近一次變更表結構的時間。
          System.out.println(acsResponse.getData().getLastDdlTime());
          // Hive分區。
          System.out.println(acsResponse.getData().getPartitionKeys());
          // Hive資料庫的儲存地址。
          System.out.println(acsResponse.getData().getLocation());
          // 表的中文名稱。
          System.out.println(acsResponse.getData().getCaption());
          // 租戶ID。
          System.out.println(acsResponse.getData().getTenantId());
          return acsResponse.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;
      }
      /**
       * DataWorks OpenAPI : GetMetaTableColumn
       * Link : https://www.alibabacloud.com/help/doc-detail/173921.html
       *
       * @param getMetaTableColumnDto
       */
      public GetMetaTableColumnResponse.Data getMetaTableColumn(GetMetaTableColumnDto getMetaTableColumnDto) {
        try {
          IAcsClient client = dataWorksOpenApiClient.createClient();
          GetMetaTableColumnRequest getMetaTableColumnRequest = new GetMetaTableColumnRequest();
          // 表的唯一標識。
          getMetaTableColumnRequest.setTableGuid(getMetaTableColumnDto.getTableGuid());
          // 請求擷取的資料頁碼數,用於翻頁。
          getMetaTableColumnRequest.setPageNum(getMetaTableColumnDto.getPageNum());
          // 每頁顯示的條數,預設為10條,最大100條。
          getMetaTableColumnRequest.setPageSize(getMetaTableColumnDto.getPageSize());
          // EMR叢集的ID,您可以登入EMR管理主控台,擷取叢集ID。
          getMetaTableColumnRequest.setClusterId(getMetaTableColumnDto.getClusterId());
          // EMR的資料庫名稱
          getMetaTableColumnRequest.setDatabaseName(getMetaTableColumnDto.getDatabaseName());
          // EMR的資料庫名稱
          getMetaTableColumnRequest.setTableName(getMetaTableColumnDto.getTableName());
          // 資料類型,當前僅支援取值為emr。
          getMetaTableColumnRequest.setDataSourceType(getMetaTableColumnDto.getDataSourceType());
          GetMetaTableColumnResponse acsResponse = client.getAcsResponse(getMetaTableColumnRequest);
          // 欄位的總數。
          System.out.println(acsResponse.getData().getTotalCount());
          for (GetMetaTableColumnResponse.Data.ColumnListItem columnListItem : acsResponse.getData()
            .getColumnList()) {
            // 欄位的唯一標識。
            System.out.println(columnListItem.getColumnGuid());
            // 欄位的名稱。
            System.out.println(columnListItem.getColumnName());
            // 欄位是否為分區欄位,取值如下:
            // true,是分區欄位。
            // false,不是分區欄位。
            System.out.println(columnListItem.getIsPartitionColumn());
            // 欄位的備忘。
            System.out.println(columnListItem.getComment());
            // 欄位的類型。
            System.out.println(columnListItem.getColumnType());
            // 欄位是否為主鍵,取值如下:
            // true,是主鍵。
            // false,不是主鍵。
            System.out.println(columnListItem.getIsPrimaryKey());
            // 欄位的排序。
            System.out.println(columnListItem.getPosition());
            // 欄位的描述。
            System.out.println(columnListItem.getCaption());
            // 欄位是否為外鍵,取值如下:
            // true,是外鍵。
            // false,不是外鍵。
            System.out.println(columnListItem.getIsForeignKey());
            // 欄位熱度。
            System.out.println(columnListItem.getRelationCount());
          }
          return acsResponse.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;
      }
        /**
       * DataWorks OpenAPI : GetMetaTablePartition
       * Link : https://www.alibabacloud.com/help/doc-detail/173923.html
       *
       * @param getMetaTablePartitionDto
       */
      public GetMetaTablePartitionResponse.Data getMetaTablePartition(GetMetaTablePartitionDto getMetaTablePartitionDto) {
        try {
          IAcsClient client = dataWorksOpenApiClient.createClient();
          GetMetaTablePartitionRequest getMetaTablePartitionRequest = new GetMetaTablePartitionRequest();
          // 請求的資料頁數,用於翻頁。
          getMetaTablePartitionRequest.setPageNumber(getMetaTablePartitionDto.getPageNumber());
          // 每頁顯示的條數,預設為10條,最大100條。
          getMetaTablePartitionRequest.setPageSize(getMetaTablePartitionDto.getPageSize());
          // 表的唯一標識。
          getMetaTablePartitionRequest.setTableGuid(getMetaTablePartitionDto.getTableGuid());
          // EMR叢集的ID,僅當資料類型為EMR時,需要配置該參數。
          getMetaTablePartitionRequest.setClusterId(getMetaTablePartitionDto.getClusterId());
          // 資料庫的名稱。僅當資料類型為EMR時,需要配置該參數。
          getMetaTablePartitionRequest.setDatabaseName(getMetaTablePartitionDto.getDatabaseName());
          // 資料庫的名稱。僅當資料類型為EMR時,需要配置該參數。
          getMetaTablePartitionRequest.setTableName(getMetaTablePartitionDto.getTableName());
          // 資料類型,支援ODPS或者EMR。
          getMetaTablePartitionRequest.setDataSourceType(getMetaTablePartitionDto.getDataSourceType());
          GetMetaTablePartitionResponse acsResponse = client.getAcsResponse(getMetaTablePartitionRequest);
          for (GetMetaTablePartitionResponse.Data.DataEntityListItem dataEntityListItem : acsResponse.getData()
            .getDataEntityList()) {
            // 分區的目錄。
            System.out.println(dataEntityListItem.getPartitionPath());
            // 分區的大小,單位為Byte。
            System.out.println(dataEntityListItem.getDataSize());
            // 分區的名稱。
            System.out.println(dataEntityListItem.getPartitionName());
            // 備忘資訊。
            System.out.println(dataEntityListItem.getComment());
            // 修改分區的時間。
            System.out.println(dataEntityListItem.getModifiedTime());
            // 建立分區的時間。
            System.out.println(dataEntityListItem.getCreateTime());
            // 分區的資料量。
            System.out.println(dataEntityListItem.getRecordCount());
            // 分區的類型。
            System.out.println(dataEntityListItem.getPartitionType());
            // 分區的唯一標識。
            System.out.println(dataEntityListItem.getPartitionGuid());
            // Hive分區的地址。
            System.out.println(dataEntityListItem.getPartitionLocation());
            // 表的唯一標識。
            System.out.println(dataEntityListItem.getTableGuid());
          }
          return acsResponse.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;
      }
    }

  2. 在MetaRestController中提供三個方法分別為getMetaTableBasicInfo、getMetaTableColumn與getMetaTablePartition供前端進行調用。

    package com.aliyun.dataworks.demo;
    
    import com.aliyun.dataworks.dto.*;
    import com.aliyun.dataworks.services.MetaServiceProxy;
    import com.aliyuncs.dataworks_public.model.v20200518.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.bind.annotation.CrossOrigin;
    
    
    /**
     * 示範如何通過DataWorks OpenAPI構建自訂中繼資料平台
     *
     * @author dataworks demo
     */
    @RestController
    @RequestMapping("/meta")
    public class MetaRestController {
      @Autowired
      private MetaServiceProxy metaService;
      /**
       * 擷取表的基本屬性
       *
       * @param getMetaTableBasicInfoDto
       * @return {@link GetMetaTableBasicInfoResponse.Data}
       */
      @GetMapping("/getTable")
      public GetMetaTableBasicInfoResponse.Data getMetaTableBasicInfo(GetMetaTableBasicInfoDto getMetaTableBasicInfoDto) {
          return metaService.getMetaTableBasicInfo(getMetaTableBasicInfoDto);
      }
      /**
       * 查詢表的列資訊
       *
       * @param getMetaTableColumnDto
       * @return {@link GetMetaTableColumnResponse.Data}
       */
      @GetMapping("/getTableColumn")
      public GetMetaTableColumnResponse.Data getMetaTableColumn(GetMetaTableColumnDto getMetaTableColumnDto) {
          return metaService.getMetaTableColumn(getMetaTableColumnDto);
      }
      /**
       * 查詢表分區
       *
       * @param getMetaTablePartitionDto
       * @return
       */
      @GetMapping("/getTablePartition")
      public GetMetaTablePartitionResponse.Data getMetaTablePartition(GetMetaTablePartitionDto getMetaTablePartitionDto) {
          return metaService.getMetaTablePartition(getMetaTablePartitionDto);
      }
    }
  3. 在前端實現一個簡易的詳情頁來調用表基本資料、表列資訊與表分區的介面,並展示出來。

    import React from 'react';
    import moment from 'moment';
    import { Form, Grid, Table, Pagination } from '@alifd/next';
    import cn from 'classnames';
    import * as services from '../services';
    import type { TableItem, TableDetailOutput, TableColumn, TableEntity } from '../services/meta';
    
    export interface Props {
      item: TableItem;
    }
    
    const formItemLayout = {
      labelCol: {
        fixedSpan: 6
      },
      wrapperCol: {
        span: 18
      },
      labelTextAlign: 'left' as const,
      colon: true,
    };
    const { Row, Col } = Grid;
    const { Item } = Form;
    const { Column } = Table;
    const DetailContent: React.FunctionComponent<Props> = (props) => {
      const [detail, setDetail] = React.useState<Partial<TableDetailOutput>>({});
      const [columns, setColumns] = React.useState<Partial<TableColumn[]>>([]);
      const [partitions, setPartitions] = React.useState<Partial<TableEntity[]>>([]);
      const [columnsTotal, setColumnsTotal] = React.useState<number>(0);
      const [partitionTotal, setPartitionTotal] = React.useState<number>(0);
      // 實現一個調用擷取表基本資料介面的方法
      const getTableDetail = React.useCallback(async () => {
        const response = await services.meta.getTableDetail({ tableGuid: props.item.tableGuid });
        setDetail(response);
      }, [props.item.tableGuid]);
      // 實現一個調用擷取表欄位資訊介面的方法
      const getTableColumns = React.useCallback(async (pageNum: number = 1) => {
        const response = await services.meta.getMetaTableColumns({
          pageNum,
          tableGuid: props.item.tableGuid,
        });
        setColumns(response.columnList);
        setColumnsTotal(response.totalCount);
      }, [props.item.tableGuid]);
      // 實現一個調用擷取表分區資訊介面的方法
      const getTablePartition = React.useCallback(async (pageNumber: number = 1) => {
        const response = await services.meta.getTablePartition({
          pageNumber,
          tableGuid: props.item.tableGuid,
        });
        setPartitions(response.dataEntityList);
        setPartitionTotal(response.totalCount);
      }, []);
      // 表的許可權映射
      const isVisible = React.useMemo(() => {
        switch (detail.isVisible) {
          case 0:
            return '對工作空間成員可見';
          case 1:
            return '對租戶內成員可見';
          case 2:
            return '對租戶間成員均可見';
          case 3:
            return '僅對責任人可見';
          default:
            return '';
        }
      }, [detail.isVisible]);
      React.useEffect(() => {
        if (props.item.tableGuid) {
          getTableDetail();
          getTableColumns();
          getTablePartition();
        }
      }, [props.item.tableGuid]);
      // 渲染前端頁面
      return (
        <div>
          <Form labelTextAlign="left">
            <Row>
              <Col>
                <Item {...formItemLayout} label="表GUID">
                  <span>{detail.tableGuid}</span>
                </Item>
              </Col>
              <Col>
                <Item {...formItemLayout} label="表名稱">
                  <span>{detail.tableName}</span>
                </Item>
              </Col>
            </Row>
            <Row>
              <Col>
                <Item {...formItemLayout} label="所屬工作空間">
                  <span className={cn(classes.formContentWrapper)}>{detail.projectName}</span>
                </Item>
              </Col>
              <Col>
                <Item {...formItemLayout} label="表所有者ID">
                  <span className={cn(classes.formContentWrapper)}>{detail.ownerId}</span>
                </Item>
              </Col>
            </Row>
            <Row>
              <Col>
                <Item {...formItemLayout} label="表生命週期">
                  <span className={cn(classes.formContentWrapper)}>{detail.lifeCycle ? `${detail.lifeCycle} 天` : '永久'}</span>
                </Item>
              </Col>
              <Col>
                <Item {...formItemLayout} label="備忘資訊">
                  <span className={cn(classes.formContentWrapper)}>{detail.comment}</span>
                </Item>
              </Col>
            </Row>
            <Row>
              <Col>
                <Item {...formItemLayout} label="儲存空間">
                  <span className={cn(classes.formContentWrapper)}>{`${detail.dataSize} Bytes`}</span>
                </Item>
              </Col>
              <Col>
                <Item {...formItemLayout} label="是否為分區表">
                  <span className={cn(classes.formContentWrapper)}>{detail.isView ? '是' : '否'}</span>
                </Item>
              </Col>
            </Row>
            <Row>
              <Col>
                <Item {...formItemLayout} label="表的可見度">
                  <span className={cn(classes.formContentWrapper)}>{isVisible}</span>
                </Item>
              </Col>
              <Col>
                <Item {...formItemLayout} label="表的讀取次數">
                  <span className={cn(classes.formContentWrapper)}>{detail.readCount}</span>
                </Item>
              </Col>
            </Row>
            <Row>
              <Col>
                <Item {...formItemLayout} label="建立時間">
                  <span className={cn(classes.formContentWrapper)}>{moment(detail.createTime).format('YYYY-MM-DD HH:mm:ss')}</span>
                </Item>
              </Col>
              <Col>
                <Item {...formItemLayout} label="最後修改時間">
                  <span className={cn(classes.formContentWrapper)}>{moment(detail.lastModifyTime).format('YYYY-MM-DD HH:mm:ss')}</span>
                </Item>
              </Col>
            </Row>
            <Item label="欄位詳情" colon>
              <Table dataSource={columns}>
                <Column title="名稱" dataIndex="columnName" />
                <Column title="類型" dataIndex="columnType" />
                <Column title="是否為主鍵" dataIndex="isPrimaryKey" />
                <Column title="是否為外鍵" dataIndex="isForeignKey" />
                <Column title="是否為分區" dataIndex="isPartitionColumn" />
                <Column title="說明" dataIndex="comment" />
              </Table>
              <Pagination
                total={columnsTotal}
                onChange={getTableColumns}
                showJump={false}
                className={cn(classes.tablePaginationWrapper)}
              />
            </Item>
            <Item label="分區詳情" style={{ marginTop: 32, marginBottom: 32 }} colon>
             <Table
              dataSource={partitions}
              emptyContent={<span>非分區表</span>}
            >
                <Column title="名稱" dataIndex="partitionName" />
                <Column title="類型" dataIndex="partitionType" />
                <Column title="分區大小" dataIndex="dataSize" cell={value => `${value} bytes`} />
                <Column title="備忘" dataIndex="comment" />
              </Table>
              <Pagination
                total={partitionTotal}
                onChange={getTablePartition}
                showJump={false}
                className={cn(classes.tablePaginationWrapper)}
              />
            </Item>
          </Form>
        </div>
      );
    }
    
    export default DetailContent;
                            
  4. 完成上述代碼開發後,您可在本地部署並運行工程代碼。部署並啟動並執行操作請參見通用操作:本地部署運行

實踐3:尋找表的血緣資訊並上下鑽

以下實踐將使用中繼資料中GetMetaTableLineage來尋找表的血緣資訊以及上下鑽的能力,實踐流程如下。

  1. 在MetaServiceProxy中構建getMetaTableLineage方法來調用OpenAPI getMetaTableLineage方法,並對入參進行處理。

    package com.aliyun.dataworks.services;
    
    import com.aliyun.dataworks.dto.*;
    import com.aliyuncs.IAcsClient;
    import com.aliyuncs.dataworks_public.model.v20200518.*;
    import com.aliyuncs.exceptions.ClientException;
    import com.aliyuncs.exceptions.ServerException;
    import com.aliyuncs.utils.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.util.CollectionUtils;
    
    /**
     * @author dataworks demo
     */
    @Service
    public class MetaServiceProxy {
      @Autowired
      private DataWorksOpenApiClient dataWorksOpenApiClient;
      /**
       * DataWorks OpenAPI : GetMetaTableLineage
       * Link : https://www.alibabacloud.com/help/doc-detail/173927.html
       *
       * @param getMetaTableLineageDto
       */
      public GetMetaTableLineageResponse.Data getMetaTableLineage(GetMetaTableLineageDto getMetaTableLineageDto) {
        try {
          IAcsClient client = dataWorksOpenApiClient.createClient();
          GetMetaTableLineageRequest getMetaTableLineageRequest = new GetMetaTableLineageRequest();
          // 表的唯一標識。
          getMetaTableLineageRequest.setTableGuid(getMetaTableLineageDto.getTableGuid());
          // 欄位的上下遊方向:up表示上遊,down表示下遊。
          getMetaTableLineageRequest.setDirection(getMetaTableLineageDto.getDirection());
          // 分頁資料。
          getMetaTableLineageRequest.setNextPrimaryKey(getMetaTableLineageDto.getTableGuid());
          // 每頁顯示的條數,預設為10條,最大100條。
          getMetaTableLineageRequest.setPageSize(getMetaTableLineageDto.getPageSize());
          // EMR叢集的ID,針對EMR情況。
          getMetaTableLineageRequest.setClusterId(getMetaTableLineageDto.getClusterId());
          // 資料庫的名稱。
          getMetaTableLineageRequest.setDatabaseName(getMetaTableLineageDto.getDatabaseName());
          // 表名
          getMetaTableLineageRequest.setTableName(getMetaTableLineageDto.getTableName());
          // 資料類型,包括odps或emr。
          getMetaTableLineageRequest.setDataSourceType(getMetaTableLineageDto.getDataSourceType());
          GetMetaTableLineageResponse acsResponse = client.getAcsResponse(getMetaTableLineageRequest);
          for (GetMetaTableLineageResponse.Data.DataEntityListItem dataEntityListItem : acsResponse.getData()
            .getDataEntityList()) {
            // 表的名稱。
            System.out.println(dataEntityListItem.getTableName());
            // 表的唯一標識。
            System.out.println(dataEntityListItem.getTableGuid());
            // 建立時間。
            System.out.println(dataEntityListItem.getCreateTimestamp());
          }
          return acsResponse.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;
      }
    }

  2. 在入口處定義一個getMetaTableLineage方法供前端調用。

    package com.aliyun.dataworks.demo;
    
    import com.aliyun.dataworks.dto.*;
    import com.aliyun.dataworks.services.MetaServiceProxy;
    import com.aliyuncs.dataworks_public.model.v20200518.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.bind.annotation.CrossOrigin;
    
    
    /**
     * 示範如何通過DataWorks OpenAPI構建自訂中繼資料平台
     *
     * @author dataworks demo
     */
    @RestController
    @RequestMapping("/meta")
    public class MetaRestController {
      @Autowired
      private MetaServiceProxy metaService;
      /**
       * 查詢表的血緣關係
       *
       * @param getMetaTableLineageDto
       * @return
       */
      @GetMapping("/getTableLineage")
      public GetMetaTableLineageResponse.Data getMetaTableLineage(GetMetaTableLineageDto getMetaTableLineageDto) {
          return metaService.getMetaTableLineage(getMetaTableLineageDto);
      }
    }
  3. 在前端實現一個調用血緣圖介面的能力並渲染出來。

    import React from 'react';
    import Graphin, { Behaviors } from '@antv/graphin';
    import type { IUserNode, IUserEdge, GraphinData, GraphEvent } from '@antv/graphin';
    import * as services from '../services';
    import type { TableItem, GetTableLineageOutput, LineageEntity } from '../services/meta';
    import '@antv/graphin/dist/index.css';
    
    export interface Props {
      item: TableItem;
    }
    
    function transNode(entity: LineageEntity | TableItem, direction?: string): IUserNode {
      return {
        id: entity.tableGuid,
        label: entity.tableName,
        data: {
          ...entity,
          direction,
        },
        style: {
          label: {
            value: entity.tableName,
          }
        },
      }
    }
    function transEdge(source: LineageEntity | TableItem, target: LineageEntity | TableItem): IUserEdge {
      return {
        source: source.tableGuid,
        target: target.tableGuid,
      };
    }
    function parse(
      source: LineageEntity | TableItem,
      data: GetTableLineageOutput,
      direction: string,
    ): [IUserNode[], IUserEdge[]] {
      const nodes: IUserNode[] = [];
      const edges: IUserEdge[] = [];
      data.dataEntityList.forEach((entity) => {
        nodes.push(transNode(entity, direction));
        if (direction === 'down') {
          edges.push(transEdge(source, entity));
        } else {
          edges.push(transEdge(entity, source));
        }
      });
      return [nodes, edges];
    }
    function mergeNodes(prev: IUserNode[], next: IUserNode[]) {
      const result: IUserNode[] = prev.slice();
      next.forEach((item) => {
        const hasValue = prev.findIndex(i => i.id === item.id) >= 0;
        !hasValue && result.push(item);
      });
      return result;
    }
    function mergeEdges(source: IUserEdge[], target: IUserEdge[]) {
      const result: IUserEdge[] = source.slice();
      target.forEach((item) => {
        const hasValue = source.findIndex(i => i.target === item.target && i.source === item.source) >= 0;
        !hasValue && result.push(item);
      });
      return result;
    }
    
    const { ActivateRelations, DragNode, ZoomCanvas } = Behaviors;
    const LineageContent: React.FunctionComponent<Props> = (props) => {
      const ref = React.useRef<Graphin>();
      const [data, setData] = React.useState<GraphinData>({ nodes: [], edges: [] });
      // 調用後端介面擷取血緣資訊
      const getTableLineage = async (
        collection: GraphinData,
        target: TableItem | LineageEntity,
        direction: string,
      ) => {
        if (!direction) {
          return collection;
        }
        const response = await services.meta.getTableLineage({
          direction,
          tableGuid: target.tableGuid,
        });
        const [nodes, edges] = parse(target, response, direction);
        collection.nodes = mergeNodes(collection.nodes!, nodes);
        collection.edges = mergeEdges(collection.edges!, edges);
        return collection;
      };
      // 初始化時擷取資料並渲染頁面
      const init = async () => {
        let nextData = Object.assign({}, data, { nodes: [transNode(props.item)] });
        nextData = await getTableLineage(nextData, props.item, 'up');
        nextData = await getTableLineage(nextData, props.item, 'down');
        setData(nextData);
        ref.current!.graph.fitCenter();
      };
      React.useEffect(() => {
        ref.current?.graph && init();
      }, [
        ref.current?.graph,
      ]);
      // 處理表節點被點擊時的事件,發起請求擷取資料
      React.useEffect(() => {
        const graph = ref.current?.graph;
        const event = async (event: GraphEvent) => {
          const source = event.item?.get('model').data;
          let nextData = Object.assign({}, data);
          nextData = await getTableLineage(nextData, source, source.direction);
          setData(nextData);
        };
        graph?.on('node:click', event);
        return () => {
          graph?.off('node:click', event);
        };
      }, [
        data,
        ref.current?.graph,
      ]);
      // 渲染血緣圖
      return (
        <div>
          <Graphin
            data={data}
            ref={ref as React.LegacyRef<Graphin>}
            layout={{
              type: 'dagre',
              rankdir: 'LR',
              align: 'DL',
              nodesep: 10,
              ranksep: 40,
            }}
          >
            <ActivateRelations />
            <DragNode disabled />
            <ZoomCanvas enableOptimize />
          </Graphin>
        </div>
      );
    };
    
    export default LineageContent;
                            
  4. 完成上述代碼開發後,您可在本地部署並運行工程代碼。部署並啟動並執行操作請參見通用操作:本地部署運行

實踐4:尋找表對應的節點

當一張表口徑發生變更時,您可以通過DataWorks OpenAPI、OpenData、訊息訂閱的方式進行下遊任務的血緣分析,尋找表對應的節點。具體操作如下。

  1. 通過GetMetaColumnLineageGetMetaTableLineage,查看錶的血緣關係。

    說明
    • 訊息目前支援表變更、任務變更等。企業版使用者可以對接表變更的訊息,當接收到表變更的時候,您可以查看錶的血緣關係。

    • GetMetaColumnLineage為欄位血緣,GetMetaTableLineage為表血緣。查詢表的血緣也可以改成查詢任務的血緣。

  2. 根據欄位血緣或表血緣,查到受影響的表的列表,根據表列表,通過GetMetaTableOutput,擷取表的任務ID。

  3. 根據指定任務ID,通過GetNode擷取任務詳情,確認對的業務影響。

通用操作:本地部署運行

  1. 準備依賴環境。

    您需準備好以下依賴環境:java8及以上、maven構建工具、node環境、pnpm工具。您可以執行以下命令來確定是否具備上述環境:

    npm -v // 如果已安裝成功,執行命令將展示版本號碼,否則會報沒有命令錯誤
    java -version // 如果已安裝成功,執行命令將展示版本號碼,否則會報沒有命令錯誤
    pnpm -v // 如果已安裝成功,執行命令將展示版本號碼,否則會報沒有命令錯誤
  2. 下載工程代碼並執行以下命令。

    工程代碼下載連結:meta-api-demo.zip

    pnpm i
  3. 在範例工程中的backend/src/main/resources路徑下找到application.properties檔案,修改檔案中的核心參數。

    • api.access-key-id與api.access-key-secret需修改為您阿里雲帳號的AccessKey ID 和 AccessKey Secret。

      說明

      您可以在AccessKey 管理頁面擷取阿里雲帳號的相關資訊。

    • api.region-id、api.endpoint需修改為待調用OpenAPI的地區資訊。

    其他參數可根據實際情況修改,修改後的填寫樣本如下。本地部署樣本

  4. 在工程根目錄下執行以下命令,運行樣本實踐代碼。

    npm run dev