互联网产品的前后端数据交互是通过一个通讯协议完成。前后台交互的协议主要包括HTTP,FTP,SMTP,TELNET,POP3…本文仅总结HTTP协议。
简单请求get 、post 、head ,简单请求与复杂请求的主要区别是:是否会触发cors预检请求。
简单请求的head不会超出一下几种字段
1、Accept
2、Accept-Language
3、Content-Language
4、Last-Event-ID
5、Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
复杂请求都会发预检请求(OPTIONS请求),用于向服务器请求权限信息的(会先向服务器发送一条预检请求,根据服务器返回的内容浏览器判断服务器是否允许该请求访问。如果web服务器采用cors的方式支持跨域访问,在处理复杂请求时这个预检请求是不可避免的),XMLHttpRequest会遵守同源策略(same-origin policy). 也即脚本只能访问相同协议/相同主机名/相同端口的资源, 如果要突破这个限制, 那就是所谓的跨域, 此时需要遵守CORS(Cross-Origin Resource Sharing)机制。可以是后端服务器设置Access-Control-Allow-Origin: * * ,表示是否允许跨域,还有一种是preflighted request在发送真正的请求前, 会先发送一个方法为OPTIONS的预请求(preflight request), 用于试探服务端是否能接受真正的请求,如果options获得的回应是拒绝性质的,比如404\403\500等http状态,就会停止post、put等请求的发出
一、基础知识
HTTP协议
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议,是一个基于TCP/IP通信协议来传递数据,也是互联网上应用最为广泛的一种网络协议。
HTTP的主要特点
(1)简单快速:客户向服务器请求服务时,只需传送请求方法和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快;
(2)灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记;
(3)无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间;
(4)无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快;
(5)支持B/S及C/S模式
无连接、无状态的理解
TCP协议对应于传输层,而HTTP协议对应于应用层。
Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求的数据完毕后,Http会立即将TCP连接断开,这个过程是很短的。所以Http连接是一种短连接,是一种无状态的连接。所谓的无状态,是指浏览器每次向服务器发起请求的时候,不是通过一个连接,而是每次都建立一个新的连接。如果是一个连接的话,服务器进程中就能保持住这个连接并且在内存中记住一些信息状态。而每次请求结束后,连接就关闭,相关的内容就释放了,所以记不住任何状态,成为无状态连接。(3次握手,四次挥手实现连接的建立与断开)
总结
数据交互的过程可简单理解为,前端想要获得某些数据,将传入参数通过URL接口地址,传递给服务器,服务器根据传入的参数了解到前台要获得什么数据,去数据库查询获取数据,然后将所需数据返回给前台,前台拿到数据做相应的页面展示。
前端<----------> 后端<------------->数据库
二、常用的接口请求方法(如何在项目中实现前后端交互 即http协议)
1、 ajax 方法
ajax是利用js创建的异步对象来实现前后端交互的http异步请求交互(它是一个异步操作,可以在不重新加载整个网页的情况下,对网页的某部分进行更新。目前所有浏览器都支持XMLHttpRequest)
// 如何项目中实现 ajax 5步走
// (1) 创建异步对象
const ajaxObj = new XMLHttpRequest()
// (2) 设置请求url等参数,使用异步对象的open方法
ajaxObj.open('get', 'url') // 若是get请求直接在url后拼接查询参数?name=xuheua&age=3
// (3) 发送请求 。使用异步对象的send方法
ajaxObj.send() // 若是post请求,此方法中放请求的参数,可以是字符串拼接或对象 此后可设置请求头等信息ajaxObj.setRequestHeader()
// (4) 注册事件,可监控请求及返回的进度(状态改变时调用的方法)
ajaxObj.onreadystatechange = function () {
if (ajaxObj.readyState === 4 && ajaxObj.status === 200) { // status表示请求的状态码 readyState表示是否相应就绪可在用户端使用
// 0 未初始化,还未send请求 1正在载入,还在发 2 载入完成即send完成 3 正在解析想用内容 4 完成 可在用户端使用
console.log(ajaxObj.responseText)// 返回的数据保存在异步对象的属性中
// (5) 此时可进行页面的渲染逻辑
}
}
// 如何设置头部信息。W3C标准文档规定,部分请求头由浏览器直接控制,开发者不可设置,设置会提示。错误。地址:https://www.cnblogs.com/cdwp8/p/5157377.html
// 放置在open方法之后
ajaxObj.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("User-Agent", "test");
// 设置多个请求头属性,最终会合并到一个请求头信息中
2、. jQuery(它是封装的js库,里面封装了ajax,直接利用jquery封装的ajax方法,实现前后端的异步交互 http 请求)
// 如何利用jquery方法进行前后端交互(利用jquery 提供的ajax9()方法)
// 首先项目中引入 jquery
//方法1:
//方法2:
//(1)首先在package.json里的dependencies加入jquery
"dependencies": {
"axios": "^0.19.2",
"element-ui": "^2.13.1",
"jquery": "^3.5.1",
"ol": "^5.2.0",
"vue": "^2.5.2",
"vue-router": "^3.0.1",
"vuex": "^3.2.0",
"xlsx": "^0.16.0"
},
// (2)终端下载jQuery 包 npm install jquery
// (3)在build/webpack.base.conf.js文件module.exports中添加plugins属性如下代码
module.exports = {
resolve:{}
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
],
module:{}
//(4) 在main.js中引入
import $ from 'jquery'
//(5) 进行前后端交互
$.ajax({
url: "fa.php", // 发送请求的地址
type: "GET", // 设置请求的方法,请求方式"POST"或"GET", 默认为"GET"。
data: "", // data主要方式有三种,html拼接的,json数组,form表单经serialize()序列化的;通过dataType指定,不指定智能判断。
// 1、json格式
//data:{
// selRollBack : selRollBack,
// selOperatorsCode : selOperatorsCode,
// PROVINCECODE : PROVINCECODE,
// pass2 : pass2
//},
// 2、form表单经serialize()序列化的
//data:$("#form1").serialize();//序列化表格内容为字符串
// 3、html拼接的 https://blog.csdn.net/mingliangniwo/article/details/45533201
dataType: "json", // 服务器返回的数据类型 "json" "jsonp" "text" 等
success: function(data) { 请求成功后的回调函数。
console.log(data);
},
error: function(xhr,error,obj) { // 请求失败的回调函数 有以下三个参数:XMLHttpRequest 对象、错误信息、(可选)捕获的异常对象。
console.log(error);
}
complete: function() { // 请求成功或失败的回调函数 ,不管成功还是失败都会执行此函数
// 函数内直接写请求完成后需要处理的事。
}
beforeSend: function(xhr) { //发送请求前可修改 XMLHttpRequest 对象的函数,如添加自定义 HTTP 头
xhr.setRequestHeader ("content-type", "application/x-www-form-urlencoded" ) // 后端让我们传参数的格式。 传输数据类型
}
3、. axios(封装的js库,基于promise的http库,专门用于前后端交互(超文本传输))
// 项目中可自己封装方法名,设置超时时间及baseurl等,对各种请求错误时的处理。例如发完请求后为let req = axios(fetchOpt);
// 处理错误的请求
req = req.catch(handleError).then((res) => {
if (res && res.status === 200 && res.data) {
const resData = res.data;
const { status, ret, msg } = resData;
if (ret || status === 0) {
return res;
}
// 处理业务处理失败的场景,多为后端异常啦!
if (msg) {
Notification.error(msg);
} else {
Notification.error('服务器返回未知错误');
}
throw new Error(msg); // 业务代码里,也可以收到
}
return true;
});
const handleError = (err) => {
let mess = err.message;
if (err.response) {
switch (err.response.status) {
case 401:
mess = 'API未认证';
break;
case 403:
mess = 'API未授权';
break;
case 404:
mess = 'API未找到';
break;
case 500:
mess = 'API服务器错误';
break;
default:
mess = '发生未知错误';
break;
}
}
Notification.error(mess); // 统一给出错误
throw new Error(err); // 各页面也可以catch到错误,从而详细记录错误埋点
};
// 处理成功的请求
req = req.then((res) => {
const resData = res.data;
const { status, ret } = resData;
if (ret || status === 0) Notification.success(config.withSuccessNotice);
return res;
});
// 简单实用介绍
// get 方法
// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// 上面的请求也可以这样做,注意写法,第二参数是config对象,属性params是一个对象参数,还可以设置别的参数
axios.get('/user', {
params: {
ID: 12345
},
baseURL: 'https://some-domain.com/api/',
headers: {'X-Requested-With': 'XMLHttpRequest'},// 设置头部信息,属性值的形式,一般不单独一个一个请求的设置,会封装一开始create中设置
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// post 方法
axios.post('/user', { // 第二参数直接是传的参数data
firstName: 'Fred',
lastName: 'Flintstone'
},{ // 第三参数是一个config对象,可以设请求的各个属性
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// 注意:可以利用default对象设置请求的一些属性,也可以直接在create方法上一开始设置
axios.defaults.baseURL = 'https://api.example.com'; // 全局axios默认值
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
// 自定义默认值
var instance = axios.create();
// 覆写库的超时默认值
// 现在,在超时前,所有请求都会等待 2.5 秒
instance.defaults.timeout = 2500;
// 为已知需要花费很长时间的请求覆写超时设置
instance.get('/longRequest', {
timeout: 5000
});
// 注意在请求或响应到对应的then或catch前想处理一些操作,实用拦截器处理,类似上方处理错误的请求与成功的请求
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
4、. fetch方法(web api ,跨网络异步获取资源,返回promise,fetch请求默认不带cookie的,需要设置fetch(url, {credentials: ‘include’}) )
// get 和post都是类似,只是设置的方法和传参数的形式不同
// get 请求的简单使用 ,第一参数为url,get请求直接将参数拼接url后?a=1&b=1
fetch(uri, {
method: 'GET',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', //传参数格式,即参数内容类型 ?序列化的拼接形式
'X-Requested-With': 'XMLHttpRequest',
timeout:10000, // 超时时间
}).then((res)=>{
}).catch((error)=>{
})
// post请求的简单使用
fetch(uri, {
method: 'POST',
'Content-Type': 'application/json; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
timeout:10000, // 超时时间
body: form ? stringify(data) : (JSON.stringify(data) || {}), // body体传的参数。看是健值对形式(name=hehe&age=10)还是json形式({"a":"hehe","age":10}) import qs from 'qs'; 专门用于对象与序列化的转换的js函数库
}).then((res)=>{
}).catch((error)=>{
})
// 封装时的常用思路
// 1、设置一个对象,里面设置get和post时请求头的设置
const HEADERS = {
get: { //
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
...MOCK_HEADERS, // 一些别的设置
},
post: {
'Content-Type': 'application/json; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
...MOCK_HEADERS, // 一些别的设置
},
};
// 2、定义暴露全局的get post请求方法
// get 方法 全局调用时传参数{data:{//请求参数},headers:{// 可以单独设置请求头},再一些别的参数}
function get(url, param = {}, withUUID) {
const {
// 是否显示默认错误提示
showError = true,
// 超时时间
timeout = 10000,
// 成功提示
successTip = false,
onlyOnce = true,
} = param;
let { data = {} } = param; //要传给后端的查询参数
const headers = { ...HEADERS.get, ...param.headers }; // 请求头信息
const newParam = { method: 'GET', headers, timeout, successTip };
// 拼接请求参数类似 ?a=1&b=2
if (data) {
const trimData = trimSearchData(data, 0);
const urlParam = encodeURIComponent(serialize(trimData))
.replace(/%26/g, '&').replace(/%3D/g, '=');
url += `${url.indexOf('?') > -1 ? '&' : '?'}${urlParam}`;
}
const uniqueData = url;
// 添加时间戳
url += `${url.indexOf('?') > -1 ? '&' : '?'}_time=${new Date() - 0}`;
return newFetch(url, newParam, showError, uniqueData, onlyOnce); // 封装的整体处理fetch请求成功与失败的处理逻辑
// 前两个参数即最重要的url(包含参数),头部设置信息等,其他的是根据业务写的逻辑
}
// post 方法
function post(url, param = {}, withUUID) {
const {
// 是否显示默认错误提示
showError = true,
// 超时时间
timeout = 10000,
// 成功提示
successTip = false,
form = false,
onlyOnce = true,
} = param;
let { data = {} } = param; // 传参对象
const headers = { ...HEADERS.post, ...param.headers };
if (form) {// 默认是json格式的传参,若后端要求是form表单的健值对格式则重新设置请求头的传参数格式
headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
}
data = trimSearchData(data, 0);
return newFetch(url, {
method: 'POST',
headers,
timeout,
body: form ? stringify(data) : (JSON.stringify(data) || {}), // post的参数是body中,并设置传参数格式
successTip,
}, showError, uniqueData, onlyOnce);
}
// 3、封装请求的整体方法以及处理请求
const errPromise = {
then() {
return errPromise;
},
catch() {
return errPromise;
},
};
export const errorHook = (response, uri) => { // 返回不是200 的状态码时统一抛错让用户及开发知晓
// 统一处理埋点
metric('request.error')
.tags({ status: response.status })
.detail({ uri })
.dot();
const handler = ERROR_MAP[response.status];
if (typeof handler === 'function') {
return handler(uri);
}
throw new Error(`${response.status} ${response.statusText}`);
};
function newFetch(url, param, showError, uniqueData, onlyOnce) {
if (onlyOnce) { // 若设置只能请求一次,此处根据项目需求来,大家可以略过
return errPromise;
}
const starTime = +new Date();
// 开始处理请求 第一个参数是请求的url,若get时已拼接请求参数及时间戳
return fetch(uri, param)
.then((response) => { // 请求成功的处理
if (response.status >= 200 && response.status < 300) { // 200-300这是响应成功时正常返回后端的响应。
if (!window.SHOP_CODE) {
window.SHOP_CODE = response.headers.get('shopCode') || '';
}
return response;
}
return errorHook(response, uri); // 当是别的状态码时添加处理逻辑
})
.then(response => response.json()) // 当上一层then没有抛异常,即200时,走到这层then。 请求成功的处理
.then((data) => {// 前面处理都正常,请求成功的处理
if (data.status !== 0) {// 若请求200,但返回的状态不是0时,即数据不是成功拿到,此时抛异常信息
let error = new Error(`请求异常 ${url}`);
if (data.msg) {
error = new Error(`${data.msg} ${url}`);
}
error.response = data;
throw error
} else {
if (param.successTip) { // 前端默认设置的请求成功的提示文案,调用接口时可设置不展示此块
notification.success(typeof param.successTip === 'string' ? param.successTip : '操作成功', 1.5);
}
return data; // 请求成功200且状态也是0。这是调用成功最终拿到的的 数据结构{}
}
})
.catch((error) => { // 请求失败的处理(状态码不是200也走此块,状态码不是0也走此块
throw error; // 失败时拿到的错误信息error:{response:{}} // 自定义处理成的结构
});
}
// 4、导出供全局使用(以上自定义一个Request中封装)
Request.get = get;
Request.post = post;
export default Request;
// 5、组件中如何调用
import Request from '......./Request';
Request.get(url, {
data: {
operateNum: this.operateNum,
},
// 还可以定义封装时兼容的其他参数
}).then(({ data }) => { // 拿到处理成功的data数据结构
// 处理逻辑
}).catch((error)=>{
const { status } = error.response;
})
Request.post('/seccenter/wdes/all/decryptOrEncrypt/v1', {
data: {
// 参数
},
// 别的参数
}).then(({ status, data }) => {
if (status === 0 && data) {
// 处理逻辑
}
}).catch((error)=>{
const { status } = error.response;
})
⚠️ 请求中唯一标识符的使用
(1)采用时间戳的 ${+new Date()}`; // “1568689340401” 唯一性较差
(2)引入uuid import uuid from ‘uuid’;
uuid的v1,v3,v4,v5分别是什么意思?
v1 timestamp(时间戳). v3 namespace(命名空间) v4 random number(随机数) uuid.v4()
项目中使用案例
import uuidV4 from 'uuid';
const uuid = uuidV4();
// 设置请求的head参数
headers: {
uuid,
},
⚠️ cookie 的使用及学习:如何在前端和后端创建,使用 HTTP cookie
cookie 是后端可以存储在用户浏览器中的小块数据。 Cookie 最常见用例包括用户跟踪,个性化以及身份验证,存储登陆人信息
一般后端在前端请求前设置cookie,展示在请求中,通过document.cookie获取,会拿到一个字符串类型的数据
// js创建cookie document.cookie="username=John Doe";
// js获取cookie document.cookie
// 使用案例 import Cookie from 'js-cookie'; 封装的js库,有方便的方法供我们操作cookie
// 写入cookie
Cookies.set('name', 'value')
// 读取
Cookies.get('name') // => 'value'
Cookies.get('nothing') // => undefined
// 读取所有可见的cookie
Cookies.get()
// 删除某项cookie值
Cookies.remove('name')
三、交互方法中传参格式的设置
1、get 请求 ‘Content-Type’: 'application/x-www-form-urlencoded(健值对的形式,一般不用设置,get默认)
2、post请求(参数在body中)
四、接口响应数据类型
// 备注:陈列、shop-admin、store-roster-web等使用的axios, 订货order-support、策略使用的fetch方法