目录
一、后台介绍
1.1 基础介绍
- 前后端分离:后端只提供接口及接口文档,前端向接口提交请求即可,遵循restful接口规范
- 种类:
- 自有后端:用DRF写接口API,跑在本地使用,具体点击此处跳转
- 在线接口:mock可以让你在没有后端程序的情况下能真实地在线模拟ajax请求,此为fastmock网站入口
1.2 fastmock简介
- 新建项目接口页面
- 编辑页面
- 查看页面
- 请求写法:注意加接口根地址
- 响应内容
- 请求写法:注意加接口根地址
注意:get方法好写,其他方法需要学学mock语法,见接口主页面的使用文档
1.3 请求及响应
1.3.1 请求体格式
- 功能:向后端发送的请求,包含附加信息(需与后端约定)
- 出处:在request.js中初始化对象config,增加或覆盖congfig属性项详见http.js
- config格式如下:
上述格式:可在request.js的config中
console.log(config)
打印
1.3.2 响应体格式
- config:请求体配置信息,同1.3.1
- data:响应体,即服务器响应的数据
- headers:服务器响应头,服务器对响应进行说明和信息提醒
- status:响应http状态码,200为响应成功
- statusText:来自服务器的响应状态信息,‘OK’为响应成功
- request:浏览器提供的XMLHttpRequest对象,该对象来发送Ajax请求(了解即可)
上述格式:可在about组件调用处res => {console.log(res)}打印
二、分项讲解
2.1 单一请求
- 安装:
npm install axios --save
,要有node环境 - 使用:以下写在组件的script标签里
// 不加相对路径,程序会到node_modules文件夹中去找 import axios from 'axios' // axios是使用promise语法,此处仅做示意 axios({ // 接口网址:包含协议名,域名,端口和路由 // 有域名后端写法:url:'https://doubi.com/api/books' url: 'http://192.168.1.123:3000/api/books', // 请求方式,默认为get,可以不写 method: 'get', // 请求可以携带的参数,用对象来写,get方法对应params,其他方法对应data params: { name: 'book', page: 1 } // 成功请求回数据后,进入then,并用console.log打印结果 }).then(res => { console.log(res) })
axios写法:
- 通用写法:axios(),需包含method(默认为get),URL,params或data项
- 增:axios.post(),需包含URL,data项
- 删:axios.delete(),需包含URL,data项(可选)
- 改:axios.put(),需包含URL,data项
- 局部改:axios.patch(),需包含URL,data项
- 查:axios.get(),,需包含URL,params项(可选)
2.2 并发请求
- 使用场景:需要等待多个请求都返回后(例如:验证账户+请求数据)才进行后续的操作
- 以下写在组件的script标签里
- 核心写法:
axios.all([axios.get(),axios.get()]).then()
,也可用async语法替换,见后续封装<script lang="ts"> import { defineComponent } from "vue"; import axios from "axios"; export default defineComponent({ setup() { // 组件创建时即请求数据 axios.all([ axios.get("http://192.168.99.123:8000/category"), axios.get("http://192.168.99.123:8000/product"), ]) // axios提供spread方法将两次请求的响应体分别放在res1和res2中 .then( axios.spread((res1, res2) => { // 浏览器终端打印响应体 console.log(res1); console.log(res2); }) ); }, }); </script>
2.3 通用配置
- 代码(了解)
// 此时再写axios.get('/category') 就可以访问了 axios.defaults.baseURL = 'http://192.168.99.123:8000' // 超时设置,为5000毫秒 axios.defaults.timeout = 5000 // 跨域是否带Token axios.defaults.withCredentials = false // 身份验证信息 axios.defaults.auth = { uname: 'doubi', pwd: '123'}
2.4 拦截器
2.4.1 请求拦截
- 功能:
- 请求体数据过滤操作:如提交接收过程中转换JSON格式
- 显示给用户的动画:请求过程中
- 请求头检查或修改:例如:token
- 写法:
instance.interceptors.request.use(config => { // 用户操作语句 console.log(config) // 此语句必须要有,否则,发出的请求就在这里被拦截掉了 return config // 有错误可以在此处做细化显示处理 }, err => { console.log(err) } )
2.4.2 响应拦截
- 功能:
- 预处理:对服务器返回的响应数据进行预处理
- 动画:关闭响应动画,进入页面
- 写法:
instance.interceptors.response.use(res=> { // 用户操作语句 console.log(res) // 此语句必须要有,否则,服务器响应就在这里被拦截掉了 return res // 有错误可以在此处做细化显示处理 }, err => { console.log(err) } )
三、封装axios
3.1 vue代理
3.1.1 跨域简介
- 跨域攻击:
- 名称:跨站请求伪造,全称Cross-site request forgery,缩写为CSRF/XSRF
- 效果:攻击者盗用了你的身份,以你的名义发送恶意请求
- 示例:当点击图片,会触发页面1的转账动作,银行接收请求,转账
- 同源策略:协议(http),域名(baidu.com),端口(80),有一个不同即为跨域,不包括路由,它是浏览器最核心也最基本的安全功能,跨域是浏览器检测,服务器无跨域限制,token也遵循同源策略,即同源域能且只能读取自己的token
3.1.2 vue跨域
-
vue服务器:vue脚手架中安装了一个服务器(@vue/cli-service ),
npm run serve
命令就是启动这个服务器,显示的网址指向vue服务器 -
vue跨域:vue项目通过axios向后端发起请求的域名与vue服务器的域名不同触发跨域(参照跨域界定)
-
vue代理服务器工作流程:
-
vue脚手架启动一个代理服务器(配置文件vue.config.js)
-
axios的所有请求会被转发到vue代理服务器并提取请求头中的路由做匹配
a. 若路由符合匹配规则
----代理服务器替用户向后端发请求
----代理服务器将后端响应(响应头、响应体)处理后返给浏览器b. 若路由不符合匹配规则
----请求被发送到vue服务器
----响应被直接返回给浏览器
注意:此服务器仅在开发环境使用,生产环境跨域可用nginx反向代理,即
proxy_pass
解决 -
3.1.3 代理服务器
脚手架的代理服务器
- 位置:新建vue.config.js,目录中跟/src为同级,注意修改完这个文件要重启vue项目
- 文件代码
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, // 代理服务器专项设置 devServer: { // 设置被代理的域名:向此域名发送请求,先进行proxy路由匹配,若匹配,则代理, // 若不匹配,则仍按http://localhost:port进行请求 host: "localhost", // 设置被代理的端口 port: 8080, // 被代理是否为https协议 https: false, // 代理设置:可配置多个代理路由匹配 proxy: { // 此为全字符匹配(也可用正则)/后端在本地:当端口后面紧跟的路由为这个字符串,执行大括号 "/api/0.0": { // 后端域名及端口:即localhost:8080被转发到本地localhost:8000端口,配合下面开关 target: "http://127.0.0.1:8000", // 开启websocket支持 ws: true, // 是否为https secure: false, // 开启域名改写:即将host:port修改为target,否则不修改 changeOrigin: true, // 路由改写:将路由"/api/1.0"重写为"空" pathRewrite: { '^/api/0.0': '' } }, // 此为全字符匹配(也可用正则)/后端在远程:当端口后面紧跟的路由为这个字符串,执行大括号 "/api/1.0": { // 后端域名及端口:即localhost:8080被转发到www.fastmock.site:443(https默认443端口) target: "https://www.fastmock.site", // 开启websocket支持 ws: true, // 是否为https secure: false, // 开启域名改写:即将host:port修改为target,否则不修改 changeOrigin: true, // 路由改写:将位于开头(正则符号^)的路由"/api/1.0"重写为'/mock/.../api' pathRewrite: { '^/api/1.0': '/mock/9facd81062c6429b00506eec7517a4e6/api' } } } } })
vite的代理服务器
- 位置:编辑vite.config.js,目录中跟/src为同级,注意修改完这个文件保存立即生效,不需重启
- 文件代码
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
// 用来解决@路径问题:时好时坏,待更新
resolve: { alias: { "@": path.resolve(__dirname, "./src") } },
plugins: [vue()],
server: {
// 开启局域网访问
host: "0.0.0.0",
// 开启代理
proxy: {
// 简单匹配本地
"^/api/0.0": {
target: "http://localhost:8000",
changeOrigin: true,
// "/"符号要转义
rewrite: (path) => path.replace(/^\/api\/0.0/, ""),
},
// 正则表达式写法
"^/api/1.0/.*": {
target: "https://www.fastmock.site",
changeOrigin: true,
rewrite: (path) =>
path.replace(
/^\/api\/1.0/,
"/mock/9facd81062c6429b00506eec7517a4e6/api"
),
},
},
},
});
3.2 封装代码
-
文件组织
-
各文件说明
- request.ts:
- 特性:最底层,封装axios实例,基本固定
- 功能:包含公共的请求配置信息、请求及响应拦截,返回axios对象
- http.ts:
- 特性:中层,加载request.ts文件,固定
- 功能:封装get\post\put\patch\delete请求,返回axios对象
- api文件夹:
- 特性:上层,加载http.ts文件,变动
- 功能:封装业务请求,文件夹内按业务逻辑再分文件,返回axios对象
js文件:解释型编程语言,会按顺序从js文件头到文件尾按顺序依次执行语句
- request.ts:
3.2.1 封装axios实例—request.ts
- 代码部分
import axios from "axios"; // 1.父类Axios==》派生类AxiosInstance==》派生类AxiosStatic(别名axios) // create函数为AxiosStatic类方法,接收属性配置对象:AxiosRequestConfig对象, // 对象限制即有固定的属性名称和类型限定,详见下面AxiosRequestConfig对象接口源码 const service = axios.create({ // 属性配置项详:https://github.com/axios/axios#axios-api // 设置默认请求域名及端口 baseURL: "http://localhost:8080", // 超时时间:单位是ms,请求超过此时间仍无反应报错 timeout: 3 * 1000, }); // 2.请求拦截器:发请求前做的一些处理,如配置请求头, // 设置token,设置loading等,按需添加 service.interceptors.request.use( (request) => { request.headers = { // 请求头键值对 }; // 返回请求配置信息 return request; }, (error) => { // 请求被拒的处理 Promise.reject(error); } ); // 3.响应拦截器:接收到后端响应后的公有处理 service.interceptors.response.use( (response) => { // 处理后必须返回,否则响应中断 return response; }, // 异常处理 (error) => { return Promise.resolve(error.response); } ); //4.导出axios实例给http.ts使用 export default service;
- AxiosRequestConfig对象接口源码
export interface AxiosRequestConfig<D = any> { url?: string; method?: Method; baseURL?: string; transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[]; transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[]; headers?: AxiosRequestHeaders; params?: any; paramsSerializer?: (params: any) => string; data?: D; timeout?: number; timeoutErrorMessage?: string; withCredentials?: boolean; adapter?: AxiosAdapter; auth?: AxiosBasicCredentials; responseType?: ResponseType; responseEncoding?: responseEncoding | string; xsrfCookieName?: string; xsrfHeaderName?: string; onUploadProgress?: (progressEvent: any) => void; onDownloadProgress?: (progressEvent: any) => void; maxContentLength?: number; validateStatus?: ((status: number) => boolean) | null; maxBodyLength?: number; maxRedirects?: number; socketPath?: string | null; httpAgent?: any; httpsAgent?: any; proxy?: AxiosProxyConfig | false; cancelToken?: CancelToken; decompress?: boolean; transitional?: TransitionalOptions; signal?: AbortSignal; insecureHTTPParser?: boolean; }
- jwt身份认证
service.interceptors.request.use( (request) => { // 在请求体中添加数据方法,...为js解构语法 // request.data请求体,request.headers请求头 // let name = {name: "duke"}; // request.data = {...request.data, name}; request.headers = { // 功能:获取响应的jwtToken值,放到请求头中,as为ts断言语法 jwtToken: localStorage.getItem("jwtToken") as string, }; return request; }, (error) => {Promise.reject(error);} ); service.interceptors.response.use( (response) => { // 功能:获取到后端返回的jwtToken值,如果有值就在localStorage中设置, // &&运算符:第一个语句为true,才进行第二个语句 response.data["jwtToken"] && localStorage.setItem("jwtToken", response.data["jwtToken"]); // 处理后必须返回,否则响应中断 return response; }, // 异常处理 (error) => { return Promise.resolve(error.response); } );
后端:DRF后端实现传送门
3.2.2 封装请求—http.js
- 代码部分
import request from "./request"; // 限定下面config类型 import { AxiosRequestConfig } from "axios"; // Content-Type标头:告诉后端发送过去的请求体的内容类型 // 常用如下: // application/json:json格式 // multipart/form-data: 二进制流数据(如常见的文件上传下载) // application/x-www-form-urlencoded:form表单数据key/value格式 // 创建http对象 const http = { // 查操作(网址,参数 均由顶层组件传入) get(url: string, params?: any) { // 创建AxiosRequestConfig对象,属性名参照上面的对象接口源码 const config: AxiosRequestConfig = { headers: { 'Content-Type': 'application/json' }, method: "get", url: url, }; // if语句:调用处若传入params就设置,没传就不设置 // get请求是设置params,其他请求是设置data,params是以?将参数追加在路由后面 // 如http:127.0.0.1:9999/test/?id=3, if (params) config.params= params; // 调用request.ts中的service,即AxiosInstance的 // (config: AxiosRequestConfig): AxiosPromise;方法 // 即return返回的是一个promise return request(config); }, // 增操作 post(url: string, params: any) { const config: AxiosRequestConfig = { headers: { 'Content-Type': 'application/json' }, method: "post", url: url, }; if (params) config.data = params; return request(config); }, // 改操作(每个参数都必须传) put(url: string, params: any) { const config: AxiosRequestConfig = { headers: { 'Content-Type': 'application/json' }, method: "put", url: url, }; if (params) config.data = params; return request(config); }, // 改操作(至少传一个参数即可) patch(url: string, params: any) { const config: AxiosRequestConfig = { headers: { 'Content-Type': 'application/json' }, method: "patch", url: url, }; if (params) config.data = params; return request(config); }, // 删操作:restful规则,路由指定删除的项, // 如http://localhost/projects/2,即删除projects表的id=2记录 delete(url: string) { const config: AxiosRequestConfig = { headers: { 'Content-Type': 'application/json' }, method: "delete", url: url, }; return request(config); }, // 传文件:FormData格式,后续框架篇会使用 file_post(url: string, fileparams: FormData) { const config: AxiosRequestConfig = { headers: { 'Content-Type': 'multipart/form-data' }, method: 'post', url: url, }; if (fileparams) config.data = fileparams; return api(config); }, }; // 导出给上层API业务封装使用 export default http;
3.2.3 封装业务—api_stu.ts
- 功能:此处封装的重点根据业务逻辑组合路由与方法,需熟悉restful路由规则
- 位置:/src/utils/api/api_stu.ts
- 代码
import http from "@/utils/http"; // restful路由版本控制:路由前缀,利于后续更新版本后,旧版本依旧可以访问 let version = "/api/0.0"; // 用于put/patch/delete操作,明确操作对象id let id = null; // 导出全部给vue组件调用 export default { // restful路由:获得stu表中全部数据,注意路由末尾/ getStuListAPI(params?: any) { // 注意这里不是单引号,tab键上面那个键的点,${}为js取变量值方法 // 以下写法均为路由拼接 // return返回的是个promise,以下return的都是 return http.get(version + "/stu/", params); }, // restful路由:在stu表末尾新增一条记录,注意路由末尾/ postStuAPI(params: any) { return http.post(version + "/stu/", params); }, // restful路由:修改stu表指定记录,传入记录全部字段,注意路由末尾/ putStuAPI(params: any) { // 组合路由中所需要的目标记录的id值 id = params.id; return http.put(version + "/stu/" + id + "/", params); }, // restful路由:修改stu表指定记录,传入记录局部字段,注意路由末尾/ patchStuAPI(params: any) { id = params.id; return http.patch(version + "/stu/" + id + "/", params); }, // restful路由:删除stu表指定记录,注意路由末尾/ deleteStuAPI(params: number) { id = params; return http.delete(version + "/stu/" + id + "/"); }, };
3.2.4 组件调用/views/home.vue
-
效果图
-
代码
- html部分
<template> <div class="Home"> <h1>学生表</h1> <table> <thead> <tr> <th>学生序号</th> <th>生日</th> <th>身高</th> <th>性别</th> </tr> </thead> <tbody v-for="item in stus" :key="item.id"> <tr> <td>{{ item.id }}</td> <td>{{ item.birthday }}</td> <td>{{ item.height }}</td> <td>{{ item.gender }}</td> </tr> </tbody> </table> <div> <h1>测试按钮</h1> <button @click="post_test">测试post</button> <button @click="put_test">测试put</button> <button @click="patch_test">测试patch</button> <button @click="delete_test">测试delete</button> </div> </div> </template>
- ts部分(重点)
<script lang="ts"> import { defineComponent, reactive, ref } from "vue"; // 调用接口文件 import Stu from "@/utils/api/api_stu"; export default defineComponent({ setup() { name: "home"; // 定义对象格式 interface stuForm { id?: number; height?: number; birthday?: string; gender?: number; } // 存储id:给后续put/patch/delete方法测试使用 const id = ref(-1); // 存储get返回的数据:给模板v-for循环使用 let stus: stuForm[] = reactive([{}]); // 定义页面加载前的发送请求函数:向后端请求数据,赋值stus const axiosload = () => { Stu.getStuListAPI() .then((res) => { // 每次设置响应数据前都清空stus stus.length = 0; // 这种方式添加数据,stus不会丢失响应式 stus.push(...res.data); // 每次都取第一个对象的id,若为undefined则为-1 id.value = stus[0].id ? stus[0].id : -1; console.log(id.value); }) .catch((err) => { console.log(err); }); }; // // post方法:此处数据写死了,以后可以前端input标签生成 let stu_post: stuForm = { birthday: "2003-12-27", height: 175.3, gender: 0, }; // 功能:向stu表中新增一条记录,数据为stu_post,触发机制为页面按钮 // async函数:异步的ES6写法,promise思路 async function post_test() { try { // 后端会将成功添加的数据返回前端,此处res接收到返回的数据 // await 表示此句执行完再执行后续,同步操作限定,返回正常则res // 接收.then的赋值,异常则后面catch接收返回 let res = await Stu.postStuAPI(stu_post); // post操作后会返回添加的那条记录,取数据为res.data.id; // 新增完记录,重新请求数据,页面响应式更新数据 axiosload(); // 所有异常处理处 } catch (err) { console.log(err); } } // // put方法:此处数据写死了,以后可以前端input标签生成 let stu_put: stuForm = { birthday: "2002-2-22", height: 180.3, gender: 1, }; // 功能:修改stu表中的记录 function put_test() { // axiosload中将id设为res.data[0].id,想修改哪个就传哪个id stu_put.id = id.value; Stu.putStuAPI(stu_put) .then((res) => { axiosload(); }) .catch((err) => { console.log(err); }); } // // patch方法:跟put方法相近 let stu_patch: stuForm = { height: 159.3 }; function patch_test() { stu_patch.id = id.value; Stu.patchStuAPI(stu_patch) .then((res) => { axiosload(); }) .catch((err) => { console.log(err); }); } // // 删除记录:按id的值选择删哪个,只传id号即可 function delete_test() { Stu.deleteStuAPI(id.value) .then((res) => { axiosload(); }) .catch((err) => { console.log(err); }); } // setup 中即为挂载前生命周期,此处获取数据 axiosload(); // 数据和方法返回给模板使用 return { stus, post_test, put_test, patch_test, delete_test, }; }, }); </script>
- html部分
3.2.5 异步、同步、执行顺序
- 定义
- 异步:前置程序的执行不影响后续程序语句的执行,即后续程序语句有可能先于前置程序语句执行完毕
- 同步:与异步相反,即程序按照书写顺序严格的一行一行执行
- 举个例子:假如你打电话去书店订书, 老板说我查查, 你不挂电话在等待, 老板把查到的结果告诉你, 这是同步;如果老板说我查查, 回头告诉你, 你把电话挂了, 这是异步
- axios:axios为基于promise的http库,属于异步语句,故promise语句后的语句有可能在其执行完毕之前执行完毕
- async语句:
- 标准写法:
async 函数名(){try{}catch(err){}}
- catch语句:catch统一处理异常输出,即try里有一个error即退出,并执行catch语句
- 优势:适合封装一批请求逻辑,并通过await语句控制执行先后顺序
- 标准写法:
- await语句:
- 位置:必须在async语句的try语句内
- 功能:强制同步,有此关键字,await后续语句必须等待此语句执行完毕后才可开始执行
- 写法:
let 变量名 = await promise语句
,其中promise语句必须可以.then与.catch,then返回给变量名接收,catch返回给async的catch接收
- 执行顺序
跳转至vue总篇目录