nuxt.js請求引數是陣列的問題

2021-05-20 11:00:09

背景:前兩天線上出現了訓練營詳情無法跳轉的問題,定位之後是由於前端在調介面時傳參有問題,導致的介面返回引數不對導致的。

原因分析:

       問題介面:http://o2o.dailyyoga.com.cn/620/yogao2school/session/馬賽克?session_id=759&dy=1&app[]=1&app[]=1&version[]=7.14.0.0&version[]=7.14.0.0&sid[]=fbdaf97d954665a879a5ce56b012f7c9&sid[]=fbdaf97d954665a879a5ce56b012f7c9&uid[]=117261794&uid[]=117261794&visitor_uid[]=117261794&visitor_uid[]=117261794&time[]=1587375765&time[]=1587375765&timezone[]=8&timezone[]=8&channels[]=200001&channels[]=200001&type[]=2&type[]=2&deviceId[]=eff881a9ef0239c36b855fb5bce1023fbe57d561&deviceId[]=eff881a9ef0239c36b855fb5bce1023fbe57d561&sign[]=08456ae9ab2a23a759f7ecab86431843&sign[]=9840a13d9798daca44d91ad60eee938e

       可以看到介面裡面拼接的uid、sid等引數後面多了一個[],這個問題是怎麼出現的呢?

       首先,h5這邊請求介面時的邏輯如下:

       

       通過請求庫axios來發起請求,其中query為該次請求需要拼接到介面後面的引數。

       query的值是由使用者端拼接到h5連結後面的引數解析之後得到,由於7.15版本之前ios在訓練營詳情頁面對應的引數拼接了兩遍的原因(抓到的連結:https://node.dailyyoga.com.cn/o2_detail/?session_id=760&dy=1&app=1&app=1&version=7.13.1.0&version=7.13.1.0&sid=b4f6ddcd51a206c189386cf6643ad686&sid=b4f6ddcd51a206c189386cf6643ad686&uid=89560032&uid=89560032&visitor_uid=89560032&visitor_uid=89560032&time=1587378408&time=1587378408&timezone=8&timezone=8&channels=200001&channels=200001&type=2&type=2&deviceId=eff881a9ef0239c36b855fb5bce1023fbe57d561&deviceId=eff881a9ef0239c36b855fb5bce1023fbe57d561&sign=e3c4c2f9bdfd15715485365bc8e9387f&sign=0faa69df1cbfa94d1a861942ba2ae7b0),導致這邊query解析出來裡面有些key值對應成了陣列,就像這樣:

{
    session_id: '738',
    dy: '1',
    app: [1, 1],
    uid: ['89560032', '89560032'],
    sid: ['b4f6ddcd51a206c189386cf6643ad686', 'b4f6ddcd51a206c189386cf6643ad686'],
    version: ['7.13.1.0', '7.13.1.0']
}

       這個時候請求庫在往url上拼接對應的引數的時候出現了問題,下面是請求庫把引數拼接到url後面時的原始碼,這段程式碼生成了上面說的有問題的url請求連結:

/**
 * Build a URL by appending params to the end
 *
 * @param {string} url The base of the url (e.g., http://www.google.com)
 * @param {object} [params] The params to be appended
 * @returns {string} The formatted url
 */
module.exports = function buildURL(url, params, paramsSerializer) {
  /*eslint no-param-reassign:0*/
  if (!params) {
    return url;
  }

  var serializedParams;
  if (paramsSerializer) {
    serializedParams = paramsSerializer(params);
  } else if (utils.isURLSearchParams(params)) {
    serializedParams = params.toString();
  } else {
    var parts = [];

    utils.forEach(params, function serialize(val, key) {
      if (val === null || typeof val === 'undefined') {
        return;
      }

      if (utils.isArray(val)) {
        key = key + '[]'; // 這裡是重點,引數裡面如果有陣列,對應的key後面就會加上[]
      } else {
        val = [val];
      }

      utils.forEach(val, function parseValue(v) {
        if (utils.isDate(v)) {
          v = v.toISOString();
        } else if (utils.isObject(v)) {
          v = JSON.stringify(v);
        }
        parts.push(encode(key) + '=' + encode(v));
      });
    });

    serializedParams = parts.join('&');
  }

  if (serializedParams) {
    var hashmarkIndex = url.indexOf('#');
    if (hashmarkIndex !== -1) {
      url = url.slice(0, hashmarkIndex);
    }

    url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
  }

  return url;
};

       然後,再來看看為啥這個query會被解析成這樣子的。

       訓練營詳情頁面通過伺服器端渲染(vue框架整合的nuxt.js)重構,nuxt中提供了一個方法,用來在頁面載入出來前非同步獲取資料。

async asyncData({ params, query, error, $axios, req, res })

       其中query引數是nuxt自己提供的,跟vue獲取query的方法一樣(這是vue處理query的原始碼地址:https://github.com/vuejs/vue-router/blob/dev/src/util/query.js),下面是其中一段核心程式碼:

function parseQuery (query: string): Dictionary<string> {
  const res = {}

  query = query.trim().replace(/^(\?|#|&)/, '')

  if (!query) {
    return res
  }

  query.split('&').forEach(param => {
    const parts = param.replace(/\+/g, ' ').split('=')
    const key = decode(parts.shift())
    const val = parts.length > 0 ? decode(parts.join('=')) : null

    if (res[key] === undefined) {
      res[key] = val
    } else if (Array.isArray(res[key])) {
      res[key].push(val) // 如果有多於兩個以上相同的key的引數,會繼續往陣列裡面push
    } else {
      res[key] = [res[key], val] // 如果物件裡面已經有了這個key值,是這個時候還有一個
                                 // 相同的key值,此時該key對應的value就會變成陣列形式
    }
  })

  return res
}

       訓練營專案重構前,解析連結裡面的引數是我們自己寫的方法,對連結中有相同的引數會做去重處理,重複的key會取連結中最後一個的值。

    DY.getUrlQueryObj = function (url) {
        url = url == null ? window.location.href : url;
        var search = url.substring(url.lastIndexOf("?") + 1);
        var obj = {};
        var reg = /([^?&=]+)=([^?&=]*)/g;
        search.replace(reg, function (rs, $1, $2) {
            var name = decodeURIComponent($1);
            var val = decodeURIComponent($2);
            obj[name] = val;
            return rs;
        });
        return obj;
    };

解決辦法:

       重構的專案中,在使用整合好的query之前,先做一遍去重,再使用。

        let queryNew = {};
        let keyLength = Object.keys(query);
        for(let i = 0; i < keyLength.length; i++) {
            // 處理連結裡面引數重複拼接  去重
            let isArray = query[keyLength[i]] instanceof Array;
            if(isArray && query[keyLength[i]].length > 1) {
                queryNew[keyLength[i]] = query[keyLength[i]][0];
            } else {
                queryNew[keyLength[i]] = query[keyLength[i]];
            }
        }

       總結:框架在給我們的開發帶來便利的同時,也埋下了一些我們無法預估的坑,後面的開發過程中還是需要多去關注一些框架底層的實現機制,多看原始碼,不斷學習,不斷提升。