DataWorks提供了豐富的OpenAPI,您可以根據需要使用DataWorks的OpenAPI等開放能力實現各種業務情境。本文以中繼資料表管理為例,為您介紹如何串聯中繼資料的OpenAPI來達成查詢列表、查詢表詳情、血緣圖展示與建立表等操作。
背景資訊
在進行本實踐之前,建議您先參考以下連結,瞭解DataWorks的OpenAPI的基本能力和概念:
下文為您提供了表管理的多個細分情境的實踐:
實踐1:查詢表列表
以下實踐將介紹如何使用OpenAPI來實現查詢表列表,獲得MaxCompute專案下的所有表的列表,並做分頁查詢,實踐的主要流程如下。
在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; } }
在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); } }
在前端實現一個簡易的可互動介面。
說明下方代碼簡化了部分非表查詢流程,只展示表查詢的最小實現。
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;
完成上述代碼開發後,您可在本地部署並運行工程代碼。部署並啟動並執行操作請參見通用操作:本地部署運行。
實踐2:查詢表詳情
以下實踐將結合中繼資料中GetMetaTableBasicInfo、GetMetaTableColumn與GetMetaTablePartition三個OpenAPI來實現查詢表詳情,實踐操作流程如下。
在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; } }
在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); } }
在前端實現一個簡易的詳情頁來調用表基本資料、表列資訊與表分區的介面,並展示出來。
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;
完成上述代碼開發後,您可在本地部署並運行工程代碼。部署並啟動並執行操作請參見通用操作:本地部署運行。
實踐3:尋找表的血緣資訊並上下鑽
以下實踐將使用中繼資料中GetMetaTableLineage來尋找表的血緣資訊以及上下鑽的能力,實踐流程如下。
在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; } }
在入口處定義一個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); } }
在前端實現一個調用血緣圖介面的能力並渲染出來。
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:尋找表對應的節點
當一張表口徑發生變更時,您可以通過DataWorks OpenAPI、OpenData、訊息訂閱的方式進行下遊任務的血緣分析,尋找表對應的節點。具體操作如下。
通過GetMetaColumnLineage和GetMetaTableLineage,查看錶的血緣關係。
說明訊息目前支援表變更、任務變更等。企業版使用者可以對接表變更的訊息,當接收到表變更的時候,您可以查看錶的血緣關係。
GetMetaColumnLineage為欄位血緣,GetMetaTableLineage為表血緣。查詢表的血緣也可以改成查詢任務的血緣。
根據欄位血緣或表血緣,查到受影響的表的列表,根據表列表,通過GetMetaTableOutput,擷取表的任務ID。
根據指定任務ID,通過GetNode擷取任務詳情,確認對的業務影響。
通用操作:本地部署運行
準備依賴環境。
您需準備好以下依賴環境:java8及以上、maven構建工具、node環境、pnpm工具。您可以執行以下命令來確定是否具備上述環境:
npm -v // 如果已安裝成功,執行命令將展示版本號碼,否則會報沒有命令錯誤 java -version // 如果已安裝成功,執行命令將展示版本號碼,否則會報沒有命令錯誤 pnpm -v // 如果已安裝成功,執行命令將展示版本號碼,否則會報沒有命令錯誤
下載工程代碼並執行以下命令。
工程代碼下載連結:meta-api-demo.zip
pnpm i
在範例工程中的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的地區資訊。
其他參數可根據實際情況修改,修改後的填寫樣本如下。
在工程根目錄下執行以下命令,運行樣本實踐代碼。
npm run dev