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

  1. 拦截:比如通过中间件判断用户权限,如果没有权限则中止后续执行
  2. 重整:比如通过中间件对前端发来的数据进行验证,并转化为期望的类型
  3. 注入:比如通过中间件向ctx注入对象,以便为后续代码提供必要的基础功能

在这里,我们通过一个虚拟的需求,用中间件实现拦截重整注入三种功能:

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

声明中间件

src/module/test-party/backend/src/config/config.js

// middlewares
config.middlewares = {
  testInterception: {
    global: false,
    dependencies: 'instance',
  },
  testRestructuring: {
    global: false,
    dependencies: 'instance',
  },
  testInjection: {
    global: false,
    dependencies: 'instance',
  },
};
名称 说明
global 是否为全局中间件,全局中间价会自动加载,局部中间件需要手动指定
dependencies 标明此中间件依赖哪些中间件,从而在那些中间件后面加载。一般要依赖于instance,因为instance提供了多实例的基础逻辑

定义中间件

src/module/test-party/backend/src/config/middleware/interception.js

module.exports = () => {
  return async function interception(ctx, next) {
    const { a, b } = ctx.request.body;
    if (a === undefined || b === undefined) return ctx.throw(1002); // 1002: 'Incomplete Parameters'
    // next
    await next();
  };
};

src/module/test-party/backend/src/config/middleware/restructuring.js

module.exports = () => {
  return async function restructuring(ctx, next) {
    const { a, b } = ctx.request.body;
    ctx.request.body.a = parseInt(a);
    ctx.request.body.b = parseInt(b);
    // next
    await next();
  };
};

src/module/test-party/backend/src/config/middleware/injection.js

module.exports = () => {
  return async function injection(ctx, next) {
    ctx.meta.__plus = (a, b) => {
      return a + b;
    };
    // next
    await next();
  };
};

引用中间件

src/module/test-party/backend/src/config/middlewares.js

const interception = require('./middleware/interception.js');
const restructuring = require('./middleware/restructuring.js');
const injection = require('./middleware/injection.js');

module.exports = {
  testInterception: interception,
  testRestructuring: restructuring,
  testInjection: injection,
};

使用中间件

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

src/module/test-party/backend/src/routes.js

// test/feat/middleware
{ method: 'post', path: 'test/feat/middleware/interception', controller: testFeatMiddleware, middlewares: 'test,testInterception' },
{ method: 'post', path: 'test/feat/middleware/restructuring', controller: testFeatMiddleware, middlewares: 'test,testInterception,testRestructuring' },
{ method: 'post', path: 'test/feat/middleware/injection', controller: testFeatMiddleware, middlewares: 'test,testInterception,testRestructuring,testInjection' },

后端控制器方法

src/module/test-party/backend/src/controller/test/feat/middleware.js

module.exports = app => {

  class TestController extends app.Controller {

    async interception() {
      const { a, b } = this.ctx.request.body;
      const c = parseInt(a) + parseInt(b);
      this.ctx.success(c);
    }

    async restructuring() {
      const { a, b } = this.ctx.request.body;
      const c = a + b;
      this.ctx.success(c);
    }

    async injection() {
      const { a, b } = this.ctx.request.body;
      const c = this.ctx.meta.__plus(a, b);
      this.ctx.success(c);
    }

  }

  return TestController;
};

单元测试

在这里,我们采用单元测试来验证中间件机制是否按预期运作

src/module/test-party/backend/test/controller/test/feat/middleware.test.js

const { app, mockUrl, mockInfo, assert } = require('egg-born-mock')(__dirname);

describe('test/controller/test/feat/middleware.test.js', () => {

  it('action:interception', async () => {
    // success
    let result = await app.httpRequest()
      .post(mockUrl('test/feat/middleware/interception'))
      .send({
        a: '1',
        b: '2',
      });
    assert.equal(result.body.data, 3);

    // fail
    result = await app.httpRequest()
      .post(mockUrl('test/feat/middleware/interception'))
      .send();
    assert.equal(result.status, 500);
  });

  it('action:restructuring', async () => {
    // success
    const result = await app.httpRequest()
      .post(mockUrl('test/feat/middleware/restructuring'))
      .send({
        a: '1',
        b: '2',
      });
    assert.equal(result.body.data, 3);
  });

  it('action:injection', async () => {
    // success
    const result = await app.httpRequest()
      .post(mockUrl('test/feat/middleware/injection'))
      .send({
        a: '1',
        b: '2',
      });
    assert.equal(result.body.data, 3);
  });

});