编写一个koa2中间件-接口必填参数的校验

写在开头

koa2 一个由 Express 原班人马打造的,基于 Node 平台的下一代 Web 开发框架。

它属于前端老熟客了,这里就不过多介绍。

本章主要讨论一下如何编写其中间件(middleware)并编写一个"接口必填参数校验"的中间件。

初始化koa2项目🏗️

为了方便快速创建 koa2 项目,我们可以全局安装一下 koa2 的脚手架 koa-generator

javascript 复制代码
npm install -g koa-generator

它等同于 vueclireactreact-create-app 这些脚手架。

测试安装是否成功:

javascript 复制代码
koa2 -V

注意命令是 koa2 哦,能正常看到版本号就说明安装成功了。

初始化项目:

javascript 复制代码
koa2 your-project-name

能得到这么一个项目目录:

安装项目依赖:

javascript 复制代码
npm install

启动项目:

javascript 复制代码
npm run start

// or

npm run dev

dev 命令具有热更新的效果,修改项目代码会及时更新,而热更新是通过 nodemon 包来实现的。

项目启动后,默认端口是 3000

基于脚手架创建的项目,默认在 routes 文件夹中帮我们生成了一些接口,你可以访问以下这些路径测试看看:

javascript 复制代码
http://localhost:3000/
http://localhost:3000/string
http://localhost:3000/json
http://localhost:3000/users
http://localhost:3000/users/bar

中间件语法🚀

准备完项目后,就可以来尝试编写 koa2 的中间件了。

那么,什么是中间件呢?

koa2 框架中,你可以将HTTP请求想象成一条水流,而中间件则像是连接在这条水流上的各种管道。每个中间件都会对这条水流(即HTTP请求和响应)进行处理和改写。

中间件本质是一个异步函数,它可以访问请求对象、响应对象和下一个中间件函数。

语法也挺简单的,写完异步函数往 app.use 注册一下就行了。

javascript 复制代码
const middleware = async (ctx, next) => {
  // TODO: 你需要处理逻辑,从ctx能获取到很多信息,如参数列表
  
  // 抛给下一个中间件
  await next();
};

app.use(middleware);

注意,中间件的注册有顺序先后的❗

必填参数校验的中间件🎯

了解中间件基本语法后,我们就可以来编写自己的中间件了,这次要写的中间件主要是针对参数校验的。

很多时候,前端拿到后端开发的接口后,经常能看到一些接口参数被标记成必填,本次要写的中间件就是为了完成这个功能。

在项目根目录下创建 middleware 文件夹,用于存放各种中间件。

创建 middleware/requiredParams.js 文件,用于编写本次的中间件,功能不复杂,咱们贴上来直接瞧瞧。

javascript 复制代码
module.exports = (options = {}) => {
  return async (ctx, next) => {
    /**
     * @name 用于校验接口必填参数的中间件
     * @param { string | Array<string> | Object } requiredParams
     * @return { boolean }
     */
    ctx.requiredParams = (requiredParams) => {
      // 获取接口的请求方式
      let method = ctx.method;
      // 获取接口的参数列表
      let params =  method.toLocaleUpperCase() === "GET" ? ctx.query : ctx.request.body;
      // 接口需要检验必填参数
      if (!isEmpty(requiredParams)) {
        // 只有一个必填参数需要验证
        if (typeof requiredParams === "string") {
          if (!(requiredParams in params) || (!params[requiredParams] && params[requiredParams] !== 0)) {
            ctx.body = "字段" + requiredParams + "为必填";
            return true;
          }
        }
        // 有多个参数需要验证必填
        if (Array.isArray(requiredParams)) {
          let paramsKeys = Object.keys(params);
          const message = [];
          for (let i = 0; i < requiredParams.length; i++) {
            if (!paramsKeys.includes(requiredParams[i]) || (!params[requiredParams[i]] && params[requiredParams[i]] !== 0)) {
              message.push("字段" + requiredParams[i] + "为必填");
            }
          }
          if (message.length > 0) {
            ctx.body = message.join("\n");
            return true;
          }
        }
        // 支持自定义提示语,key为参数名,value为自定义的提示语
        if (Object.prototype.toString.call(requiredParams) === "[object Object]") {
          const message = [];
          for (let key in requiredParams) {
            if (!(key in params) || (!params[key] && params[key] !== 0)) {
              if (requiredParams[key]) {
                message.push(requiredParams[key]);
              } else {
                message.push("字段" + key + "为必填");
              }
            }
          }
          if (message.length > 0) {
            ctx.body = message.join("\n");
            return true;
          }
        }
      }
    };
    await next();
  };
};

/**
 * @name 检验非空的工具函数
 * @param { * } val 检验目标
 * @return { boolean }
 */
function isEmpty(val) {
  if (val === 0) return false;
  // null or undefined
  if (val == null) return true;
  if (typeof val === "boolean") return false;
  if (typeof val === "number") return !val;
  if (val instanceof Error) return val.message === "";
  switch (Object.prototype.toString.call(val)) {
    // String or Array
    case "[object String]":
    case "[object Array]":
      return !val.length;
    // Map or Set or File
    case "[object File]":
    case "[object Map]":
    case "[object Set]": {
      return !val.size;
    }
    // Plain Object
    case "[object Object]": {
      return !Object.keys(val).length;
    }
  }
  return false;
}

app.js 文件中注册:

js 复制代码
...
const requiredParams = require("./middleware/requiredParams.js");


...
app.use(json());
app.use(logger());
app.use(require("koa-static")(__dirname + "/public"));
// 注册
app.use(requiredParams());

中间件代码都写有注释,可以仔细看看,其主要写了三种验证形式,字符串、数组、对象,我们可以分别来看看应用的情况。

http://localhost:3000/users/bar 接口为例:

javascript 复制代码
// routes/users.js
router.get("/bar", function (ctx, next) {
  // 要求 name 参数必传
  if (ctx.requiredParams("name")) return;
  ctx.body = "我是bar接口";
});

效果如下:

测试多个必填参数情况:

javascript 复制代码
// routes/users.js
router.get("/bar", function (ctx, next) {
  // 要求 name、age、sex 参数必传
  if (ctx.requiredParams(["name", "age", "sex"])) return;
  ctx.body = "我是bar接口";
});

测试自定义提示语情况:

javascript 复制代码
// routes/users.js
router.get("/bar", function (ctx, next) {
  if (ctx.requiredParams({
    name: "这个name参数一定一定要填",
    age: "年龄也要填哦",
  })) return;
  ctx.body = "我是bar接口";
});

至此,本篇文章就写完啦,撒花撒花。

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。

老样子,点赞+评论=你会了,收藏=你精通了。

相关推荐
写不出来就跑路2 分钟前
基于 Vue 3 的智能聊天界面实现:从 UI 到流式响应全解析
前端·vue.js·ui
OpenTiny社区5 分钟前
盘点字体性能优化方案
前端·javascript
FogLetter9 分钟前
深入浅出React Hooks:useEffect那些事儿
前端·javascript
Savior`L9 分钟前
CSS知识复习4
前端·css
0wioiw025 分钟前
Flutter基础(前端教程④-组件拼接)
前端·flutter
花生侠1 小时前
记录:前端项目使用pnpm+husky(v9)+commitlint,提交代码格式化校验
前端
猿榜1 小时前
魔改编译-永久解决selenium痕迹(二)
javascript·python
阿幸软件杂货间1 小时前
阿幸课堂随机点名
android·开发语言·javascript
一涯1 小时前
Cursor操作面板改为垂直
前端