請在這裏下載示例代碼,並在終端中運行以下命令啓動 Demo:html
npm install
複製代碼
npm start
複製代碼
示例代碼的服務端,由Koa實現。前端
因爲谷歌瀏覽器無此問題,請在**火狐瀏覽器**中,打開http://localhost:8080/進行測試。git
用戶點擊支付按鈕。github
前端發起一個 AJAX 請求到服務端。npm
服務端返回一個由支付寶生成的表單,其中帶有支付頁地址,以及相關參數。json
前端使用表單的參數,在新窗口打開支付寶的支付頁面。瀏覽器
在火狐瀏覽器中,該操做會被瀏覽器攔截,以下圖所示:bash
結論是,只要在 AJAX 後,用 JavaScript 觸發的跳轉,都會被火狐攔截。app
固然,火狐並不是對全部場景都進行了攔截,例如這些操做:koa
進行 AJAX 請求後,在當前窗口打開連接。
由用戶直接點擊後,在新窗口打開連接。
// 經過直接點擊,能夠在新窗口打開。
// 經過a標籤打開和表單提交一樣能夠。
document
.querySelector('#open')
.addEventListener('click', function () {
window.open(action);
});
// 經過計時器延遲以後,依然能夠在新窗口打開。
document
.querySelector('#open')
.addEventListener('click', function () {
setTimeout(() => {
window.open(action);
}, 100);
});
複製代碼
列舉幾個失敗的方案,能夠在http://localhost:8080/中點擊相應按鈕查看效果。
// 獲取支付寶支付的表單
async function getFormStr() {
const response = await fetch('/getFormStr');
const result = await response.json();
document.querySelector('#formWrapper').innerHTML = result.formStr;
const action = document
.querySelector('#formWrapper form')
.getAttribute('action');
return action;
}
// 方法1:window.open
document
.querySelector('#method1')
.addEventListener('click', async function () {
const action = await getFormStr();
window.open(action);
});
複製代碼
// 方法2:form.submit
document
.querySelector('#method2')
.addEventListener('click', async function () {
const action = await getFormStr();
let formEle = document.createElement('form');
formEle.style.display = 'none';
formEle.method = 'post';
formEle.target = '_blank';
formEle.action = action;
document.body.appendChild(formEle);
formEle.submit();
});
複製代碼
// 方法3:a標籤打開
document
.querySelector('#method3')
.addEventListener('click', async function () {
const action = await getFormStr();
let anchorEle = document.createElement('a');
anchorEle.href = action;
anchorEle.target = '_blank';
document.body.appendChild(anchorEle);
anchorEle.click();
});
複製代碼
// 獲取支付寶支付的表單
router.get('/getFormStr', async (ctx, next) => {
const response = await fetch(
'http://rap2.taobao.org:38080/app/mock/250475/getFormStr',
);
const result = await response.json();
ctx.body = result;
await next();
});
複製代碼
該問題的解決思路以下:
在前端用 form 表單,打開新窗口提交參數。
由服務端進行處理以後,直接重定向到支付寶的支付頁。
<button id="method4">方法4:服務端重定向</button>
<form id="redirectForm" action="/redirectForm" target="_blank"></form>
複製代碼
// 方法4:服務端重定向
document
.querySelector('#method4')
.addEventListener('click', async function () {
document.querySelector('#redirectForm').submit();
});
複製代碼
// 獲取支付寶支付的表單,以後重定向到支付寶支付頁
router.get('/redirectForm', async (ctx, next) => {
const response = await fetch(
'http://rap2.taobao.org:38080/app/mock/250475/getFormStr',
);
const result = await response.json();
// 進行Entity轉換
const decodedFormStr = he.decode(result.formStr, {
isAttributeValue: true,
});
const decodedFormStrArr = decodedFormStr.split('\n');
// 截取表單中的有用參數,並拼接成跳轉支付寶的URL
const aliPayUrl = `${decodedFormStrArr[0].match(/action="([\s|\S]*)">/)[1]}&${
decodedFormStrArr[1].match(/name="([\s|\S]*)" value="/)[1] }=${decodedFormStrArr[1].match(/value="([\s|\S]*)">/)[1]}`; ctx.response.redirect(aliPayUrl); await next(); }); 複製代碼
因爲支付寶返回的表單中,有 "
字符,即爲 Entity(實體),若是直接表單寫入頁面,瀏覽器可以正確將其識別成 "
。
若是你想要查看完整的 Entity 列表,能夠點擊這裏。
但在服務端只能將其當作字符串處理,所以須要用he庫進行轉換,避免參數拼接出錯,以下:
// 進行Entity轉換
const decodedFormStr = he.decode(result.formStr, {
isAttributeValue: true,
});
複製代碼
如今前端的能力愈來愈強,但始終仍是有其侷限性,解決問題的時候不該當把思路侷限在前端領域,要充分利用服務端的資源。
Entity 雖然不多使用,但遇到的時候能夠經過he庫進行轉換。