导语

优维低代码技术专栏,是一个全新的、技术为主的专栏,由优维技术委员会成员执笔,基于优维7年低代码技术研发及运维成果,主要介绍低代码相关的技术原理及架构逻辑,目的是给广大运维人提供一个技术交流与学习的平台。

连载第四十三期

《现场定制:定制Providers》

# 何为 provider ?

provider 也是一种构件,设计的原意是为了封装后台接口,提供统一的前端 SDK 。在介绍 provider 之前,要先介绍下优维科技在 2019 年开始推行的“契约为中心”的开发模式。

在 2019 年前,优维科技的后台主流开发语言为 python 和 php ,前端则为 JavaScript。因为这弎都是弱类型,开发者一不注意,接口的输入和输出就会出现了大量的 map。随着系统的不断膨胀,在接口对接过程中,总是会出现各种字段不一致的情况,特别是在重构的时候,就更加是“动态类型一时爽,代码重构火葬场”。因此,2019 年,整个优维技术研发部开始推行以“契约为中心”的开发模式,后台主流开发语言切到了 go,前端开发语言也切到了 TypeScript。

在开发一个接口的时候,都要先定义契约(点击查看 接口契约介绍 ),然后再基于该契约直接生成前端的 SDK(provider)和后端的框架代码及后端的 SDK(go,python)及 API 文档。这样,前后台都强制遵循契约精神,保证各方统一。

# 定制化 provider

我们推行前端尽量少写处理逻辑,当前我们绝大部分 provider 都是自动生成的(点击查看内置 provider 文档),不需要写任何一行代码就可以将展示构件与后台接口对接起来。但,不可否认的,在某些特殊场景还是需要写些处理逻辑,另外,如果有第三方 API 数据接入的时候,也需要写定制 provider。

yarn yo脚手架封装了 provider 的生成。参考如下,按提示执行:

➜  brick-next git:(master) ✗ yarn yoyarn run v1.12.3$ brick-scripts? What do you want? a new custom provider brick? which package do you want to put the new brick in? search? What's the name of your new brick (in lower-kebab-case)? provider-demo-providerFile created: ./bricks/search/src/data-providers/DemoProvider.spec.tsFile created: ./bricks/search/src/data-providers/DemoProvider.tsFile updated: ./bricks/search/src/index.ts
No worries!✨  Done in 53.99s.

# 示例

# 封装第三方 API 接口请求

import { createProviderClass } from "@next-core/brick-utils";import { http } from "@next-core/brick-http";
export interface TestParams {  a: string;  b: string;}
export async function Test(  params: TestParams): Promise<any> {  return http.put(    "http://localhost:8080/test",    params  );}
customElements.define(  "demo.provider-test",  createProviderClass(Test));

注意:请检查项目一级 package.json 的 devDependencies 有没声明@next-core/brick-http 的依赖,如果没有,请加入:

  • "@next-core/brick-http": "^1.0.0",

  • "@next-core/brick-dll": "^1.0.61",

第三方接口接入优维的 api_gateway

如上示例直接请求后端接口 http://localhost:8080/test 会有几个问题:

  1. 跨域的问题

  2. 安全的问题

建议统一接入到优维的 api_gateway 来转发,具体配置方式见第三方接口接入。由此,这里需要改为:

import { createProviderClass } from "@next-core/brick-utils";import { http } from "@next-core/brick-http";
export interface TestParams {  a: string;  b: string;}
export async function Test(  params: TestParams): Promise<any> {  return http.put(    // 注意不要写成全路径/api,而应该写成 api    "api/gateway/your-api-prefix/test",    params  );}
customElements.define(  "demo.provider-test",  createProviderClass(Test));

# 纯逻辑处理的 provider

index.ts

import { createProviderClass } from "@next-core/brick-utils";
import { listBrickStory, categoryList } from "./processor";
customElements.define(  "developers.providers-of-brick-story",  createProviderClass(listBrickStory));
customElements.define(  "developers.get-category-list",  createProviderClass(categoryList));

processor.ts

import i18next from "i18next";import { MenuIcon } from "@next-core/brick-types";import { atomBook } from "../stories/chapters/atom-bricks";import { businessBook } from "../stories/chapters/business-bricks";import { Story, Chapter, I18nString } from "../stories/interfaces";
export const categoryList = (storyType: string): Promise<string[]> => {  let books: Chapter[] = [];  if (storyType === "atom") {    books = atomBook;  } else if (storyType === "business") {    books = businessBook;  }  const.language    ? (i18next.language.split("-")[0] as keyof I18nString)    : "zh";  const categoryList = books.map((book: Chapter) => {    return book.title[lang];  });  return Promise.resolve(categoryList);};
// 省略 listBrickStory 函数

# 基于已有 SDK 修改

import { createProviderClass } from "@next-core/brick-utils";import { HttpOptions } from "@next-core/brick-http";import { InstanceTreeApi } from "@sdk/cmdb-sdk";import { AntTreeNodeProps } from "antd/lib/tree";import { MenuIcon } from "@next-core/brick-types";import { CustomIconComponentProps } from "antd/lib/icon";
import { Instance } from "../../interfaces";
interface Business extends Instance {  _businesses_APP?: Instance[];  _sub_system?: Business[];}
type TreeIcon = MenuIcon | React.ComponentType<CustomIconComponentProps>;
export interface BrickTreeNodeProps extends AntTreeNodeProps {  title?: string;  icon?: TreeIcon;  children?: BrickTreeNodeProps[];}
function convertBusinessesToTreeNodes(businesses: Business[]) {  const treeNodes: BrickTreeNodeProps[] = [];
 businesses.forEach((business) => {    let children: BrickTreeNodeProps[] = [];
   if (business._sub_system) {      children = children.concat(        convertBusinessesToTreeNodes(business._sub_system)      );    }
   business._businesses_APP &&      business._businesses_APP.forEach((app) => {        children.push({          title: app.name,          key: app.instanceId,          icon: { lib: "fa", icon: "cube" },        });      });
   if (children.length > 0) {      treeNodes.push({        title: business.name,        key: `_${business.instanceId}`,        icon: { lib: "fa", icon: "briefcase" },        selectable: false,        children,      });    }  });
 return treeNodes;}
async function getBusinessAppTree(options?: HttpOptions) {  const data: {    BUSINESS?: Business[];    APP?: Instance[];  } = await InstanceTreeApi.instanceTree(    {      tree: {        object_id: "BUSINESS",        fields: { name: true },        child: [          { relation_field_id: "_sub_system", fields: { name: true } },          { relation_field_id: "_businesses_APP", fields: { name: true } },        ],      },    },    options  );  let treeNodes: BrickTreeNodeProps[] = [];
 if (data.BUSINESS) {    treeNodes = treeNodes.concat(convertBusinessesToTreeNodes(data.BUSINESS));  }
 data.APP &&    data.APP.forEach((app) => {      treeNodes.push({        title: app.name,        key: app.instanceId,        icon: { lib: "fa", icon: "cube" },      });    });
 return treeNodes;}
customElements.define(  "app-deploy-statistics.provider-business-app-tree",  createProviderClass(getBusinessAppTree));

# 使用方式

点击查看[构件事件传递](/next-docs/docs/micro-app/brick-event#调用 provider)



- end -