全部產品
Search
文件中心

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

更新時間:Oct 14, 2025

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

前置概念

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

一、查詢表列表

本實踐介紹如何使用OpenAPI查詢MaxCompute專案/模式下的表列表,並支援分頁查詢。主要流程如下:

後端:使用MetaServiceProxy查詢表詳情

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

    • 支援查詢的參數:父層級中繼資料實體ID、名稱(模糊比對)、注釋(模糊比對)、類型以及排序參數和分頁的參數。

      說明

      參考中繼資料實體相關概念說明,此處的父層級中繼資料實體ID可能存在兩種格式。

      • 父層級中繼資料實體ID為MaxCompute專案ID,格式為:maxcompute-project:${主帳號ID}::{projectName}

      • 父層級中繼資料實體ID為MaxCompute模式ID,格式為:maxcompute-schema:${主帳號ID}::{projectName}:{schemaName}

    • 查詢結果中包含內容:資料表總數、分頁資訊、每個資料表的ID、父層級中繼資料實體ID、表名稱、注釋、資料庫名稱、模式名稱、類型、分區欄位列表、建立時間與修改時間。

    • 擷取主帳號ID:無論是RAM子帳號還是主帳號都可在頁面右上方的帳號處查看主帳號ID。

      1. 登入主帳號或RAM子帳號到DataWorks控制台

      2. 滑鼠懸浮右上方的頭像按鈕上即可查看主帳號ID。

        • 主帳號:帳號ID即為需要擷取的主帳號ID。

        • RAM帳號:可直接查看到主帳號ID

    • 範例程式碼:

      /**
       * @author dataworks demo
       */
      @Service
      public class MetaServiceProxy {
      
          @Autowired
          private DataWorksOpenApiClient dataWorksOpenApiClient;
      
          /**
           * DataWorks OpenAPI : ListTables
           *
           * @param listTablesDto
           */
          public ListTablesResponseBodyPagingInfo listTables(ListTablesDto listTablesDto) {
              try {
                  Client client = dataWorksOpenApiClient.createClient();
                  ListTablesRequest listTablesRequest = new ListTablesRequest();
                  // 父層級實體ID,中繼資料相關實體概念說明。
                  listTablesRequest.setParentMetaEntityId(listTablesDto.getParentMetaEntityId());
                  // 表名稱,支援模糊比對
                  listTablesRequest.setName(listTablesDto.getName());
                  // 表注釋,支援模糊比對
                  listTablesRequest.setComment(listTablesDto.getComment());
                  // 表類型,預設返回所有類型
                  listTablesRequest.setTableTypes(listTablesDto.getTableTypes());
                  // 排序方式,CreateTime (預設) / ModifyTime / Name / TableType
                  listTablesRequest.setSortBy(listTablesDto.getSortBy());
                  // 排序方向,支援 Asc (預設) / Desc
                  listTablesRequest.setOrder(listTablesDto.getOrder());
                  // 分頁頁碼,預設為1
                  listTablesRequest.setPageNumber(listTablesDto.getPageNumber());
                  // 分頁大小,預設為10,最大100
                  listTablesRequest.setPageSize(listTablesDto.getPageSize());
                  ListTablesResponse response = client.listTables(listTablesRequest);
                  // 擷取符合要求的表總數
                  System.out.println(response.getBody().getPagingInfo().getTotalCount());
                  for (Table table : response.getBody().getPagingInfo().getTables()) {
                      // 表ID
                      System.out.println(table.getId());
                      // 表的父層級實體ID
                      System.out.println(table.getParentMetaEntityId());
                      // 表名稱
                      System.out.println(table.getName());
                      // 表注釋
                      System.out.println(table.getComment());
                      // 表所屬資料庫/maxcompute專案名稱
                      System.out.println(table.getId().split(":")[3]);
                      // 表所屬schema名稱
                      System.out.println(table.getId().split(":")[4]);
                      // 表類型
                      System.out.println(table.getTableType());
                      // 表建立時間
                      System.out.println(table.getCreateTime());
                      // 表修改時間
                      System.out.println(table.getModifyTime());
                      // 表的分區欄位列表
                      System.out.println(table.getPartitionKeys());
                  }
                  return response.getBody().getPagingInfo();
              } catch (TeaException error) {
                  // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                  // 錯誤 message
                  System.out.println(error.getMessage());
                  // 診斷地址
                  System.out.println(error.getData().get("Recommend"));
                  com.aliyun.teautil.Common.assertAsString(error.message);
              } catch (Exception _error) {
                  TeaException error = new TeaException(_error.getMessage(), _error);
                  // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                  // 錯誤 message
                  System.out.println(error.getMessage());
                  // 診斷地址
                  System.out.println(error.getData().get("Recommend"));
                  com.aliyun.teautil.Common.assertAsString(error.message);
              }
              return null;
          }
      }
  2. MetaRestController中添加一個listTables方法作為入口供前端訪問。

    /**
     * 示範如何通過DataWorks OpenAPI構建自訂中繼資料平台
     *
     * @author dataworks demo
     */
    @RestController
    @RequestMapping("/meta")
    public class MetaRestController {
    
        @Autowired
        private MetaServiceProxy metaService;
    
        /**
         * 分頁查詢表資料
         *
         * @param listTablesDto
         * @return {@link ListTablesResponseBodyPagingInfo}
         */
        @CrossOrigin(origins = "http://localhost:8080")
        @GetMapping("/listTables")
        public ListTablesResponseBodyPagingInfo listTables(ListTablesDto listTablesDto) {
            System.out.println("listTablesDto = " + listTablesDto);
            return metaService.listTables(listTablesDto);
        }
    }

前端:展示表中繼資料資訊

import React from "react";
import cn from "classnames";
import {
  Table,
  Form,
  Field,
  Input,
  Button,
  Pagination,
  Dialog,
  Card,
  Grid,
  Select,
} from "@alifd/next";
import type { TableListInput, TableEntity } from "../services/meta";
import * as services from "../services";
import DetailContent from "./detailContent";
import LineageContent from "./lineageContent";
import classes from "../styles/app.module.css";
import moment from "moment";

export interface Props {}

const { Column } = Table;
const { Item } = Form;
const { Header, Content } = Card;
const { Row, Col } = Grid;
const { Option } = Select;

const App: React.FunctionComponent<Props> = () => {
  const field = Field.useField();
  const [datasource, setDatasource] = React.useState<TableEntity[]>([]);
  const [loading, setLoading] = React.useState<boolean>(false);
  const [total, setTotal] = React.useState<number>(0);
  const [current, setCurrent] = React.useState<number>(1);
  const onSubmit = React.useCallback(
    (pageNumber: number = 1) => {
      field.validate(async (errors, values) => {
        if (errors) {
          return;
        }
        setLoading(true);
        try {
          const response = await services.meta.getTableList({
            pageNumber,
            ...values,
          } as TableListInput);
          setDatasource(response.tables);
          setCurrent(pageNumber);
          setTotal(response.totalCount);
        } catch (e) {
          throw e;
        } finally {
          setLoading(false);
        }
      });
    },
    [field]
  );
  const onViewDetail = React.useCallback((record: TableEntity) => {
    Dialog.show({
      title: "資料表詳情",
      content: <DetailContent item={record} />,
      shouldUpdatePosition: true,
      footerActions: ["ok"],
    });
  }, []);
  const onViewLineage = React.useCallback((record: TableEntity) => {
    Dialog.show({
      title: "表血緣關係",
      content: <LineageContent item={record} />,
      shouldUpdatePosition: true,
      footerActions: ["ok"],
    });
  }, []);
  React.useEffect(() => {
    field.setValue("dataSourceType", "odps");
  }, []);
  return (
    <div className={cn(classes.appWrapper)}>
    <Card free hasBorder={false}>
    <Header title="中繼資料表管理情境Demo(MaxCompute)" />
    <Content style={{ marginTop: 24 }}>
  <Form field={field} colon fullWidth>
  <Row gutter="1">
  <Col>
  <Item
label="MaxCompute專案/模式ID"
name="parentMetaEntityId"
required
help="ID"
  >
  <Input />
  </Item>
  </Col>
  </Row>

  <Row gutter="4">
  <Col>
  <Item label="名稱" name="name">
  <Input placeholder="輸入表名稱,模糊比對" />
  </Item>
  </Col>
  <Col>
                <Item label="注釋" name="comment">
                  <Input placeholder="輸入表注釋,模糊比對" />
                </Item>
              </Col>
              <Col>
                <Item label="類型" name="tableTypes">
                  <Select mode="multiple" style={{ width: "100%" }}>
                    <Option value="TABLE">表</Option>
                    <Option value="VIEW">視圖</Option>
                    <Option value="MATERIALIZED_VIEW">物化視圖</Option>
                  </Select>
                </Item>
              </Col>
              <Col>
                <Item label="排序方式" name="sortBy">
                  <Select defaultValue="CreateTime" style={{ width: "100%" }}>
                    <Option value="CreateTime">建立時間</Option>
                    <Option value="ModifyTime">修改時間</Option>
                    <Option value="Name">名稱</Option>
                    <Option value="TableType">類型</Option>
                  </Select>
                </Item>
              </Col>
              <Col>
                <Item label="排序方向" name="order">
                  <Select defaultValue="Asc" style={{ width: "100%" }}>
                    <Option value="Asc">升序</Option>
                    <Option value="Desc">降序</Option>
                  </Select>
                </Item>
              </Col>
            </Row>
            <div
              className={cn(
                classes.searchPanelButtonWrapper,
                classes.buttonGroup
              )}
            >
              <Button type="primary" onClick={() => onSubmit()}>
                查詢
              </Button>
            </div>
          </Form>
          <div>
            <Table
              dataSource={datasource}
              loading={loading}
              className={cn(classes.tableWrapper)}
              emptyContent={
                <div className={cn(classes.noDataWrapper)}>暫無資料</div>
              }
            >
              <Column
                title="專案名稱"
                dataIndex="id"
                cell={(value) => {
                  const parts = value.split(":");
                  return parts.length > 1 ? parts[parts.length - 3] : "";
                }}
              />
              <Column
                title="schema"
                dataIndex="id"
                cell={(value) => {
                  const parts = value.split(":");
                  return parts.length > 1 ? parts[parts.length - 2] : "";
                }}
              />
              <Column title="表名稱" dataIndex="name" />
              <Column title="表注釋" dataIndex="comment" />
              <Column title="表類型" dataIndex="tableType" />
              <Column
                title="是否分區表"
                dataIndex="partitionKeys"
                cell={(value) => {
                  return value != null && value.length > 0 ? "是" : "否";
                }}
              />
              <Column
                title="建立時間"
                dataIndex="createTime"
                cell={(value) => {
                  return moment(value).format("YYYY-MM-DD HH:mm:ss");
                }}
              />
              <Column
                title="修改時間"
                dataIndex="modifyTime"
                cell={(value) => {
                  return moment(value).format("YYYY-MM-DD HH:mm:ss");
                }}
              />
              <Column
                title="操作"
                width={150}
                cell={(value, index, record) => (
                  <div className={cn(classes.buttonGroup)}>
                    <Button
                      type="primary"
                      onClick={() => onViewDetail(record)}
                      text
                    >
                      查看詳情
                    </Button>
                    <Button
                      type="primary"
                      onClick={() => onViewLineage(record)}
                      text
                    >
                      查看錶血緣
                    </Button>
                  </div>
                )}
              />
            </Table>
            <Pagination
              current={current}
              total={total}
              onChange={onSubmit}
              showJump={false}
              className={cn(classes.tablePaginationWrapper)}
            />
          </div>
        </Content>
      </Card>
    </div>
  );
};

export default App;

完成上述代碼開發後,您可在通用操作:本地部署運行工程代碼。您可以輸入MaxCompute專案空間ID及其他參數進行查詢,獲得專案空間下的所有表的列表,支援分頁查詢。

二、尋找表詳情

以下實踐將結合中繼資料中GetTableListColumnsListPartitions三個OpenAPI來實現查詢表詳情,通過UpdateColumnBusinessMetadataAPI,更新欄位的業務說明,實踐操作流程如下。

後端:使用MetaServiceProxy查詢表詳情

  1. MetaServiceProxy中建立getTablelistColumnslistPartitions方法來調用OpenAPI服務擷取表的基礎資訊、表的欄位資訊與表的分區資訊,建立updateColumnBusinessMetadata方法來更新欄位的業務說明

    /**
     * @author dataworks demo
     */
    @Service
    public class MetaServiceProxy {
    
        @Autowired
        private DataWorksOpenApiClient dataWorksOpenApiClient;
    
        /**
         * DataWorks OpenAPI : getTableDto
         *
         * @param getTableDto
         */
        public Table getTable(GetTableDto getTableDto) {
            try {
                Client client = dataWorksOpenApiClient.createClient();
                GetTableRequest getTableRequest = new GetTableRequest();
                // 資料表ID
                getTableRequest.setId(getTableDto.getId());
                // 是否返回業務中繼資料
                getTableRequest.setIncludeBusinessMetadata(getTableDto.getIncludeBusinessMetadata());
    
                GetTableResponse response = client.getTable(getTableRequest);
                // 對應的資料表
                Table table = response.getBody().getTable();
                // 表ID
                System.out.println(table.getId());
                // 表的父層級實體ID
                System.out.println(table.getParentMetaEntityId());
                // 表名稱
                System.out.println(table.getName());
                // 表注釋
                System.out.println(table.getComment());
                // 表所屬資料庫 / maxcompute專案名稱
                System.out.println(table.getId().split(":")[3]);
                // 表所屬schema名稱
                System.out.println(table.getId().split(":")[4]);
                // 表類型
                System.out.println(table.getTableType());
                // 表建立時間
                System.out.println(table.getCreateTime());
                // 表修改時間
                System.out.println(table.getModifyTime());
                // 是否分區表
                System.out.println(table.getPartitionKeys() != null && !table.getPartitionKeys().isEmpty());
                // 表的技術中繼資料
                TableTechnicalMetadata technicalMetadata = table.getTechnicalMetadata();
                // 表的負責人(名稱)
                System.out.println(technicalMetadata.getOwner());
                // 是否壓縮表
                System.out.println(technicalMetadata.getCompressed());
                // 輸入格式(HMS, DLF等類型支援)
                System.out.println(technicalMetadata.getInputFormat());
                // 輸出格式(HMS, DLF等類型支援)
                System.out.println(technicalMetadata.getOutputFormat());
                // 表序列化使用的類(HMS, DLF等類型支援)
                System.out.println(technicalMetadata.getSerializationLibrary());
                // 表格儲存體位置(HMS, DLF等類型支援)
                System.out.println(technicalMetadata.getLocation());
                // 其他參數
                Map<String, String> parameters = technicalMetadata.getParameters();
                // 上次DDL表更時間(毫秒級時間戳記),僅MaxCompute類型支援
                System.out.println(parameters.get("lastDDLTime"));
                // 生命週期(單位:天),僅MaxCompute類型支援
                System.out.println(parameters.get("lifecycle"));
                // 儲存量(單位:位元組),非即時,僅MaxCompute類型支援
                System.out.println(parameters.get("dataSize"));
                // 表的業務中繼資料
                TableBusinessMetadata businessMetadata = table.getBusinessMetadata();
                if (businessMetadata != null) {
                    // 使用說明
                    System.out.println(businessMetadata.getReadme());
                    // 自訂標籤資訊
                    List<TableBusinessMetadataTags> tags = businessMetadata.getTags();
                    if (tags != null && !tags.isEmpty()) {
                        for (TableBusinessMetadataTags tag : tags) {
                            System.out.println(tag.getKey() + ":" + tag.getValue());
                        }
                    }
                    // 上遊產出任務
                    List<TableBusinessMetadataUpstreamTasks> upstreamTasks = businessMetadata.getUpstreamTasks();
                    if (upstreamTasks != null && !upstreamTasks.isEmpty()) {
                        for (TableBusinessMetadataUpstreamTasks upstreamTask : upstreamTasks) {
                            // 任務ID 與任務名稱,可通過 GetTask API 擷取任務詳情資訊
                            System.out.println(upstreamTask.getId() + ":" + upstreamTask.getName());
                        }
                    }
                    // 所屬類目資訊
                    List<List<TableBusinessMetadataCategories>> categories = businessMetadata.getCategories();
                    if (categories != null && !categories.isEmpty()) {
                        // 多級類目的遍曆
                        for (List<TableBusinessMetadataCategories> category : categories) {
                            // 輸出單個多級類目
                            System.out.println(
                                    category.stream()
                                            .map(TableBusinessMetadataCategories::getName)
                                            .collect(Collectors.joining("->"))
                            );
                        }
                    }
                    // 擴充資訊,僅MaxCompute類型支援
                    TableBusinessMetadataExtension extension = businessMetadata.getExtension();
                    if (extension != null) {
                        // 所屬專案空間ID
                        System.out.println(extension.getProjectId());
                        // 所屬環境類型,Prod 生產 / Dev 開發 
                        System.out.println(extension.getEnvType());
                        // 收藏次數
                        System.out.println(extension.getFavorCount());
                        // 讀取次數
                        System.out.println(extension.getReadCount());
                        // 瀏覽次數
                        System.out.println(extension.getViewCount());
                    }
                }
                return table;
            } catch (TeaException error) {
                // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                // 錯誤 message
                System.out.println(error.getMessage());
                // 診斷地址
                System.out.println(error.getData().get("Recommend"));
                com.aliyun.teautil.Common.assertAsString(error.message);
            } catch (Exception _error) {
                TeaException error = new TeaException(_error.getMessage(), _error);
                // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                // 錯誤 message
                System.out.println(error.getMessage());
                // 診斷地址
                System.out.println(error.getData().get("Recommend"));
                com.aliyun.teautil.Common.assertAsString(error.message);
            }
            return null;
        }
    
        /**
         * DataWorks OpenAPI : ListColumns
         *
         * @param listColumnsDto
         */
        public ListColumnsResponseBodyPagingInfo listColumns(ListColumnsDto listColumnsDto) {
            try {
                Client client = dataWorksOpenApiClient.createClient();
                ListColumnsRequest listColumnsRequest = new ListColumnsRequest();
                // 資料表ID,可通過 ListTables 介面擷取
                listColumnsRequest.setTableId(listColumnsDto.getTableId());
                // 欄位名稱,支援模糊比對
                listColumnsRequest.setName(listColumnsDto.getName());
                // 欄位注釋,支援分詞匹配
                listColumnsRequest.setComment(listColumnsDto.getComment());
                // 排序方式,支援 Name / Position (預設)
                listColumnsRequest.setSortBy(listColumnsDto.getSortBy());
                // 排序方向,支援 Asc (預設) / Desc
                listColumnsRequest.setOrder(listColumnsDto.getOrder());
                // 分頁頁碼,預設為1
                listColumnsRequest.setPageNumber(listColumnsDto.getPageNumber());
                // 分頁大小,預設為10,最大100
                listColumnsRequest.setPageSize(listColumnsDto.getPageSize());
                ListColumnsResponse response = client.listColumns(listColumnsRequest);
                // 擷取符合要求的欄位總數
                System.out.println(response.getBody().getRequestId());
                System.out.println(response.getBody().getPagingInfo().getTotalCount());
                for (Column column : response.getBody().getPagingInfo().getColumns()) {
                    // 欄位ID
                    System.out.println(column.getId());
                    // 欄位名稱
                    System.out.println(column.getName());
                    // 欄位注釋
                    System.out.println(column.getComment());
                    // 欄位類型
                    System.out.println(column.getType());
                    // 欄位位置
                    System.out.println(column.getPosition());
                    // 是否分區欄位
                    System.out.println(column.getPartitionKey());
                    // 是否主鍵,僅MaxCompute類型支援
                    System.out.println(column.getPrimaryKey());
                    // 是否外鍵,僅MaxCompute類型支援
                    System.out.println(column.getForeignKey());
                    // 欄位業務說明,目前MaxCompute, HMS(EMR叢集), DLF類型支援
                    if (column.getBusinessMetadata() != null) {
                        System.out.println(column.getBusinessMetadata().getDescription());
                    }
                }
                return response.getBody().getPagingInfo();
            } catch (TeaException error) {
                // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                // 錯誤 message
                System.out.println(error.getMessage());
                // 診斷地址
                System.out.println(error.getData().get("Recommend"));
                com.aliyun.teautil.Common.assertAsString(error.message);
            } catch (Exception _error) {
                TeaException error = new TeaException(_error.getMessage(), _error);
                // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                // 錯誤 message
                System.out.println(error.getMessage());
                // 診斷地址
                System.out.println(error.getData().get("Recommend"));
                com.aliyun.teautil.Common.assertAsString(error.message);
            }
            return null;
        }
    
        /**
         * DataWorks OpenAPI : UpdateColumnBusinessMetadata
         *
         * @param updateColumnBusinessMetadataDto
         */
        public boolean updateColumnBusinessMetadata(UpdateColumnBusinessMetadataDto updateColumnBusinessMetadataDto) {
            try {
                // 當前支援MaxCompute, DLF, HMS(EMR叢集)類型
                Client client = dataWorksOpenApiClient.createClient();
                UpdateColumnBusinessMetadataRequest updateColumnBusinessMetadataRequest = new UpdateColumnBusinessMetadataRequest();
                // 欄位ID
                updateColumnBusinessMetadataRequest.setId(updateColumnBusinessMetadataDto.getId());
                // 欄位業務說明
                updateColumnBusinessMetadataRequest.setDescription(updateColumnBusinessMetadataDto.getDescription());
                UpdateColumnBusinessMetadataResponse response = client.updateColumnBusinessMetadata(updateColumnBusinessMetadataRequest);
                System.out.println(response.getBody().getRequestId());
                // 是否更新成功
                return response.getBody().getSuccess();
            } catch (TeaException error) {
                // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                // 錯誤 message
                System.out.println(error.getMessage());
                // 診斷地址
                System.out.println(error.getData().get("Recommend"));
                com.aliyun.teautil.Common.assertAsString(error.message);
            } catch (Exception _error) {
                TeaException error = new TeaException(_error.getMessage(), _error);
                // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                // 錯誤 message
                System.out.println(error.getMessage());
                // 診斷地址
                System.out.println(error.getData().get("Recommend"));
                com.aliyun.teautil.Common.assertAsString(error.message);
            }
            return false;
        }
    
        /**
         * DataWorks OpenAPI : ListPartitions
         *
         * @param listPartitionsDto
         */
        public ListPartitionsResponseBodyPagingInfo listPartitions(ListPartitionsDto listPartitionsDto) {
            try {
                // 當前支援 MaxCompute類型和HMS(EMR叢集)類型
                Client client = dataWorksOpenApiClient.createClient();
                ListPartitionsRequest listPartitionsRequest = new ListPartitionsRequest();
                // 資料表ID,可通過 ListTables 介面擷取
                listPartitionsRequest.setTableId(listPartitionsDto.getTableId());
                // 分區名稱,支援模糊比對
                listPartitionsRequest.setName(listPartitionsDto.getName());
                // 排序方式,支援 Name(HMS方式預設,Maxcompute類型支援) / CreateTime (MaxCompute類型預設) / ModifyTime (MaxCompute類型支援)/ RecordCount(MaxCompute類型支援)/ DataSize(MaxCompute類型支援)
                listPartitionsRequest.setSortBy(listPartitionsDto.getSortBy());
                // 排序方向,支援 Asc (預設) / Desc
                listPartitionsRequest.setOrder(listPartitionsDto.getOrder());
                // 分頁頁碼,預設為1
                listPartitionsRequest.setPageNumber(listPartitionsDto.getPageNumber());
                // 分頁大小,預設為10,最大100
                listPartitionsRequest.setPageSize(listPartitionsDto.getPageSize());
                ListPartitionsResponse response = client.listPartitions(listPartitionsRequest);
                // 擷取符合要求的分區總數
                System.out.println(response.getBody().getPagingInfo().getTotalCount());
                for (Partition partition : response.getBody().getPagingInfo().getPartitionList()) {
                    // 所屬資料表ID
                    System.out.println(partition.getTableId());
                    // 分區名稱
                    System.out.println(partition.getName());
                    // 建立時間(毫秒級時間戳記)
                    System.out.println(partition.getCreateTime());
                    // 修改時間(毫秒級時間戳記)
                    System.out.println(partition.getModifyTime());
                    // 分區記錄數,僅MaxCompute類型支援
                    System.out.println(partition.getRecordCount());
                    // 分區儲存量(單位:位元組),僅MaxCompute類型支援
                    System.out.println(partition.getDataSize());
                }
                return response.getBody().getPagingInfo();
            } catch (TeaException error) {
                // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                // 錯誤 message
                System.out.println(error.getMessage());
                // 診斷地址
                System.out.println(error.getData().get("Recommend"));
                com.aliyun.teautil.Common.assertAsString(error.message);
            } catch (Exception _error) {
                TeaException error = new TeaException(_error.getMessage(), _error);
                // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                // 錯誤 message
                System.out.println(error.getMessage());
                // 診斷地址
                System.out.println(error.getData().get("Recommend"));
                com.aliyun.teautil.Common.assertAsString(error.message);
            }
            return null;
        }
    }
  2. MetaRestController中提供四個方法分別為getTablelistColumnslistPartitionsupdateColumnBusinessMetadata供前端進行調用。

    /**
     * 示範如何通過DataWorks OpenAPI構建自訂中繼資料平台
     *
     * @author dataworks demo
     */
    @RestController
    @RequestMapping("/meta")
    public class MetaRestController {
    
        @Autowired
        private MetaServiceProxy metaService;
    
        /**
         * 擷取表詳情
         *
         * @param getTableDto
         * @return {@link Table}
         */
        @CrossOrigin(origins = "http://localhost:8080")
        @GetMapping("/getTable")
        public Table getTable(GetTableDto getTableDto) {
            System.out.println("getTableDto = " + getTableDto);
            return metaService.getTable(getTableDto);
        }
    
        /**
         * 分頁查詢欄位資料
         *
         * @param listColumnsDto
         * @return {@link ListColumnsResponseBodyPagingInfo}
         */
        @CrossOrigin(origins = "http://localhost:8080")
        @GetMapping("/listColumns")
        public ListColumnsResponseBodyPagingInfo listColumns(ListColumnsDto listColumnsDto) {
            System.out.println("listColumnsDto = " + listColumnsDto);
            return metaService.listColumns(listColumnsDto);
        }
    
        /**
         * 更新欄位業務說明
         *
         * @param updateColumnBusinessMetadataDto
         * @return {@link Boolean}
         */
        @CrossOrigin(origins = "http://localhost:8080")
        @PostMapping("/updateColumnBusinessMetadata")
        public Boolean updateColumnBusinessMetadata(@RequestBody UpdateColumnBusinessMetadataDto updateColumnBusinessMetadataDto) {
            System.out.println("updateColumnBusinessMetadataDto = " + updateColumnBusinessMetadataDto);
            return metaService.updateColumnBusinessMetadata(updateColumnBusinessMetadataDto);
        }
    
        /**
         * 分頁查詢分區資料
         *
         * @param listPartitionsDto
         * @return {@link ListPartitionsResponseBodyPagingInfo}
         */
        @CrossOrigin(origins = "http://localhost:8080")
        @GetMapping("/listPartitions")
        public ListPartitionsResponseBodyPagingInfo listPartitions(ListPartitionsDto listPartitionsDto) {
            System.out.println("listPartitionsDto = " + listPartitionsDto);
            return metaService.listPartitions(listPartitionsDto);
        }
    }

前端:展示表基本資料、表欄位資訊以及表分區的資訊。

import React from "react";
import moment from "moment";
import {
  Form,
  Grid,
  Table,
  Pagination,
  Tag,
  Field,
  Input,
  Button,
  Select,
  Dialog,
} from "@alifd/next";
import cn from "classnames";
import * as services from "../services";
import {
  type ListColumnsInput,
  type TableColumn,
  type TableEntity,
  type TablePartition,
} from "../services/meta";
import classes from "../styles/detailContent.module.css";

export interface Props {
  item: TableEntity;
}

const formItemLayout = {
  labelCol: {
    fixedSpan: 6,
  },
  wrapperCol: {
    span: 18,
  },
  labelTextAlign: "left" as const,
  colon: true,
  className: cn(classes.formItemWrapper),
};
const { Row, Col } = Grid;
const { Item } = Form;
const { Column } = Table;
const { Option } = Select;
const DetailContent: React.FunctionComponent<Props> = (props) => {
  let columnsPageSize = 10;
  let partitionsPageSize = 5;
  const listColumnsField = Field.useField();
  const listPartitionsField = Field.useField();
  const labelAlign = "top";
  const [detail, setDetail] = React.useState<Partial<TableEntity>>({});
  const [columns, setColumns] = React.useState<Partial<TableColumn[]>>([]);
  const [partitions, setPartitions] = React.useState<Partial<TablePartition[]>>(
    []
  );
  const [columnsTotal, setColumnsTotal] = React.useState<number>(0);
  const [partitionTotal, setPartitionTotal] = React.useState<number>(0);
  const [editingKey, setEditingKey] = React.useState<string>("");
  const [editingDescription, setEditingDescription] =
    React.useState<string>("");

  const [updateColumnDialogVisible, setUpdateColumnDialogVisible] =
    React.useState<boolean>(false);
  const onUpdateColumnDialogOpen = () => {
    setUpdateColumnDialogVisible(true);
  };
  const onUpdateColumnDialogOk = () => {
    handleColumnDescriptionSave();
    onUpdateColumnDialogClose();
  };
  const onUpdateColumnDialogClose = () => {
    setUpdateColumnDialogVisible(false);
  };
  const handleEditColumnDescription = async (record: TableColumn) => {
    setEditingKey(record.id);
    setEditingDescription(record.businessMetadata?.description);
    onUpdateColumnDialogOpen();
  };
  const handleColumnDesrciptionChange = async (e: string) => {
    setEditingDescription(e);
  };
  const handleColumnDescriptionSave = async () => {
    try {
      const res = await services.meta.updateColumnDescription({
        id: editingKey,
        description: editingDescription,
      });
      if (!res) {
        console.log("請求更新失敗");
      }
    } catch (e) {
      console.error("更新失敗", e);
    } finally {
      listColumns();
      setEditingKey("");
    }
  };
  const getTableDetail = React.useCallback(async () => {
    const response = await services.meta.getTableDetail({
      id: props.item.id,
      includeBusinessMetadata: true,
    });
    setDetail(response);
  }, [props.item.id]);
  const listColumns = React.useCallback(
    async (pageNumber: number = 1) => {
      listColumnsField.setValue("tableId", props.item.id);
      listColumnsField.setValue("pageSize", columnsPageSize);
      listColumnsField.validate(async (errors, values) => {
        if (errors) {
          return;
        }
        try {
          const response = await services.meta.getMetaTableColumns({
            pageNumber,
            ...values,
          } as ListColumnsInput);
          setColumns(response.columns);
          setColumnsTotal(response.totalCount);
        } catch (e) {
          throw e;
        } finally {
        }
      });
    },
    [listColumnsField]
  );
  const listPartitions = React.useCallback(
    async (pageNumber: number = 1) => {
      listPartitionsField.setValue("tableId", props.item.id);
      listPartitionsField.setValue("pageSize", partitionsPageSize);
      listPartitionsField.validate(async (errors, values) => {
        if (errors) {
          return;
        }
        try {
          const response = await services.meta.getTablePartition({
            pageNumber,
            ...values,
          } as ListColumnsInput);
          setPartitions(response.partitionList);
          setPartitionTotal(response.totalCount);
        } catch (e) {
          throw e;
        } finally {
        }
      });
    },
    [listPartitionsField]
  );
  React.useEffect(() => {
    if (props.item.id) {
      getTableDetail();
      listColumns();
      listPartitions();
    }
  }, [props.item.id]);
  return (
    <div className={cn(classes.detailContentWrapper)}>
      <Dialog
        v2
        title="更新欄位業務中繼資料"
        visible={updateColumnDialogVisible}
        onOk={onUpdateColumnDialogOk}
        onClose={onUpdateColumnDialogClose}
      >
        <Row>
          <Col>
            <Item
              {...formItemLayout}
              labelAlign={labelAlign}
              label="欄位業務說明"
            >
              <Input
                value={editingDescription}
                onChange={(e) => handleColumnDesrciptionChange(e.toString())}
              ></Input>
            </Item>
          </Col>
        </Row>
      </Dialog>
      <Form labelTextAlign="left">
        <Row>
          <Col>
            <Item {...formItemLayout} label="表ID">
              <span className={cn(classes.formContentWrapper)}>
                {detail.id}
              </span>
            </Item>
          </Col>
        </Row>
        <Row>
          <Col>
            <Item {...formItemLayout} label="MaxCompute專案">
              <span className={cn(classes.formContentWrapper)}>
                {detail?.id?.split(":").slice(-3, -2)[0]}
              </span>
            </Item>
          </Col>
          <Col>
            <Item {...formItemLayout} label="schema名稱">
              <span className={cn(classes.formContentWrapper)}>
                {detail?.id?.split(":").slice(-2, -1)[0]}
              </span>
            </Item>
          </Col>
        </Row>
        <Row>
          <Col>
            <Item {...formItemLayout} label="表名稱">
              <span className={cn(classes.formContentWrapper)}>
                {detail.name}
              </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.tableType}
              </span>
            </Item>
          </Col>
          <Col>
            <Item {...formItemLayout} label="是否壓縮表">
              <span className={cn(classes.formContentWrapper)}>
                {detail.technicalMetadata?.compressed ? "是" : "否"}
              </span>
            </Item>
          </Col>
        </Row>
        <Row>
          <Col>
            <Item {...formItemLayout} label="所屬工作空間ID">
              <span className={cn(classes.formContentWrapper)}>
                {detail.businessMetadata?.extension?.projectId}
              </span>
            </Item>
          </Col>
          <Col>
            <Item {...formItemLayout} label="環境">
              <span className={cn(classes.formContentWrapper)}>
                {detail.businessMetadata?.extension?.envType === "Dev"
                  ? "開發"
                  : "生產"}
              </span>
            </Item>
          </Col>
        </Row>
        <Row>
          <Col>
            <Item {...formItemLayout} label="表生命週期">
              <span className={cn(classes.formContentWrapper)}>
                {detail.technicalMetadata?.parameters["lifecycle"]
                  ? `${detail.technicalMetadata?.parameters["lifecycle"]} 天`
                  : "永久"}
              </span>
            </Item>
          </Col>
          <Col>
            <Item {...formItemLayout} label="儲存空間">
              <span
                className={cn(classes.formContentWrapper)}
              >{`${detail.technicalMetadata?.parameters["dataSize"]} Bytes`}</span>
            </Item>
          </Col>
        </Row>
        <Row>
          <Col>
            <Item {...formItemLayout} label="DDL上次變更時間">
              <span className={cn(classes.formContentWrapper)}>
                {moment(
                  Number(detail.technicalMetadata?.parameters["lastDDLTime"])
                ).format("YYYY-MM-DD HH:mm:ss")}
              </span>
            </Item>
          </Col>
          <Col>
            <Item {...formItemLayout} label="是否為分區表">
              <span className={cn(classes.formContentWrapper)}>
                {detail.partitionKeys != null && detail.partitionKeys.length > 0
                  ? "是"
                  : "否"}
              </span>
            </Item>
          </Col>
        </Row>
        <Row>
          <Col>
            <Item {...formItemLayout} label="標籤">
              <div className={cn(classes.formContentWrapper)}>
                {detail.businessMetadata?.tags?.map((tag, index) => (
                  <span key={index} className={cn(classes.tag)}>
                    <Tag type="primary" size="small">
                      {tag.key + (tag.value ? ":" + tag.value : "")}
                    </Tag>
                  </span>
                ))}
              </div>
            </Item>
          </Col>
        </Row>
        <Row>
          <Col>
            <Item {...formItemLayout} label="類目">
              <span className={cn(classes.formContentWrapper)}>
                {detail.businessMetadata?.categories?.map((category, index) => (
                  <span key={index} className={cn(classes.tag)}>
                    <Tag type="normal" color="gray" size="small">
                      {category.map((obj) => obj.name).join("->")}
                    </Tag>
                  </span>
                ))}
              </span>
            </Item>
          </Col>
        </Row>
        <Row>
          <Col>
            <Item {...formItemLayout} label="上遊產出任務">
              <span className={cn(classes.formContentWrapper)}>
                {detail.businessMetadata?.upstreamTasks?.map((task, index) => (
                  <span key={index} className={cn(classes.tag)}>
                    <Tag type="primary" size="small">
                      {task.name + " (" + task.id + ")"}
                    </Tag>
                  </span>
                ))}
              </span>
            </Item>
          </Col>
        </Row>
        <Row>
          <Col>
            <Item {...formItemLayout} label="瀏覽次數">
              <span className={cn(classes.formContentWrapper)}>
                <Tag type="primary" size="small">
                  {detail.businessMetadata?.extension?.viewCount}
                </Tag>
              </span>
            </Item>
          </Col>
          <Col>
            <Item {...formItemLayout} label="讀取次數">
              <span className={cn(classes.formContentWrapper)}>
                <Tag type="primary" size="small">
                  {detail.businessMetadata?.extension?.readCount}
                </Tag>
              </span>
            </Item>
          </Col>
        </Row>
        <Row>
          <Col>
            <Item {...formItemLayout} label="收藏次數">
              <span className={cn(classes.formContentWrapper)}>
                <Tag type="primary" size="small">
                  {detail.businessMetadata?.extension?.favorCount}
                </Tag>
              </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.modifyTime).format("YYYY-MM-DD HH:mm:ss")}
              </span>
            </Item>
          </Col>
        </Row>
        <br />
        <Item label="欄位詳情" colon>
          <Form
            field={listColumnsField}
            colon
            fullWidth
            style={{ marginTop: 10, marginBottom: 10 }}
          >
            <Row gutter="4">
              <Col>
                <Item
                  {...formItemLayout}
                  labelAlign={labelAlign}
                  label="名稱"
                  name="name"
                >
                  <Input placeholder="輸入欄位名稱,模糊比對" />
                </Item>
              </Col>
              <Col>
                <Item
                  {...formItemLayout}
                  labelAlign={labelAlign}
                  label="注釋"
                  name="comment"
                >
                  <Input placeholder="輸入欄位注釋,模糊比對" />
                </Item>
              </Col>
              <Col>
                <Item
                  {...formItemLayout}
                  labelAlign={labelAlign}
                  label="排序方式"
                  name="sortBy"
                >
                  <Select defaultValue="Position" style={{ width: "100%" }}>
                    <Option value="Position">位置</Option>
                    <Option value="Name">名稱</Option>
                  </Select>
                </Item>
              </Col>
              <Col>
                <Item
                  {...formItemLayout}
                  labelAlign={labelAlign}
                  label="排序方向"
                  name="order"
                >
                  <Select defaultValue="Asc" style={{ width: "100%" }}>
                    <Option value="Asc">升序</Option>
                    <Option value="Desc">降序</Option>
                  </Select>
                </Item>
              </Col>
              <Col>
                <div
                  className={cn(
                    classes.searchPanelButtonWrapper,
                    classes.buttonGroup
                  )}
                  style={{ marginTop: "20px", textAlign: "right" }}
                >
                  <Button type="primary" onClick={() => listColumns()}>
                    查詢
                  </Button>
                </div>
              </Col>
            </Row>
          </Form>
          <Table dataSource={columns}>
            <Column title="名稱" dataIndex="name" />
            <Column title="類型" dataIndex="type" />
            <Column title="注釋" dataIndex="comment" />
            <Column
              title="業務說明"
              dataIndex="businessMetadata"
              cell={(value, index, record) => (
                <div
                  style={{
                    display: "flex",
                    justifyContent: "space-between",
                    alignItems: "center",
                  }}
                >
                  <span>{value?.description}</span>
                  <Button
                    onClick={() => handleEditColumnDescription(record)}
                    size="small"
                    type="primary"
                    text
                    style={{ marginLeft: 8 }}
                  >
                    編輯
                  </Button>
                </div>
              )}
            />
            <Column
              title="是否為主鍵"
              dataIndex="primaryKey"
              cell={(value) => (value ? "是" : "")}
            />
            <Column
              title="是否為外鍵"
              dataIndex="foreignKey"
              cell={(value) => (value ? "是" : "")}
            />
            <Column
              title="是否為分區欄位"
              dataIndex="partitionKey"
              cell={(value) => (value ? "是" : "")}
            />
            <Column title="位置" dataIndex="position" />
          </Table>
          <Pagination
            total={columnsTotal}
            pageSize={columnsPageSize}
            onChange={listColumns}
            showJump={false}
            className={cn(classes.tablePaginationWrapper)}
          />
        </Item>
        <br />
        <Item label="分區詳情" style={{ marginBottom: 32 }} colon>
          <Form
            field={listPartitionsField}
            colon
            fullWidth
            style={{ marginTop: 10, marginBottom: 10 }}
          >
            <Row gutter="4">
              <Col>
                <Item
                  {...formItemLayout}
                  labelAlign={labelAlign}
                  label="名稱"
                  name="name"
                >
                  <Input placeholder="輸入分區名稱,模糊比對" />
                </Item>
              </Col>
              <Col>
                <Item
                  {...formItemLayout}
                  labelAlign={labelAlign}
                  label="排序方式"
                  name="sortBy"
                >
                  <Select defaultValue="CreateTime" style={{ width: "100%" }}>
                    <Option value="CreateTime">建立時間</Option>
                    <Option value="ModifyTime">修改時間</Option>
                    <Option value="Name">分區名稱</Option>
                    <Option value="RecordCount">分區記錄數</Option>
                    <Option value="DataSize">分區大小</Option>
                  </Select>
                </Item>
              </Col>
              <Col>
                <Item
                  {...formItemLayout}
                  labelAlign={labelAlign}
                  label="排序方向"
                  name="order"
                >
                  <Select defaultValue="Asc" style={{ width: "100%" }}>
                    <Option value="Asc">升序</Option>
                    <Option value="Desc">降序</Option>
                  </Select>
                </Item>
              </Col>
              <Col>
                <div
                  className={cn(
                    classes.searchPanelButtonWrapper,
                    classes.buttonGroup
                  )}
                  style={{ marginTop: "20px", textAlign: "right" }}
                >
                  <Button type="primary" onClick={() => listPartitions()}>
                    查詢
                  </Button>
                </div>
              </Col>
            </Row>
          </Form>
          <Table dataSource={partitions} emptyContent={<span>非分區表</span>}>
            <Column title="名稱" dataIndex="name" />
            <Column
              title="建立時間"
              dataIndex="createTime"
              cell={(value) => moment(value).format("YYYY-MM-DD HH:mm:ss")}
            />
            <Column
              title="修改時間"
              dataIndex="modifyTime"
              cell={(value) => moment(value).format("YYYY-MM-DD HH:mm:ss")}
            />
            <Column
              title="分區大小"
              dataIndex="dataSize"
              cell={(value) => `${value} bytes`}
            />
            <Column title="分區記錄數" dataIndex="recordCount" />
          </Table>
          <Pagination
            total={partitionTotal}
            pageSize={partitionsPageSize}
            onChange={listPartitions}
            showJump={false}
            className={cn(classes.tablePaginationWrapper)}
          />
        </Item>
      </Form>
    </div>
  );
};

export default DetailContent;

完成上述代碼開發後,您可在本地部署並運行工程代碼。部署並啟動並執行操作請參見通用操作:本地部署運行

三、尋找表血緣

當一張表口徑發生變更時,您可以通過DataWorks OpenAPI進行上下遊任務的血緣分析,尋找表對應的節點。

說明

使用ListLineages來查詢資料表的上下遊血緣。

後端:使用MetaServiceProxy查詢表血緣

  1. 在MetaServiceProxy中建立listTableLineages方法來調用OpenAPI服務擷取表的上下遊血緣實體和關係資訊。

    /**
     * @author dataworks demo
     */
    @Service
    public class MetaServiceProxy {
    
        @Autowired
        private DataWorksOpenApiClient dataWorksOpenApiClient;
    
        /**
         * DataWorks OpenAPI : ListLineages
         *
         * @param listTableLineagesDto
         */
        public ListLineagesResponseBodyPagingInfo listTableLineages(ListTableLineagesDto listTableLineagesDto) {
            try {
                Client client = dataWorksOpenApiClient.createClient();
                ListLineagesRequest listLineagesRequest = new ListLineagesRequest();
                // 血緣查詢方向,是否為上遊
                boolean needUpstream = "up".equalsIgnoreCase(listTableLineagesDto.getDirection());
                // 資料表ID,可通過 ListTables 介面擷取
                // 分區名稱,支援模糊比對
                if (needUpstream) {
                    listLineagesRequest.setDstEntityId(listTableLineagesDto.getTableId());
                    listLineagesRequest.setSrcEntityName(listTableLineagesDto.getName());
                } else {
                    listLineagesRequest.setSrcEntityId(listTableLineagesDto.getTableId());
                    listLineagesRequest.setDstEntityName(listTableLineagesDto.getName());
                }
                // 排序方式,支援 Name
                listLineagesRequest.setSortBy(listTableLineagesDto.getSortBy());
                // 排序方向,支援 Asc (預設) / Desc
                listLineagesRequest.setOrder(listTableLineagesDto.getOrder());
                // 分頁頁碼,預設為1
                listLineagesRequest.setPageNumber(listTableLineagesDto.getPageNumber());
                // 分頁大小,預設為10,最大100
                listLineagesRequest.setPageSize(listTableLineagesDto.getPageSize());
                // 是否需要返回具體的血緣關係資訊
                listLineagesRequest.setNeedAttachRelationship(true);
                ListLineagesResponse response = client.listLineages(listLineagesRequest);
                // 擷取符合要求的血緣實體總數
                System.out.println(response.getBody().getPagingInfo().getTotalCount());
                if (response.getBody().getPagingInfo().getLineages() == null) {
                    response.getBody().getPagingInfo().setLineages(Collections.emptyList());
                }
                if (response.getBody().getPagingInfo().getLineages() != null) {
                    for (ListLineagesResponseBodyPagingInfoLineages lineage : response.getBody().getPagingInfo().getLineages()) {
                        LineageEntity entity;
                        // 擷取對應血緣實體
                        if (needUpstream) {
                            entity = lineage.getSrcEntity();
                        } else {
                            entity = lineage.getDstEntity();
                        }
                        // 所屬實體ID
                        System.out.println(entity.getId());
                        // 所屬實體名稱
                        System.out.println(entity.getName());
                        // 所屬實體的屬性資訊
                        System.out.println(entity.getAttributes());
                        // 實體間血緣關係列表
                        List<LineageRelationship> relationships = lineage.getRelationships();
                        if (relationships != null) {
                            relationships.forEach(relationship -> {
                                // 血緣關係ID
                                System.out.println(relationship.getId());
                                // 建立時間
                                System.out.println(relationship.getCreateTime());
                                // 血緣關係對應的任務
                                LineageTask task = relationship.getTask();
                                if (task != null) {
                                    // 任務ID,對於DataWorks任務,可以通過GetTask擷取任務詳情資訊
                                    System.out.println(task.getId());
                                    // 任務類型
                                    System.out.println(task.getType());
                                    // 任務屬性資訊
                                    Map<String, String> attributes = task.getAttributes();
                                    if (attributes != null) {
                                        // 對於DataWorks任務,可以從中擷取任務和執行個體的屬性資訊
                                        // 專案空間ID
                                        System.out.println(attributes.get("projectId"));
                                        // 任務ID
                                        String taskId = attributes.containsKey("taskId") ? attributes.get("taskId") : attributes.get("nodeId");
                                        System.out.println(taskId);
                                        // 執行個體ID
                                        String taskInstanceId = attributes.containsKey("taskInstanceId") ? attributes.get("taskInstanceId") : attributes.get("jobId");
                                        System.out.println(taskInstanceId);
                                    }
                                }
                            });
                        }
                    }
                    return response.getBody().getPagingInfo();
                }
            } catch (TeaException error) {
                // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                // 錯誤 message
                System.out.println(error.getMessage());
                // 診斷地址
                System.out.println(error.getData().get("Recommend"));
                com.aliyun.teautil.Common.assertAsString(error.message);
            } catch (Exception _error) {
                TeaException error = new TeaException(_error.getMessage(), _error);
                // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
                // 錯誤 message
                System.out.println(error.getMessage());
                // 診斷地址
                System.out.println(error.getData().get("Recommend"));
                com.aliyun.teautil.Common.assertAsString(error.message);
            }
            return null;
        }
    }
  2. 在MetaRestController中提供listTableLineages方法供前端進行調用。

    /**
     * 示範如何通過DataWorks OpenAPI構建自訂中繼資料平台
     *
     * @author dataworks demo
     */
    @RestController
    @RequestMapping("/meta")
    public class MetaRestController {
    
        @Autowired
        private MetaServiceProxy metaService;
    
        /**
         * 查詢表的血緣關係
         *
         * @param listTableLineagesDto
         * @return
         */
        @CrossOrigin(origins = "http://localhost:8080")
        @GetMapping("/listTableLineages")
        public ListLineagesResponseBodyPagingInfo listTableLineages(ListTableLineagesDto listTableLineagesDto) {
            System.out.println("listTableLineagesDto = " + listTableLineagesDto);
            return metaService.listTableLineages(listTableLineagesDto);
        }
    
    }

前端:展示表血緣關係

import React from 'react';
import Graphin, { Behaviors } from '@antv/graphin';
import type { IUserNode, IUserEdge, GraphinData, GraphEvent } from '@antv/graphin';
import cn from 'classnames';
import * as services from '../services';
import type { TableEntity, ListTableLineageOutput, LineageEntity } from '../services/meta';
import classes from '../styles/lineageContent.module.css';
import '@antv/graphin/dist/index.css';

export interface Props {
  item: TableEntity;
}

function transNode(entity: LineageEntity | TableEntity, direction?: string): IUserNode {
  return {
    id: entity.id,
    label: entity.name,
    data: {
      ...entity,
      direction,
    },
    style: {
      label: {
        value: entity.name,
      }
    },
  }
}
function transEdge(source: LineageEntity | TableEntity, target: LineageEntity | TableEntity): IUserEdge {
  return {
    source: source.id,
    target: target.id,
    style: {
      keyshape: {
        lineDash: [8, 4],
        lineWidth: 2,
      },
      animate: {
        type: 'line-dash',
        repeat: true,
      },
    }
  };
}
function parse(
  source: LineageEntity | TableEntity,
  data: ListTableLineageOutput,
  direction: string,
): [IUserNode[], IUserEdge[]] {
  const nodes: IUserNode[] = [];
  const edges: IUserEdge[] = [];
  data.lineages.forEach((lineageRelationship) => {
    if (direction === 'down') {
      nodes.push(transNode(lineageRelationship.dstEntity, direction));
      edges.push(transEdge(source, lineageRelationship.dstEntity));
    }  else {
      nodes.push(transNode(lineageRelationship.srcEntity, direction));
      edges.push(transEdge(lineageRelationship.srcEntity, 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: TableEntity | LineageEntity,
    direction: string,
  ) => {
    if (!direction) {
      return collection;
    }
    const response = await services.meta.getTableLineage({
      direction,
      tableId: target.id,
    });
    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 className={cn(classes.lineageContentWrapper)}>
      <Graphin
        data={data}
        ref={ref as React.LegacyRef<Graphin>}
        layout={{
          type: 'dagre',
          rankdir: 'LR',
          align: 'DL',
          nodesep: 10,
          ranksep: 40,
        }}
      >
        <ActivateRelations />
        <DragNode/>
        <ZoomCanvas enableOptimize />
      </Graphin>
    </div>
  );
};

export default LineageContent;

完成上述代碼開發後,您可在本地部署並運行工程代碼。部署並啟動並執行操作請參見通用操作:本地部署運行

四、尋找表對應的任務

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

說明

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

血緣任務

  1. 通過ListLineages,查看錶的血緣關係,從血緣關係的Task 中擷取DataWorks 任務ID (以及任務執行個體ID)。

  2. 根據擷取的任務ID,使用GetTask擷取任務詳情。

產出任務

  1. 根據實體ID,通過 GetTableAPI擷取表的業務中繼資料,其中的上遊產出任務中包含有DataWorks任務ID。

  2. 根據指定任務ID,通過GetTask擷取任務詳情。

通用操作:本地部署運行

  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)擷取AccessKey等阿里雲帳號的相關資訊。

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

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

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

    npm run dev