guoyu/fronted_uniapp/utils/request.js
2025-12-05 23:36:21 +08:00

399 lines
14 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.

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使用localhost通过Vite代理
if (typeof window !== 'undefined' && window.location) {
const hostname = window.location.hostname
if (hostname === 'localhost' || hostname === '127.0.0.1') {
// H5开发环境通过 Vite 代理 /api -> 本地后端
return '/api'
}
}
// #endif
// #ifdef APP-PLUS
// App环境如果使用localhost给出提示
if (serverHost === 'localhost' || serverHost === '127.0.0.1') {
console.error('❌ App环境无法使用localhost请配置电脑的局域网IP地址')
console.error('❌ 配置方法uni.setStorageSync("server_host", "你的电脑IP")')
console.error('❌ 例如uni.setStorageSync("server_host", "192.168.1.155")')
}
// #endif
return baseURL
} catch (error) {
console.error('获取baseURL失败:', error)
// 返回默认值
return 'http://192.168.137.1:30091'
}
}
/**
* 请求拦截器
*/
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