guoyu/fronted_uniapp/fronted_uniapp/src/utils/request.js

400 lines
14 KiB
JavaScript
Raw Normal View History

2025-12-03 18:58:36 +08:00
import config from './config.js'
/**
* 网络请求封装
*/
class Request {
constructor() {
// 安全地获取配置
this.timeout = (config && config.REQUEST_TIMEOUT) ? config.REQUEST_TIMEOUT : 30000
// 重试配置
this.maxRetries = 2 // 最大重试次数
this.retryDelay = 1000 // 重试延迟(毫秒)
// 调试信息(生产环境关闭)
// if (process.env.NODE_ENV === 'development') {
// console.log('Request initialized')
// }
}
/**
* 获取当前baseURL动态获取支持运行时切换
*/
getBaseURL() {
try {
// 每次请求时动态获取服务器配置
let serverHost = 'localhost'
let serverPort = 8080
// 安全地获取服务器配置
if (config && typeof config.getServerConfig === 'function') {
const serverConfig = config.getServerConfig()
if (serverConfig) {
serverHost = serverConfig.serverHost || serverHost
serverPort = serverConfig.serverPort || serverPort
}
} else {
// 如果config.getServerConfig不可用尝试直接从存储读取
try {
const storedHost = uni.getStorageSync('server_host')
const storedPort = uni.getStorageSync('server_port')
if (storedHost) serverHost = storedHost
if (storedPort) serverPort = parseInt(storedPort) || serverPort
} catch (e) {
console.warn('读取服务器配置失败,使用默认值:', e)
}
}
let baseURL = `http://${serverHost}:${serverPort}`
// #ifdef H5
// H5环境如果访问的是localhost使用Vite代理
if (typeof window !== 'undefined' && window.location) {
const hostname = window.location.hostname
if (hostname === 'localhost' || hostname === '192.168.137.1') {
2025-12-03 18:58:36 +08:00
// H5环境使用后端服务器地址不通过Vite代理
// 因为Vite代理只配置了/api和/ws其他路径需要直接访问后端
return baseURL
}
}
// #endif
// #ifdef APP-PLUS
// App环境如果使用localhost给出提示
if (serverHost === 'localhost' || serverHost === '127.0.0.1') {
2025-12-03 18:58:36 +08:00
console.error('❌ App环境无法使用localhost请配置电脑的局域网IP地址')
console.error('❌ 配置方法uni.setStorageSync("server_host", "你的电脑IP")')
console.error('❌ 例如uni.setStorageSync("server_host", "192.168.137.1")')
2025-12-03 18:58:36 +08:00
}
// #endif
return baseURL
} catch (error) {
console.error('获取baseURL失败:', error)
// 返回默认值(本地开发服务器)
return 'http://192.168.137.1:30091'
2025-12-03 18:58:36 +08:00
}
}
/**
* 请求拦截器
*/
interceptRequest(requestConfig) {
// 检查是否需要token注册接口等不需要token
const isToken = requestConfig.header && requestConfig.header.isToken === false
// 添加tokenRuoYi使用Authorization Bearer格式
const token = uni.getStorageSync('token')
if (token && !isToken) {
requestConfig.header = requestConfig.header || {}
// RuoYi使用Authorization Bearer格式
requestConfig.header['Authorization'] = `Bearer ${token}`
}
// 设置请求头
requestConfig.header = requestConfig.header || {}
requestConfig.header['Content-Type'] = requestConfig.header['Content-Type'] || 'application/json'
// 删除isToken标记不发送到服务器
if (requestConfig.header.isToken !== undefined) {
delete requestConfig.header.isToken
}
return requestConfig
}
/**
* 响应拦截器
*/
interceptResponse(response) {
return new Promise((resolve, reject) => {
const { statusCode, data } = response
// HTTP状态码检查
if (statusCode !== 200) {
reject(new Error(`请求失败: ${statusCode}`))
return
}
// 处理响应数据(可能是字符串需要解析)
let responseData = data
if (typeof data === 'string') {
try {
responseData = JSON.parse(data)
} catch (e) {
reject(new Error('响应数据解析失败'))
return
}
}
// 业务状态码检查RuoYi使用code字段
if (responseData.code === 401) {
// Token过期清除登录信息并跳转到登录页
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
uni.reLaunch({
url: '/pages/login/login'
})
reject(new Error('登录已过期,请重新登录'))
return
}
// RuoYi响应格式{ code: 200, msg: "操作成功", data: {...} }
if (responseData.code !== 200) {
reject(new Error(responseData.msg || responseData.message || '请求失败'))
return
}
// 返回整个响应对象包含code、msg、data等
resolve(responseData)
})
}
/**
* 判断错误是否可重试
*/
isRetryableError(error) {
if (!error || !error.errMsg) return false
const errMsg = error.errMsg.toLowerCase()
// 可重试的错误类型
const retryableErrors = [
'unexpected end of stream',
'abort',
'timeout',
'network',
'connection',
'fail'
]
return retryableErrors.some(keyword => errMsg.includes(keyword))
}
/**
* 通用请求方法带重试机制
*/
request(options, retryCount = 0) {
return new Promise((resolve, reject) => {
try {
// 动态获取baseURL支持运行时切换服务器地址
const baseURL = this.getBaseURL()
// 处理URL如果options.url已经是完整URL则不拼接baseURL
let requestUrl = options.url
if (!requestUrl || typeof requestUrl !== 'string') {
reject(new Error('请求URL无效'))
return
}
if (!requestUrl.startsWith('http://') && !requestUrl.startsWith('https://')) {
requestUrl = baseURL + (requestUrl.startsWith('/') ? requestUrl : '/' + requestUrl)
}
// 请求配置
const requestConfig = {
url: requestUrl,
method: options.method || 'GET',
data: options.data || {},
header: options.header || {},
timeout: this.timeout,
...options
}
// 覆盖url确保使用处理后的URL
requestConfig.url = requestUrl
// 请求拦截
const interceptedConfig = this.interceptRequest(requestConfig)
// 调试日志(生产环境关闭)
// if (process.env.NODE_ENV === 'development') {
// if (retryCount > 0) {
// console.log(`🔄 重试请求 (${retryCount}/${this.maxRetries}):`, requestUrl)
// } else {
// console.log('发送请求:', {
// url: requestUrl,
// method: requestConfig.method,
// data: requestConfig.data,
// header: interceptedConfig.header
// })
// }
// }
// 发送请求
uni.request({
...interceptedConfig,
success: (response) => {
// 调试日志(生产环境关闭)
// if (process.env.NODE_ENV === 'development') {
// console.log('请求成功:', response)
// }
this.interceptResponse(response)
.then(resolve)
.catch(reject)
},
fail: (error) => {
// 调试日志
console.error('请求失败:', error)
// 判断是否可重试
if (retryCount < this.maxRetries && this.isRetryableError(error)) {
console.log(`⚠️ 请求失败,将在 ${this.retryDelay / 1000} 秒后重试...`)
setTimeout(() => {
// 递归重试
this.request(options, retryCount + 1)
.then(resolve)
.catch(reject)
}, this.retryDelay)
} else {
// 不可重试或已达到最大重试次数
let errorMessage = '网络请求失败'
if (error.errMsg) {
if (error.errMsg.includes('unexpected end of stream')) {
errorMessage = '连接中断,请检查网络或服务器状态'
} else if (error.errMsg.includes('timeout')) {
errorMessage = '请求超时,请检查网络连接'
} else if (error.errMsg.includes('abort')) {
errorMessage = '请求被中断,请重试'
} else {
errorMessage = error.errMsg
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
})
reject(error)
}
}
})
} catch (error) {
console.error('request() 方法执行失败:', error)
reject(error)
}
})
}
/**
* GET请求
*/
get(url, params = {}, options = {}) {
try {
return this.request({
url,
method: 'GET',
data: params,
...options
})
} catch (error) {
console.error('request.get() 调用失败:', error)
return Promise.reject(error)
}
}
/**
* POST请求
*/
post(url, data = {}, options = {}) {
// 调试日志(生产环境关闭)
// if (process.env.NODE_ENV === 'development') {
// console.log('POST请求:', url, '数据:', data)
// }
return this.request({
url,
method: 'POST',
data: data,
...options
})
}
/**
* PUT请求
*/
put(url, data = {}, options = {}) {
return this.request({
url,
method: 'PUT',
data,
...options
})
}
/**
* DELETE请求
*/
delete(url, data = {}, options = {}) {
return this.request({
url,
method: 'DELETE',
data,
...options
})
}
/**
* 文件上传
*/
upload(url, filePath, formData = {}, options = {}) {
return new Promise((resolve, reject) => {
const token = uni.getStorageSync('token')
// 动态获取baseURL支持运行时切换服务器地址
const baseURL = this.getBaseURL()
// 处理URL
let uploadUrl = url
if (!uploadUrl.startsWith('http://') && !uploadUrl.startsWith('https://')) {
uploadUrl = baseURL + (uploadUrl.startsWith('/') ? uploadUrl : '/' + uploadUrl)
}
uni.uploadFile({
url: uploadUrl,
filePath: filePath,
name: options.name || 'file',
formData: formData,
header: {
'Authorization': token ? `Bearer ${token}` : '' // RuoYi使用Authorization Bearer格式
},
success: (response) => {
try {
const data = JSON.parse(response.data)
if (data.code === 200) {
resolve(data)
} else {
reject(new Error(data.message || '上传失败'))
}
} catch (e) {
reject(new Error('响应解析失败'))
}
},
fail: (error) => {
uni.showToast({
title: '上传失败',
icon: 'none'
})
reject(error)
}
})
})
}
}
// 创建实例并导出
const request = new Request()
// 确保所有方法都正确定义(防止编译优化导致的问题)
if (typeof request.get !== 'function') {
console.error('❌ request.get 方法不可用!')
}
if (typeof request.post !== 'function') {
console.error('❌ request.post 方法不可用!')
}
if (typeof request.request !== 'function') {
console.error('❌ request.request 方法不可用!')
}
export default request