最近做項(xiàng)目的時(shí)候苔货,前端異步請(qǐng)求用到了尤大推薦的axios,發(fā)現(xiàn)一個(gè)問題立哑,就是POST請(qǐng)求的時(shí)候夜惭,后臺(tái)人員說(shuō)他們的接口里面取不到我傳過去的數(shù)據(jù)。
案例重現(xiàn)
axios.js
let axios = import('axios');
instance = axios.create({
baseURL: '/ghcws',
timeout: 10000,
});
export default instance;
userService.js
import axios = import('./axios');
export async function () {
axios.post('/api/doLogin', {
usesrname: 'admin',
password: 'admin'
})
}
后端的springMVC的關(guān)鍵代碼(簡(jiǎn)化版)
@RequestMappting("/api/doLogin")
public Object doLogin(@RequestParam String username, @RequestParam String password) throws Exception {
System.out.println("username: "+username);
System.out.println("password: "+password);
JSONObject json = new JSONObject();
json.put("success", true);
return json;
}
這個(gè)時(shí)候前端發(fā)的請(qǐng)求后端就接收不到參數(shù)了刁憋。
我們可以打開chrome開發(fā)者工具滥嘴,看看axios的請(qǐng)求的請(qǐng)求頭詳情,發(fā)現(xiàn)Request-Headers的Content-Type是application/json;charset=UTF-8至耻,Request Payload為
{username: "admin", password: "admin"}
我們同樣的用jquery的ajax把我們這個(gè)請(qǐng)求同樣的發(fā)送一遍
發(fā)現(xiàn)Request-Headers的Content-Type是application/x-www-form-urlencoded;charset=UTF-8若皱,URL encode為
username=admin&password=admin
到這里镊叁,由于是前端換了一個(gè)發(fā)送ajax請(qǐng)求的工具,導(dǎo)致以前的接口不能用了走触,后端朋友們首先想到的就是我們前端人員寫錯(cuò)了晦譬,然后我們就要開始苦逼的研究了。
可以看出互广,兩個(gè)請(qǐng)求唯一的不同就是Content-Type的問題敛腌,朋友們,是Request Headers中的Content-Type哈惫皱,不是Response中的哈像樊,不要搞錯(cuò)了。
那不同點(diǎn)找到了旅敷,那我們就可以開始搞了生棍,我們大膽的猜想,如果把a(bǔ)xios的post請(qǐng)求的Content-Type也變成application/x-www-form-urlencoded媳谁,那么問題想必就迎刃而解了涂滴。
我們看看axios的源碼
axios.create = function create(instanceConfig) {
return createInstance(utils.merge(defaults, instanceConfig));
};
create方法就是把我們傳入的參數(shù)和默認(rèn)參數(shù)合并,然后創(chuàng)建一個(gè)axios實(shí)例晴音,我們?cè)倏纯磀efaults這個(gè)配置對(duì)象
var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');
/* 這個(gè)表明默認(rèn)的Content-Type就是我們想要的 */
var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
};
/* 看方法名就知道柔纵,這個(gè)是設(shè)置ContentType用的(Content-Type沒有設(shè)置的時(shí)候) */
function setContentTypeIfUnset(headers, value) {
if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}
/* 這個(gè)是用來(lái)區(qū)別對(duì)待瀏覽器和nodejs請(qǐng)求發(fā)起工具的區(qū)別的 */
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined') {
// For node use HTTP adapter
adapter = require('./adapters/http');
}
return adapter;
}
/* 這里終于看到了萬(wàn)眾期待的默認(rèn)配置 */
var defaults = {
adapter: getDefaultAdapter(),
/* 這個(gè)transformRequest配置就厲害了
* 官方描述`transformRequest` allows changes to the request data before it is sent to the server
* 這個(gè)函數(shù)是接受我們傳遞的參數(shù),并且在發(fā)送到服務(wù)器前锤躁,可以對(duì)其進(jìn)行更改
* */
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
/* 關(guān)鍵點(diǎn)1搁料、如果用URLSearchParams對(duì)象傳遞參數(shù),就可以用我們想要的Content-Type傳遞 */
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
/* 關(guān)鍵點(diǎn)2进苍、這里我們看到加缘,如果參數(shù)Object的話,就是通過json傳遞 */
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}],
transformResponse: [function transformResponse(data) {
/*eslint no-param-reassign:0*/
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
}
};
defaults.headers = {
common: {
'Accept': 'application/json, text/plain, */*'
}
};
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
defaults.headers[method] = {};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
module.exports = defaults;
通過上面的源碼注解觉啊,我們找到了著手點(diǎn):
- 用URLSearchParams傳遞參數(shù)
- 改寫transformRequest
很顯然,如果我們不是通過axios.create方法創(chuàng)建實(shí)例沈贝,再拿來(lái)調(diào)用杠人,我們就只能采用第一種解決辦法
第一種方法解決方案
改寫userService
import axios = import('axios');
let param = new URLSearchParams();
param.append("username", "admin");
param.append("password", "admin");
export async function () {
axios.post('/api/doLogin', param)
}
果不其然戏锹,這就成功了孵稽。
如果不想用URLSearchParams,還是覺得Json方便术吝,那么我們可以重新配置transformRequest
第二種方法解決方案
改寫axios的create的配置
import axios from 'axios';
// 這里我自己重寫了一下類型判斷的所有方法学歧,當(dāng)然也可以用util庫(kù)
import { isFormData,
isArrayBuffer,
isStream,
isFile,
isBlob,
isURLSearchParams,
isObject,
isUndefined } from './Type';
function setContentTypeIfUnset(headers, value) {
if (!isUndefined(headers) && isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}
const instance = axios.create({
baseURL: '/ghcws',
timeout: 10000,
transformRequest: [function transformRequest(data, headers) {
/* 把類似content-type這種改成Content-Type */
let keys = Object.keys(headers);
let normalizedName = 'Content-Type';
keys.forEach(name => {
if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
headers[normalizedName] = headers[name];
delete headers[name];
}
});
if (isFormData(data) ||
isArrayBuffer(data) ||
isStream(data) ||
isFile(data) ||
isBlob(data)
) {
return data;
}
if (isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
/* 這里是重點(diǎn)罩引,其他的其實(shí)可以照著axios的源碼抄 */
/* 這里就是用來(lái)解決POST提交json數(shù)據(jù)的時(shí)候是直接把整個(gè)json放在request payload中提交過去的情況
* 這里簡(jiǎn)單處理下,把 {name: 'admin', pwd: 123}這種轉(zhuǎn)換成name=admin&pwd=123就可以通過
* x-www-form-urlencoded這種方式提交
* */
if (isObject(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
let keys2 = Object.keys(data);
/* 這里就是把json變成url形式枝笨,并進(jìn)行encode */
return encodeURI(keys2.map(name => `${name}=${data[name]}`).join('&'));
}
return data;
}]
});
export default instance;
當(dāng)然不用create方法也是可以通過修改axios.defaults.transformRequest實(shí)現(xiàn)相同效果袁铐。
那么現(xiàn)在問題雖然解決了揭蜒,但是為什么之前后端就是接收不到j(luò)son類型的參數(shù)呢????
其實(shí)原因很簡(jiǎn)單,因?yàn)閍xios post一個(gè)對(duì)象到后端的時(shí)候剔桨,是直接把json放到請(qǐng)求體中的屉更,提交到后端的,而后端是怎么取參數(shù)的洒缀,是用的
@RequestParam
這個(gè)是什么意思瑰谜,這個(gè)是只能從請(qǐng)求的地址中取出參數(shù),也就是只能從username=admin&password=admin這種字符串中解析出參數(shù)树绩,這樣是不能提取出請(qǐng)求體中的參數(shù)的萨脑。
那么現(xiàn)在我們又可以大膽的猜想了,如果我們不這么去取參數(shù)饺饭,而是直接去請(qǐng)求體中取參數(shù)不就行了么渤早。
我們可以不改前端,只需要改改后端代碼就可以了砰奕。
解決方案
@RequestMappting("/api/doLogin")
@ResponseBody
public Object doLogin(@RequestBody Map map) throws Exception {
System.out.println("username: "+map.get("username"));
System.out.println("password: "+map.get("password"));
JSONObject json = new JSONObject();
json.put("success", true);
return json;
}
通過@RequestBody 注解蛛芥,springmvc可以把json中的數(shù)據(jù)綁定到Map中, 我們就可以取出了.
或者也可以
@RequestBody Pojo pojo
這樣也可以把json綁定到實(shí)體類中军援,也能取到參數(shù)仅淑。
總結(jié)
總共三種解決方案:
- 前端不用json傳參,改用URLSearchParams
- 前端改寫axios的transformRequest
- 后端使用@RequestBody注解綁定json到實(shí)體類