2024-03-26 17:08:45 +08:00
|
|
|
|
import { assign, MergeStrategies, requireModule } from '@nocobase/utils';
|
2022-09-29 21:05:31 +08:00
|
|
|
|
import compose from 'koa-compose';
|
|
|
|
|
import _ from 'lodash';
|
|
|
|
|
import Middleware, { MiddlewareType } from './middleware';
|
2020-10-24 15:34:43 +08:00
|
|
|
|
import Resource from './resource';
|
|
|
|
|
import { HandlerType } from './resourcer';
|
|
|
|
|
|
|
|
|
|
export type ActionType = string | HandlerType | ActionOptions;
|
|
|
|
|
|
|
|
|
|
export type DefaultActionType = 'list' | 'create' | 'get' | 'update' | 'destroy' | 'set' | 'add' | 'remove';
|
|
|
|
|
|
2023-04-25 13:12:14 +08:00
|
|
|
|
export type ActionName = DefaultActionType | Omit<string, DefaultActionType>;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
|
|
|
|
|
export interface ActionContext {
|
|
|
|
|
action?: Action;
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-06 21:23:34 +08:00
|
|
|
|
export type FieldsOptions =
|
|
|
|
|
| string[]
|
|
|
|
|
| {
|
|
|
|
|
only?: string[];
|
|
|
|
|
appends?: string[];
|
|
|
|
|
}
|
|
|
|
|
| {
|
|
|
|
|
except?: string[];
|
|
|
|
|
appends?: string[];
|
|
|
|
|
};
|
2020-10-24 15:34:43 +08:00
|
|
|
|
|
|
|
|
|
export type FieldsOptionsFn = (ctx: ActionContext) => FieldsOptions | Promise<FieldsOptions>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 过滤参数
|
2021-12-06 21:23:34 +08:00
|
|
|
|
*
|
2020-10-24 15:34:43 +08:00
|
|
|
|
* TODO:细节待定
|
|
|
|
|
*/
|
|
|
|
|
export interface FilterOptions {
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type FilterOptionsFn = (ctx: ActionContext) => FilterOptions | Promise<FieldsOptions>;
|
|
|
|
|
|
|
|
|
|
export type ParamsCallback = (ctx: ActionContext) => ActionParams | Promise<ActionParams>;
|
|
|
|
|
|
|
|
|
|
export interface ActionOptions {
|
|
|
|
|
/**
|
|
|
|
|
* 默认数据
|
|
|
|
|
*/
|
2021-01-13 16:23:15 +08:00
|
|
|
|
values?: any;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
|
|
|
|
* 字段
|
2021-12-06 21:23:34 +08:00
|
|
|
|
*
|
2020-10-24 15:34:43 +08:00
|
|
|
|
* 示例一:
|
|
|
|
|
* ['col1', 'col2', 'relation.col1'];
|
2021-12-06 21:23:34 +08:00
|
|
|
|
*
|
2020-10-24 15:34:43 +08:00
|
|
|
|
* 示例二:
|
|
|
|
|
* {
|
|
|
|
|
* only: ['col1'],
|
|
|
|
|
* }
|
2021-12-06 21:23:34 +08:00
|
|
|
|
*
|
2020-10-24 15:34:43 +08:00
|
|
|
|
* 示例三:
|
|
|
|
|
* {
|
|
|
|
|
* except: ['col1'],
|
|
|
|
|
* }
|
|
|
|
|
*/
|
2021-12-06 21:23:34 +08:00
|
|
|
|
fields?: string[];
|
|
|
|
|
|
|
|
|
|
appends?: string[];
|
|
|
|
|
|
|
|
|
|
except?: string[];
|
|
|
|
|
|
|
|
|
|
whitelist?: string[];
|
|
|
|
|
|
|
|
|
|
blacklist?: string[];
|
|
|
|
|
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
|
|
|
|
* 过滤
|
|
|
|
|
*/
|
|
|
|
|
filter?: FilterOptions;
|
|
|
|
|
/**
|
|
|
|
|
* 排序
|
|
|
|
|
*/
|
2021-12-06 21:23:34 +08:00
|
|
|
|
sort?: string[];
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
|
|
|
|
* 当前页码
|
|
|
|
|
*/
|
|
|
|
|
page?: number;
|
|
|
|
|
/**
|
|
|
|
|
* 每页显示数量
|
|
|
|
|
*/
|
2022-02-18 09:46:10 +08:00
|
|
|
|
pageSize?: number;
|
2020-11-23 16:49:46 +08:00
|
|
|
|
/**
|
|
|
|
|
* 最大每页显示数量
|
|
|
|
|
*/
|
2022-02-18 09:46:10 +08:00
|
|
|
|
maxPageSize?: number;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
|
|
|
|
* 中间件
|
|
|
|
|
*/
|
|
|
|
|
middleware?: MiddlewareType;
|
|
|
|
|
/**
|
|
|
|
|
* 中间件
|
2021-12-06 21:23:34 +08:00
|
|
|
|
*
|
2020-10-24 15:34:43 +08:00
|
|
|
|
* 与 middleware 用法相同
|
|
|
|
|
*/
|
|
|
|
|
middlewares?: MiddlewareType;
|
|
|
|
|
/**
|
|
|
|
|
* 当前 Action 待执行的方法
|
2021-12-06 21:23:34 +08:00
|
|
|
|
*
|
2020-10-24 15:34:43 +08:00
|
|
|
|
* 支持 Function 和 require 调用
|
|
|
|
|
*/
|
|
|
|
|
handler?: HandlerType;
|
|
|
|
|
/**
|
|
|
|
|
* 其他扩展配置
|
|
|
|
|
*/
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* action params 与 action options 略有不同
|
|
|
|
|
* - options 的参数更灵活,主要用于开发配置
|
|
|
|
|
* - params 是开发配置参数 + 客户端参数的结合体
|
|
|
|
|
*/
|
|
|
|
|
export interface ActionParams {
|
2022-01-23 12:42:35 +08:00
|
|
|
|
filterByTk?: any;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
|
|
|
|
* 输出哪些字段
|
2021-12-06 21:23:34 +08:00
|
|
|
|
*
|
2020-10-24 15:34:43 +08:00
|
|
|
|
* 与 ActionOptions 的不同,这里的 fields 是 object,提供 only,except,appends 三种情况
|
|
|
|
|
*/
|
2021-12-06 21:23:34 +08:00
|
|
|
|
fields?: string[];
|
|
|
|
|
|
|
|
|
|
appends?: string[];
|
|
|
|
|
|
|
|
|
|
except?: string[];
|
|
|
|
|
|
|
|
|
|
whitelist?: string[];
|
|
|
|
|
|
|
|
|
|
blacklist?: string[];
|
|
|
|
|
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
|
|
|
|
* 过滤
|
|
|
|
|
*/
|
|
|
|
|
filter?: FilterOptions;
|
|
|
|
|
/**
|
|
|
|
|
* 排序
|
2021-12-06 21:23:34 +08:00
|
|
|
|
*
|
2020-10-24 15:34:43 +08:00
|
|
|
|
* 与 ActionOptions 的不同,这里的 sort 只有一种 array 类型
|
|
|
|
|
*/
|
|
|
|
|
sort?: string[];
|
|
|
|
|
/**
|
|
|
|
|
* 当前页码
|
|
|
|
|
*/
|
|
|
|
|
page?: number;
|
|
|
|
|
/**
|
|
|
|
|
* 每页显示数量
|
|
|
|
|
*/
|
2022-02-18 09:46:10 +08:00
|
|
|
|
pageSize?: number;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
|
|
|
|
* 数据,默认为 options.defaultValues + request.body
|
|
|
|
|
*/
|
|
|
|
|
values?: any;
|
|
|
|
|
/**
|
2024-03-26 17:08:45 +08:00
|
|
|
|
* This method is deprecated and should not be used.
|
|
|
|
|
* Use {@link action.resourceName.split(',')[0]} instead.
|
|
|
|
|
* @deprecated
|
2020-10-24 15:34:43 +08:00
|
|
|
|
*/
|
|
|
|
|
resourceName?: string;
|
|
|
|
|
/**
|
2024-03-26 17:08:45 +08:00
|
|
|
|
* This method is deprecated and should not be used.
|
|
|
|
|
* Use {@link filterByTk} instead.
|
|
|
|
|
* @deprecated
|
2020-10-24 15:34:43 +08:00
|
|
|
|
*/
|
2021-12-04 16:28:52 +08:00
|
|
|
|
resourceIndex?: string;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
2024-03-26 17:08:45 +08:00
|
|
|
|
* This method is deprecated and should not be used.
|
|
|
|
|
* Use {@link action.resourceName.split(',')[1]} instead.
|
|
|
|
|
* @deprecated
|
2020-10-24 15:34:43 +08:00
|
|
|
|
*/
|
|
|
|
|
associatedName?: string;
|
|
|
|
|
/**
|
2024-03-26 17:08:45 +08:00
|
|
|
|
* This method is deprecated and should not be used.
|
|
|
|
|
* Use {@link action.sourceId} instead.
|
|
|
|
|
* @deprecated
|
2020-10-24 15:34:43 +08:00
|
|
|
|
*/
|
2021-12-04 16:28:52 +08:00
|
|
|
|
associatedIndex?: string;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
2024-03-26 17:08:45 +08:00
|
|
|
|
* This method is deprecated and should not be used.
|
|
|
|
|
* @deprecated
|
2020-10-24 15:34:43 +08:00
|
|
|
|
*/
|
2021-03-28 13:34:51 +08:00
|
|
|
|
associated?: any;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
/**
|
2024-03-26 17:08:45 +08:00
|
|
|
|
* This method is deprecated and should not be used.
|
|
|
|
|
* Use {@link action.actionName} instead.
|
|
|
|
|
* @deprecated
|
2020-10-24 15:34:43 +08:00
|
|
|
|
*/
|
|
|
|
|
actionName?: string;
|
|
|
|
|
/**
|
|
|
|
|
* 其他扩展配置
|
|
|
|
|
*/
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class Action {
|
|
|
|
|
protected handler: any;
|
|
|
|
|
|
|
|
|
|
protected resource: Resource;
|
|
|
|
|
|
|
|
|
|
protected name: ActionName;
|
|
|
|
|
|
|
|
|
|
protected options: ActionOptions;
|
|
|
|
|
|
|
|
|
|
protected context: ActionContext = {};
|
|
|
|
|
|
2021-12-06 21:23:34 +08:00
|
|
|
|
public params: ActionParams = {};
|
|
|
|
|
|
2022-01-23 12:42:35 +08:00
|
|
|
|
public actionName: string;
|
2024-03-26 17:08:45 +08:00
|
|
|
|
|
2022-01-23 12:42:35 +08:00
|
|
|
|
public resourceName: string;
|
2024-03-26 17:08:45 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This method is deprecated and should not be used.
|
|
|
|
|
* Use {@link this.sourceId} instead.
|
|
|
|
|
* @deprecated
|
|
|
|
|
*/
|
2022-01-23 12:42:35 +08:00
|
|
|
|
public resourceOf: any;
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
public sourceId: any;
|
|
|
|
|
|
2020-10-24 15:34:43 +08:00
|
|
|
|
public readonly middlewares: Array<Middleware> = [];
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
constructor(options: ActionOptions) {
|
|
|
|
|
options = requireModule(options);
|
|
|
|
|
if (typeof options === 'function') {
|
|
|
|
|
options = { handler: options };
|
|
|
|
|
}
|
2021-12-06 21:23:34 +08:00
|
|
|
|
const { middleware, middlewares = [], handler, ...params } = options;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
this.middlewares = Middleware.toInstanceArray(middleware || middlewares);
|
|
|
|
|
this.handler = handler;
|
|
|
|
|
this.options = options;
|
2021-12-06 21:23:34 +08:00
|
|
|
|
this.mergeParams(params);
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2022-11-12 17:12:50 +08:00
|
|
|
|
toJSON() {
|
|
|
|
|
return {
|
|
|
|
|
actionName: this.actionName,
|
|
|
|
|
resourceName: this.resourceName,
|
2024-03-26 17:08:45 +08:00
|
|
|
|
resourceOf: this.sourceId,
|
|
|
|
|
sourceId: this.sourceId,
|
2022-11-12 17:12:50 +08:00
|
|
|
|
params: this.params,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
clone() {
|
|
|
|
|
const options = _.cloneDeep(this.options);
|
|
|
|
|
delete options.middleware;
|
|
|
|
|
delete options.middlewares;
|
|
|
|
|
const action = new Action(options);
|
|
|
|
|
action.setName(this.name);
|
|
|
|
|
action.setResource(this.resource);
|
|
|
|
|
action.middlewares.push(...this.middlewares);
|
|
|
|
|
return action;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
setContext(context: any) {
|
|
|
|
|
this.context = context;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-06 21:23:34 +08:00
|
|
|
|
mergeParams(params: ActionParams, strategies: MergeStrategies = {}) {
|
2023-01-03 13:26:50 +08:00
|
|
|
|
if (!this.params) {
|
|
|
|
|
this.params = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!params) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-06 21:23:34 +08:00
|
|
|
|
assign(this.params, params, {
|
|
|
|
|
filter: 'andMerge',
|
|
|
|
|
fields: 'intersect',
|
|
|
|
|
appends: 'union',
|
|
|
|
|
except: 'union',
|
|
|
|
|
whitelist: 'intersect',
|
|
|
|
|
blacklist: 'intersect',
|
|
|
|
|
sort: 'overwrite',
|
|
|
|
|
...strategies,
|
2021-01-13 16:23:15 +08:00
|
|
|
|
});
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
setResource(resource: Resource) {
|
|
|
|
|
this.resource = resource;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
getResource() {
|
|
|
|
|
return this.resource;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
getOptions(): ActionOptions {
|
|
|
|
|
return this.options;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
setName(name: ActionName) {
|
|
|
|
|
this.name = name;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
getName() {
|
|
|
|
|
return this.name;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
getMiddlewareHandlers() {
|
|
|
|
|
return this.middlewares
|
2021-12-06 21:23:34 +08:00
|
|
|
|
.filter((middleware) => middleware.canAccess(this.name))
|
|
|
|
|
.map((middleware) => middleware.getHandler());
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
getHandler() {
|
|
|
|
|
const handler = requireModule(this.handler || this.resource.resourcer.getRegisteredHandler(this.name));
|
|
|
|
|
if (typeof handler !== 'function') {
|
|
|
|
|
throw new Error('Handler must be a function!');
|
|
|
|
|
}
|
2023-01-03 13:26:50 +08:00
|
|
|
|
|
2020-10-24 15:34:43 +08:00
|
|
|
|
return handler;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
getHandlers() {
|
2023-01-03 13:26:50 +08:00
|
|
|
|
const handlers = [
|
2022-11-12 17:12:50 +08:00
|
|
|
|
...this.resource.resourcer.getMiddlewares(),
|
|
|
|
|
...this.getMiddlewareHandlers(),
|
|
|
|
|
this.getHandler(),
|
|
|
|
|
].filter(Boolean);
|
2023-01-03 13:26:50 +08:00
|
|
|
|
|
2023-12-12 23:02:09 +08:00
|
|
|
|
// handlers = handlers.map((handler) => prePerfHooksWrap(handler));
|
|
|
|
|
|
2023-01-03 13:26:50 +08:00
|
|
|
|
return handlers;
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
async execute(context: any, next?: any) {
|
|
|
|
|
return await compose(this.getHandlers())(context, next);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 17:08:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2020-10-24 15:34:43 +08:00
|
|
|
|
static toInstanceMap(actions: object, resource?: Resource) {
|
2021-12-06 21:23:34 +08:00
|
|
|
|
return new Map(
|
|
|
|
|
Object.entries(actions).map(([key, options]) => {
|
|
|
|
|
let action: Action;
|
|
|
|
|
if (options instanceof Action) {
|
|
|
|
|
action = options;
|
|
|
|
|
} else {
|
|
|
|
|
action = new Action(options);
|
|
|
|
|
}
|
|
|
|
|
action.setName(key);
|
|
|
|
|
action.setResource(resource);
|
|
|
|
|
resource && action.middlewares.unshift(...resource.middlewares);
|
|
|
|
|
return [key, action];
|
|
|
|
|
}),
|
|
|
|
|
);
|
2020-10-24 15:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-25 13:12:14 +08:00
|
|
|
|
export default Action;
|