前言

在一个前后端分离的新项目中,前端执行post请求,后端springMVC的 @RequestMapping 接收不到对应的请求参数。开始以为是前端webpack的proxy(将请求跨域代理到后台服务)配置问题。然而,并不是这样…

proxy配置如下

1
2
3
4
5
6
7
"proxy": {
"api": {
"target": "http://localhost:7000/",
"changeOrigin": true,
"pathRewrite": { "^/api/v1": "" }
}
}

前端post请求:

1
2
case 'post':
return axios.post(url, data) // data = {name: 'xiaoming'}

后端接收参数:

1
2
3
4
5
// SysAccount: private String name;
@PostMapping("/accounts")
public ResponseEntity<?>createAccount(SysAccount account) {
"dahuang".equsls(account.getName()); // false
}

看了前端发起的请求,请求正文并不是熟悉的Form Data,而是Request Payload。

区别?

Request Payload

Request Payload
对于Request Payload的HTTP请求报文格式大概如下,请求头部Content-Type: application/json,并且请求正文是一个json格式的字符串。(适用于一些复杂的数据对象,例如对象里面再嵌套数组)

1
2
3
4
POST /some-path HTTP/1.1
Content-Type: application/json

{ "foo" : "bar", "name" : "John" }

Form Data

Form Data
Form Data 大概格式如下,请求头部的 Content-Type: application/x-www-form-urlencoded,并且请求正文是类似 get 请求 url 的请求参数。

1
2
3
4
POST /some-path HTTP/1.1
Content-Type: application/x-www-form-urlencoded

foo=bar&name=John

解决方案

后端处理

对于 Request Payload 请求, 必须加 @RequestBody 才能将请求正文解析到对应的 bean 中,通过 request.getReader() 来获取请求正文内容(一说可以使用getRequestPayload方法来获取)。
Request Payload
对于Form Data请求,springmvc 会自动使用 MessageConverter 将请求参数解析到对应的 bean,且通过 request.getParameter(…) 能获取请求参数。
Form Data

前端处理

在前端使用Form Data的方式来发起请求,使用qs库将json对象转化为字符串。
(如 { name: ‘dahuang’,age: 11 } 转化为 name=dahuang&age=11)
所以,通过下面的方法来解决:

1
2
3
4
5
6
7
8
9
10
// 将
axios.post(url, cloneData)
// 改写为
axios.post(url, qs.stringify(cloneData))
// 或者
axios.post(url, {data: {},
headers: {
content-type: 'application/x-www-form-unlencoded'
}
})

注意

当请求头的Content-Type为application/json时浏览器认为该请求为复杂请求,这时浏览器会先进行一次预请求,执行一次OPTIONS 请求,向服务器求证该请求是否合法,如果服务器没有给出正确回应浏览器会报跨域请求导致请求失败。
一般这三种方式会导致这种现象:

  1. 请求的方法不是GET/HEAD/POST
  2. POST请求的Content-Type并非application/x-www-form-urlencoded, multipart/form-data, 或text/plain
  3. 请求设置了自定义的header字段

    拓展

    传统的Form表单提交

    1
    2
    3
    4
    5
    <form action="/" method="POST">
    <input name="name" type="text">
    <input name="password" type="text">
    <button>提交</button>
    </form>
    如果我这里点击提交按钮,就会触发浏览器的提交功能,那结果是什么样呢?

    可以看到Content-Type为application/x-www-form-urlencoded。值得形式是以key1=value1&key2=value2的形式提交的。

    传统的Ajax提交

    1
    2
    3
    4
    5
    6
    7
    function submit2() {
    var xhr = new XMLHttpRequest();
    xhr.timeout = 3000;
    var obj = {a: 1, b: 2};
    xhr.open('POST', '/');
    xhr.send(obj);
    }
    首先我们构造一个简单的函数,然后触发它:
  4. 默认的Content-Type为text/plain。
  5. Request Payload会对非字符串做字符串转换。
  6. 通过xhr.send(JSON.stringify(obj));可修正要发的内容

    axios方式提交

    由于axios已经是vue、react的准标配请求方式了,所以这里探究一下它。
    首先我门看axios的文档,当post提交时候可以传递什么类型参数:

    注意这个类型,我们分别构造两个场景。对应它。
    1
    2
    3
    4
    5
    function submit3() {
    var sence1 = 'name=123&val=456';
    var sence2 = {name: 123, val: 456};
    axios.post('/', sence1)
    }
    分别传递字符串与对象,提交post请求,然后观察结果:
    场景1 — 传递字符串时候的结果:

    场景2 — 传递对象的结果:

    当我们传递字符串的时候,Content-Type自动转为xxx-form-xxx的形式。当为对象的时候,自动转化为xxx/json。字符串的时候以key1=val1&key2=val2的形式体现,对象以JSON字符串形式体现。