快速验证器:分离逻辑

express validator: separating logic

这个问题更多的是关于代码组织而不是error/bug问题。

我正在处理请求正文验证,json 的结构如下:

  {
      "title": "Beetlejuice",
      "year": "1988",
      "runtime": "92",
      "genres": [
          "Comedy",
          "Fantasy"
      ],
      "director": "Tim Burton",
      "actors": "Alec Baldwin, Geena Davis, Annie McEnroe, Maurice Page",
      "plot": "A couple of recently deceased ghosts contract the services of a \"bio-exorcist\" in order to remove the obnoxious new owners of their house.",
      "posterUrl": "https://images-na.ssl-images-amazon.com/images/M/MV5BMTUwODE3MDE0MV5BMl5BanBnXkFtZTgwNTk1MjI4MzE@._V1_SX300.jpg"
  }

虽然 json 不大,但在简单的 POST 请求上验证仍然相当大:

router.post('/api/movies/',
    body('genres')
        .isArray()
        .withMessage('Property genres should be array of string.').bail()
        .custom(validateGenres).bail(),
    body('title')
        .not().isEmpty()
        .withMessage('Property title is required.').bail()
        .isString()
        .withMessage('Property title must be string.').bail()
        .isLength({ max: 255 })
        .withMessage('Property title must have maximum 255 characters.').bail(),
    body('year')
        .not().isEmpty()
        .withMessage('Property year is required.').bail()
        .isNumeric()
        .withMessage('Property year must be number.').bail(),
    body('runtime')
        .not().isEmpty()
        .withMessage('Property runtime is required.').bail()
        .isNumeric()
        .withMessage('Property runtime must be number.').bail(),
    body('director')
        .not().isEmpty()
        .withMessage('Property director is required.').bail()
        .isString()
        .withMessage('Property director must be string.').bail()
        .isLength({ max: 255 })
        .withMessage('Property director must have maximum 255 characters.').bail(),
    body('actors')
        .isString()
        .withMessage('Property actors  must be string.').bail(),
    body('plot')
        .isString()
        .withMessage('Property plot must be string.').bail(),
    body('posterUrl')
        .isString()
        .withMessage('Property plot must be string.').bail(),
    (req, res) => {

        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).send(errors.array())
        }

        const movieCreated = movie.create(req.body);
        
        return res.status(201).json(movieCreated);
    }) 

这里的问题是:这种大型验证是否被视为不良做法?如果是,我应该创建一个中间件来验证这个特定的 json 或者有什么可能的架构解决方案来解决这种重构问题?

这里的良好做法是您正在验证输入 ✅

你现在的做法的缺点是很难长期维持 ❌

您可以采用中间件方式,为每个需要验证的 route/body 创建多个中间件。它有效,但是,工作量和维护负担会随着时间的推移而增加。

您可以遵循的一种方法是创建输入的预期模式(例如,描述预期字段和值的模型定义),并使用验证器根据您创建的模式检查当前输入。

对于 Node.js 我们有多种工具,例如:AJV, JOI, YUP

使用带有 JOI 的示例,您可以通过执行以下操作来替换您的验证流程:

// ./validation/post-create-user-schema.js
const Joi = require('joi');

const postCreateUserSchema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  password: Joi.string().pattern(new RegExp("^[a-zA-Z0-9]{3,30}$")).required(),
  repeat_password: Joi.ref("password").required(),
  access_token: [Joi.string(), Joi.number()],
  birth_year: Joi.number().integer().min(1900).max(2013),
  email: Joi.string().email({
    minDomainSegments: 2,
    tlds: { allow: ["com", "net"] },
  }),
});

module.exports = { postCreateUserSchema };



// ./routes.js
const { postCreateUserSchema } = require('./validation/post-create-user-schema.js')

router.post('/api/users/', (req, res) => {
  try {
    let json = JSON.parse(req.body);
    postCreateUserSchema.validate(json);
  } catch (err) {
    // -> { value: {}, error: '"username" is required' }
    console.error(err);
    return res.status(400).send({ ok: false, message: err.error });
  }

  // ... route code here
});