zoukankan      html  css  js  c++  java
  • 后端接口迁移(从 webapi 到 openapi)前端经验总结

    此文已由作者张磊授权网易云社区发布。

    欢迎访问网易云社区,了解更多网易技术产品运营经验。

    前情提要

    1. 以前用的是 webapi 现在统一切成 openapi,字段结构统统都变了

    2. 接入接口 20+,涉及模块的创建等主要流程。

    3. 页面基本无改,仅有一个新需求,创建时新增一个字段

    4. 其他依赖接口需要接入模块

    预想解决方案

    20+ 接口,如果根据返回值去更改页面,由于返回值整个结构都变掉了,修改起来这个工程量吃不消,再加上回测,基本上不可能在一个迭代内完成,所以需要一个新的方案。想一下变的是数据结构,不变的是什么,不变的是业务,那么方案就出来了,对接口的发送和数据的接收阶段对数据进行转换不就完美解决了。比如:

    // 原请求service.xxx = function (ops) {
    
    };// 新请求// 重点都在 transform 这个函数上service.xxx = function (ops) {    const newOps = transform.xxx.pre(ops); 
        return request.xxxNew(newOps).then((res) => {        return transform.xxx.post(res); 
        });
    };

    采用这样的方案,修改的范围会大大的缩减,基本集中在对接口的修改,对数据的转换上。

    实施

    页面的接口有些是原子性的,比如 删除 等,仅仅传递少量参数,转换即可。即使是创建,字段比较多,但仅仅对 发送数据阶段进行转换 即可。 问题是出在 返回值 上。以前返回的字段,现在没有返回的,就需要找到该字段,尽量复原有结构。这一阶段需要涉及到和 openapi 开发的反复沟通,确认每一个值怎么取,确认哪些字段是没有用的,总之尽量把数据结构给复原回来。 这里需要特殊说明

    列表页的数据结构不完整,可以对部分数据用异步添加的方式,将数据结构补完,这样用户可以尽早的看到列表。 但是详情页,必须是完整的数据结构,因为之前的实现方案,统统没考虑数据延迟拿到时页面的渲染处理,如果不一次性返回所有的数据,出问题的几率就很高。举例

    // 列表的接口,需要特别传递 refresh 函数
    service.list = function (refresh) {    return request.xx().then((list) => {
            list.forEach((item) => {            // 异步添加
                request.xx2().then((detail) => {
                    Object.assign(item, detail);
                    refresh();
                });
            });        // 首先返回主体内容        return list;
        });
    };// 详情的接口
    service.detail = function () {    return Promise.all(
            request.xx(),
            request.xx2(),
        ).then((detail, help1) => {
            Object.assgin(detail, help1);        return request.xx3().then((help2) => {
                Object.assgin(detail, help2);            return detail; // 注意这里 return 的 detail
            });
        });
    };

    后端的文档,存在部分无实时更新,部分是错误的情况。 由于是 20+ 的接口,并没有采用手动复制接口的方案,因为手动复制,可能都要占用不少的时间,而且无法保证正确性,采用的是使用 jQuery 在 console 调用对文档进行格式化。脚本例子如下(脚本本身不具备通用性,因为后端写文档的风格不一样)

    var result = {};var eles = $('h2').splice(1);
    eles.forEach((item, index) => {    let cur = $(item);    const desc = cur.text();    const info = {
            desc,
        };    let target;    while((cur = cur.next())[0] !== eles[index + 1] && (index !== eles.length - 1)) {        if (cur.is('table')) {            if (!info[target]) {
                    info[target] = {};
                }            const map = [];
                [...cur.find('tr:nth-child(1) th')].forEach((item, index) => {                const th = $(item);                if (th.text().includes('名称')) {                    // map.push('name');
                    }                if (th.text().includes('类型')) {
                        map.push('type');
                    }                if (th.text().includes('是否必须')) {
                        map.push('require');
                    }                if (th.text().includes('描述')) {
                        map.push('desc');
                    }
                });
                [...cur.find('tr td:nth-child(1)')].forEach((item, index) => {                const td = $(item);                const mapClone = [...map];                let next = td.next();                const key = td.text().trim();                while (next.length) {
                        info[target][key] = {
                            [mapClone.shift()]: next.text().trim(),
                        };
                        next = next.next();
                    }                if (key === 'Action') {
                        result[info[target][key].desc.match(/[a-zA-Z]+/)[0]] = info;
                        info[target][key].value = info[target][key].desc.match(/[a-zA-Z]+/)[0];
                    }                if (key === 'Version') {
                        info[target][key].value = info[target][key].desc.match(/[d-]+/)[0];
                    }
                });
            } else if (cur.is('p')) {            const subDesc = cur.text();            const attr = subDesc.match(/[a-zA-Z]+/)[0].trim();
                target = target + '_' + attr;
                info[target] = {                desc: subDesc,
                };
            } else {            const subDesc = cur.text().replace(/[
    ]/g, '');            let pass = false;            if (subDesc.includes('url')) {
                    target = 'url';
                }            if (subDesc.includes('body')) {
                    target = 'body';
                }            if (subDesc.includes('返回参数')) {
                    target = 'response';
                }            if (subDesc.includes('请求示例')) {
                    target = 'reqDemo';
                    pass = true;
                }            if (subDesc.includes('返回示例')) {
                    target = 'resDemo';
                    pass = true;
                }            if (subDesc.includes('方法')) {
                    info.method = subDesc.match(/get|post/i)[0].toUpperCase();
                }            if (target) {                if ((target === 'reqDemo' || target === 'resDemo') && !pass) {
                        info[target].value = subDesc;
                    } else {                    if (!info[target]) {
                            info[target] = {                            desc: subDesc,
                            };
                        }
                    }
    
                }
            }
        }
    });console.log(result);var map = Object.values(result).map((item) => ({query:{Action: item.url.Action.value,Version: item.url.Version.value,},method:item.method})).reduce((a,b) => Object.assign(a, {[b.query.Action]: {url: {query: b.query,method: b.method,path: 'path'}}}), {});
    
    JSON.stringify(map, null, '	').replace(/"(.*?)":/g, '$1:').replace(/: "path",{0,1}/g, ',');

    经验

    离数据核心越近,则需要写的转换越少,离数据核心远的话,可能要在多个地方写同样的转换,所以建议不要把转换写在各个地方,另外前端建设 service 层的必要性,不要把接口写的到处都是,很难找的。

    其他

    顺便简单写一下实践,由实践的例子可以看到对接口的定义,采用了新的方案,这种方案的说明,会在后续的文章进行介绍。

    // service 页面define([    'pro/service/base',    './apis/module.js',    './transform.js',
    ], (base, api, transform) => {
        const service = base.transformOAI(apis, transform);    return service;
    });// apis/module.jsdefine([], function () {
        const path = '/module';    return {        Create: {            url: {                query: {                    Action: "Create",                    Version: "2017-12-14"
                    },                method: "GET",
                    path,
                }
            },        Delete: {            url: {                query: {                    Action: "Delete",                    Version: "2017-12-14"
                    },                method: "GET",
                    path,
                }
            },        DescribeList: {            url: {                query: {                    Action: "DescribeList",                    Version: "2017-12-14"
                    },                method: "POST",
                    path,
                }
            },
        };
    });// transform.jsdefine([
    ], () => {
        const formatRes = function (res) {        return {            code: res.Code,            msg: res.Message,            requestId: res.RequestId,
            };
        };    return {        Create: {
                pre(params) {                return {                    query: {                        InstanceId: params.data.instanceId,                        Name: params.data.name,                        Description: base64._$str2b64(params.data.description),
                        },                    config: {                        noAlert: params.noAlert,
                        },
                    };
                },            post: formatRes,
            },        Delete: {
                pre(params) {                return {                    query: {                        Id: params.data.id,
                        },
                    };
                },            post: formatRes,
            },
        };
    });


    免费体验云安全(易盾)内容安全、验证码等服务

    更多网易技术、产品、运营经验分享请点击


    相关文章:
    【推荐】 21分钟学会写编译器
    【推荐】 “网易有钱”sketch使用分享

  • 相关阅读:
    Window 窗口类
    使用 Bolt 实现 GridView 表格控件
    lua的table库
    Windows编程总结之 DLL
    lua 打印 table 拷贝table
    使用 xlue 实现简单 listbox 控件
    使用 xlue 实现 tips
    extern “C”
    COleVariant如何转换为int double string cstring
    原来WIN32 API也有GetOpenFileName函数
  • 原文地址:https://www.cnblogs.com/163yun/p/9914381.html
Copyright © 2011-2022 走看看