feat: midway注解方式编写插件

pull/9/head^2
xiaojunnuo 2023-01-07 23:22:02 +08:00
parent e4ec4e1404
commit 52522f27e9
16 changed files with 156 additions and 238 deletions

View File

@ -17,7 +17,8 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"log4js": "^6.3.0", "log4js": "^6.3.0",
"node-forge": "^0.10.0", "node-forge": "^0.10.0",
"qs": "^6.9.4" "qs": "^6.9.4",
"reflect-metadata": "^0.1.13"
}, },
"devDependencies": { "devDependencies": {
"@alicloud/cs20151215": "^3.0.3", "@alicloud/cs20151215": "^3.0.3",

View File

@ -13,3 +13,6 @@ export type AccessDefine = Registrable & {
export interface IAccessService { export interface IAccessService {
getById(id: any): Promise<any>; getById(id: any): Promise<any>;
} }
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface IAccess {}

View File

@ -1,71 +1,39 @@
// src/decorator/memoryCache.decorator.ts // src/decorator/memoryCache.decorator.ts
import {
attachClassMetadata,
attachPropertyDataToClass,
getClassMetadata,
listModule,
listPropertyDataFromClass,
saveClassMetadata,
saveModule,
} from "@midwayjs/decorator";
import { AccessDefine, AccessInputDefine } from "./api"; import { AccessDefine, AccessInputDefine } from "./api";
import { Decorator } from "../decorator";
import _ from "lodash"; import _ from "lodash";
import { accessRegistry } from "./registry"; import { accessRegistry } from "./registry";
// 提供一个唯一 key // 提供一个唯一 key
export const ACCESS_CLASS_KEY = "decorator:access"; export const ACCESS_CLASS_KEY = "pipeline:access";
export const ACCESS_INPUT_KEY = "pipeline:access:input";
export function IsAccess(define: AccessDefine): ClassDecorator { export function IsAccess(define: AccessDefine): ClassDecorator {
console.log("is access define:", define);
return (target: any) => { return (target: any) => {
console.log("is access load:", target); target = Decorator.target(target);
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(ACCESS_CLASS_KEY, target); const inputs: any = {};
// 保存一些元数据信息,任意你希望存的东西 const properties = Decorator.getClassProperties(target);
saveClassMetadata( for (const property in properties) {
ACCESS_CLASS_KEY, const input = Reflect.getMetadata(ACCESS_INPUT_KEY, target, property);
{ if (input) {
inputs[property] = input;
}
}
_.merge(define, { inputs });
Reflect.defineMetadata(ACCESS_CLASS_KEY, define, target);
target.define = define;
accessRegistry.register(define.name, {
define, define,
}, target,
target });
);
}; };
} }
export const ACCESS_INPUT_KEY = "decorator:access:input";
export function IsAccessInput(input?: AccessInputDefine): PropertyDecorator { export function IsAccessInput(input?: AccessInputDefine): PropertyDecorator {
return (target, propertyKey) => { return (target, propertyKey) => {
attachPropertyDataToClass(ACCESS_INPUT_KEY, { input }, target, propertyKey, propertyKey as string); target = Decorator.target(target);
// const _type = Reflect.getMetadata("design:type", target, propertyKey);
attachClassMetadata( Reflect.defineMetadata(ACCESS_INPUT_KEY, input, target, propertyKey);
ACCESS_CLASS_KEY,
{
inputs: {
[propertyKey]: input,
},
},
target
);
}; };
} }
export function registerAccess() {
const modules = listModule(ACCESS_CLASS_KEY);
for (const mod of modules) {
console.log("mod", mod);
const define = getClassMetadata(ACCESS_CLASS_KEY, mod);
console.log("define", define);
const inputs = listPropertyDataFromClass(ACCESS_INPUT_KEY, mod);
console.log("inputs", inputs);
for (const input of inputs) {
define.inputs = {};
_.merge(define.inputs, input.inputs);
}
accessRegistry.register(define.name, {
define,
target: mod,
});
}
}

View File

@ -149,7 +149,7 @@ export class Executor {
// @ts-ignore // @ts-ignore
const define: PluginDefine = plugin.define; const define: PluginDefine = plugin.define;
//从outputContext读取输入参数 //从outputContext读取输入参数
_.forEach(define.inputs, (item, key) => { _.forEach(define.input, (item, key) => {
if (item.component?.name === "pi-output-selector") { if (item.component?.name === "pi-output-selector") {
const contextKey = step.input[key]; const contextKey = step.input[key];
if (contextKey != null) { if (contextKey != null) {
@ -164,12 +164,12 @@ export class Executor {
instance[key] = context[key]; instance[key] = context[key];
}); });
const res = await instance.execute(); await instance.execute();
//输出到output context //输出到output context
_.forEach(define.outputs, (item, key) => { _.forEach(define.output, (item, key) => {
const contextKey = `step.${step.id}.${key}`; const contextKey = `step.${step.id}.${key}`;
this.runtime.context[contextKey] = res[key]; this.runtime.context[contextKey] = instance[key];
}); });
} }
} }

View File

@ -0,0 +1,28 @@
const propertyMap: any = {};
function attachProperty(target: any, propertyKey: string | symbol) {
let props = propertyMap[target];
if (props == null) {
props = {};
propertyMap[target] = props;
}
props[propertyKey] = true;
}
function getClassProperties(target: any) {
return propertyMap[target] || {};
}
function target(target: any, propertyKey?: string | symbol) {
if (typeof target === "object" && target.constructor) {
target = target.constructor;
}
if (propertyKey != null) {
attachProperty(target, propertyKey);
}
return target;
}
export const Decorator = {
target,
attachProperty,
getClassProperties,
};

View File

@ -1,42 +1,20 @@
// src/decorator/memoryCache.decorator.ts // src/decorator/memoryCache.decorator.ts
import { getClassMetadata, listModule, Provide, saveClassMetadata, saveModule, Scope, ScopeEnum } from "@midwayjs/decorator";
import { dnsProviderRegistry } from "./registry"; import { dnsProviderRegistry } from "./registry";
import { DnsProviderDefine } from "./api"; import { DnsProviderDefine } from "./api";
import { Decorator } from "../decorator";
// 提供一个唯一 key // 提供一个唯一 key
export const DNS_PROVIDER_CLASS_KEY = "decorator:dnsProvider"; export const DNS_PROVIDER_CLASS_KEY = "pipeline:dns-provider";
export function IsDnsProvider(define: DnsProviderDefine): ClassDecorator { export function IsDnsProvider(define: DnsProviderDefine): ClassDecorator {
console.log("is task plugin define:", define);
return (target: any) => { return (target: any) => {
console.log("is task plugin load:", target); target = Decorator.target(target);
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class Reflect.defineMetadata(DNS_PROVIDER_CLASS_KEY, define, target);
saveModule(DNS_PROVIDER_CLASS_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(
DNS_PROVIDER_CLASS_KEY,
{
define,
},
target
);
// 指定 IoC 容器创建实例的作用域,这里注册为请求作用域,这样能取到 ctx
Scope(ScopeEnum.Prototype)(target);
// 调用一下 Provide 装饰器,这样用户的 class 可以省略写 @Provide() 装饰器了 target.define = define;
Provide()(target);
};
}
export function registerDnsProviders() {
const modules = listModule(DNS_PROVIDER_CLASS_KEY);
for (const mod of modules) {
console.log("mod", mod);
const define = getClassMetadata(DNS_PROVIDER_CLASS_KEY, mod);
console.log("define", define);
dnsProviderRegistry.register(define.name, { dnsProviderRegistry.register(define.name, {
define, define,
target: mod, target,
}); });
} };
} }

View File

@ -2,9 +2,6 @@ import { Config, Configuration, Inject, Logger } from "@midwayjs/decorator";
// @ts-ignore // @ts-ignore
import { ILogger } from "@midwayjs/logger"; import { ILogger } from "@midwayjs/logger";
import { IMidwayContainer, MidwayDecoratorService } from "@midwayjs/core"; import { IMidwayContainer, MidwayDecoratorService } from "@midwayjs/core";
import { registerPlugins } from "../plugin/decorator";
import { registerAccess } from "../access/decorator";
import { registerDnsProviders } from "../dns-provider";
// ... (see below) ... // ... (see below) ...
@Configuration({ @Configuration({
@ -26,9 +23,6 @@ export class PipelineConfiguration {
async onReady(container: IMidwayContainer) { async onReady(container: IMidwayContainer) {
this.logger.info("pipeline install"); this.logger.info("pipeline install");
registerPlugins();
registerAccess();
registerDnsProviders();
//this.implPropertyDecorator(container); //this.implPropertyDecorator(container);
this.logger.info("pipeline installed"); this.logger.info("pipeline installed");
} }

View File

@ -21,14 +21,16 @@ export type TaskInputDefine = FormItemProps;
export type PluginDefine = Registrable & { export type PluginDefine = Registrable & {
default?: any; default?: any;
inputs?: { input?: {
[key: string]: TaskInputDefine; [key: string]: TaskInputDefine;
}; };
outputs?: { output?: {
[key: string]: TaskOutputDefine; [key: string]: TaskOutputDefine;
}; };
autowire?: any; autowire?: {
[key: string]: any;
};
}; };
export interface ITaskPlugin { export interface ITaskPlugin {

View File

@ -1,136 +1,77 @@
import {
attachClassMetadata,
attachPropertyDataToClass,
getClassMetadata,
listModule,
listPropertyDataFromClass,
saveClassMetadata,
saveModule,
} from "@midwayjs/decorator";
import _ from "lodash"; import _ from "lodash";
import { pluginRegistry } from "./registry"; import { pluginRegistry } from "./registry";
import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api"; import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api";
import { Decorator } from "../decorator";
// 提供一个唯一 key // 提供一个唯一 key
export const PLUGIN_CLASS_KEY = "decorator:plugin"; export const PLUGIN_CLASS_KEY = "pipeline:plugin";
export function IsTaskPlugin(define: PluginDefine): ClassDecorator { export function IsTaskPlugin(define: PluginDefine): ClassDecorator {
console.log("is task plugin define:", define);
return (target: any) => { return (target: any) => {
console.log("is task plugin load:", target); target = Decorator.target(target);
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(PLUGIN_CLASS_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(
PLUGIN_CLASS_KEY,
{
define,
},
target
);
// // 指定 IoC 容器创建实例的作用域,这里注册为请求作用域,这样能取到 ctx
// Scope(ScopeEnum.Prototype)(target);
// 调用一下 Provide 装饰器,这样用户的 class 可以省略写 @Provide() 装饰器了 const inputs: any = {};
// Provide()(target); const autowires: any = {};
const outputs: any = {};
const properties = Decorator.getClassProperties(target);
for (const property in properties) {
const input = Reflect.getMetadata(PLUGIN_INPUT_KEY, target, property);
if (input) {
inputs[property] = input;
}
const autowire = Reflect.getMetadata(PLUGIN_AUTOWIRE_KEY, target, property);
if (autowire) {
autowires[property] = autowire;
}
const output = Reflect.getMetadata(PLUGIN_OUTPUT_KEY, target, property);
if (output) {
outputs[property] = output;
}
}
_.merge(define, { input: inputs, autowire: autowires, output: outputs });
Reflect.defineMetadata(PLUGIN_CLASS_KEY, define, target);
target.define = define;
pluginRegistry.register(define.name, {
define,
target,
});
}; };
} }
export const PLUGIN_INPUT_KEY = "decorator:plugin:input"; export const PLUGIN_INPUT_KEY = "pipeline:plugin:input";
export function TaskInput(input?: TaskInputDefine): PropertyDecorator { export function TaskInput(input?: TaskInputDefine): PropertyDecorator {
return (target, propertyKey) => { return (target, propertyKey) => {
attachPropertyDataToClass(PLUGIN_INPUT_KEY, { input }, target, propertyKey, propertyKey as string); target = Decorator.target(target, propertyKey);
Reflect.defineMetadata(PLUGIN_INPUT_KEY, input, target, propertyKey);
attachClassMetadata(
PLUGIN_CLASS_KEY,
{
inputs: {
[propertyKey]: input,
},
},
target
);
}; };
//
// return createCustomPropertyDecorator(CLASS_PROPS_KEY, {
// input,
// });
} }
// 装饰器内部的唯一 id // 装饰器内部的唯一 id
export const PLUGIN_OUTPUT_KEY = "decorator:plugin:output"; export const PLUGIN_OUTPUT_KEY = "pipeline:plugin:output";
export function TaskOutput(output?: TaskOutputDefine): PropertyDecorator { export function TaskOutput(output?: TaskOutputDefine): PropertyDecorator {
return (target, propertyKey) => { return (target, propertyKey) => {
attachPropertyDataToClass(PLUGIN_OUTPUT_KEY, { output }, target, propertyKey, propertyKey as string); target = Decorator.target(target, propertyKey);
Reflect.defineMetadata(PLUGIN_OUTPUT_KEY, output, target, propertyKey);
attachClassMetadata(
PLUGIN_CLASS_KEY,
{
outputs: {
[propertyKey]: output,
},
},
target
);
}; };
//
// return createCustomPropertyDecorator(CLASS_PROPS_KEY, {
// input,
// });
} }
export type AutowireProp = { export type AutowireProp = {
name?: string; name?: string;
type?: any;
}; };
export const PLUGIN_AUTOWIRE_KEY = "decorator:plugin:autowire"; export const PLUGIN_AUTOWIRE_KEY = "pipeline:plugin:autowire";
export function Autowire(props?: AutowireProp): PropertyDecorator { export function Autowire(props?: AutowireProp): PropertyDecorator {
return (target, propertyKey) => { return (target, propertyKey) => {
attachPropertyDataToClass( const _type = Reflect.getMetadata("design:type", target, propertyKey);
PLUGIN_AUTOWIRE_KEY, target = Decorator.target(target, propertyKey);
{ props = props || {};
autowire: { props.type = _type;
[propertyKey]: props, Reflect.defineMetadata(PLUGIN_AUTOWIRE_KEY, props || {}, target, propertyKey);
},
},
target,
propertyKey,
propertyKey as string
);
}; };
} }
export function registerPlugins() {
const modules = listModule(PLUGIN_CLASS_KEY);
for (const mod of modules) {
console.log("mod", mod);
const define: PluginDefine = getClassMetadata(PLUGIN_CLASS_KEY, mod);
console.log("define", define);
const inputs = listPropertyDataFromClass(PLUGIN_INPUT_KEY, mod);
console.log("inputs", inputs);
for (const input of inputs) {
define.inputs = {};
_.merge(define.inputs, input.inputs);
}
const outputs = listPropertyDataFromClass(PLUGIN_OUTPUT_KEY, mod);
console.log("outputs", outputs);
for (const output of outputs) {
define.outputs = {};
_.merge(define.outputs, output.outputs);
}
const autowire = listPropertyDataFromClass(PLUGIN_AUTOWIRE_KEY, mod);
console.log("autowire", autowire);
for (const auto of autowire) {
define.autowire = {};
_.merge(define.autowire, auto.autowire);
}
pluginRegistry.register(define.name, {
define,
target: mod,
});
}
}

View File

@ -1,26 +1,30 @@
import { AbstractPlugin, IsTask, TaskInput, TaskOutput, TaskPlugin } from "../src"; import { IsTaskPlugin, TaskInput, ITaskPlugin, LOGGER, Autowire, TaskOutput } from "../src";
@IsTask(() => { @IsTaskPlugin({
return {
name: "EchoPlugin", name: "EchoPlugin",
title: "测试插件【echo】", title: "测试插件【echo】",
input: { })
cert: { export class EchoPlugin implements ITaskPlugin {
@TaskInput({
title: "cert", title: "cert",
component: { component: {
name: "pi-output-selector", name: "pi-output-selector",
}, },
helper: "输出选择", helper: "输出选择",
},
},
output: {},
};
}) })
export class EchoPlugin extends AbstractPlugin implements TaskPlugin { cert!: any;
async execute(input: TaskInput): Promise<TaskOutput> {
for (const key in input) { @Autowire()
this.logger.info("input :", key, input[key]); logger!: LOGGER;
}
return input; @TaskOutput({
title: "cert info",
})
certInfo!: any;
// eslint-disable-next-line @typescript-eslint/no-empty-function
async onInit(): Promise<void> {}
async execute(): Promise<void> {
console.log("input :cert", this.cert);
} }
} }

View File

@ -3,10 +3,14 @@ import "mocha";
import { EchoPlugin } from "./echo-plugin"; import { EchoPlugin } from "./echo-plugin";
describe("task_plugin", function () { describe("task_plugin", function () {
it("#taskplugin", function () { it("#taskplugin", function () {
console.log("before new plugin");
const echoPlugin = new EchoPlugin(); const echoPlugin = new EchoPlugin();
console.log("before set property", echoPlugin);
echoPlugin.cert = { test: 1 };
console.log("before execute");
// @ts-ignore // @ts-ignore
const define = echoPlugin.getDefine(); echoPlugin.execute();
echoPlugin.execute({ context: {}, input: { test: 111 } }); console.log("after execute");
expect(define.name).eq("EchoPlugin"); expect(echoPlugin.cert).eq("EchoPlugin");
}); });
}); });

View File

@ -1,7 +1,7 @@
import { AbstractAccess, IAccessService } from "../../src"; import { IAccess, IAccessService } from "../../src";
import { aliyunSecret } from "../user.secret"; import { aliyunSecret } from "../user.secret";
export class AccessServiceTest implements IAccessService { export class AccessServiceTest implements IAccessService {
async getById(id: any): Promise<AbstractAccess> { async getById(id: any): Promise<IAccess> {
return { return {
...aliyunSecret, ...aliyunSecret,
} as any; } as any;

View File

@ -12,7 +12,8 @@
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["ESNext", "DOM"], "lib": ["ESNext", "DOM"],
"skipLibCheck": true, "skipLibCheck": true,
"experimentalDecorators": true "experimentalDecorators": true,
"emitDecoratorMetadata": true
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","test/**/*.ts"], "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","test/**/*.ts"],
} }

@ -1 +1 @@
Subproject commit 08f08022903aed6ba5150a80ea3484a8bc1830b7 Subproject commit b7422ab48ef81f5c98129bacb69d0eefdfe50645

@ -1 +1 @@
Subproject commit 70f3fd5359ac27b6dbd452f30fbf6630d4c9fa26 Subproject commit 5901fb5a440a7cebe3a2b6dfaec1b014e30b3a0c

View File

@ -1,6 +0,0 @@
packages:
# all packages in direct subdirs of packages/
- 'packages/*/*'
# exclude packages that are inside test directories
- '!**/test/**'
- '!packages/ui/*'