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

本文主要探讨了在使用axios发送POST请求时,SpringMVC无法接收到参数的问题。问题根源在于Content-Type的设置,axios默认设置为application/json,而SpringMVC的@RequestParam注解无法从请求体中获取JSON数据。解决方案包括:1) 前端使用URLSearchParams构造参数;2) 重写axios的transformRequest方法以转换Content-Type;3) 后端使用@RequestBody注解从请求体中读取JSON数据。

最近做项目的时候,前端异步请求用到了尤大推荐的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-Type是application/json;charset=UTF-8,Request Payload为

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

我们同样的用jquery的ajax把我们这个请求同样的发送一遍

发现Request-Headers的Content-Type是application/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;

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

用URLSearchParams传递参数

改写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到实体类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值