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://help.aliyun.com/document_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;
完成上述代码开发后,您可在本地部署并运行工程代码。部署并运行的操作请参见通用操作:本地部署运行。
完成上述实践步骤后,您可以输入MaxCompute项目空间ID与数据库名称后进行查询,获得项目空间下的所有表的列表,也可以做分页查询,如下图所示。
实践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://help.aliyun.com/document_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://help.aliyun.com/document_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://help.aliyun.com/document_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