跳到主要内容

输入数据处理

对于一个 web 项目,使用最多并且漏洞最多的就是数据的输入处理了,正所谓病从口入,web 项目亦是如此,如果客户端传递了恶意的数据进来你又没有做相关检查,轻则500错误,重则数据泄漏,服务器提权。 所以我们宁愿多麻烦一点做多一点数据检查,也不能有任何偷懒不检查而直接使用的想法!

所以本章内容关键点在于使用各种 Helper 助手功能,代码里面取得原始数据的例子只是为了方便参照原始数据是什么。

Query 查询参数

Query 参数指的是请求地址 ? 后的数据

import { mapping, Context, QueryHelper, $query } from 'zenweb';

export class DemoController {
// 取得原始解析对象
// demo1?age=10&name=Bob
@mapping()
demo1(ctx: Context) {
return ctx.query; // 取得已被解析的数据对象 { age: "10", name: "Bob" }
}

// 使用 QueryHelper 进行参数类型转换与校验
@mapping()
demo2(query: QueryHelper) {
return query.get({
age: 'int',
name: 'string',
}); // 解析并转换类型 { age: 10, name: "Bob" }
}

// 如果启用全局应用实例,可以使用 $query 对象进行参数类型转换与校验
// $query 对象可以在任意方法内直接调用
@mapping()
demo2() {
return $query.get({
age: 'int',
name: 'string',
}); // 解析并转换类型 { age: 10, name: "Bob" }
}
}

内置的通用分页处理

分页处理是最常见的数据处理功能,所以专门做了一个标准的处理方法。

分页处理支持如下功能:

  • 条数限制检查
  • 起始位置检查
  • 设置默认条数
  • 排序字段检查
import { mapping, QueryHelper } from 'zenweb';

export class DemoController {
// 内置的分页校验功能
// 请求 demo?limit=5&offset=10&order=-id
@mapping()
demo(query: QueryHelper) {
return query.page({
// 这里可以设置参数
}); // 校验分页参数并处理
}
}

服务端配置参数

参数名默认值功能描述
inputctx.query输入数据,默认取 Query 参数
minLimit1最少限制条数
maxLimit100最大限制条数
defaultLimit20不指定条数的情况下的默认条数
allowOrder可排序的字段名称列表
maxOrder1最多允许的同时排序字段数
defaultOrder不指定排序字段时默认排序字段

客户端输入参数

参数名默认值功能描述
limitdefaultLimit取出条数限制(每页多少条)
offset0起始位置,在不指定 page 参数时有效
page1第几页
orderdefaultOrder指定排序字段。例如:正序 id 倒序 -id,多个用逗号分隔

Params 路由参数

params 参数指的是在路由地址中所包含的变量参数,例如 /user/:id 中的 :id 参数

import { mapping, Context, ParamHelper } from 'zenweb';

export class DemoController {
// 请求地址: /user/99
@mapping({ path: '/user/:id' })
userDetail(ctx: Context, ph: ParamHelper) {
const orig = ctx.params; // 取得原始数据对象 { id: "99" }
const data = ph.get({ id: 'int' }); // 使用 ParamHelper 解析并转换类型 { id: 99 }
return {
orig,
data,
};
}
}

Body 请求主体数据

类 POST 请求往往会提交一些数据内容在 Body 中,例如 form, json, xml, 文件上传等

关于为什么采用依赖注入替代主动解析的原因:

  1. 解析这些内容需要一定的服务器开销,从 zenweb 3.10 开始就不再主动解析提交内容,而是由依赖注入来初始化并解析内容。
  2. 如遇到特殊内容格式,在之前的版本往往由于无法正确识别其格式而拒绝请求。
  3. 多种格式的解析配置不灵活,无法按需配置

@zenweb/body 模块支持格式有:form-urlencodedjson 以及其他文本格式:xmltext/*

关于 xml

这里的 xml 格式并不是会解析成 xml 对象,而是支持读取 xml 原始文本,如需要解析 xml 原始文本为 js 对象可以使用 @zenweb/xml-body 模块让 body 模块支持 xml 文本解析。

BodyHelper 对象

BodyHelper 最常用的数据获取方法,支持数据类型的转换与数据校验。

BodyHelper 只支持能被解析为对象的数据结构。也就是 BodyHelper 读取的是 ObjectBody 产生的结果,例如 json、form表单。

import { mapping, BodyHelper } from 'zenweb';

export class DemoController {
@mapping({ method: 'POST' })
demo(bh: BodyHelper) {
return bh.get({
age: '!int', // 必须为整数
name: {
type: '!trim', // 必须有字符串,去除两端空格
minLength: 2, // 最小长度 2 个字
maxLength: 10, // 最大长度 10 个字
},
});
}
}

ObjectBody 对象

可被解析为对象的数据,直接返回已解析数据对象。

import { mapping, ObjectBody } from 'zenweb';

export class DemoController {
@mapping({ method: 'POST' })
// 例如form提交的数据: age=10&name=Bob
demo(body: ObjectBody) {
return body; // 返回格式: {age: "10", name: "Bob"}
}
}

Body 对象

Body 对象通常用来取得更详细的数据类型,以及数据是经过何种解析器处理过,如果数据不能被配置的解析器所处理则会尝试是否为已配置的文本类型。否则不会返回任何类型。

import { mapping, Body } from 'zenweb';

export class DemoController {
@mapping({ method: 'POST' })
demo(body: Body) {
console.log(body.parser); // 匹配的解析器对象
console.log(body.type); // 匹配的数据类型
console.log(body.data); // 如果有数据 body.data 已经处理完成的结果
return '查看命令行输出';
}
}

取得原始数据 RawBody

RawBody 对象支持任意数据类型,这个对象并不常用,如果原始数据使用了标准的 http 压缩协议会被自动解压缩,但是不会经过任何编码转换或任何数据解析处理。

import { mapping, RawBody } from 'zenweb';

export class DemoController {
@mapping({ method: 'POST' })
demo(raw: RawBody) {
// 如果有数据 raw.data 返回 Buffer 对象
console.log(raw.data);
return '查看命令行输出';
}
}

使用 Helper 参数校验转换助手

对于数据的校验与转换,在传统方案中总是处理,十分繁琐,例如下面的代码

src/controller/index.ts
import { Context, mapping, QueryHelper } from "zenweb";

export class IndexController {
// 传统方案
@mapping()
get(ctx: Context) {
// 取得 id 参数
let id = ctx.query.id;
// 判断有没有
if (!id) {
fail('id-error');
}
// 尝试转换类型
id = parseInt(id);
// 判断是否转换成功
if (id === NaN) {
fail('id-type-error');
}
return { id };
}

// 使用 helper
@mapping()
get2(query: QueryHelper) {
const { id } = query.get({
id: '!int', // { 参数名: 类型 } 前面的 ! 感叹号代表必填项不能为空
});
return { id };
}
}

此外,helper 助手还支持列表格式、值规则校验。

如果一个输入格式为 ids=1,2,3,4,5,我们可以使用 { ids: '!int[]' }

helper.get({
ids: '!int[]',
}); // { ids: [1, 2, 3, 4, 5] }

如果希望值在某一个范围内,例如年龄 >16 <30,可以写成:

helper.get({
age: {
type: 'int',
validate: {
gt: 16,
lt: 30,
},
}
});

参数说明

参数名说明默认值
type目标类型,详见:支持的转换类型
default默认值
splitter是否切割为数组,定义切割字符','
minItems切割数组后最少需要的元素数量
maxItems切割数组后最多不能超过的元素数量
required是否为必须false
notNull是否允许 null 值false
validate数据验证规则,详见:支持的数据验证

支持的转换类型

类型名目标类型功能描述
numbernumber任何数值类型,整数、浮点
intnumber转换为整数类型
floatnumber转换为浮点数类型
boolboolean转换为布尔类型,只有值为 y, 1, yes, on, true (不区分类型)才会被判定为 true,其他都为 false
trimstring转换为字符串并去除两端的空字符
stringstring转换为字符串
anyany不进行类型转换,直接输出原始类型
dateDate转换为日期类型

支持的数据验证

验证名功能描述参数
lt小于 <any
lte小于等于 <=any
gt大于 >any
gte大于等于 >=any
eq等于 ==any
neq不等于 !=any
maxLength最大长度,调用值的 .length 属性number
minLength最小长度,调用值的 .length 属性number
in只能出现的值any[]
notIn不能出现的值any[]
regexp正则表达式匹配string \| RegExp
email是否满足Email地址规则boolean
slug是否满足URL路径规则boolean
url是否满足 URL 规则string[] \| boolean
提示

此助手的核心代码已经作为独立项目剥离出,详情可以查看 typecasts