美文网首页js css html
使用json-server,实现更独立的前后端分离(vue-cl

使用json-server,实现更独立的前后端分离(vue-cl

作者: 南慕瑶 | 来源:发表于2019-03-31 20:18 被阅读0次

    一、背景

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

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

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

    使用内部在线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-cl

        本文链接:https://www.haomeiwen.com/subject/rnbuvqtx.html