优维低代码技术专栏,是一个全新的、技术为主的专栏,由优维技术委员会成员执笔,基于优维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 yo
yarn 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-provider
File created: ./bricks/search/src/data-providers/DemoProvider.spec.ts
File created: ./bricks/search/src/data-providers/DemoProvider.ts
File 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 会有几个问题:
- 跨域的问题
- 安全的问题
建议统一接入到优维的 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 lang = i18next.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)