TypeScript:用装饰器来进行参数校验
一. 构建项目
1.创建一个React项目,在本地有npm命令的情况下,通过umi脚手架来搭建项目(注意,这里用了umi,也可以用React自带的脚手架,只不过后续修改跨域的配置文件不一样):
npm create @umijs/umi-app
2.初始化下项目:
npm install
3.创建Egg项目,执行命令:
npm init egg --type=simple
4.初始化egg项目:
npm install
二. 先解决本地的跨域问题
React项目中,修改.umirc.ts
文件,增加以下内容:(若是React脚手架搭建的,则修改package.json
文件)
proxy: {
// 以api为前缀的都会走这个代理
"/api": {
// 将请求代理到egg服务商
target: "http://localhost:7001",
changeOrigin: true,
// 重写路径,最终请求到egg的路由是不带api前缀的
pathRewrite: {
"^/api": ""
}
}
}
修改pages
目录下的index.tsx
文件:(记得npm install axios
)
import styles from './index.less';
import { Input, Button } from 'antd';
import axios from 'axios'
import { useState } from 'react'
export default function IndexPage() {
const [state, setState] = useState('')
const click = async () => {
console.log(state)
await axios({
url: '/api/test',
method: 'post',
withCredentials: true,
data: {
message: state,
},
})
}
return (
<div>
<h1 className={styles.title}>Page index</h1>
<Input onChange={(event) => { setState(event.target.value) }}></Input>
<Button onClick={click}>传递参数</Button>
</div>
);
}
然后启动项目,npm start
,页面效果如下:
修改Egg项目,增加一条路由配置:
内容如下:
router.post('/test', controller.home.index);
在home.js
中添加一条提示语:
启动egg项目:npm run dev
然后最终效果如下:
到这里,准备工作都已经做好了,那么来讲一讲本文的重点内容。在进行前后开发的过程中,往往我们需要注意前台传给后台的参数是否合法、是否有效。
在Java里面,我们可以通过@NotBlank、@NotEmpty等注解来控制一个变量的值,那么在Node里面,我们可以怎样去实现这样的效果呢?
三. 用装饰器实现参数校验
首先配置下Egg项目中的一些配置,在根目录下创建一个tsconfig.json
文件,添加以下内容:
{
"compilerOptions": {
"target": "es2017",
"experimentalDecorators": true,
}
}
其次来编写下装饰器,创建一个decorator
文件夹,创建myValid.ts
文件:
import { Controller } from 'egg';
// 校验方法中参数的类型
export type CheckFunc<T> = (paramVal: T) => boolean;
/**
* x:就是参数的名称
* value则是一个函数,该函数的参数则是这个参数对应的值(返回true/false)
* 参考:
* @ParamValid({
message: valid<string>([notBlank()]),
})
*/
interface ParamEntity {
[x: string]: (paramVal: any) => boolean;
}
export function preAction(preFn: any): any {
/**
* _target:目标对象
* _name:目标属性名称
* descriptor描述符,包含目标的全部属性
*/
return function (_target: any, _name: any, descriptor: PropertyDescriptor): any {
// 获取旧属性值
const oldValue = descriptor.value;
descriptor.value = function (...args: any) {
// 执行preFn函数,查看这个参数是否校验通过,若某个参数不通过,则后续的就因短路运算就不走了
const isContinueFlag= preFn?.apply(this, args);
return isContinueFlag && oldValue.apply(this, args);
};
return descriptor;
};
}
/**
* T:校验的参数的类型
* paramVal:我们传入的需要校验的前端参数
*/
export function valid<T>(rules: CheckFunc<T>[]): (paramVal: T) => boolean {
return (paramVal: T) => rules.reduce<boolean>((prev: boolean, curr) => { return prev ? curr(paramVal) : prev; }, true);
}
export function ParamValid(paramEntity: ParamEntity) {
return preAction(function (this: Controller) {
// 前端请求参数体
const requestBodyFromFront = this.ctx.request.body;
// 校验注解中传入的参数数组
const paramNames: string[] = Object.keys(paramEntity);
const result: boolean = paramNames.reduce<boolean>((prev: boolean, paramName) => {
// 根据参数名称去获得对应 前端请求体中 对应参数的值
const paramVal = requestBodyFromFront[paramName];
// 获得对应的校验API(对应上述的valid方法)
const checkFunc = paramEntity[paramName];
// 若不通过校验,则后续的方法就不执行了
return prev ? checkFunc(paramVal) : prev;
}, true);
if (result === false) {
this.ctx.body = {
content: '参数不合法',
success: false,
};
} else {
this.ctx.body = {
success: true,
};
}
return result;
});
}
export const notBlank = (): CheckFunc<string> => (str: string) => str?.trim().length > 0;
在Controller
层的方法上调用:
import { Controller } from 'egg';
import { ParamValid, valid, notBlank } from '../decorator/myValid'
class HomeController extends Controller {
@ParamValid({
message: valid<string>([notBlank()]),
})
async index() {
const { ctx } = this;
const { message } = this.ctx.request.body;
console.log('获得请求了:' + message)
ctx.body = '校验通过';
}
}
export default HomeController;
最终的实现效果如下:
到这里一个简单的校验参数功能就做好啦。