背景
在电商系统中,一些场景需要使用nuxt服务器端渲染技术,如首页、商品详情页等等,目的是为了弥补vue单页的seo的不足。
基于nuxt项目的api请求需要处理服务器端请求、客户端请求分离,权限拦截,token统一传递等逻辑,本文以javashop电商系统中的代码为例,分享相关经验。
一 先引入
首先创建一个resquest.js,这里面存放的是axios配置以及拦截器,最后导出一个axios对象
import Vue from 'vue'
import axios from 'axios'
import https from 'https'
import { Loading } from 'element-ui'
import { api } from '@/ui-domain'
import Storage from '@/utils/storage'
import checkToken from '@/utils/checkToken'
const qs = require('qs')
二 创建axios实例,并且添加默认的配置
const service = axios.create({
timeout: 8000, // 请求超时时间
baseURL: api.buyer, // 后端API,用于在服务器端进行请求
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
paramsSerializer: params => qs.stringify(params, { arrayFormat: 'repeat' })
})
三 创建request拦截器
配置全屏加载,引入ElementUI库里面的loading;传递uuid,token配置
service.interceptors.request.use(config => {
const { loading } = config
// 如果是put/post请求,用qs.stringify序列化参数
const is_put_post = config.method === 'put' || config.method === 'post' // 请求方式
const is_json = config.headers['Content-Type'] === 'application/json' // 发送数据的格式
const is_file = config.headers['Content-Type'] === 'multipart/form-data'// 发送数据的格式
// 如果是put/post请求,用qs.stringify序列化参数
if (is_put_post && is_json) {
config.data = JSON.stringify(config.data)
}
if (is_put_post && !is_file && !is_json) {
config.data = qs.stringify(config.data, { arrayFormat: 'repeat' })
}
/** 配置全屏加载 */
// process.client 浏览器环境
if (process.client && loading !== false) {
config.loading = Loading.service({
fullscreen: true,
background: 'rgba(255,255,255,.3)',
spinner: 'icon-custom-loading',
lock: false
})
}
// 如果是浏览器环境,请求头需要传递uuid
if (process.client) {
const uuid = Storage.getItem('uuid')
config.headers['uuid'] = uuid
} else {
const { $store } = Vue.prototype.$nuxt
const referer = $store.state.referer
config.headers['Referer'] = referer || api.buyer
}
// 获取访问Token
let accessToken = Storage.getItem('access_token')
if (accessToken && config.needToken) {
config.headers['Authorization'] = accessToken
}
return config
}, error => {
Promise.reject(error)
})
四 创建response拦截器
service.interceptors.response.use(
async response => {
await closeLoading(response) // 关闭loading
return response.data
},
// 错误处理
async error => {
// 如果是服务器端直接返回错误信息
if (process.server) return Promise.reject(error)
await closeLoading(error)
const error_response = error.response || {}
const error_data = error_response.data || {}
// 状态码403登录状态以失效,移除tonken信息,返回登录页
if (error_response.status === 403) {
const { $store, $router, $route } = Vue.prototype.$nuxt
if (!Storage.getItem('refresh_token')) return
$store.dispatch('cart/cleanCartStoreAction')
$store.dispatch('user/removeUserAction')
$store.dispatch('user/removeAccessTokenAction')
$store.dispatch('user/removeRefreshTokenAction')
$router.push(`/login?forward=${$route.fullPath}`)
return Promise.reject(error)
}
if (error.config.message !== false) {
let _message = error.code === 'ECONNABORTED' ? '连接超时,请稍候再试!' : '网络错误,请稍后再试!'
Vue.prototype.$message.error(error_data.message || _message)
}
return Promise.reject(error)
}
)
五 关闭全局加载
Methodconst closeLoading = (target) => {
if (!target.config || !target.config.loading) return true
return new Promise((resolve, reject) => {
setTimeout(() => {
target.config.loading.close()
resolve()
}, 200)
})
}
六 导出Method
创建请求方法枚举字典,所有API的请求方法都在这个枚举字典中查找使用
export const Method = {
GET: 'get',
POST: 'post',
PUT: 'put',
DELETE: 'delete'
}
七 导出resquest
export default function request(options) {
// 如果是服务端或者是请求的刷新token,不需要检查token直接请求。
if (process.server || options.url.indexOf('passport/token') !== -1) {
return service(options)
}
return new Promise((resolve, reject) => {
checkToken(options).then(() => {
service(options).then(resolve).catch(reject)
})
})
}