1. 程式人生 > >後端介面遷移(從 webapi 到 openapi)前端經驗總結

後端介面遷移(從 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攻擊深度檢測和防禦系統”入選