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

前言

对每个接口的传入参数进行校验,是一个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 codereturn {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: stringbuttons: 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: ValidateUtilprivate 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会帮我们生成这样的文档:

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

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

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

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

filename: ./employers.tsexport 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)
复制代码

就会生成文档:

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

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

转载于:https://juejin.im/post/5c9b3b53f265da612e6d708c

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/536616.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux服务器下降,linux - 远程升级Ubuntu:如何最大程度地降低丢失服务器的风险? - Ubuntu问答...

问题描述背景&#xff1a;由于raid控制器的不兼容性问题&#xff0c;我被迫将服务器从Ubuntu 8.04 LTS远程升级到10.04 LTS。与服务器的Internet连接比较稳定&#xff0c;很少掉线。尽管如此&#xff0c;我还是担心在升级时会丢失通过SSH的连接&#xff0c;从而使服务器处于无法…

Linux中英文命令对应

本文我们把Linux的中英文命令做了对应翻译&#xff0c;给需要的朋友参考一下。很多朋友在论坛上找Linux英文命令&#xff0c;我们给大家整理了比较全的Linux英文命令&#xff0c;并且附上了对应的中文意思。 su&#xff1a;Swith user 切换用户&#xff0c;切换到root用户 cat:…

linux实验四文件安全,西北农林科技大学Linux实验四 用户和文件安全

一、 实验目的实验四 用户和文件安全1. 理解用户、组的概念2. 理解/etc/passwd、/etc/group、/etc/shadow文件 3. 练习useradd、groupadd、gpasswd、groups命令二、 实验内容 1. 添加用户 2. 添加组3. 添加用户到工作组 4. 改变目录原始组 5. 建立多个用户共享的目录6. 理解文件…

Unable to resolve dependency问题解决

Unable to resolve dependency 是一个让我头疼的问题 之前总是阴差阳错调试好 但是也没有总结出来方法 但是今天找到了 方法来源 https://jingyan.baidu.com/article/19192ad8c489dfe53e5707ee.html 原因就是用户的gradle.properties 设置了代理&#xff0c;将文件内的代理注释…

linux 服务器进程,如何查看Linux服务器的进程

Linux服务器正常启动后&#xff0c;提供服务时会调用程序&#xff0c;占用进程。这时候如何查看系统中有哪些进程在被调用呢&#xff1f;我们可以通过以下命令来查看。1.psps命令是最基本同时也是非常强大的进程查看命令。使用该命令可以确定有哪些进程正在运行和它所运行的状态…

计数排序与桶排序python实现

计数排序与桶排序python实现 计数排序 计数排序原理&#xff1a; 找到给定序列的最小值与最大值 创建一个长度为最大值-最小值1的数组&#xff0c;初始化都为0 然后遍历原序列&#xff0c;并为数组中索引为当前值&#xff0d;最小值的值&#xff0b;&#xff11; 此时数组中…

perl脚本执行linux命令行,Perl调用shell命令方法小结

一、systemperl也可以用system调用shell的命令,它和awk的system一样,返回值也是它调用的命令的退出状态.代码如下:[rootAX3sp2 ~]# cat aa.pl#! /usr/bin/perl -w$file "wt.pl";system("ls -l wt.pl");$result system "ls -l $file";print &qu…

JVM快速调优手册02:常见的垃圾收集器

2019独角兽企业重金招聘Python工程师标准>>> 如果说收集算法是内存回收的方法论&#xff0c;那么垃圾收集器就是内存回收的具体实现。 Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定&#xff0c;因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器都可…

linux运维平台工具,Linux运维自动化工具 Kickstart

简介&#xff1a;批量安装操作系统工具之 Kickstart &#xff0c;RedHat 早前推出的产品( 不多说了&#xff0c;现在都玩 Cobbler 啦&#xff0c;见 http://www.linuxidc.com/Linux/2016-04/129977.htm )。测试环境&#xff1a;CentOS 6.6 x86_64 minimal一、安装软件包shell &…

PostgreSQL 并行查询概述

2019独角兽企业重金招聘Python工程师标准>>> PostgreSQL从9.6版本开始加入并行查询&#xff0c;并在PostgreSQL10和PostgreSQL11分别做了大量加强工作。下面从&#xff1a; 何时启用并行查询功能并行查询是如何工作的worker进程数量越多&#xff0c;查询性能越高吗三…

linux下得到date命令,linux下date命令获得今天日期的用法

1。获取今天日期的各类用法&#xff1a;oracle[roottest ~]# date %Y_%m_%d2016_05_22[roottest ~]# date %Y_%m_%d2016_05_22ide[roottest ~]# date "%Y_%m_%d"2016_05_22[roottest ~]# date %Y_%m_%d2016_05_22[roottest ~]# date "%Y_%m_%d"2016_05_22i…

Quarkus:一个Kubernetes原生Java框架

Red Hat发布了Quarkus&#xff0c;这是一个为GraalVM和OpenJDK HotSpot量身定制的Kubernetes原生Java框架。Quarkus的目标是使Java成为Kubernetes和无服务器环境中的领先平台&#xff0c;为开发人员提供统一的反应式和命令式编程模型。 Quarkus利用Java开发人员使用的一系列库&…

分区安装linux,怎样安装Linux?

我的机子上装了win2000&#xff0c;想装个Linux可是在安装时&#xff0c;竟然D 、E盘都不见了&#xff0c;win2000也进不去了我只得重装2000&#xff0c;现在我都不敢装Linux了请高手指点&#xff01;|你最好用PQMAGIC先分区&#xff0c;大约2。5G空间就够了&#xff0c;可以参…

linux scp传输文件命令

scp -r /opt/test root192.168.2.105:/opt 转载于:https://www.cnblogs.com/LynnChen/p/10620576.html

pg10 10.3 1 linux64,Install Postgresql 10 In Ubutnu 16.04 LTS

PostgreSQL数据库是一个高性能的全功能的开源关系型数据库&#xff0c;这里讲解一下如何在Ubuntu 16.04 LTS 下安装 PostgreSQL 10。添加软件源wget -q -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -sudo sh -c echo "deb http://apt.po…

nginx能访问html静态文件但无法访问php文件

nginx.conf中红框部分修改成你的实际网站根目录转载于:https://www.cnblogs.com/IT-Crowd/p/10626549.html

linux虚拟光驱挂载方法,Linux操作系统下虚拟光驱(iso)的挂载

1、挂载iso文件一般查看iso文件内容&#xff0c;只需要&#xff1a;#mount -t iso9660 -o loop xxx.iso /mnt/cdrom就可以在/mnt/cdrom下看到xxx.iso的内容。2、复制光盘为iso镜像#dd if/dev/hdb ofxxx.iso或者#cp /dev/cdrom xxx.iso3、虚拟iso为设备#rm -rf /dev/cdrom //删除…

[深度概念]·K-Fold 交叉验证 (Cross-Validation)的理解与应用

个人主页--> xiaosongshine.github.io/ 1.K-Fold 交叉验证概念在机器学习建模过程中&#xff0c;通行的做法通常是将数据分为训练集和测试集。测试集是与训练独立的数据&#xff0c;完全不参与训练&#xff0c;用于最终模型的评估。在训练过程中&#xff0c;经常会出现过拟合…

linux mariadb 升级,linux mariadb

linux mariadb转载 一 安装下载mariaDB MariaDB-5.5.29-rhel5-x86_64-common.rpm 和MariaDB-5.5.29-rhel5-x86_64-server.rpm 包,MariaDB-5.5.29-rhel5-x86_64-client.rpm2.然后再http.//yum。mariadb。org/ 找到 RPM-GPG-KEY-MariaDB 这个PGP文件&#xff0c;把文件放入到/etc…

Linux Note

日期&#xff1a;2019/3/31 内容&#xff1a;Linux学习笔记 一、Linux命令 ls -l 操作效果 第一列&#xff1a;文件权限 一共10位。 01(r)2(w)3(x)4(r)5(w)6(x)7(r)8(w)9(x)文件类型文件所有者权限 usr权限&#xff0c;u权限文件所有者所属组成员的权限 group权限&#xff0c;g…