chore: add middleware name (#5594)

* chore: middleware name

* chore: add name to middleware

* chore: logging
This commit is contained in:
ChengLei Shao 2024-11-06 00:29:06 +08:00 committed by GitHub
parent 124379528d
commit aad8fd7bab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 125 additions and 87 deletions

View File

@ -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);

View File

@ -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);
};
}

View File

@ -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,
});

View File

@ -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] });

View File

@ -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));
}
/**

View File

@ -306,7 +306,9 @@ export class ResourceManager {
}
middleware({ prefix, accessors, skipIfDataSourceExists = false }: KoaMiddlewareOptions = {}) {
return async (ctx: ResourcerContext, next: () => Promise<any>) => {
const self = this;
return async function resourcerMiddleware(ctx: ResourcerContext, next: () => Promise<any>) {
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;

View File

@ -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<StateT = DefaultState, ContextT = DefaultContext> exten
middleware: Koa.Middleware<StateT & NewStateT, ContextT & NewContextT>,
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<StateT = DefaultState, ContextT = DefaultContext> exten
group: 'parseVariables',
after: 'acl',
});
this._dataSourceManager.use(dataTemplate, { group: 'dataTemplate', after: 'acl' });
this._locales = new Locale(createAppProxy(this));

View File

@ -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 });
};
}

View File

@ -28,4 +28,3 @@ export {
} from './plugin-manager/findPackageNames';
export { runPluginStaticImports } from './run-plugin-static-imports';
export { wrapMiddlewareWithLogging } from './helper';

View File

@ -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;

View File

@ -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();
};
}

View File

@ -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';

View File

@ -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 });
};
}

View File

@ -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) {

View File

@ -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

View File

@ -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;
}

View File

@ -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') {

View File

@ -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 (

View File

@ -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');

View File

@ -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,