内置的中间件

接口总是要接口客户端传递的参数,外部传入的参数属于不可信任的参数,因为黑客和无聊的人可以模拟客户端调用你的接口。我们需要找个合适的位置过滤参数,但最好不要在控制器里做这些事,否则会显得逻辑很乱

// 错误示范
router.get('/users', {
  action(ctx) {
    const page = ctx.request.query.page
      ? Math.max(1, Number.parseInt(ctx.body.page) || 1)
      : 1;
    const pageSize = ctx.request.query.pageSize
      ? Math.min(50, Math.max(5, Number.parseInt(ctx.body.pageSize))) || 10
      : 10;
    // ...逻辑
    ctx.send(users);
  },
});

这种写法应该不是个例,新手以及不使用框架的开发者及其容易使用这种方式过滤参数。写法重复、琐碎、容易缺失判断场景、没有类型提示、不易维护。可以说是百害无一利!

接下来我们聊聊如何正确过滤参数。

query

说明: 过滤查询字符串上的参数

import { rule } from '@aomex/core';
import { query } from '@aomex/web';
import { Router } from '@aomex/router';

export const router = new Router();

router.get('/users', {
  mount: [
    query({
      page: rule.int().min(1).default(1),
      pageSize: rule.int().min(5).max(50).default(10),
    }),
  ],
  /**
   * ctx类型自动推导:
   * query: { page: number; pageSize: number; }
   */
  action(ctx) {
    const { page, pageSize } = ctx.query;
    // ...逻辑
    ctx.send(users);
  },
});

高下立判。通过中间件和验证器的结合,可读性好逻辑简洁类型推导包含swagger文档规则可抽取复用,简直不要太爽

body

说明: 过滤请求体的参数,支持表单、JSON、XML

import { rule } from '@aomex/core';
import { body } from '@aomex/web';
import { Router } from '@aomex/router';

export const router = new Router();

router.post('/users', {
  mount: [
    body({
      username: rule.string().trim().docs({ description: '真实姓名' }),
      nickname: rule.string().docs({ description: '昵称' }),
      age: rule.int().min(1).optional(),
      address: rule.string().optional(),
    }),
  ],
  /**
   * ctx类型自动推导:
   * body: {
   *   username: string;
   *   nickname: string;
   *   age: number | undefined;
   *   address: string | undefined;
   * }
   */
  async action(ctx) {
    await logic.createUser(ctx.body);
    ctx.send(201);
  },
});

不在验证范围的数据会被抛弃以保证安全

params

说明: 过滤路径参数

import { rule } from '@aomex/core';
import { params } from '@aomex/web';
import { Router } from '@aomex/router';

export const router = new Router();

router.get('/users/:id', {
  mount: [
    params({
      id: rule.int(),
    }),
  ],
  /**
   * ctx类型自动推导:
   * params: { id: number }
   */
  action(ctx) {
    const user = logic.getUser(ctx.params.id);
    ctx.send(user);
  },
});

response

说明: 过滤响应数据,同时用于生成openapi/swagger文档

import { rule } from '@aomex/core';
import { response } from '@aomex/web';
import { Router } from '@aomex/router';

export const router = new Router();

router.get('/users', {
  mount: [
    response({
      statusCode: 200,
      contentType: 'json',
      schema: rule.array({
        id: rule.int(),
        username: rule.string(),
        nickname: rule.string(),
        // 通常从数据库中取出的是null而不是undefined
        age: rule.int().nullable(),
        address: rule.int().nullable(),
      }),
    }),
  ],
  action(ctx) {
    const user = logic.getUser(ctx.params.id);
    ctx.send(user);
  },
});

在debug模式下,我们会执行验证程序以保证接口文档的准确性,验证失败时使用500状态码。如果你不想验证,可以关掉它(不推荐)

// 关闭单个响应的验证功能。优先级:高
response({
  validate: false,
});

// 关闭所有响应的验证功能。优先级:中
const app = new WebApp({
  validateResponse: false,
});

// 关闭debug模式,从而关闭所有响应的验证功能。优先级:低
const app = new WebApp({
  debug: false,
});

判断顺序: response.validate ?? app.validateResponse ?? app.debug