使用json-server,实现更独立的前后端分离(vue-cli3项目为例)

一、背景

前后端分离,在日常开发中,一直是开发人员的刚需。

作为前端,期望能够在开发过程中,不依赖后台接口的开发进度,直接自己模拟后台返回数据,在没有真实接口和数据的情况下,跑通接口调用逻辑,对返回数据进行处理、或是界面展示。

笔者所在团队的解决方案:

使用内部在线mock平台,由后台同学在平台上定义各个接口名称及其返回的数据结构,前端同学根据平台上给出的接口,进行相应开发,及接口调用逻辑测试。

笔者开发体验:

1.接口名称及返回数据结构,都由后台同学定义给出,按照规范,前端同学除添加数据外,不可以擅自操作mock平台上的接口。(避免造成混乱)因此,mock数据的创建,十分依赖后台同学的工作,前端同学没有自主性。实际开发中,也常出现需要等待mock数据的情况,造成前端接口调用相关逻辑被阻滞、无法流畅开发的问题。

2.在线的mock平台要切换到浏览器界面操作。当需要对mock数据进行编辑,需将页面切换到编辑页、并进行保存等相关操作,效率比较低下。



二、期望开发体验

笔者期望的开发体验是,当我需要调用某个接口的时候,可以自主模拟这样一个接口,以及它返回的数据结构+数据,而不需要依赖后台同学的工作。并且,对接口返回数据的编辑,全部放在我们最熟悉的编辑器里。

【最优体验】

本地mock数据的文件夹层级,与接口url相对应。可以通过文件夹名称,快速定位到指定接口返回的mock数据。

如:接口 /jsonServer/user/getUserName ,对应的mock数据,存放在 mock文件夹下的 jsonServer/user/getUserName.json 中。

有人可能会说,不知道接口的url和返回的数据结构,凭自己的想象模拟,和后台同学实际设计的接口大概率不会吻合,拿到真实接口之后,还需要做修改,这是一种浪费。

我认为,这种浪费,相比被动等待、依赖别人给mock接口,高效太多。况且,很多接口返回的数据结构,是完全可以推测出来的(类似返回列表、详情等),对不上的,可能只是字段名称而已,修改成本很低。

同时,如果有了自主mock的能力,我们甚至可以拿着自己推测的数据结构,找后台同学对接,最起码,这个时候,我们有主动推进的资本,而不是完全被动等待。



三、创建mock-server.js

const jsonServer = require('json-server');

// mark:要提前创建db.js,返回我们的mock数据

const $db = require('./db');

const server = jsonServer.create();

const middlewares = jsonServer.defaults();

const router = jsonServer.router($db);


server.use(router);

// Set default middlewares (logger, static, cors and no-cache)

server.use(middlewares);

// To handle POST, PUT and PATCH you need to use a body-parser

server.use(jsonServer.bodyParser);

server.listen(3001, () => {     

    console.log('JSON Server is running at 3001');

});



四、整合出db.json

根据json-server的原理,它返回的数据全部存储在db.json这一个json文件中。

而我们开发用的mock数据,显然不能全部手动塞到这一个json中。

为了达到前面说的最优体验,在创建好各个接口对应的json文件后,我们需要用简单的文件操作,将这些json文件合并为db.json。

我的mock目录(mock位于根目录下)结构如下(因为要进行文件操作,所以使用db.js):

db.js如下:

const $fs = require('fs');

const mockData = {

    DB: { },

    readDir(path) {

        const stats = $fs.statSync(path);   

        if (stats.isDirectory()) {        

            const files = $fs.readdirSync(path);

            files.forEach(file => {

                this.readDir(`${path}/${file}`);

            });

        } else {

            const data = $fs.readFileSync(path);

            // 把json文件的路径作为key

            const key = path.replace('.json', '');

            this.DB[key] = JSON.parse(data);

        }

    }

};

mockData.readDir('mock/data');

module.exports = mockData.DB;


ok,至此,mock-server和mock数据全部准备好了,node mock-server.js,启动试一下。

错误写的很明白,database、也就是db.json的属性里,不能包含字符 / 。

很显然,是 this.DB[key] = JSON.parse(data); 这句话,造成的报错。

怎么办?

key值里面不能有 / ,改造呗。

 const key = path.replace('.json', '');

改成

const key = path.replace('.json', '').replace(/\//g, '_');

再启动,不报错。



五、读取db.json的数据

成功生成db.json后,看一下它的结构(浏览器访问:localhost:3001/db):

db.json

显然,这时候,当我们想获取某一个接口对应的数据时,需要这样访问:

http://localhost:3001/mock_data_steps_step1

/mock_data_steps_step1

这自然不是我们期望的访问方式。

我们期望的是,访问:http://localhost:3001/steps/step1

可以拿到上图所示的数据。

这才是模拟接口调用该有的样子。



六、做些努力,实现更真实的接口调用方式

1.首先去掉当前key的mock_data_字样,简化key值

const key = path.replace('.json', '').replace(/\//g, '_');

改成

const key = path.replace('.json', '').replace('mock/data/', '').replace(/\//g, '_');

再看一下db.json:

嗯,清爽多了。


2.将接口url映射成db.json的key值

还记得自定义路由吗(参考:json-server全攻略)?说到路由映射,用自定义路由呗

非最终解决方案。存在bug,笔者未解决,如有方案,欢迎留言讨论!最终解决方案见【七 — 4】

其中,routeHandler.js需要返回一个json对象,指明路由配置规则。

这里的routeHandler.js:

function routeHandler(db) {

    const rewriter = {};

    Object.keys(db).forEach(key => {        

        const routeKey = `/${key.replace(/_/g, '/')}`;        

        rewriter[routeKey] = `/${key}`;    

    });

    return rewriter;

}

module.exports = routeHandler;

打印一下返回的rewiriter:

访问 http://localhost:3001/steps/step1:

http://localhost:3001/steps/step1

成功拿到所需数据。

但是

但是

但是

从页面发送请求,会有问题!详情见下文。



七、从页面发请求,拉取mock数据

mock服务和数据准备好,接下来自然是要使用了。

同时启动vue-cli-service和mock-server两个服务(一个跑页面,一个跑数据),并在页面发送请求。


1.首先,将所有请求代理到3001端口上:

vue.config.js配置:

devServer: {

        port: 3000,

        proxy: 'http://localhost:3001'

}


2.简单封装request.js。这里使用axios发送异步请求:

import $axios from 'axios';

function request(options) {

    let conf = Object.assign({

        url: '',

        method: 'get',

        responseType: 'text',

        ContentType: 'application/json'

    }, options);

    let pm = $axios(conf).then(xhr => {

        console.log('xhr:', xhr);

        return Promise.resolve(xhr.data);

    }).catch(err => {

        console.log('err:', err);

        return Promise.reject(new Error('request 抛出错误'));

    });

    return pm;

}

export default request;


3.页面模拟请求发送:

page.vue:

$request({

    url: '/steps/step2'

    method: 'get',

    params: { name: 'test' }

}).then(rs => {

    console.log('rs:', rs);

}).catch(err => {

    console.log('err:', err);

}


404,推测两种可能,要么是代理没生效,要么是路由映射出了问题。

将请求的 url: '/steps/step2' 改成  url: '/steps_step2':

/steps_step2

成功拿到mock数据。说明3001端口代理生效。


4.放弃自定义路由jsonServer.rewriter,改为手动映射路由(路由问题最终解决方案)

更改mock-server.js:

const jsonServer = require('json-server');

const $db = require('./db');

const server = jsonServer.create();

const middlewares = jsonServer.defaults();

const router = jsonServer.router($db);


// Set default middlewares (logger, static, cors and no-cache)

server.use(middlewares);

//  To handle POST, PUT and PATCH you need to use a body-parser

server.use(jsonServer.bodyParser);

// 拦截客户端请求,进行自定义处理

server.use((req, res, next) => {

    // 手动映射,更改请求url(/steps/step1 => /steps_step1)

    req.url = req.url.replace(/\//g, '_').replace('_', '/');

    next();

});

server.use(router);

server.listen(3001, () => {

    console.log('JSON Server is running at 3001');

});


再次尝试发送请求:

成功拿到mock数据。



八、优化mock数据设计

经过前面的配置,我们已经可以自由地使用json-server,进行完全由我们自己掌控的mock体验。

但是,后端给我们返回的数据结构,通常如前面例子中所示。往往是一个对象的形式,其中包含code、data,这样两个字段。

这就造成了一些问题:

(1)当接口返回的data是数组数据(通常是列表),由于数组被包在 { code: 0, data: [ xxx ] } 这个数据结构里面,我们就没有办法使用json-server各种便捷的数组过滤功能。

(2)我们无法使用json-server的功能,对db.json的数据进行期望的写入操作。一旦操作,就会破坏掉 { code: 0, data: [ xxx ] } 这个数据结构。

显而易见,在mock数据中直接使用code+data的数据结构,并不合适。

或许你会说,很简单啊,创建mock数据的时候,不按照这种格式,直接假设返回的是data的值,不就可以了吗?

没错,这样是可以完美解决上面两个问题。

但是造成了另一个问题:如何模拟code不为0的返回结果呢?

【解决方案】

笔者想到的解决方案是,在data目录下,创建success和fail文件夹,分别存放模拟成功和失败的数据。

目录结构可参考“四”中截图。

这样,success文件夹下的数据,默认都是 code: 0 的,fail下的数据,依然采用 { code: xxx, data: xxx } 的结构。

优化后的目录结构:


success下的step1.json:


fail下的step1.json:




九、一次开发,无限复用。健壮清爽的mock体验,你值得拥有

最后,总结一下各文件:

1.mock-server.js

const jsonServer = require('json-server');

const $db = require('./db');

const server = jsonServer.create();

const middlewares = jsonServer.defaults();

const router = jsonServer.router($db);


// Set default middlewares (logger, static, cors and no-cache)

server.use(middlewares);

//  To handle POST, PUT and PATCH you need to use a body-parser

server.use(jsonServer.bodyParser);

// 拦截客户端请求,进行自定义处理

server.use((req, res, next) => {

    // 手动映射,更改请求url(/steps/step1 => /steps_step1)

    req.url = req.url.replace(/\//g, '_').replace('_', '/');

    next();

});

server.use(router);

server.listen(3001, () => {

    console.log('JSON Server is running at 3001');

});


2.db.js

const $fs = require('fs');

const mockData = {

    DB: {},

    readDir(path) {

        const stats = $fs.statSync(path);

        if (stats.isDirectory()) {

            const files = $fs.readdirSync(path);

            files.forEach(file => {

                this.readDir(`${path}/${file}`);

            });

        } else {

            const data = $fs.readFileSync(path);

            const key = path.replace('.json', '').replace('mock/data/', '').replace(/\//g, '_');

            this.DB[key] = JSON.parse(data);

        }

    }

};

mockData.readDir('mock/data');

module.exports = mockData.DB;


3.request.js

import $axios from 'axios';

function request(options) {

    // 是否走mock数据。开发期间手动更改

    const isMock = false;

    let conf = Object.assign({

        url: '',

        method: 'get',

        responseType: 'text',

        ContentType: 'application/json'

    }, options);

    // 处理mock。默认返回成功数据,如指定mockFail,则返回失败数据

    if (options.mockFail) {

        conf.url = '/fail' + conf.url;

    } else if (isMock) {

        conf.url = '/success' + conf.url;

    }

    let pm = $axios(conf).then(xhr => {

        console.log('xhr:', xhr);

        return Promise.resolve(xhr.data);

    }).catch(err => {

        console.log('err:', err);

        return Promise.reject(new Error('request 抛出错误'));

    });

    return pm;

}

export default request;


4.页面请求示例

$request({

    url: '/test',

    method: 'get',

    params: { name: '111' },

    mockFail: true

}).then(rs => {

    console.log('rs:', rs);

}).catch(err => {

    console.log('err:', err);

});



【附:mock方案设计中的其他问题】

db.json是由db.js生成并返回的,因此,每次重启mock-server,都会根据mock文件夹下的json文件重新生成db.json。也就是说,上一次对db.json的任何写入、删除等操作,都不会被保存,只要重启服务,数据就会恢复原样。这里,可以根据自己的需求,自行选择要不要在操作db.json的同时,同步改写mock文件夹保存的json数据。




#菜鸟一枚,如有错误、或更优方案等,诚请指出、指导。另,jsonServer.rewriter未生效问题,求指导。

你可能感兴趣的:(使用json-server,实现更独立的前后端分离(vue-cli3项目为例))