当前位置: 首页 > 工具软件 > mock-fs > 使用案例 >

自定义mock服务器

萧煜
2023-12-01

关于本地开发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);
})

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);
  },
};

fetch("/api/list?pageSize=20", { method: "get" })
      .then((res) => {
        return res.json();
      })
      .then((res) => {
        console.log("res: ", res);
      });
  }

创建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,
        });
      });
    },
},

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);
              }
        );
      }
    },
},


上面会在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();

在 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,
            });
          }
        }
      });
    },
  },

当然返回值也是动态取的,上面只是演示原理。不过又带了了新问题:因为代理了所有接口,我们项目获取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());
    },
  },

 类似资料: