Mock数据从未如此简单

5,016 阅读2分钟

前后端分离的项目中,前端往往需要自己构造假数据进行渲染。作为一名合格且优秀的前端开发人员当然是希望一次性编码,后面前后端联调的时候最好什么都不用做,直接一个文档丢给后端自己就可以愉快的进行下一个项目了。为了写代码能够一泻千里,我们一般在编写代码之前做一个简单的数据格式文档定义,然后前后端就可以开始自己做自己的任务了。

前端渲染页面往往需要假数据,假数据如果放在逻辑代码中去构造或者在 api 管理接口中统一返回假数据都会导致后期需要修改代码,最终会导致工作量增加无心谈恋爱。所以我们前端最好的 mock 数据方式就是写一个 node 服务,模拟真实的交互环境。

目标

使用 node 编写一个微服务,允许各种请求方式,并返回相应的数据。

依赖

node、koa、koa-router、koa-logger、koa-cors、nodemon

我希望:

  1. 能够模拟各种场景,允许跨域、允许 POST、GET、DELETE、PUT、PATCH 请求
  2. 只关心数据
  3. 热响应

思路

  1. 实现自动遍历目录下的 json 文件,作为数据返回
  2. 根据文件后缀 .get.json.post.json 等自动响应各种请求
  3. 使用 nodemon 启动服务实现热响应
  4. 代码写好后,以后只关心 json 数据文件
  5. 随机返回成功和失败(小概率失败,大概率成功。暂时未实现,留给你们自己)

目录结构大致如下:

目录结构

请求方式:

// 模拟用户登录
http://127..0.0.1:8080/api/v1/user/login

// 模拟从远程获取消息记录
http://127..0.0.1:8080/api/v1/message/remote/list

代码

const Koa = require('koa')
const KoaRouter = require('koa-router')
const KoaLogger = require('koa-logger')

const cors = require('@koa/cors')
const { resolve } = require('path')
const fs = require('fs')

const app = new Koa()
const router = new KoaRouter({ prefix: '/api' })

glob.sync(resolve('./api', "**/*.json")).forEach(item => {
    let apiJsonPath = item && item.split('/api')[1];

    // inspect koa-router allowedMethods
    let method = apiJsonPath.endsWith('.get.json')
                    ? 'GET' : apiJsonPath.endsWith('.post.json')
                    ? 'POST' : apiJsonPath.endsWith('.patch.json')
                    ? 'PATCH' : apiJsonPath.endsWith('.put.json')
                    ? 'PUT' : apiJsonPath.endsWith('.delete.json')
                    ? 'DEL' : 'ALL'
    let apiPath = apiJsonPath
                    .replace('.get.json', '')
                    .replace('.post.json', '')
                    .replace('.put.json', '')
                    .replace('.delete.json', '');

    if (method === 'ALL') {
        ctx.body = 'OK'
        return
    }

    router[method.toLowerCase()](apiPath, ctx => {
        try {
            let jsonStr = fs.readFileSync(item).toString();
            ctx.body = {
                data: JSON.parse(jsonStr),
                code: 200,
                msg: 'OK',
                success: true
            }
        } catch(err) {
            ctx.throw('服务器错误', 500);
        }
    });
});

app
    .use(cors({
        origin: ctx => {
            const whiteList = ['http://localhost:8080','http://localhost:8081']
            let url = ctx.header.referer.substr(0,ctx.header.referer.length - 1) // remove '/'
            if(whiteList.includes(url)){
                return url
            }
            return "http://localhost:3000"
        },
        maxAge: 3, // https://zhuanlan.zhihu.com/p/86953757
        credentials: true, // is allowed cookie
        allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], // set allowed HTTP methods
        allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'Access-Control-Allow-Origin'], // set allowed HTTP fields
    }))
    .use(KoaLogger())
    .use(router.routes())
    .use(router.allowedMethods())

app.on('error', (err, ctx) => {
    console.error('server error', err, ctx)
})

app.listen(3000);

预览