diff --git a/packages/core/auth/src/auth-manager.ts b/packages/core/auth/src/auth-manager.ts index f0c625a1e5..40b53094ec 100644 --- a/packages/core/auth/src/auth-manager.ts +++ b/packages/core/auth/src/auth-manager.ts @@ -106,7 +106,9 @@ export class AuthManager { * @description Auth middleware, used to check the authentication status. */ middleware() { - return async (ctx: Context & { auth: Auth }, next: Next) => { + const self = this; + + return async function AuthManagerMiddleware(ctx: Context & { auth: Auth }, next: Next) { const token = ctx.getBearerToken(); if (token && (await ctx.app.authManager.jwt.blacklist?.has(token))) { return ctx.throw(401, { @@ -115,7 +117,8 @@ export class AuthManager { }); } - const name = ctx.get(this.options.authKey) || this.options.default; + const name = ctx.get(self.options.authKey) || self.options.default; + let authenticator: Auth; try { authenticator = await ctx.app.authManager.get(name, ctx); diff --git a/packages/core/data-source-manager/src/data-source-manager.ts b/packages/core/data-source-manager/src/data-source-manager.ts index 25551e7d85..a8b141d9e9 100644 --- a/packages/core/data-source-manager/src/data-source-manager.ts +++ b/packages/core/data-source-manager/src/data-source-manager.ts @@ -84,17 +84,20 @@ export class DataSourceManager { } middleware() { - return async (ctx, next) => { + const self = this; + + return async function dataSourceManager(ctx, next) { const name = ctx.get('x-data-source') || 'main'; - if (!this.dataSources.has(name)) { + if (!self.dataSources.has(name)) { ctx.throw(`data source ${name} does not exist`); } - const ds = this.dataSources.get(name); + const ds = self.dataSources.get(name); ctx.dataSource = ds; - return ds.middleware(this.middlewares)(ctx, next); + const composedFn = ds.middleware(self.middlewares); + return composedFn(ctx, next); }; } diff --git a/packages/core/data-source-manager/src/data-source.ts b/packages/core/data-source-manager/src/data-source.ts index 5a90ae86a0..8a45cac8f9 100644 --- a/packages/core/data-source-manager/src/data-source.ts +++ b/packages/core/data-source-manager/src/data-source.ts @@ -14,6 +14,7 @@ import compose from 'koa-compose'; import { loadDefaultActions } from './load-default-actions'; import { ICollectionManager } from './types'; import { Logger } from '@nocobase/logger'; +import { wrapMiddlewareWithLogging } from '@nocobase/utils'; export type DataSourceOptions = any; @@ -79,6 +80,7 @@ export abstract class DataSource extends EventEmitter { for (const [fn, options] of middlewares) { this.resourceManager.use(fn, options); } + this['_used'] = true; } @@ -91,7 +93,9 @@ export abstract class DataSource extends EventEmitter { return this.collectionManager.getRepository(resourceName, resourceOf); }; - return compose([this.collectionToResourceMiddleware(), this.resourceManager.middleware()])(ctx, next); + const middlewares = [this.collectionToResourceMiddleware(), this.resourceManager.middleware()]; + + return compose(middlewares.map((fn) => wrapMiddlewareWithLogging(fn)))(ctx, next); }; } @@ -117,15 +121,16 @@ export abstract class DataSource extends EventEmitter { abstract createCollectionManager(options?: any): ICollectionManager; protected collectionToResourceMiddleware() { - return async (ctx, next) => { + const self = this; + return async function collectionToResource(ctx, next) { const params = parseRequest( { path: ctx.request.path, method: ctx.request.method, }, { - prefix: this.resourceManager.options.prefix, - accessors: this.resourceManager.options.accessors, + prefix: self.resourceManager.options.prefix, + accessors: self.resourceManager.options.accessors, }, ); if (!params) { @@ -133,7 +138,7 @@ export abstract class DataSource extends EventEmitter { } const resourceName = getNameByParams(params); // 如果资源名称未被定义 - if (this.resourceManager.isDefined(resourceName)) { + if (self.resourceManager.isDefined(resourceName)) { return next(); } @@ -141,11 +146,11 @@ export abstract class DataSource extends EventEmitter { const collectionName = splitResult[0]; - if (!this.collectionManager.hasCollection(collectionName)) { + if (!self.collectionManager.hasCollection(collectionName)) { return next(); } - this.resourceManager.define({ + self.resourceManager.define({ name: resourceName, }); diff --git a/packages/core/logger/src/request-logger.ts b/packages/core/logger/src/request-logger.ts index 39efbc490e..81a07ec3ac 100644 --- a/packages/core/logger/src/request-logger.ts +++ b/packages/core/logger/src/request-logger.ts @@ -34,7 +34,7 @@ export interface RequestLoggerOptions extends LoggerOptions { } export const requestLogger = (appName: string, requestLogger: Logger, options?: RequestLoggerOptions) => { - return async (ctx, next) => { + return async function requestLoggerMiddleware(ctx, next) { const reqId = ctx.reqId; const path = /^\/api\/(.+):(.+)/.exec(ctx.path); const contextLogger = ctx.app.log.child({ reqId, module: path?.[1], submodule: path?.[2] }); diff --git a/packages/core/resourcer/src/action.ts b/packages/core/resourcer/src/action.ts index f2a91affde..34d6b61ad9 100644 --- a/packages/core/resourcer/src/action.ts +++ b/packages/core/resourcer/src/action.ts @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { assign, MergeStrategies, requireModule } from '@nocobase/utils'; +import { assign, MergeStrategies, requireModule, wrapMiddlewareWithLogging } from '@nocobase/utils'; import compose from 'koa-compose'; import _ from 'lodash'; import Middleware, { MiddlewareType } from './middleware'; @@ -375,9 +375,7 @@ export class Action { this.getHandler(), ].filter(Boolean); - // handlers = handlers.map((handler) => prePerfHooksWrap(handler)); - - return handlers; + return handlers.map((fn) => wrapMiddlewareWithLogging(fn)); } /** diff --git a/packages/core/resourcer/src/resourcer.ts b/packages/core/resourcer/src/resourcer.ts index f8609e6c80..b5779ba48b 100644 --- a/packages/core/resourcer/src/resourcer.ts +++ b/packages/core/resourcer/src/resourcer.ts @@ -306,7 +306,9 @@ export class ResourceManager { } middleware({ prefix, accessors, skipIfDataSourceExists = false }: KoaMiddlewareOptions = {}) { - return async (ctx: ResourcerContext, next: () => Promise) => { + const self = this; + + return async function resourcerMiddleware(ctx: ResourcerContext, next: () => Promise) { if (skipIfDataSourceExists) { const dataSource = ctx.get('x-data-source'); if (dataSource) { @@ -314,7 +316,7 @@ export class ResourceManager { } } - ctx.resourcer = this; + ctx.resourcer = self; let params = parseRequest( { @@ -322,8 +324,8 @@ export class ResourceManager { method: ctx.request.method, }, { - prefix: this.options.prefix || prefix, - accessors: this.options.accessors || accessors, + prefix: self.options.prefix || prefix, + accessors: self.options.accessors || accessors, }, ); @@ -332,7 +334,7 @@ export class ResourceManager { } try { - const resource = this.getResource(getNameByParams(params)); + const resource = self.getResource(getNameByParams(params)); // 为关系资源时,暂时需要再执行一遍 parseRequest if (resource.options.type && resource.options.type !== 'single') { @@ -343,8 +345,8 @@ export class ResourceManager { type: resource.options.type, }, { - prefix: this.options.prefix || prefix, - accessors: this.options.accessors || accessors, + prefix: self.options.prefix || prefix, + accessors: self.options.accessors || accessors, }, ); @@ -354,7 +356,7 @@ export class ResourceManager { } // action 需要 clone 之后再赋给 ctx - ctx.action = this.getAction(getNameByParams(params), params.actionName).clone(); + ctx.action = self.getAction(getNameByParams(params), params.actionName).clone(); ctx.action.setContext(ctx); ctx.action.actionName = params.actionName; diff --git a/packages/core/server/src/application.ts b/packages/core/server/src/application.ts index 85131a3655..4d5cb81239 100644 --- a/packages/core/server/src/application.ts +++ b/packages/core/server/src/application.ts @@ -24,7 +24,16 @@ import { } from '@nocobase/logger'; import { ResourceOptions, Resourcer } from '@nocobase/resourcer'; import { Telemetry, TelemetryOptions } from '@nocobase/telemetry'; -import { applyMixins, AsyncEmitter, importModule, Toposort, ToposortOptions } from '@nocobase/utils'; + +import { + applyMixins, + AsyncEmitter, + importModule, + Toposort, + ToposortOptions, + wrapMiddlewareWithLogging, +} from '@nocobase/utils'; + import { Command, CommandOptions, ParseOptions } from 'commander'; import { randomUUID } from 'crypto'; import glob from 'glob'; @@ -50,7 +59,6 @@ import { enablePerfHooks, getCommandFullName, registerMiddlewares, - wrapMiddlewareWithLogging, } from './helper'; import { ApplicationVersion } from './helpers/application-version'; import { Locale } from './locale'; @@ -466,10 +474,7 @@ export class Application exten middleware: Koa.Middleware, options?: ToposortOptions, ) { - this.middleware.add( - wrapMiddlewareWithLogging(middleware, middleware.toString().slice(0, 100), this.logger), - options, - ); + this.middleware.add(wrapMiddlewareWithLogging(middleware, this.logger), options); return this; } @@ -1197,6 +1202,7 @@ export class Application exten group: 'parseVariables', after: 'acl', }); + this._dataSourceManager.use(dataTemplate, { group: 'dataTemplate', after: 'acl' }); this._locales = new Locale(createAppProxy(this)); diff --git a/packages/core/server/src/helper.ts b/packages/core/server/src/helper.ts index 5af2995c7d..bd8c7885ea 100644 --- a/packages/core/server/src/helper.ts +++ b/packages/core/server/src/helper.ts @@ -76,7 +76,7 @@ export function registerMiddlewares(app: Application, options: ApplicationOption ); } - app.use(async (ctx, next) => { + app.use(async function getBearerToken(ctx, next) { ctx.getBearerToken = () => { const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, ''); return token || ctx.query.token; @@ -158,22 +158,3 @@ export const enablePerfHooks = (app: Application) => { app.acl.allow('perf', '*', 'public'); }; - -export function wrapMiddlewareWithLogging(fn, name, logger) { - if (process.env['LOGGER_LEVEL'] !== 'trace') { - return fn; - } - - return async (ctx, next) => { - const reqId = ctx.reqId; - logger.trace(`--> Entering middleware: ${name}`, { reqId }); - const start = Date.now(); - - await fn(ctx, async () => { - await next(); - }); - - const ms = Date.now() - start; - logger.trace(`<-- Exiting middleware: ${name} - ${ms}ms`, { reqId }); - }; -} diff --git a/packages/core/server/src/index.ts b/packages/core/server/src/index.ts index ce1f6f8a1c..2d7f26dc29 100644 --- a/packages/core/server/src/index.ts +++ b/packages/core/server/src/index.ts @@ -28,4 +28,3 @@ export { } from './plugin-manager/findPackageNames'; export { runPluginStaticImports } from './run-plugin-static-imports'; -export { wrapMiddlewareWithLogging } from './helper'; diff --git a/packages/core/server/src/middlewares/data-template.ts b/packages/core/server/src/middlewares/data-template.ts index 09a5d5c4aa..473645b623 100644 --- a/packages/core/server/src/middlewares/data-template.ts +++ b/packages/core/server/src/middlewares/data-template.ts @@ -10,7 +10,7 @@ import { Context } from '@nocobase/actions'; import { Collection } from '@nocobase/database'; -export const dataTemplate = async (ctx: Context, next) => { +export async function dataTemplate(ctx: Context, next) { const { resourceName, actionName } = ctx.action; const { isTemplate, fields } = ctx.action.params; @@ -22,7 +22,7 @@ export const dataTemplate = async (ctx: Context, next) => { include: fields, }); } -}; +} type TraverseOptions = { collection: Collection; diff --git a/packages/core/server/src/middlewares/parse-variables.ts b/packages/core/server/src/middlewares/parse-variables.ts index 109587707f..5e4a5bb655 100644 --- a/packages/core/server/src/middlewares/parse-variables.ts +++ b/packages/core/server/src/middlewares/parse-variables.ts @@ -36,7 +36,7 @@ function isNumeric(str: any) { return !isNaN(str as any) && !isNaN(parseFloat(str)); } -export const parseVariables = async (ctx, next) => { +export async function parseVariables(ctx, next) { const filter = ctx.action.params.filter; if (!filter) { return next(); @@ -66,4 +66,4 @@ export const parseVariables = async (ctx, next) => { }, }); await next(); -}; +} diff --git a/packages/core/utils/src/index.ts b/packages/core/utils/src/index.ts index 7f20a0f15b..13da3540c8 100644 --- a/packages/core/utils/src/index.ts +++ b/packages/core/utils/src/index.ts @@ -34,6 +34,7 @@ export * from './toposort'; export * from './uid'; export * from './url'; export * from './i18n'; +export * from './wrap-middleware'; export { dayjs, lodash }; export { Schema } from '@formily/json-schema'; diff --git a/packages/core/utils/src/wrap-middleware.ts b/packages/core/utils/src/wrap-middleware.ts new file mode 100644 index 0000000000..820ba15c25 --- /dev/null +++ b/packages/core/utils/src/wrap-middleware.ts @@ -0,0 +1,36 @@ +export function wrapMiddlewareWithLogging(fn, logger?) { + if (process.env['LOGGER_LEVEL'] !== 'trace') { + return fn; + } + + const name = fn.name || fn.toString().slice(0, 100); + + return async (ctx, next) => { + const reqId = ctx.reqId; + + if (!logger && !ctx.logger) { + return await fn(ctx, next); + } + + if (!logger && ctx.logger) { + logger = ctx.logger; + } + + logger.trace(`--> Entering middleware: ${name}`, { reqId }); + + const start = Date.now(); + + await fn(ctx, async () => { + const beforeNext = Date.now(); + logger.trace(`--> Before next middleware: ${name} - ${beforeNext - start}ms`, { reqId }); + + await next(); + + const afterNext = Date.now(); + logger.trace(`<-- After next middleware: ${name} - ${afterNext - beforeNext}ms`, { reqId }); + }); + + const ms = Date.now() - start; + logger.trace(`<-- Exiting middleware: ${name} - ${ms}ms`, { reqId }); + }; +} diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts index 151b89ab83..f082ed398a 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts @@ -452,7 +452,7 @@ export class PluginACLServer extends Plugin { }; }); - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function showAnonymous(ctx, next) { const { actionName, resourceName, params } = ctx.action; const { showAnonymous } = params || {}; if (actionName === 'list' && resourceName === 'roles') { @@ -578,7 +578,7 @@ export class PluginACLServer extends Plugin { // append allowedActions to list & get response this.app.use( - async (ctx, next) => { + async function withACLMetaMiddleware(ctx, next) { try { await withACLMeta(ctx, next); } catch (error) { diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts index 6ce490b326..de17682381 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts @@ -304,7 +304,7 @@ export class PluginDataSourceMainServer extends Plugin { this.app.on('beforeStart', loadCollections); - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function pushUISchemaWhenUpdateCollectionField(ctx, next) { const { resourceName, actionName } = ctx.action; if (resourceName === 'collections.fields' && actionName === 'update') { const { updateAssociationValues = [] } = ctx.action.params; @@ -384,7 +384,7 @@ export class PluginDataSourceMainServer extends Plugin { }, ); - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function mergeReverseFieldWhenSaveCollectionField(ctx, next) { if (ctx.action.resourceName === 'collections.fields' && ['create', 'update'].includes(ctx.action.actionName)) { ctx.action.mergeParams({ updateAssociationValues: ['reverseField'], @@ -425,7 +425,7 @@ export class PluginDataSourceMainServer extends Plugin { } }; - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function handleFieldSourceMiddleware(ctx, next) { await next(); // handle collections:list diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts index 20a6c08bd9..8ec528ab37 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts @@ -492,8 +492,9 @@ export class PluginDataSourceManagerServer extends Plugin { }); }); + const self = this; // add global roles check - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function appendDataToRolesCheck(ctx, next) { const action = ctx.action; await next(); const { resourceName, actionName } = action.params; @@ -503,12 +504,12 @@ export class PluginDataSourceManagerServer extends Plugin { ctx.bodyMeta = { dataSources: dataSources.reduce((carry, dataSourceModel) => { - const dataSource = this.app.dataSourceManager.dataSources.get(dataSourceModel.get('key')); + const dataSource = self.app.dataSourceManager.dataSources.get(dataSourceModel.get('key')); if (!dataSource) { return carry; } - const dataSourceStatus = this.dataSourceStatus[dataSourceModel.get('key')]; + const dataSourceStatus = self.dataSourceStatus[dataSourceModel.get('key')]; if (dataSourceStatus !== 'loaded') { return carry; } diff --git a/packages/plugins/@nocobase/plugin-field-china-region/src/server/index.ts b/packages/plugins/@nocobase/plugin-field-china-region/src/server/index.ts index 652a51db02..78407f53d9 100644 --- a/packages/plugins/@nocobase/plugin-field-china-region/src/server/index.ts +++ b/packages/plugins/@nocobase/plugin-field-china-region/src/server/index.ts @@ -30,7 +30,7 @@ export class PluginFieldChinaRegionServer extends Plugin { this.app.acl.allow('chinaRegions', 'list', 'loggedIn'); - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function blockChinaRegionList(ctx, next) { const { resourceName, actionName } = ctx.action.params; if (resourceName == 'chinaRegions' && actionName !== 'list') { diff --git a/packages/plugins/@nocobase/plugin-users/src/server/server.ts b/packages/plugins/@nocobase/plugin-users/src/server/server.ts index b1c00d914b..b6c0c13523 100644 --- a/packages/plugins/@nocobase/plugin-users/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-users/src/server/server.ts @@ -153,7 +153,7 @@ export default class PluginUsersServer extends Plugin { } async load() { - this.app.resourceManager.use(async (ctx, next) => { + this.app.resourceManager.use(async function deleteRolesCache(ctx, next) { await next(); const { associatedName, resourceName, actionName, values } = ctx.action.params; if ( diff --git a/packages/plugins/@nocobase/plugin-verification/src/server/Plugin.ts b/packages/plugins/@nocobase/plugin-verification/src/server/Plugin.ts index dd9a18a944..806a129c9f 100644 --- a/packages/plugins/@nocobase/plugin-verification/src/server/Plugin.ts +++ b/packages/plugins/@nocobase/plugin-verification/src/server/Plugin.ts @@ -138,16 +138,17 @@ export default class PluginVerficationServer extends Plugin { await initProviders(this); initActions(this); + const self = this; // add middleware to action - app.resourcer.use(async (context, next) => { + app.resourceManager.use(async function verificationIntercept(context, next) { const { resourceName, actionName, values } = context.action.params; const key = `${resourceName}:${actionName}`; - const interceptor = this.interceptors.get(key); + const interceptor = self.interceptors.get(key); if (!interceptor || interceptor.manual) { return next(); } - return this.intercept(context, next); + return self.intercept(context, next); }); app.acl.allow('verifications', 'create', 'public'); diff --git a/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/ActionTrigger.ts b/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/ActionTrigger.ts index bcf2e9ef9b..00389090a2 100644 --- a/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/ActionTrigger.ts +++ b/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/ActionTrigger.ts @@ -24,7 +24,25 @@ export default class extends Trigger { constructor(workflow: WorkflowPlugin) { super(workflow); - workflow.app.dataSourceManager.use(this.middleware); + const self = this; + + async function triggerWorkflowActionMiddleware(context: Context, next: Next) { + const { resourceName, actionName } = context.action; + + if (resourceName === 'workflows' && actionName === 'trigger') { + return self.workflowTriggerAction(context, next); + } + + await next(); + + if (!['create', 'update'].includes(actionName)) { + return; + } + + return self.collectionTriggerAction(context); + } + + workflow.app.dataSourceManager.use(triggerWorkflowActionMiddleware); } /** @@ -43,22 +61,6 @@ export default class extends Trigger { return this.collectionTriggerAction(context); } - middleware = async (context: Context, next: Next) => { - const { resourceName, actionName } = context.action; - - if (resourceName === 'workflows' && actionName === 'trigger') { - return this.workflowTriggerAction(context, next); - } - - await next(); - - if (!['create', 'update'].includes(actionName)) { - return; - } - - return this.collectionTriggerAction(context); - }; - private async collectionTriggerAction(context: Context) { const { resourceName,