pet/http/config/request.js

273 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// HTTP请求配置和拦截器
// 基于uView-next的luch-request库实现
import { HTTP_CONFIG, checkApiAuth, AUTH_REQUIRED_APIS } from './config.js'
/**
* 清理认证数据
*/
function clearAuthData() {
console.log('HTTP拦截器: 清理认证数据被调用')
console.trace('调用堆栈:')
uni.removeStorageSync(HTTP_CONFIG.storageKeys.token)
uni.removeStorageSync(HTTP_CONFIG.storageKeys.refreshToken)
uni.removeStorageSync(HTTP_CONFIG.storageKeys.userInfo)
uni.removeStorageSync('loginStep')
uni.removeStorageSync('wxLoginCode')
uni.removeStorageSync('wxUserInfo')
uni.removeStorageSync('loginDate')
}
/**
* 检查接口是否需要强制登录
* @param {String} url 接口URL
* @returns {Boolean} 是否需要强制登录
*/
function checkAuthRequiredApi(url) {
// 移除查询参数,只保留路径
const path = url.split('?')[0]
// 检查是否在需要强制登录的列表中
for (const authApi of AUTH_REQUIRED_APIS) {
// 支持通配符匹配
if (authApi.includes('*')) {
const regex = new RegExp('^' + authApi.replace(/\*/g, '.*') + '$')
if (regex.test(path)) {
return true
}
} else if (path === authApi || path.startsWith(authApi + '/')) {
return true
}
}
return false
}
/**
* 检查用户是否已完成登录
* @returns {Boolean} 是否已完成登录
*/
function isUserLoggedIn() {
const token = uni.getStorageSync(HTTP_CONFIG.storageKeys.token)
const loginStep = uni.getStorageSync('loginStep')
// 检查是否有有效的token和完成的登录流程
return !!(token && (loginStep === 'profile_completed' || loginStep === 'phone_authed'))
}
/**
* 尝试自动登录
* @returns {Promise<Boolean>} 是否登录成功
*/
async function tryAutoLogin() {
try {
return await autoLogin()
} catch (error) {
console.error('自动登录失败:', error)
return false
}
}
/**
* 处理需要鉴权的情况
*/
function handleAuthRequired() {
const loginStep = uni.getStorageSync('loginStep')
// 根据当前登录状态决定跳转页面
let targetPage = '/pages/login/login'
let toastMessage = '请先登录'
switch (loginStep) {
case 'wx_logged':
targetPage = '/pages/auth/phone-auth'
toastMessage = '请完成手机号授权'
break
case 'phone_authed':
case 'phone_skipped':
targetPage = '/pages/profile/user-info?mode=setup'
toastMessage = '请完善个人信息'
break
default:
targetPage = '/pages/login/login'
toastMessage = '请先登录'
}
// 显示提示并跳转
uni.showToast({
title: toastMessage,
icon: 'none',
duration: 1500
})
setTimeout(() => {
uni.navigateTo({ url: targetPage }).catch(() => {
uni.reLaunch({ url: targetPage })
})
}, 1500)
}
/**
* 初始化HTTP配置
* @param {Object} vm Vue实例用于访问vuex等全局状态
*/
export default (vm) => {
// 初始化请求配置
uni.$u.http.setConfig((config) => {
config.baseURL = HTTP_CONFIG.baseURL
config.timeout = HTTP_CONFIG.timeout
config.header = {
'Content-Type': 'application/json;charset=UTF-8'
}
return config
})
// 请求拦截器
uni.$u.http.interceptors.request.use((config) => {
// 初始化请求拦截器时会执行此方法此时data为undefined赋予默认{}
config.data = config.data || {}
config.custom = config.custom || {}
// 自动检查接口是否需要鉴权(如果没有明确指定)
if (config.custom.auth === undefined) {
config.custom.auth = checkApiAuth(config.url)
}
// 检查是否为需要强制登录的接口
const isAuthRequiredApi = checkAuthRequiredApi(config.url)
// 如果是需要强制登录的接口,检查登录状态
if (isAuthRequiredApi) {
if (!isUserLoggedIn()) {
console.log('访问需要鉴权的接口但未完成登录,跳转到登录流程:', config.url)
handleAuthRequired()
return Promise.reject({
message: '需要登录',
code: 'AUTH_REQUIRED',
url: config.url
})
}
}
// 添加Authorization请求头
if (config.custom.auth !== false) {
const token = uni.getStorageSync(HTTP_CONFIG.storageKeys.token)
if (token) {
config.header.Authorization = `Bearer ${token}`
}
}
// 根据custom参数配置是否显示loading
if (config.custom.loading !== false) {
uni.showLoading({
title: config.custom.loadingText || '请稍候...',
mask: true
})
}
return config
}, config => {
// 请求错误处理
// 如果是AUTH_REQUIRED错误不显示loading
if (config.code === 'AUTH_REQUIRED') {
// 不显示loading因为会跳转页面
return Promise.reject(config)
}
return Promise.reject(config)
})
// 响应拦截器
uni.$u.http.interceptors.response.use((response) => {
/* 对响应成功做点什么 可使用async await 做异步操作*/
// 隐藏loading
uni.hideLoading()
const data = response.data
const custom = response.config?.custom
// 统一的响应数据处理
if (data.code !== undefined) {
// 如果服务端返回的状态码不等于0成功则reject()
if (data.code !== 0) {
// 如果没有显式定义custom的toast参数为false的话默认对报错进行toast弹出提示
if (custom?.toast !== false) {
uni.$u.toast(data.message || '请求失败')
}
// 特殊状态码处理
if (data.code === 401) {
// token过期清除本地认证信息并跳转到登录流程
clearAuthData()
handleAuthRequired()
return Promise.reject(data)
}
// 如果需要catch返回则进行reject
if (custom?.catch) {
return Promise.reject(data)
} else {
// 否则返回一个pending中的promise请求不会进入catch中
return new Promise(() => {})
}
}
}
// 返回处理后的数据
return data.data !== undefined ? data.data : data
}, (response) => {
/* 对响应错误做点什么 statusCode !== 200*/
// 隐藏loading
uni.hideLoading()
const custom = response.config?.custom
// 网络错误处理
let errorMessage = '网络错误,请检查网络连接'
if (response.statusCode) {
switch (response.statusCode) {
case 400:
errorMessage = '请求参数错误'
break
case 401:
errorMessage = '未授权,请重新登录'
clearAuthData()
handleAuthRequired()
break
case 403:
errorMessage = '拒绝访问'
break
case 404:
errorMessage = '请求地址不存在'
break
case 500:
errorMessage = '服务器内部错误'
break
case 502:
errorMessage = '网关错误'
break
case 503:
errorMessage = '服务不可用'
break
case 504:
errorMessage = '网关超时'
break
default:
errorMessage = `连接错误${response.statusCode}`
}
}
// 显示错误提示
if (custom?.toast !== false) {
uni.$u.toast(errorMessage)
}
console.error('请求错误:', response)
return Promise.reject(response)
})
}