eggjs 打造定制化mock工具

关于 egg 的基础,此处不做太多追述,如果不理解的可以看我之前写的文章---egg-从入门到上线

项目的构建

什么是mock

mock 的意思是模拟,也就是模拟接口返回的信息,用已有的信息替换它需要返回的信息,从实现对模块的测试。

当后端的接口还未完成,前端为了不影响工作效率,实现前后端分离,手动模拟后端接口

通用的方案 优点 缺点
json 书写简单,直接写一个js文件或者 json 文件,直接引入至项目中,完成后续代码编写 无法模拟数据的增删改查
json-server 模拟数据的增删改查 不能随机生成所需数据
mockjs 可随机生成所需数据,可模拟对数据的增删改查 代码入侵至项目中,需要维护
apifox 实现 mock 数据接口导入,可以直接导入 swagger 数据,代码入侵小 随机的数据对于项目的展示不打友好
node-service 通过 node 完成项目中的 mock 工具,自由度和数据完成极高 编写代码需要时间成本

项目的初衷

遇到项目中需要完整的演示出、上传、增删改查功能,对于后端接口的完成需要很长的一段时间(不确定的因素),产生了这个项目的初衷。所以如果你的项目,迭代紧凑,请谨慎选择。

关于数据存储设计

这一点考虑过 MongoDB、MySQL 等数据设计、但是根据业务场景出发,如果不能提供给我们数据库,那么该怎么办呢?那么我们就可以考虑通过做一个本地的 json 数据的增删改查设计,一样可以实现项目的运转,也可以考虑后期项目的迁移。

项目构建

bash 复制代码
npm i egg-init -g
egg-init egg-mock --type=simple   //例如:egg-init 项目名称 --type=simple
cd egg-mock
npm i

运行

arduino 复制代码
npm run dev  

你将会看到 hi-egg

部署

你仅仅需要将 NGINX 指向 127.0.0.1 即可完成部署

arduino 复制代码
npm run start // 启用
npm run stop // 停止

编写第一个接口

首先 egg 的流程是什么

router(路由) -> controller(控制层) -> service(业务逻辑层)

在controller/user.js中写上

javascript 复制代码
'use strict';

const Controller = require('egg').Controller;

class UserController extends Controller {
    async index() {
        const { ctx } = this;
        ctx.body = 'hi, user';
    }
}

module.exports = UserController;

router.js

javascript 复制代码
'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  router.get('/user', controller.user.index);
};

然后访问 127.0.0.1 => hi,user

第一个接口就完成了

拓展工具helper的封装

JSON 格式

  • JSON 指的是 JavaScript 对象表示法(J avaS cript O bject Notation)
  • JSON 是轻量级的文本数据交换格式

目录结构

arduino 复制代码
├── app // 项目中的配置
│   ├── controller // 控制层
│   │   ├── home.js
│   │   └── user.js
│   ├── public // 公共部分
│   └── router.js // 路由
├── extend // 拓展工具
│   │  helper.js // 帮助
├── config // 项目配置
│   ├── config.default.js
│   └── plugin.js // 插件
├── db // 数据 json
│   ├── user.json

help.js函数的封装

返回结构

在 helper 中可以引入一个 jsonFormat 的工具,如果返回status是 0 那么就是抛出异常

javascript 复制代码
const dayjs = require("dayjs");
module.exports = class Helper {
    // 处理 json 数据,默认是成功
    static async jsonFormat(row, status = 1) {
        // 如果是失败的情况下,则返回 0
        if (status === 0) {
          return {
            status: 0,
            message: row,
            timestamp: dayjs().format("YYYY-MM-DD HH:mm:ss"),
            success: false,
          };
        }

        return {
          status: 1,
          message: "成功",
          data: row,
          timestamp: dayjs().format("YYYY-MM-DD HH:mm:ss"),
          success: true,
        };
    }
}
js 复制代码
this.ctx.helper.jsonFormat('请求失败',0)

获取请求参数

vbnet 复制代码
/**
 * 
 * 可以使用ctx.params获取get或post请求参数
 */
module.exports = () => {
  return async function params(ctx, next) {
    ctx.params = {
      ...ctx.query,
      ...ctx.request.body
    }
    await next()
  }
}

db

这里的 db 文件夹,可以帮他想象出一个数据仓库,每一个 json 就是一张数据库的表

db/user.json

json 复制代码
[{
  "id":0,
  "name":"张三",
  "age": 18
},
{
  "id":1,
  "name":"李四",
  "age": 20
}]

这时候其实你也可以通过db/*.json来存储各种表的数据,在未来如果有数据库了,也可以存储在MongoDB 或者 mysql 数据中作为数据转换的过度与拓展。

构建 CURD 的封装

首先 JSON 是什么,它其实就是一组数组对象首先要获取到这个 json 数据对吧

json获取数据操作

extend/helper.js

javascript 复制代码
const { readFile, writeFile } = require("fs"); // 解构出并加载fs模块中的两个方法
const { join } = require('path')

// 获取到 json 数据的位置
const path = (name) => join(__dirname, `../../db/${name}.json`)

module.exports = class Helper {
  // 读取数据
  static async readFile(name) {
    const filename = path(name);
    return new Promise((resolve, reject) => {
      readFile(filename, "utf-8", (err, data) => {
        if (err) reject(err);
        const res = JSON.parse(data); // 把JSON转成JS数组
        console.log(res);
        resolve(res);
      });
    });
  }

  // 写入数据
  static async writeFile(name, param) {
    const filename = path(name);
    return new Promise((resolve,reject) => {
      writeFile(filename, JSON.stringify(param), (err) => {
        if (err) reject(err);
        resolve("修改成功");
      });
    });
  }
};

总结JSON 作为数据源的处理

其实 JSON 的 CURD 本质上,就是就是针对于数组的CURD 方法,然后读写进入文件,这样的话,就可以持久化保持数据。

查询接口

设计接口

首先这里有一个知识点,设计接口的时候,要注意什么?没错,设计的时候,要注意 MVC 的设计

MVC 模式

MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。

Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。

View(视图) - 视图代表模型包含的数据的可视化。

Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。

那么为什么呢?为前端,有时候很难理解 MVC 的设计架构,其实就像咱们得组件化管理一样,如果把所有的组件放在一个组件中,那势必会难以维护,分层的作用,就可以达到复用性的效果。

对于 egg 来说

M 就是 service

V 就是 public

C 就是 controller

我们通过路由进入controller,然后调用 service的过程,就是接口执行的过程。

查询全部接口

以下所有的 helper 工具,都在拓展工具helper的封装所写,可以查看该文

router.js

arduino 复制代码
// R 读取(Read)
router.get("/user", controller.user.read);

controller/user.js

js 复制代码
 async read() {
    const { ctx } = this;
    try {
      const res = await ctx.service.user.read();
      ctx.body = await ctx.helper.jsonFormat(res);
    } catch (error) {
      ctx.body = await ctx.helper.jsonFormat(error, 0);
    }
  }

service/user.js

csharp 复制代码
// 读取所有的数据
async read() {
    try {
      return await this.ctx.helper.query("user");
    } catch (error) {
      return error;
    }
  }

extend/helper.js

javascript 复制代码
  // 查询
  static async query(name) {
    try {
      return await this.readFile(name);
    } catch (error) {
      return error;
    }
  }

接口测试

输入接口地址

在 user.json中填写

json 复制代码
[
  {
    "id": 0,
    "name":"张三",
    "age": 20
  }
]

那么这时候的返回值是

json 复制代码
{
    "status": 1,
    "message": "成功",
    "data": [
        {
            "id": "张三",
            "age": 2
        }
    ],
    "timestamp": "2022-08-26 21:00:32",
    "success": true
}

查询id 的接口

router.js

csharp 复制代码
// R 读取(Read)
router.get("/user/:id", controller.user.readId);

controller/user.js

js 复制代码
async readId() {
    const { ctx } = this;
    const { id } = ctx.params;
    try {
      const res = await ctx.service.user.readId(id);
      ctx.body = await ctx.helper.jsonFormat(res || {});
    } catch (error) {
      ctx.body = await ctx.helper.jsonFormat(error, 0);
    }
}

service/user.js

js 复制代码
async readId(id) {
    try {
      return await this.ctx.helper.queryId("user", id);
    } catch (error) {
      return error;
    }
}

extend/helper.js

javascript 复制代码
 // 查询id,将 id 传入
  static async queryId(name, id) {
    try {
      const res = await this.readFile(name);
      // 这里会遇到一个问题,id 有可能是 number、string所以统一转成 number
      const info = res.find((item) => +item.id === +id);
    } catch (error) {
      return error;
    }
  }

新增、更新、删除接口

首先要解决 cor 跨域

config/config.default.js

ini 复制代码
config.security = {
    domainWhiteList: ["*"],
    csrf: {
      enable: false,
    },
  };

新增接口

router.js

csharp 复制代码
// C 创建(Create)
router.post("/user", controller.user.add);

controller/user.js

csharp 复制代码
// 新增数据
async add() {
    const { ctx } = this;
    try {
    // 将外部传入的数据都传入,此处也可以做一些校验
      const res = await ctx.service.user.add(ctx.request.body);
      ctx.body = await ctx.helper.jsonFormat(res);
    } catch (error) {
      ctx.body = await ctx.helper.jsonFormat(error, 0);
    }
}  

service/user.js

csharp 复制代码
 async add(row) {
    try {
      return await this.ctx.helper.add("user", row);
    } catch (error) {
      return error;
    }
  }

extend/helper.js

javascript 复制代码
// 添加
  static async add(name, row) {
    try {
      const res = this.readFile(name);
      // 如果是一个空的 json 数组,就给 id赋值0
      row.id = 0;
      // 如果是有数据的,那就自增
      if (res.length !== 0) {
        const id = res[res.length - 1].id + 1;
        row.id = id;
      }
      res.push(row);
      // 写入数据
      await this.writeFile(res);
      return "添加成功";
    } catch (error) {
      return error;
    }
  }

extend/helper.js

javascript 复制代码
// 添加
  static async add(name, row) {
    try {
      const res = await this.readFile(name);
      // 如果是一个空的 json 数组,就给 id赋值0
      row.id = 0;
      // 如果是有数据的,那就自增
      if (res.length !== 0) {
        const id = res[res.length - 1].id + 1;
        row.id = id;
      }
      res.push(row);
      console.log(res);
      // 写入数据
      await this.writeFile(name, res);
      return "添加成功";
    } catch (error) {
      return error;
    }
  }

接口测试

删除数据

router.js

go 复制代码
// D 删除(Delete)
router.delete("/user/:id", controller.user.delete);

controller/user.js

csharp 复制代码
// 删除接口
 async delete() {
    const { ctx } = this;
    const { id } = ctx.params; // 传入一个 id 即可
    try {
      const res = await ctx.service.user.del(id);
      ctx.body = await ctx.helper.jsonFormat(res);
    } catch (error) {
      ctx.body = await ctx.helper.jsonFormat(error, 0);
    }
  }

service/user.js

javascript 复制代码
async del(id) {
    try {
      return await this.ctx.helper.queryId("user", id);
    } catch (error) {
      return error;
    }
  }

extend/helper.js

javascript 复制代码
static async del(name, id) {
    try {
      // 获取整张数据
      const res = await this.readFile(name);
      //  过滤掉这个 id 的数据,返回成一个新的数组
      const result = res.filter((item) => +item.id !== +id);
      // 写入数据
      await this.writeFile(name, result);
      return "删除成功";
    } catch (error) {
      return error;
    }
  }

接口测试

更新接口

router.js

arduino 复制代码
// U 更新(Update)
router.put("/user", controller.user.update);

controller/user.js

csharp 复制代码
// 更新数据
async update() {
    const { ctx } = this;
    try {
      const res = await ctx.service.user.update(ctx.request.body);
      ctx.body = await ctx.helper.jsonFormat(res);
    } catch (error) {
      ctx.body = await ctx.helper.jsonFormat(error, 0);
    }
  }

service/user.js

javascript 复制代码
async update(row) {
    try {
      return await this.ctx.helper.update("user", row);
    } catch (error) {
      return error;
    }
  }

extend/helper.js

javascript 复制代码
 static async update(name, param) {
    try {
      // 获取整张数据
      const res = await this.readFile(name);
      // 找到你想修改值的id,所在的位置
      const index = res.findIndex((item) => +item.id === +param.id);
      // 用想修改的值,直接替换掉数据
      res.splice(index, 1, { ...res[index], ...param });
      // 写入数据
      await this.writeFile(name, res);
      return "修改成功";
    } catch (error) {
      return error;
    }
  }

接口测试

代码地址

github.com/masonjs-cn/...

相关推荐
腾讯TNTWeb前端团队44 分钟前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪4 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试