Uknow | 优维低代码:定制 Providers
导语
连载第四十三期
《现场定制:定制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 的生成。参考如下,按提示执行: # 示例 # 封装第三方 API 接口请求 注意:请检查项目一级 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 来转发,具体配置方式见第三方接口接入。由此,这里需要改为: # 纯逻辑处理的 provider index.ts processor.ts # 基于已有 SDK 修改 # 使用方式 点击查看[构件事件传递](/next-docs/docs/micro-app/brick-event#调用 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.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));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));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));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 函数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));
- end -