axios发送post请求,springMVC接收不到数据问题

最近做项目的时候,前端异步请求用到了尤大推荐的axios,发现一个问题,就是POST请求的时候,后台人员说他们的接口里面取不到我传过去的数据。

案例重现

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的关键代码(简化版)
@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;
}

这个时候前端发的请求后端就接收不到参数了。

我们可以打开chrome开发者工具,看看axios的请求的请求头详情,发现Request-Headers的Content-Typeapplication/json;charset=UTF-8,Request Payload为

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

我们同样的用jquery的ajax把我们这个请求同样的发送一遍
发现Request-Headers的Content-Typeapplication/x-www-form-urlencoded;charset=UTF-8,URL encode为

username=admin&password=admin

到这里,由于是前端换了一个发送ajax请求的工具,导致以前的接口不能用了,后端朋友们首先想到的就是我们前端人员写错了,然后我们就要开始苦逼的研究了。

可以看出,两个请求唯一的不同就是Content-Type的问题,朋友们,是Request Headers中的Content-Type哈,不是Response中的哈,不要搞错了。

那不同点找到了,那我们就可以开始搞了,我们大胆的猜想,如果把axios的post请求的Content-Type也变成application/x-www-form-urlencoded,那么问题想必就迎刃而解了。
我们看看axios的源码

axios.create = function create(instanceConfig) {
  return createInstance(utils.merge(defaults, instanceConfig));
};

create方法就是把我们传入的参数和默认参数合并,然后创建一个axios实例,我们再看看defaults这个配置对象

var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');
/* 这个表明默认的Content-Type就是我们想要的 */
var DEFAULT_CONTENT_TYPE = {
  'Content-Type': 'application/x-www-form-urlencoded'
};
/* 看方法名就知道,这个是设置ContentType用的(Content-Type没有设置的时候) */
function setContentTypeIfUnset(headers, value) {
  if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
    headers['Content-Type'] = value;
  }
}
/* 这个是用来区别对待浏览器和nodejs请求发起工具的区别的 */
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;
}
/* 这里终于看到了万众期待的默认配置 */
var defaults = {
  adapter: getDefaultAdapter(),
  /* 这个transformRequest配置就厉害了
   * 官方描述`transformRequest` allows changes to the request data before it is sent to the server 
   * 这个函数是接受我们传递的参数,并且在发送到服务器前,可以对其进行更改
   * */
  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;
    }
    /* 关键点1、如果用URLSearchParams对象传递参数,就可以用我们想要的Content-Type传递 */
    if (utils.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
      return data.toString();
    }
    /* 关键点2、这里我们看到,如果参数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;

通过上面的源码注解,我们找到了着手点:

  1. 用URLSearchParams传递参数
  2. 改写transformRequest
    很显然,如果我们不是通过axios.create方法创建实例,再拿来调用,我们就只能采用第一种解决办法
第一种方法解决方案

改写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';
// 这里我自己重写了一下类型判断的所有方法,当然也可以用util库
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();
    }
    /* 这里是重点,其他的其实可以照着axios的源码抄 */
    /* 这里就是用来解决POST提交json数据的时候是直接把整个json放在request payload中提交过去的情况
     * 这里简单处理下,把 {name: 'admin', pwd: 123}这种转换成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形式,并进行encode */
      return encodeURI(keys2.map(name => `${name}=${data[name]}`).join('&'));
    }
    return data;
  }]
});
export default instance;

当然不用create方法也是可以通过修改axios.defaults.transformRequest实现相同效果。

那么现在问题虽然解决了,但是为什么之前后端就是接收不到json类型的参数呢????

其实原因很简单,因为axios post一个对象到后端的时候,是直接把json放到请求体中的,提交到后端的,而后端是怎么取参数的,是用的

@RequestParam

这个是什么意思,这个是只能从请求的地址中取出参数,也就是只能从username=admin&password=admin这种字符串中解析出参数,这样是不能提取出请求体中的参数的。
那么现在我们又可以大胆的猜想了,如果我们不这么去取参数,而是直接去请求体中取参数不就行了么。
我们可以不改前端,只需要改改后端代码就可以了。

解决方案
@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中的数据绑定到Map中, 我们就可以取出了.
或者也可以

@RequestBody Pojo pojo

这样也可以把json绑定到实体类中,也能取到参数。

总结

总共三种解决方案:

  • 前端不用json传参,改用URLSearchParams
  • 前端改写axios的transformRequest
  • 后端使用@RequestBody注解绑定json到实体类
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,601评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,367评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,249评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,539评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,967评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,929评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,098评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,825评论 0 207
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,609评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,796评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,282评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,603评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,277评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,159评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,959评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,079评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,874评论 2 277

推荐阅读更多精彩内容