中间件的作用

灵活应用中间件机制,可以有效扩展架构的功能。中间件主要的作用有:拦截重整

  1. 拦截:比如通过中间件判断用户权限,如果没有权限则中止后续执行
  2. 重整:比如通过中间件对前端发来的数据进行验证,并转化为期望的类型

拦截

在执行API路由之前,对请求参数进行判断,如果不符合预期,就拒绝

比如中间件test,判断当前运行环境,如果不是测试环境就抛出异常:

src/module-system/a-base-sync/backend/src/bean/middleware.test.js

  1. 1module.exports = ctx => {
  2. 2 class Middleware {
  3. 3 async execute(options, next) {
  4. 4 if (!ctx.app.meta.isTest) ctx.throw(403);
  5. 5 // next
  6. 6 await next();
  7. 7 }
  8. 8 }
  9. 9 return Middleware;
  10. 10};

重整

在执行API路由之前,对请求参数进行调整,使其符合预期的格式(如果不符合预期也可以拒绝)

比如中间件validate,验证前端发送的表单数据,并转换为预期的数据类型:

src/module-system/a-validation/backend/src/bean/middleware.validate.js

  1. 1module.exports = ctx => {
  2. 2 const moduleInfo = ctx.app.meta.mockUtil.parseInfoFromPackage(__dirname);
  3. 3 class Middleware {
  4. 4 async execute(options, next) {
  5. 5 // must exists
  6. 6 const validator = options.validator;
  7. 7 if (!validator) ctx.throw.module(moduleInfo.relativeName, 1001);
  8. 8 // params
  9. 9 const module = options.module || ctx.module.info.relativeName;
  10. 10 const schema = options.schema || (ctx.meta._validator && ctx.meta._validator.schema);
  11. 11 const data = ctx.request.body[options.data || 'data'];
  12. 12 // if error throw 422
  13. 13 await ctx.bean.validation.validate({
  14. 14 module,
  15. 15 validator,
  16. 16 schema,
  17. 17 data,
  18. 18 });
  19. 19 // next
  20. 20 await next();
  21. 21 }
  22. 22 }
  23. 23 return Middleware;
  24. 24};

中间件的类型

EggBornJS主要有三个来源的中间件:

  1. EggJS中间件:在EggBornJS中可以直接使用EggJS生态的中间件,请参考:Egg中间件
  2. EggBornJS系统中间件:EggBornJS提供的系统级别的中间件,比如:instance
  3. EggBornJS模块中间件:EggBornJS通过业务模块提供的中间件,比如:transactionauthrightvalidate

模块中间件的开发

在这里,我们通过一个虚拟的需求,用中间件实现拦截重整的逻辑:

  1. 前端发送两个参数:a、b,后端计算二者之和,并返回前端
  2. 拦截:中间件判断参数是否为空,如果为空则中止后续执行
  3. 重整:将参数类型强制转换为Integer类型

- 声明中间件

src/suite-vendor/test-party/modules/test-party/backend/src/config/config.js

  1. 1// middlewares
  2. 2config.middlewares = {
  3. 3 testInterception: {
  4. 4 bean: 'testInterception',
  5. 5 global: false,
  6. 6 dependencies: 'instance',
  7. 7 },
  8. 8 testRestructuring: {
  9. 9 bean: 'testRestructuring',
  10. 10 global: false,
  11. 11 dependencies: 'instance',
  12. 12 },
  13. 13};
名称 说明
bean 用于实现中间件逻辑的Bean组件名称
global 是否为全局中间件,全局中间件会自动加载,局部中间件需要手动指定
dependencies 标明此中间件依赖哪些中间件,从而在那些中间件后面加载。一般要依赖于instance,因为instance提供了多实例的基础逻辑

- 中间件:定义Bean组件

src/suite-vendor/test-party/modules/test-party/backend/src/bean/middleware.interception.js

  1. 1module.exports = ctx => {
  2. 2 class Middleware {
  3. 3 async execute(options, next) {
  4. 4 const { a, b } = ctx.request.body;
  5. 5 if (a === undefined || b === undefined) return ctx.throw(1002); // 1002: 'Incomplete Parameters'
  6. 6 // next
  7. 7 await next();
  8. 8 }
  9. 9 }
  10. 10 return Middleware;
  11. 11};

src/suite-vendor/test-party/modules/test-party/backend/src/bean/middleware.restructuring.js

  1. 1module.exports = ctx => {
  2. 2 class Middleware {
  3. 3 async execute(options, next) {
  4. 4 const { a, b } = ctx.request.body;
  5. 5 ctx.request.body.a = parseInt(a);
  6. 6 ctx.request.body.b = parseInt(b);
  7. 7 // next
  8. 8 await next();
  9. 9 }
  10. 10 }
  11. 11 return Middleware;
  12. 12};

- 中间件:注册Bean组件

src/suite-vendor/test-party/modules/test-party/backend/src/beans.js

  1. 1const middlewareTestInterception = require('./bean/middleware.interception.js');
  2. 2const middlewareTestRestructuring = require('./bean/middleware.restructuring.js');
  3. 3
  4. 4module.exports = app => {
  5. 5 const beans = {
  6. 6 // middleware
  7. 7 'middleware.testInterception': {
  8. 8 mode: 'ctx',
  9. 9 bean: middlewareTestInterception,
  10. 10 },
  11. 11 'middleware.testRestructuring': {
  12. 12 mode: 'ctx',
  13. 13 bean: middlewareTestRestructuring,
  14. 14 },
  15. 15 };
  16. 16 return beans;
  17. 17};
注册名称 场景 所属模块 global beanFullName
testInterception middleware test-party false test-party.middleware.testInterception
testRestructuring middleware test-party false test-party.middleware.testRestructuring

- 使用中间件

因为testInterceptiontestRestructuring是局部中间件,因此需要手动在API路由上指定

src/suite-vendor/test-party/modules/test-party/backend/src/routes.js

  1. 1// test/feat/middleware
  2. 2{ method: 'post', path: 'test/feat/middleware/interception', controller: 'testFeatMiddleware', middlewares: 'test,testInterception' },
  3. 3{ method: 'post', path: 'test/feat/middleware/restructuring', controller: 'testFeatMiddleware', middlewares: 'test,testInterception,testRestructuring' },

- 后端控制器

src/suite-vendor/test-party/modules/test-party/backend/src/controller/test/feat/middleware.js

  1. 1module.exports = app => {
  2. 2
  3. 3 class TestController extends app.Controller {
  4. 4
  5. 5 async interception() {
  6. 6 const { a, b } = this.ctx.request.body;
  7. 7 const c = parseInt(a) + parseInt(b);
  8. 8 this.ctx.success(c);
  9. 9 }
  10. 10
  11. 11 async restructuring() {
  12. 12 const { a, b } = this.ctx.request.body;
  13. 13 const c = a + b;
  14. 14 this.ctx.success(c);
  15. 15 }
  16. 16
  17. 17 }
  18. 18
  19. 19 return TestController;
  20. 20};

中间件的使用

- 全局中间件

全局中间件自动加载,不需在后端路由中指定

- 局部中间件

只需设置后端API路由参数middlewares,如:

  1. 1{ method: 'post', path: 'version/update', controller: version, middlewares: 'inner' }

- 中间件参数

可通过后端API路由参数meta指定中间件的参数,如:

src/suite-vendor/test-party/modules/test-party/backend/src/routes.js

  1. 1const testKitchensinkGuide = require('./controller/kitchen-sink/guide.js');
  2. 2
  3. 3module.exports = app => {
  4. 4 const routes = [
  5. 5 {
  6. 6 method: 'post',
  7. 7 path: 'kitchen-sink/guide/echo9',
  8. 8 controller: 'testKitchensinkGuide',
  9. 9 action: 'echo9',
  10. 10 middlewares: 'test,transaction'
  11. 11 meta: {
  12. 12 right: {
  13. 13 type: 'resource',
  14. 14 name: 'kitchenSink',
  15. 15 },
  16. 16 },
  17. 17 },
  18. 18 ];
  19. 19 return routes;
  20. 20};
名称 说明
meta.right 全局中间件right的参数,通过配置type和name,从而验证当前用户是否具有访问此API的权限

- 禁用中间件

可通过两种方式禁用中间件:

1. config参数配置

通过属性ignore指定哪些API路由禁用此中间件

a-instance/backend/src/config/config.js

  1. 1// middlewares
  2. 2config.middlewares = {
  3. 3 instance: {
  4. 4 global: true,
  5. 5 dependencies: 'cachemem',
  6. 6 ignore: /(\/version\/(update))/,
  7. 7 },
  8. 8};

2. 后端API路由

直接在后端API路由中通过属性enable禁用中间件

a-base/backend/src/routes.js

  1. 1{ method: 'post', path: 'auth/echo', controller: auth,
  2. 2 meta: {
  3. 3 auth: {
  4. 4 enable: false
  5. 5 }
  6. 6 }
  7. 7}
名称 说明
auth.enable 禁用全局中间件auth

当某中间件被禁用后,依赖于此中间件的其他中间件也自动被禁用