后端接口迁移(从 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(/[\r\n]/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, ‘\t‘).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,
        },
    };
});

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

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

相关文章:
【推荐】 网易云易盾朱星星:最容易被驳回的10大APP过检项
【推荐】 关于网易云验证码V1.0版本的服务介绍
【推荐】 工信部公示网络安全示范项目网易云易盾“自适应DDoS攻击深度检测和防御系统”入选

原文地址:https://www.cnblogs.com/zyfd/p/9914403.html

时间: 2024-08-01 16:06:48

后端接口迁移(从 webapi 到 openapi)前端经验总结的相关文章

前端吐槽的后端接口那些事

今天与另一位前端开发人员扯起了后端接口的皮(我也是前端人员),那个兄弟对后端人员提供的接口很大的意见(我是司空见惯),不过他说的也确实有道理,所以结合我的见解,希望提供接口的人员能多加注意. 1.没有文档... 例如新的前端人员到了一个新的公司,使用接口时,问这个这个不知道,问那个那个不知道,要文档没文档,这绝对是前端人员最抓狂的事,心里肯定是一千只草泥马奔腾而过. 为什么要文档? 1. 文档是当前开发者甚至后面的接盘侠(后面开发者)能够清晰往下做的指引. 2. 即便是简单的东西,但如果不写文档

BugPhobia准备篇章:Beta阶段前后端接口文档

0x00:序言 Two strangers fell in love, Only one knows it wasn’t by chance. To the searching tags, you may well fall in love with http:// 10.2.26.67 Github地址:Beta阶段前后端接口定义说明.md 版本管理 修改记录 V1.0 Beta阶段前后端接口文档一稿整理完成,记录用户.标签(含推荐).搜索和问答部分的主要接口 0x01 :接口定义说明 接口定

购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session

原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session chsakell分享了前端使用AngularJS,后端使用ASP.NET Web API的购物车案例,非常精彩,这里这里记录下对此项目的理解. 文章:http://chsakell.com/2015/01/31/angularjs-feat-web-api/http://chsakell.com/2015/03/07/angularjs-feat-web-api-en

Lync 2010迁移Lync 2013部署后前端服务无法启动处理案例

Lync Server 2013部署后,前端服务无法启动,准备删除拓扑,删除Lync组件,删除AD域架构准备,删除ADSI架构. 当时的操作步骤如下,有点粗糙,供参考: 1.Remove-CsConferenceDirectory -Identity 3 -force 2.拓扑中删除Lync Server 2013部署 3.发布拓扑 3.删除Lync 组件 4.Stop-CsWindowsService 5.C:\Program Files\Microsoft Lync Server 2013\

SpringCloud微服务之跨服务调用后端接口

SpringCloud微服务系列博客: SpringCloud微服务之快速搭建EurekaServer:https://blog.csdn.net/egg1996911/article/details/78787540 SpringCloud微服务之注册服务至EurekaServer:https://blog.csdn.net/egg1996911/article/details/78859200 SpringCloud微服务之集成thymeleaf访问html页面/静态页面&热部署:https

小程序调用后端接口服务 配置文件详解

前言:为了开发阶段的效率更高,方便项目接口管理,在做web项目时,我们需要把后端提供的接口地址进行配置,这样我们自己在调用时,要方便得多,利己利人.在配置小程序接口地址时,和web的配置大同小异,下面总结几点配置小程序接口地址的思路: 1.所有接口地址,要丢在一个对象里[为了方便下面解释,这里设置一个对象名:config],为什么了,因为要对外暴露,方便外部访问,这样[key:value]方式是最合理的,那就是对象了. 2.真实接口地址,也就是对象键值对的value,要用英文模式下Tab键的上一

在C#后端处理一些结果然传给前端Javascript或是jQuery

在C#后端处理一些结果然传给前端Javascript或是jQuery,以前Insus.NET有做过一个例子<把CS值传给JS使用 >http://www.cnblogs.com/insus/archive/2011/05/04/2036271.html 是使用Literal控件来实现. 不过还是有些复杂. 本次Insus.NET再简化一些,把值传给前端的jQuery. 用户请求一个网页,送至服务器之后,然后传给客户端.因此服务端传值给jQuery或是javascript是件很容易的事.反之的话

【译】设计师/后端工程师也能马上实践!前端设计(CSS设计)的技巧

本文是我在众成翻译平台上认领并翻译的:设计师/后端工程师也能马上实践!前端设计(CSS设计)的技巧 ,正文内容如下: 大家好,我是2015年的应届毕业生小原,职位是前端工程师.不久前我加入了新业务的开发,负责前端的设计和实现.我想在回顾当时做法的同时,总结出一套能够轻松实现的前端编程设计方法. 什么是高灵活性的前端开发设计 不论开发什么样的业务,都可能面临界面样式没有完全确定,或者今后还需要增加或改变功能点的情况.因此代码开发与业务构思一样,都需要具备高度的灵活性,对变化频繁的前端来说更是如此.

js跨越请求的2中实现 JSONP /后端接口设置运行跨越header

由于浏览器同源策略,a域名的js向b域名ajax请求会被禁止.JS实现跨越访问接口有2中办法. 1.后端接口设置允许跨越的header头. //header('Access-Control-Allow-Origin:*'); //支持全域名访问,不安全,部署后需要固定限制为客户端网址 header('Access-Control-Allow-Origin:http://www.example-a.com'); //设置的是带协议的url,而不是一个域名.可以设置多个url,用逗号分隔 echo