背景:前兩天線上出現了訓練營詳情無法跳轉的問題,定位之後是由於前端在調介面時傳參有問題,導致的介面返回引數不對導致的。
原因分析:
可以看到介面裡面拼接的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]];
}
}
總結:框架在給我們的開發帶來便利的同時,也埋下了一些我們無法預估的坑,後面的開發過程中還是需要多去關注一些框架底層的實現機制,多看原始碼,不斷學習,不斷提升。