文档章节

使用class-validator替换Joi包的方法

AI考拉
 AI考拉
发布于 03/27 17:18
字数 1332
阅读 14
收藏 0

前言

对每个接口的传入参数进行校验,是一个Web后端项目的必备功能,有一个npm包叫Joi可以很优雅的完成这个工作,比如这样子:

const schema = {
    userId: Joi.string()
};
const {error, value} = Joi.validate({ userId: 'a string' }, schema);

我们使用Typescript是希望得到明确的类型定义,减少出错的可能性。在一个后端项目中,给每个接口定义它的传入参数结构以及返回结果的结构,是一件很值得做的事情,因为这样给后续的维护带来极大的便利。比如这样子:

export type IFooParam = {
  userId: string
}

export type IFooResponse = {
  name: string
}

async foo (param: IFooParam): Promise<IFooResponse> {
  // Your business code
  return {name: 'bar'}
}

现在问题就来了,如果传入参数希望加多一个字段,我们必须得修改2个地方,一个是Joi的校验,一个是IFooParam类型的定义。有没有好的办法解决这个问题呢?

Class-validaotr

有一个npm包叫class-validator, 是采用注解的方式进行校验,底层使用的是老牌的校验包validator.js
这次试用,发现通过一些小包装,居然做到像Joi一样优雅的写法,而且更好用!

定义传入/返回结构

import {Length, Min, Max} from 'class-validator'

export class IRegister {
  @Length(11)
  phone: string

  @Length(2, 10)
  name: string

  @Min(18)
  @Max(50)
  age: number
}

class Button {
  text: string
}

export class ORegister {
  /**
   * user's id
   */
  userId: string

  buttons: Button[]
}

这里定义了2个类,IRegister为传入参数,通过class-validator规定的注解方式做校验,ORegister为返回结果。

class-validator官方提供的方式还不能直接对一个请求的body进行校验,它要求必须要是IRegister类的一个对象,所以需要做一些处理。

使用class-transformer做转化

跟class-validator的作者也开源了另外一个包,叫class-transformer, 可以将一个json转成指定的类的对象,官方的例子是这样的:

import {plainToClass} from "class-transformer";

let users = plainToClass(User, userJson); // to convert user plain object a single user. also supports arrays

利用这一点,我们写一个小工具:

import * as classTransformer from 'class-transformer'
import {validate} from 'class-validator'
import * as lodash from 'lodash'

export class ValidateUtil {
  private static instance: ValidateUtil

  private constructor () {
  }

  static getInstance () {
    return this.instance || (this.instance = new ValidateUtil())
  }

  async validate (Clazz, data): Promise<any> {
    const obj = classTransformer.plainToClass(Clazz, data)
    const errors = await validate(obj)
    if (errors.length > 0) {
      console.info(errors)
      throw new Error(lodash.values(errors[0].constraints)[0])
    }
    return obj
  }
}

这个小工具提供了一个validate方法,第一个参数是一个类定义,第二个是一个json,它先利用class-transformer将json转成指定类的对象,然后使用class-validator做校验,如果校验错误将抛出错误,否则返回转化后的对象。

在Controller中使用

有了上面的工具,就可以方便地在代码中对传入参数做校验了,比如这样:

  static async register(ctx) {
    const iRegister = await ValidateUtil.getInstance().validate(IRegister, ctx.request.body)
    const oRegister = await UserService.register(iRegister)
    ctx.body = oRegister
  }

新问题

到了这里,完美地使用class-validator替换掉了Joi。

但是还有一个问题没解决,也是之前一直遗留的问题。

我们使用apidoc编写接口文档,当新增或修改一个接口时,是通过编写一段注释,让apidoc自动生成html文档,将文档地址发给前端,可以减少双方的频繁沟通,而且对前端的体验也是非常好的。比如写这样一段注释:

  /**
   * @api {post} /user/registerOld registerOld
   * @apiGroup user
   * @apiName registerOld
   * @apiParam {String} name user's name
   * @apiParam {Number} age user's age
   * @apiSuccess {String} userId user's id 
   */
  router.post('/user/registerOld', UserController.register)

apidoc会帮我们生成这样的文档: oldApidocDemo

问题比较明显,当我们要新增一个参数时,需要修改一次类的定义,同时还要修改一次apidoc的注释,很烦,由于很烦,文档会慢慢变得没人维护,新同事就会吐槽没有文档或者文档太旧了。

理想的情况是代码即文档,只需要修改类的定义,apidoc文档自动更新。

探索apidoc根据class-validator的定义生成

从同事的分享中得知一个废弃的npm包,叫apidoc-plugin-ts, 可以实现根据ts的interface定义来生成apidoc的。官方的例子:

filename: ./employers.ts
 
export interface Employer {
  /**
   * Employer job title
   */
  jobTitle: string;
  /**
   * Employer personal details
   */
  personalDetails: {
    name: string;
    age: number;
  }
}
 @apiInterface (./employers.ts) {Person}

会转化成:

 @apiSuccess {String} jobTitle Job title
 @apiSuccess {Object} personalDetails Empoyer personal details
 @apiSuccess {String} personalDetails.name
 @apiSuccess {Number} personalDetails.age

虽然不知道为什么作者要废弃它,但是它的思想很好,源码也很有帮助。

给我的启发是,参考这个npm包,写一个针对class定义来生成apidoc的插件就行了。

造轮子: apidoc-plugin-class-validator

轮子的制造细节不适合在这里陈述,基本上参考apidoc-plugin-ts,目前已经发布在npm上了,apidoc-plugin-class-validator

使用apidoc-plugin-class-validator

以上面的注册接口为例,使用方法:

  /**
   * @api {post} /user/register register
   * @apiGroup user
   * @apiName register
   * @apiParamClass (src/user/io/Register.ts) {IRegister}
   * @apiSuccessClass (src/user/io/Register.ts) {ORegister}
   */
  router.post('/user/register', UserController.register)

就会生成文档: demo

后续新增字段,只需修改IRegister类的定义就行,真正做到了修改一处,处处生效,代码即文档的效果。

本文的demo代码在这里,这是一个简单的web后端项目,看代码更容易理解。

© 著作权归作者所有

AI考拉
粉丝 5
博文 35
码字总数 47816
作品 0
广州
私信 提问
Struts 学习笔记之ActionForm

Struts 中定义了一些JavaBeans,主要是以ActionForm为父类扩展开来的,如下图: ① org.apache.struts.action包中 public abstract class ActionForm implements Serializable public class ......

xiahuawuyu
2012/05/17
50
0
Hibernate Validator

本文主要讲述如何使用hibernate validator来校验入参,避免在业务代码里进行每个接口进行入参校验,提搞代码的简洁及欣赏性。 主要涉及针对接口方法的入参简单校验,不涉及使用group进行的组...

A-I-O
2018/11/07
446
0
SpringMVC+hibernate-validator配置失败

求助: 配置文件为: 使用的包为hibernate-validator-5.1.3 报找不到org/hibernate/validator/resourceloading/ResourceBundleLocator这个类 谢谢...

niufennan
2015/03/26
2.4K
1
struts2.2.3升级到2.3.15.3过程记录

以下按照步骤,依次介绍。 1.jar包替换。共包括一下几个。 commons-lang3-3.1.jar ognl-3.0.6.jar struts2-convention-plugin-2.3.15.3.jar struts2-core-2.3.15.3.jar struts2-json-plugin-......

lawrenceli
2013/10/25
1K
0
struts2中两种validation.xml的配置方式_百度文库

在struts中,根据配置的validation.xml文件进行页面输入项目的验证已经众所周知,本文介绍在struts2中两种validation.xml的配置方式。可以根据不同的需要进行不同的配置。 以下以login页面输...

lzw_me
2014/07/29
162
0

没有更多内容

加载失败,请刷新页面

加载更多

经典系统设计面试题解析:如何设计TinyURL(二)

原文链接:https://www.educative.io/courses/grokking-the-system-design-interview/m2ygV4E81AR 编者注:本文以一道经典的系统设计面试题:《如何设计TinyURL》的参考答案和解析为例,帮助...

APEMESH
25分钟前
7
0
使用logstash同步MySQL数据到ES

概述   在生成业务常有将MySQL数据同步到ES的需求,如果需要很高的定制化,往往需要开发同步程序用于处理数据。但没有特殊业务需求,官方提供的logstash就很有优势了。   在使用logstas...

zxiaofan666
35分钟前
8
0
X-MSG-IM-分布式信令跟踪能力

经过一周多的鏖战, X-MSG-IM的分布式信令跟踪能力已基本具备, 特点是: 实时. 只有要RX/TX就会实时产生信令跟踪事件, 先入kafka, 再入influxdb待查. 同时提供实时sub/pub接口. 完备. 可以完整...

dev5
45分钟前
6
0
OpenJDK之CyclicBarrier

OpenJDK8,本人看的是openJDK。以前就看过,只是经常忘记,所以记录下 图1 CyclicBarrier是Doug Lea在JDK1.5中引入的,作用就不详细描述了,主要有如下俩个方法使用: await()方法,如果当前线...

克虏伯
48分钟前
6
0
实战项目-学成在线(八)

在前后端分离架构中,服务层被拆分成了很多的微服务,微服务的信息如何管理?Spring Cloud中提供服务注册中心来管理微服务信息。 注册中心作用: 1、微服务数量众多,要进行远程调用就需要知...

lianbang_W
49分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部