预览本文的实现效果:
# gitee
git clone git@gitee.com:cloudyly/dscloudy-admin-single.git
# github
git clone git@github.com:cloudyly/dscloudy-admin-single.git
git checkout 04_Axios
本文主要内容:使用 Axios 进行网络请求,并进行二次封装。
Git 本地仓库切换新分支:
git checkout -b 04_Axios
确认分支:
git branch
1 数据格式
1.1 RESTful 简介
无论是传统的 JSP、Freemarker,还是当下主流的前后端分离模式,几乎所有的应用都有前端和后端两个部分。前端设备除了 PC 浏览器,还有移动端 App、公众号程序、小程序等,前后端之间进行通信就需要有一种统一的机制。 RESTful API 是目前比较成熟的一套 API 设计理论。
REST 全称是 Representational State Transfer 表述性状态转换。按照我最浅层次的理解,RESTful API 就是一种前后端通信 API 的风格,所谓 “表述性状态转换”,最核心包括两个内容:
- 表述性:描述资源,即针对什么资源;
- 状态转换:对资源进行什么操作,如对资源的增删改查,就是资源的状态改变。
传统的 API,通常只使用 GET、POST 请求,在 RESTful 中,会充分利用各种请求。以用户的增删改查为例:
添加用户
- 传统 API: [POST] /addUser
- RESTful API: [POST] /users
根据 ID 删除用户
- 传统 API: [POST] /deleteUserById?id=xxx
- RESTful API:[DELETE] /users/xxx
根据 ID 修改用户
- 传统 API:[POST] /updateIUserById?id=xxx
- RESTful API:[PUT] /users/xxx
根据 ID 查询用户
- 传统 API:[GET] /getUserById?id=xxx
- RESTful API:[GET] /users/xxx
查看用户列表
- 传统 API:[GET] /getUserList
- RESTful API:[GET] /users
RESTful API 充分利用 HTTP 请求的特性,如请求方式、状态码等信息。
1.2 响应参数格式
本系统后台接口尽量按照 RESTful 方式进行开发。常规增删改查与上面示例的 RESTful API 风格一致,只有特殊的操作才会有差异。
关于请求的响应,充分使用 HTTP 的状态码,无论是 HTTP 请求成功与否、还是业务成功与否,都统一汇聚在 HTTP 的状态码中。如果 HTTP 状态码返回 200,表示请求成功并且业务成功,此时响应体中只返回业务数据。如果业务发生异常,则 HTTP 状态码返回 500,响应体返回错误具体信息:
{
"code": "XXXXXX",
"msg": "xxxxxxx"
}
以根据 ID 查询用户为例:
- ID 为空 或 根据 ID 未查询到用户,HTTP 状态码返回 500,响应体返回格式为
{ "code": "BS0001", "msg": "未查询到用户" }
- 查询到用户,HTTP 状态码返回 200,请求体中直接返回用户 JSON。
特别注意:
本系统不像市面上部分设计那样,无论成功与否,都返回 code、msg、data 等一堆参数,然后将业务数据放在 data 中。本系统只要 HTTP 状态码为 200,响应体中只有业务数据,没有多余的字段。其他信息在请求头或响应头中设置。
2 封装 Loading
由于网络请求是一个异步耗时操作,在请求过程中,为了优化用户体验,建议显示 loading 效果,告知用户系统不是没有反应。我使用 element-ui 中提供的 Loading 组件,并对其进行二次封装。
src/common
目录存放通用的 JS 文件(高大上的方式来说,就是存放咱自己开发的类库
),loading 效果属于通用 JS,故在该目录下创建 loading.js
文件,用来控制 loading。
src/common/loading.js
:
import { Loading } from 'element-ui'
import i18n from '@/i18n'
/**
* Loading 组件的实例对象
*/
let loadingInstance = null
/**
* 当前需要显示 loading 的请求数
*/
let loadingRequestCount = 0
/*
* 内部方法:开始显示 loading 框
*/
const beginLoading = () => {
loadingInstance = Loading.service({
lock: true,
text: i18n.t('app.loadingText'),
background: 'rgba(0, 0, 0, 0.1)',
target: document.querySelector('#app'),
customClass: '.ds-loading'
})
}
/*
* 内部方法:结束显示 loading 框
*/
const endLoading = () => {
if (loadingInstance) {
loadingInstance.close()
}
}
/**
* 显示 loading 框
*/
export const showLoading = () => {
if (loadingRequestCount === 0) {
beginLoading()
}
loadingRequestCount++
}
/**
* 关闭 loading 框
*/
export const closeLoading = () => {
if (loadingRequestCount <= 0) {
return
}
loadingRequestCount--
if (loadingRequestCount === 0) {
endLoading()
}
}
由于还没有创建主界面,这里将 loading 框挂在 #app
上。另外,上面使用到了 app.loadingText
语言文件,需要分别在 src/i18n/
目录下的 en.js
和 zh.js
中定义,这里将接下来要用到的网络请求的提示也配置进去:
app: {
appName: '微服务微前端基础框架',
loadingText: '加载中,请稍候...',
requestSendError: '请求失败,请刷新页面后重试',
requestTimeoutError: '请求超时,请稍后重试'
},
app: {
appName: 'Micro Service Micro Front Platform',
loadingText: 'Loading ...',
requestSendError: 'Request Error, Please refresh page and try again',
requestTimeoutError: 'Request Timeout, please try it latter'
},
3 封装 Axios
3.1 安装依赖
npm install --save axios
3.2 定义 axios.js
网络请求也是属于通用 JS,故在该目录下创建 axios.js
文件。该文件主要做五件事:
1、 axios 的设置,如超时时间等;
2、 请求的通用拦截器,如显示 loading,请求头设置 token 等;
3、 响应的通用拦截器,如异常(业务 & 请求)时的错误提示、响应参数的解析等
4、 创建各种请求(get、post、put、delete)
src/common/axios.js
import axios from 'axios'
import { Message } from 'element-ui'
import systemConfig from '@/config'
import * as loading from './loading'
import i18n from '@/i18n'
// 创建 axios 对象
const axiosInstance = axios.create({
timeout: systemConfig.timeout
})
// 显示错误信息
const showError = (msg) => Message.error({
message: msg,
type: 'error',
duration: 3 * 1000
})
// 请求拦截器
axiosInstance.interceptors.request.use(
config => {
// 后面需要设置 token
loading.showLoading()
return config
},
error => {
console.error(error)
loading.closeLoading()
showError(`${i18n.t('app.sendRequestError')}`)
return Promise.reject(error)
}
)
// 响应拦截器
axiosInstance.interceptors.response.use(
response => {
loading.closeLoading()
// HTTP 状态码为 200 表示成功,其他情况均为失败
if (response.status === 200) {
return Promise.resolve(response.data)
}
const respData = response.data
Message.error(`${respData.msg}(${respData.code})`)
return Promise.reject(response.data)
},
error => {
console.error(error)
loading.closeLoading()
const { code, message } = error
if (code === 'ECONNABORTED' || message === 'Network Error') { // 请求超时
showError(`${i18n.t('app.requestTimeoutError')}`)
return Promise.reject(error)
}
if (error.response) {
if (error.response.status === 401) {
// 针对无权限的处理
} else {
showError(`${error.response.data.msg}(${error.response.data.code})`)
}
}
return Promise.reject(error)
}
)
export const createApi = (url, method, data) => {
const config = {}
if (method === 'get') {
config.params = data
} else {
config.data = data || {}
}
return axiosInstance({
url,
method,
...config
})
}
export const get = (url, data) => createApi(url, 'get', data)
export const post = (url, data) => createApi(url, 'post', data)
export const put = (url, data) => createApi(url, 'put', data)
export const del = (url, data) => createApi(url, 'delete', data)
上面导出了 createApi、get、post、put、del 方法,其他地方在使用时,只需要引入这个文件即可:
import { get } from '@/common/axios'
还有一些内容该文件还没有定义,将会在后面的开发中完善进去,主要包括:
1、请求无权限时的处理(界面提示及页面跳转);
2、文件上传下载封装
3、form 表单方式进行提交
4 登录请求
4.1 定义 URL
在 src/config/urls.js
中定义请求路径:core.login
const urls = {
/**
* 核心模块
*/
core: {
login: `${config.host.core}/login`, // 登录
test: `${config.host.core}/test`
},
... // 其他内容省略
}
export default urls
4.2 创建 API
所有发送请求的代码都放在各个 module 的 api 目录中,在 src/modules/core/api
中创建文件 core-api.js
:
import { post } from '@/common/axios'
import urls from '@/config/urls'
/**
* 登录
*/
export const login = param => post(urls.core.login, param)
4.3 调用 API
上一篇文章已经开发了登录界面 src/modules/core/pages/login.vue
,修改点击登录按钮的事件,在表单校验成功后调用接口(后面还需要改造,在 vuex 中发送请求并管理返回的数据)
async onLoginBtnClick () {
const valid = await this.$refs.form.validate()
if (!valid) {
return
}
// 表单校验通过,提交数据
const param = { ...this.loginForm }
const result = await coreApi.login(param)
console.log(result)
}
在浏览器中点击登录按钮进行测试,会提示 Network Error。现在暂时没有提供正确的登录接口地址,主要原因是咱们要做一套 mock 数据,这样便可以不依赖服务器进行开发。下一篇就是如何优雅的设计 mock。
login.vue 界面做了一小个改动,在验证码输入框上加了键盘回车事件 @keyup.enter.native="onLoginBtnClick"
,用户输入验证码后回车,就相当于点击了登录按钮:
<el-input v-model="loginForm.validCode" :placeholder="$t('core.login.validCodePlaceHolder')"
@keyup.enter.native="onLoginBtnClick">
提交代码:
git add .
git cz
[框架开发] Axios封装
合并到 master 分支:
git checkout master
git merge 04_Axios
将本地分支分别全部推送到 Gitee 和 GitHub
git push --all gitee_origin
git push --all github_origin
更多内容请关注我的个人公众号,留言可加我个人微信或交流问题