• 自定义mock服务器


    关于本地开发mock方式概述

    为了避免后端接口开发进度影响前端开发,一般和后端约定好接口后前端可以使用本地mock数据的方式进行开发

    本文会演示如何搭建一个mock服务器demo地址
    说明一下,本文搭建的mock服务器的优点:
    1、mock文件可拆分(不需要在任何地方引入)
    2、mock数据可动态修改(不需要重启服务器)
    3、完全模拟后端接口请求,可模拟接口错误、接口延迟、接口参数等
    4、支持文件上传接口
    5、可自定义接口返回数据逻辑



    一、前端本地mock开发的几种方式

    1、直接mock数据

    优点:简单
    缺点:直接通过前端写死数据,这种方式侵入性比较大,后面替换正式接口改动较多,模拟效果不好

    2、使用mockjs拦截请求

    优点:代码侵入性较小
    缺点:mockjs拦截的是ajax请求,所以对使用fetch无效;network看不到发送的请求;对代码还是有侵入性

    const mock = Mock = require('mockjs')
    Mock.mock('/api/sign/out', 'get', (options) => {
      console.log(options);
      return {
        data: true,
        code: 0,
        message:'success'
      }
    })
    
    
    // 请求代码如下
    axios.get('/api/sign/out')
      .then(function (response) {
      console.log(response);
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3、使用第三方在线mock工具(yapi等)

    优点:无代码侵入性
    缺点:配置麻烦、mock数据不方便、需要后端支持



    4、搭建本地mock服务器

    优点:无代码侵入性
    缺点:暂无



    在介绍如何搭建mock服务器时,先看看最终搭建mock服务器后,前端是如何使用mock服务器的

    二、使用mock服务

    在mock文件夹下创建mock数据即可

    /**
     * feat 1.支持 mock 文件拆分:mock/ 文件夹下面的所有 js 都会被框架自动加载,因此你可以把接口合理的拆分到多个 mock 文件中
     * feat 2.支持模拟延迟(setTimeout 返回)
     * feat 3.支持 mock 动态数据
     *
     */
    const Mock = require("mockjs");
    // 项目接口 本地 mock 数据
    module.exports = {
      // 支持值为 Object 和 Array
      "GET /api/users": { users: [1, 2] },
      
      // GET 可忽略
      "/api/users/1": { id: 1422 },
    
      "/api/error": (req, res) => {
        setTimeout(() => {
          // 模拟接口报错
          res.status(400).json({
            error: "模拟接口报错",
          });
        }, 1000);
      },
    
      // 支持mockjs和function,req和res参考express
      'GET /api/list': (req, res) => {
        const { pageSize = 10, pageIndex = 1 } = req.query;
        const startIndex = (pageIndex - 1) * pageSize + 1;
        setTimeout(() => {
          res.send(
            Mock.mock({
              success: 444,
              data: {
                [`data|${pageSize}`]: [
                  {
                    'id|+1': startIndex,
                    name: '@name',
                    'number|+1': startIndex,
                    title: '@title',
                    'price|1000-4000': 400,
                    city: '@city',
                    'age|20-30': 22,
                    address: `@county(true)`,
                    link: 'www.baidu.com',
                    column: 'column',
                    'status|+1': ['unpack', 'mailed', 'sending', 'received'],
                    purchasePerson: '@csentence',
                    time: '@date',
                  },
                ],
                total: 100,
              },
            })
          );
        }, 1000);
      },
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    fetch("/api/list?pageSize=20", { method: "get" })
          .then((res) => {
            return res.json();
          })
          .then((res) => {
            console.log("res: ", res);
          });
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    创建mock文件后不需做任何处理,直接可以使用mock服务了。



    三、如何创建mock服务器

    我们项目本地开发时一般都会有一个服务器,比如webpack的webpack-dev-server,所以我们不需要单独的再开发一个node服务器,而直接使用webpack-dev-server编写mock服务器即可

    devServer: {
        before(app) {
          app.get('/test', function (req, res) {
            res.json({
              msg: '接口成功',
              success: true,
              code: 0,
            });
          });
    
          app.post('/test2', function (req, res) {
            res.json({
              data: [{ name: 1 }],
              msg: '接口成功',
              success: true,
              code: 0,
            });
          });
        },
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    webpack-dev-server提供在服务器内部的所有其他中间件之前执行自定义中间件的能力。before配置(webpack5.0是onBeforeSetupMiddleware配置)
    我们可以在这里自定义我们要mock的接口即可。但是要考虑新的问题:
    1、每次新增或修改mock接口时都要重新去修改webpack配置文件吗(webpack配置文件修改是需要重新启动的)
    2、mock数据和webpack配置耦合


    解决方法:
    我们可以在项目根目录下创建mock文件夹
    然后使用node递归遍历mock文件夹生成mock数据map

    function getFilesSync(filePath, result) {
      try {
        const stats = fs.statSync(filePath);
        if (stats.isDirectory()) {
          const filePaths = fs.readdirSync(filePath);
          filePaths.forEach((item) => {
            const itemFilePath = path.join(filePath, item);
            getFilesSync(itemFilePath, result);
          });
        } else {
          delete require.cache[require.resolve(filePath)];
          const file = require(filePath);
          Object.assign(result, file);
        }
      } catch (error) {
        console.log("error: ", error);
      }
    }
    
    devServer: {
        before(app) {
          const apiMap = {};
          getFilesSync(path.join(process.cwd(), "mock"), apiMap);
          for (const [key, handle] of Object.entries(apiMap)) {
            const arr = key.split(" ").reverse();
            const api = arr[0];
            const method = arr[1] ? arr[1].toLowerCase() : "get";
            app[method](
              api,
              typeof handle === "function"
                ? handle
                : (req, res) => {
                    res.send(handle);
                  }
            );
          }
        },
    },
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    上面会在webpack-dev-server启动时自动遍历mock文件夹并注册好所有mock接口。但这里只是解决了自动注册mock接口,mock接口修改时,还是需要手动重启服务器。如何解决?


    我的想法是监听mock文件夹,当mock文件夹修改时自动重启服务器
    webpack-dev-server内部也是用chokidar这个库来监听的文件变化的,这里我们也是用chokidar

    // server.js
    const Webpack = require('webpack');
    const WebpackDevServer = require('webpack-dev-server');
    const webpackConfig = require('./webpack.config.js');
    const proxy = require('./proxyConfig');
    const chokidar = require('chokidar');
    
    const compiler = Webpack(webpackConfig);
    const server = new WebpackDevServer({
      ...webpackConfig.devServer,
      proxy
    }, compiler);
    
    function createServer() {
      const server = new WebpackDevServer({
        ...webpackConfig.devServer,
        proxy
      }, compiler);
    
      server.startCallback(() => {
        const watcher = chokidar.watch('./proxyConfig.js').on('change', () => {
          console.log("checked dev-server proxy changes, restarting server");
          server.stopCallback(() => {
            watcher.close();
            createServer();
          });
        });
      });
    }
    
    createServer();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在 WDS 提供的 Nodejs API 的基础上,我们利用startCallback回调注册chokidar监听文件的程序,当检测到proxy配置的文件变化后,stop WDS 并重启一个新的 WDS 实例。

    到此我们就基本完成了一个mock服务器。但是上述mock服务器还是有一个非常大的缺点。
    每次修改mock接口,比如新增一个mock接口都需要重启服务器,虽然重启操作是自动完成的,但我还是需要等待重启。
    理想情况下是我对mock数据修改后服务器无需重启就能生效。但是修改接口是需要重新注册接口的,重新注册接口就必须要重启服务器,似乎陷入了一个死循环。


    换个思路,既然每添加一个接口路由都需要重新注册,那我们直接代理所有路由,然后根据接口路径返回不同数据

    devServer: {
        before(app) {
    
          app.use('*', function (req, res) {
            const method = req.method.toLowerCase();
            const baseUrl = req.baseUrl;
    
            if (method === 'get') {
              if (baseUrl === '/test') {
                res.send({
                  msg: '接口成功',
                  success: true,
                  code: 0,
                });
              }
            }
    
            if (method === 'post') {
              if (baseUrl === '/test2') {
                res.send({
                  data: [{ name: 1 }],
                  msg: '接口成功',
                  success: true,
                  code: 0,
                });
              }
            }
          });
        },
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    当然返回值也是动态取的,上面只是演示原理。不过又带了了新问题:因为代理了所有接口,我们项目获取html的接口实际上也被代理了,但这是并没有返回html所以页面都打不开。我第一想法是所有不匹配的路由直接返回html,但是webpack-dev-server打包的文件都存放在内存中,我通过node获取不到webpack-dev-server打包的文件,所以无法返回。如何解决?gg

    最后我想到使用express中间件,当没有匹配的接口路由时,直接调用next() 交个webpack-dev-server自己处理
    最终代码:

    // mockMiddleware.js
    function getFilesSync(filePath, result) {
      try {
        const stats = fs.statSync(filePath);
        if (stats.isDirectory()) {
          const filePaths = fs.readdirSync(filePath);
          filePaths.forEach((item) => {
            const itemFilePath = path.join(filePath, item);
            getFilesSync(itemFilePath, result);
          });
        } else {
          delete require.cache[require.resolve(filePath)];
          const file = require(filePath);
          Object.assign(result, file);
        }
      } catch (error) {
        // console.log('error: ', error);
      }
    }
    
    module.exports = function mockMiddleware() {
      return function (req, res, next) {
        const method = req.method.toLowerCase();
        const baseUrl = req.baseUrl;
        const apiMap = {};
        getFilesSync(path.join(process.cwd(), 'mock'), apiMap);
        for (const [key, handle] of Object.entries(apiMap)) {
          const arr = key.split(' ').reverse();
          const api = arr[0];
          const _method = arr[1] ? arr[1].toLowerCase() : 'get';
          if (_method === method && api === baseUrl) {
            if (typeof handle === 'function') {
              handle(req, res);
            } else {
              res.send(handle);
            }
            return;
          }
        }
        next();
      };
    };
    
    
    
    // webpack配置
     devServer: {
        contentBase: 'static',
        before(app) {
          app.use('*', mockMiddleware());
        },
      },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
  • 相关阅读:
    【vue+ts】性能优化,defer优化白屏
    Android 5.1 open data flow 数据开启流程
    Kafka简单入门01
    XUbuntu22.04之解决桌面突然放大,屏幕跟着鼠标移动问题(一百九十)
    (178)Verilog HDL:设计一个计数器之exams/ece241_2014_q7a
    Camx-Dump Raw Frames
    2023昆明理工大学计算机考研信息汇总
    SpringBoot序幕——SpringBoot概述及其项目创建
    【Java 进阶篇】JDBC PreparedStatement 详解
    C++:vector
  • 原文地址:https://blog.csdn.net/qq_37860930/article/details/125262129