axios 发送 post 请求,springMVC 接收不到数据问题
我们同样的用 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 实现相同效果。
那么现在问题虽然解决了,但是为什么之前后端就是接收不到 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 json 类型的参数呢????
其实原因很简单,因为 axios post 一个对象到后端的时候,是直接把 json 放到**请求体**中的,提交到后端的,而后端是怎么取参数的,是用的
@RequestParam
这个是什么意思,这个是只能从请求的地址中取出参数,也就是只能从**username=admin&password=admin**这种字符串中解析出参数,这样是不能提取出请求体中的参数的。
那么现在我们又可以大胆的猜想了,如果我们不这么去取参数,而是直接去请求体中取参数不就行了么。
我们可以不改前端,只需要改改后端代码就可以了。
评论