Node在Controller层进行数据校验的过程详解


Posted in Javascript onAugust 28, 2020

前言

幽默风趣的后端程序员一般自嘲为 CURD Boy。CURD, 也就是对某一存储资源的增删改查,这完全是面向数据编程啊。

真好呀,面向数据编程,往往会对业务理解地更加透彻,从而写出更高质量的代码,造出更少的 BUG。既然是面向数据编程那更需要避免脏数据的出现,加强数据校验。否则,难道要相信前端的数据校验吗,毕竟前端数据校验直达用户,是为了 UI 层更友好的用户反馈。

数据校验层

后端由于重业务逻辑以及待处理各种数据,以致于分成各种各样的层级,以我经历过的后端项目就有分为 Controller、Service、Model、Helper、Entity 等各种命名的层,五花八门。但这里肯定有一个层称为 Controller,站在后端最上层直接接收客户端传输数据。

由于 Controller 层是服务器端中与客户端数据交互的最顶层,秉承着 Fail Fast 的原则,肩负着数据过滤器的功能,对于不合法数据直接打回去,如同秦琼与尉迟恭门神般威严。

数据校验同时衍生了一个半文档化的副产品,你只需要看一眼数据校验层,便知道要传哪些字段,都是些什么格式。

以下都是常见的数据校验,本文讲述如何对它们进行校验:

  1. required/optional
  2. 基本的数据校验,如 number、string、timestamp 及值需要满足的条件
  3. 复杂的数据校验,如 IP、手机号、邮箱与域名
const body = {
 id,
 name,
 mobilePhone,
 email
}

山月接触过一个没有数据校验层的后端项目,if/else 充斥在各种层级,万分痛苦,分分钟向重构。

JSON Schema

JSON Schema 基于 JSON 进行数据校验格式,并附有一份规范 json-schema.org,目前 (2020-08) 最新版本是 7.0。各种服务器编程语言都对规范进行了实现,如 go、java、php 等,当然伟大的 javascript 也有,如不温不火的ajv。

以下是校验用户信息的一个 Schema,可见语法复杂与繁琐:

{
 "$schema": "http://json-schema.org/draft-04/schema#",
 "title": "User",
 "description": "用户信息",
 "type": "object",
 "properties": {
 "id": {
 "description": "用户 ID",
 "type": "integer"
 },
 "name": {
 "description": "用户姓名",
 "type": "string"
 },
 "email": {
 "description": "用户邮箱",
 "type": "string",
 "format": "email",
 "maxLength": 20
 },
 "mobilePhone": {
 "description": "用户手机号",
 "type": "string",
 "pattern": "^(?:(?:\+|00)86)?1[3-9]\d{9}$",
 "maxLength": 15
 }
 },
 "required": ["id", "name"]
}

对于复杂的数据类型校验,JSON Schema 内置了以下 Format,方便快捷校验

  • Dates and times
  • Email addresses
  • Hostnames
  • IP Addresses
  • Resource identifiers
  • URI template
  • JSON Pointer
  • Regular Expressions

对于不在内置 Format 中的手机号,使用 ajv.addFormat 可手动添加 Format

ajv.addFormat('mobilePhone', (str) => /^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(str));

Joi

joi 自称最强大的 JS 校验库,在 github 也斩获了一万六颗星星。相比 JSON Schema 而言,它的语法更加简洁并且功能强大。

The most powerful data validation library for JS

完成相同的校验,仅需要更少的代码,并能够完成更加强大的校验。以下仅做示例,更多示例请前往文档。

const schema = Joi.object({
 id: Joi.number().required(),
 name: Joi.number().required(),
 email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } }),
 mobilePhone: Joi.string().pattern(/^(?:(?:\+|00)86)?1[3-9]\d{9}$/),

 password: Joi.string().pattern(/^[a-zA-Z0-9]{3,30}$/),
 // 与 password 相同的校验
 repeatPassword: Joi.ref('password'),
})
 // 密码与重复密码需要同时发送
 .with('password', 'repeat_password');
 // 邮箱与手机号提供一个即可
 .xor('email', 'mobilePhone')

数据校验与路由层集成

由于数据直接从路由传递,因此 koajs 官方基于 joi 实现了一个joi-router,前置数据校验到路由层,对前端传递来的 query、body 与 params 进行校验。

joi-router 也同时基于 co-body 对前端传输的各种 content-type 进行解析及限制。如限制为 application/json,也可在一定程度上防止 CSRF 攻击。

const router = require('koa-joi-router');
const public = router();

public.route({
 method: 'post',
 path: '/signup',
 validate: {
 header: joiObject,
 query: joiObject,
 params: joiObject,
 body: joiObject,
 maxBody: '64kb',
 output: { '400-600': { body: joiObject } },
 type: 'json',
 failure: 400,
 continueOnError: false
 },
 pre: async (ctx, next) => {
 await checkAuth(ctx);
 return next();
 },
 handler: async (ctx) => {
 await createUser(ctx.request.body);
 ctx.status = 201;
 },
});

正则表达式与安全正则表达式

山月在一次排查性能问题时发现,一条 API 竟在数据校验层耗时过久,这是我未曾想到的。而问题根源在于不安全的正则表达式,那什么叫做不安全的正则表达式呢?

比如下边这个能把 CPU 跑挂的正则表达式就是一个定时炸弹,回溯次数进入了指数爆炸般的增长。

可以参考文章 浅析 ReDos 原理与实践

const safe = require('safe-regex')
const re = /(x+x+)+y/

// 能跑死 CPU 的一个正则
re.test('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')

// 使用 safe-regex 判断正则是否安全
safe(re) // false

数据校验,针对的大多是字符串校验,也会充斥着各种各样的正则表达式,保证正则表达式的安全相当紧要。safe-regex 能够发现哪些不安全的正则表达式。

总结

  1. Controller 层需要进行统一的数据校验,可以采用 JSON Schema (Node 实现 ajv) 与 Joi
  2. JSON Schema 有官方规范及各个语言的实现,但语法繁琐,可使用校验功能更为强大的 Joi
  3. 进行字符串校验时,注意不安全的正则引起的性能问题

到此这篇关于Node在Controller层进行数据校验的文章就介绍到这了,更多相关Node在Controller层数据校验内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
jquery属性选择器not has怎么写 行悬停高亮显示
Nov 13 Javascript
Jquery创建一个层当鼠标移动到层上面不消失效果
Dec 12 Javascript
理解Javascript的动态语言特性
Jun 17 Javascript
js添加事件的通用方法推荐
May 15 Javascript
JS实现显示带倒影的图片横排居中放大展示特效实例【测试可用】
Aug 23 Javascript
AngularJS 过滤器(自带和自建)详解
Sep 19 Javascript
bootstrap3 dialog 更强大、更灵活的模态框
Apr 20 Javascript
浅谈MUI框架中加载外部网页或服务器数据的方法
Jan 31 Javascript
微信小程序保存多张图片的实现方法
Mar 05 Javascript
bootstrap-table后端分页功能完整实例
Jun 01 Javascript
详解JavaScript之ES5的继承
Jul 08 Javascript
JavaScript中变量提升和函数提升的详解
Aug 07 Javascript
Postman无法正常返回结果问题解决
Aug 28 #Javascript
Vue+Element UI 树形控件整合下拉功能菜单(tree + dropdown +input)
Aug 28 #Javascript
vue自定义指令和动态路由实现权限控制
Aug 28 #Javascript
vue 动态给每个页面添加title、关键词和描述的方法
Aug 28 #Javascript
vue-cli+webpack项目打包到服务器后,ttf字体找不到的解决操作
Aug 28 #Javascript
vue select 获取value和lable操作
Aug 28 #Javascript
VSCode 添加自定义注释的方法(附带红色警戒经典注释风格)
Aug 27 #Javascript
You might like
php 用checkbox一次性删除多条记录的方法
2010/02/23 PHP
wamp下修改mysql访问密码的解决方法
2013/05/07 PHP
javascript showModalDialog,open取得父窗口的方法
2010/03/10 Javascript
jQuery 选择器理解
2010/03/16 Javascript
浅谈Javascript嵌套函数及闭包
2010/11/09 Javascript
jquery 读取页面load get post ajax 四种方式代码写法
2011/04/02 Javascript
js动画效果制件让图片组成动画代码分享
2014/01/14 Javascript
jquery果冻抖动效果实现方法
2015/01/15 Javascript
JavaScript实现向OL列表内动态添加LI元素的方法
2015/03/21 Javascript
js仿QQ邮箱收件人选择与搜索功能
2017/02/10 Javascript
php简单数据库操作类的封装
2017/06/08 Javascript
create-react-app修改为多页面支持的方法
2018/05/17 Javascript
JavaScript数组基于交换的排序示例【冒泡排序】
2018/07/21 Javascript
自己动手封装一个React Native多级联动
2018/09/19 Javascript
js对象属性名驼峰式转下划线的实例代码
2020/09/17 Javascript
NodeJS开发人员常见五个错误理解
2020/10/14 NodeJs
JavaScript实现滚动加载更多
2020/12/27 Javascript
深入理解Python装饰器
2016/07/27 Python
用 Python 连接 MySQL 的几种方式详解
2018/04/04 Python
python多线程之事件Event的使用详解
2018/04/27 Python
python Spyder界面无法打开的解决方法
2018/04/27 Python
django框架之cookie/session的使用示例(小结)
2018/10/15 Python
python中读入二维csv格式的表格方法详解(以元组/列表形式表示)
2020/04/24 Python
python爬虫今日热榜数据到txt文件的源码
2021/02/23 Python
Html5中的桌面通知Notification的实现
2018/09/25 HTML / CSS
美国最大网上鞋店:Zappos
2016/07/25 全球购物
阿拉伯世界最大的电子卖场:Souq埃及
2016/08/01 全球购物
京东奢侈品:全球奢侈品牌
2018/03/17 全球购物
奥地利领先的在线药房:SHOP APOTHEKE
2019/10/07 全球购物
教师评优事迹材料
2014/01/10 职场文书
七一表彰活动方案
2014/01/18 职场文书
六一节目主持词
2014/04/01 职场文书
低碳生活的宣传标语
2014/06/23 职场文书
公司委托书格式范文
2014/10/09 职场文书
离婚案件上诉状
2015/05/23 职场文书
Nginx反爬虫策略,防止UA抓取网站
2021/03/31 Servers