399 lines
14 KiB
JavaScript
399 lines
14 KiB
JavaScript
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.1.8:30091'
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 请求拦截器
|
||
*/
|
||
interceptRequest(requestConfig) {
|
||
// 检查是否需要token(注册接口等不需要token)
|
||
const isToken = requestConfig.header && requestConfig.header.isToken === false
|
||
|
||
// 添加token(RuoYi使用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
|
||
|
||
|