axios發(fā)送post請(qǐng)求法梯,springMVC接收不到數(shù)據(jù)問題

最近做項(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-Typeapplication/json;charset=UTF-8至耻,Request Payload為

{username: "admin", password: "admin"}

我們同樣的用jquery的ajax把我們這個(gè)請(qǐng)求同樣的發(fā)送一遍
發(fā)現(xiàn)Request-Headers的Content-Typeapplication/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):

  1. 用URLSearchParams傳遞參數(shù)
  2. 改寫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í)體類
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胸哥,一起剝皮案震驚了整個(gè)濱河市涯竟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌空厌,老刑警劉巖庐船,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嘲更,居然都是意外死亡筐钟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門赋朦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)篓冲,“玉大人,你說(shuō)我怎么就攤上這事宠哄∫冀” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵毛嫉,是天一觀的道長(zhǎng)诽俯。 經(jīng)常有香客問我,道長(zhǎng)承粤,這世上最難降的妖魔是什么暴区? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任闯团,我火速辦了婚禮,結(jié)果婚禮上颜启,老公的妹妹穿的比我還像新娘偷俭。我一直安慰自己,他們只是感情好缰盏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布涌萤。 她就那樣靜靜地躺著,像睡著了一般口猜。 火紅的嫁衣襯著肌膚如雪负溪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天济炎,我揣著相機(jī)與錄音川抡,去河邊找鬼。 笑死须尚,一個(gè)胖子當(dāng)著我的面吹牛崖堤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耐床,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼密幔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了撩轰?” 一聲冷哼從身側(cè)響起胯甩,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎堪嫂,沒想到半個(gè)月后偎箫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡皆串,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年淹办,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恶复。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娇唯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出寂玲,到底是詐尸還是另有隱情,我是刑警寧澤梗摇,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布拓哟,位于F島的核電站,受9級(jí)特大地震影響伶授,放射性物質(zhì)發(fā)生泄漏断序。R本人自食惡果不足惜流纹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望违诗。 院中可真熱鬧漱凝,春花似錦、人聲如沸诸迟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)阵苇。三九已至壁公,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绅项,已是汗流浹背紊册。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留快耿,地道東北人囊陡。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像掀亥,于是被迫代替她去往敵國(guó)和親撞反。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容