HomeLease/utils/request.js
2025-08-20 11:45:42 +08:00

820 lines
22 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 { getTempToken, shouldUseTempToken, getAppId } from '@/config/dev.js'
import {
showLoading,
hideLoading,
forceHideLoading,
initGlobalLoadingManager,
config as loadingConfig,
getLoadingStatus,
setLoadingConfig,
getLoadingConfig,
showLoadingWithDelay,
hideLoadingWithDelay,
AutoLoadingManager,
} from './loading-manager.js'
// 环境配置
const ENV_CONFIG = {
develop: {
// 开发环境
// baseUrl: 'http://192.168.2.136:4501',
baseUrl: 'http://192.168.2.143:4601',
appId: 1, // TODO: 根据实际后端配置调整
},
trial: {
// 体验版
baseUrl: 'http://192.168.2.143:4601',
appId: 1, // TODO: 根据实际后端配置调整
},
release: {
// 正式版
baseUrl: 'http://192.168.2.143:4601',
appId: 1, // TODO: 根据实际后端配置调整
},
}
/**
* 清理token移除无效字符
* @param {string} token - 原始token
* @returns {string} 清理后的token
*/
function cleanToken(token) {
if (!token) return ''
// 移除换行符、空格等无效字符
return token.trim().replace(/[\r\n\s]/g, '')
}
// 获取当前环境配置
const getCurrentConfig = () => {
try {
const { envVersion } = wx.getAccountInfoSync().miniProgram
console.log('当前环境:', envVersion)
const envConfig = ENV_CONFIG[envVersion] || ENV_CONFIG.release
// 确保配置对象包含所有必要属性
return {
baseUrl: envConfig.baseUrl,
appId: envConfig.appId || 1, // 确保appId有默认值
}
} catch (error) {
console.warn('获取环境失败,默认使用正式环境:', error)
const fallbackConfig = ENV_CONFIG.release
return {
baseUrl: fallbackConfig.baseUrl,
appId: fallbackConfig.appId || 1, // 确保appId有默认值
}
}
}
const config = getCurrentConfig()
const BASE_URL = config.baseUrl
// 调试信息
console.log('HTTP配置:', {
baseUrl: BASE_URL,
config: config,
})
/**
* 获取请求头
* @param {Object} customHeader - 自定义请求头
* @returns {Object} 请求头对象
*/
function getRequestHeaders(customHeader = {}) {
let token = uni.getStorageSync('token')
// 开发环境使用临时token
if (shouldUseTempToken() && !token) {
token = getTempToken()
}
// 清理token
token = cleanToken(token)
let authorization = ''
if (token) {
// 平台差异化处理
// #ifdef H5
authorization = `Bearer ${token}`
// #endif
// #ifndef H5
authorization = token
// #endif
}
const headers = {
'Content-Type': 'application/json;charset=UTF-8',
...customHeader,
}
// 只有在有token时才添加Authorization头部
if (authorization) {
headers.Authorization = authorization
}
return headers
}
/**
* 处理响应错误
* @param {Object} res - 响应对象
* @param {Function} reject - Promise reject函数
*/
function handleResponseError(res, reject, options = {}) {
// 先清除loading状态
if (options.showLoading !== false) {
hideLoading()
}
const errorMap = {
401: {
title: '登录已过期,请重新登录',
action: () => {
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/login',
})
}, 1500)
},
},
403: {
title: '权限不足',
action: () => {},
},
404: {
title: '请求的资源不存在',
action: () => {},
},
500: {
title: '服务器错误',
action: () => {},
},
}
const error = errorMap[res.statusCode] || {
title: res.data?.msg || '请求失败',
action: () => {},
}
// 显示错误提示
uni.showToast({
title: error.title,
icon: 'none',
duration: 2000,
})
// 执行错误处理动作
error.action()
reject(new Error(error.title))
}
// Loading相关函数已从loading-manager.js导入
/**
* 统一请求方法
* @param {Object} options - 请求配置
* @param {string} options.url - 请求地址
* @param {string} options.method - 请求方法
* @param {Object} options.params - 查询参数
* @param {Object} options.data - 请求体数据
* @param {Object} options.header - 请求头
* @param {number} options.timeout - 超时时间
* @param {boolean} options.showLoading - 是否显示加载状态默认true
* @param {string} options.loadingText - 加载提示文字
* @param {boolean} options.noToken - 是否需要token
* @returns {Promise} 返回请求结果
*/
export function request(options = {}) {
return new Promise((resolve, reject) => {
// 获取token优先使用本地存储的token如果没有则使用临时token
const localToken = uni.getStorageSync('token')
let token = localToken
// 如果本地没有token且启用了临时token则使用临时token
if (!token && shouldUseTempToken() && !options.noToken) {
token = getTempToken()
console.log('使用临时token进行开发测试')
}
// 验证URL格式
if (!options.url || typeof options.url !== 'string') {
reject(new Error('无效的URL'))
return
}
// 确保URL以/开头
const url = options.url.startsWith('/') ? options.url : '/' + options.url
// 构建请求配置
const requestOptions = {
url: BASE_URL + url,
method: options.method || 'GET',
header: getRequestHeaders(options.header),
timeout: options.timeout || 60000, // 默认60秒超时
success: res => {
// 隐藏加载状态
if (options.showLoading !== false) {
hideLoading()
}
// 请求成功处理
if (res.statusCode === 200) {
resolve(res.data)
} else {
// 处理错误响应
handleResponseError(res, reject, options)
}
},
fail: err => {
// 隐藏加载状态
if (options.showLoading !== false) {
hideLoading()
}
// 请求失败处理
console.error('请求失败:', {
error: err,
url: requestOptions.url,
method: requestOptions.method,
baseUrl: BASE_URL,
originalUrl: options.url,
})
// 网络错误处理
let errorMessage = '网络错误'
if (err.errMsg) {
if (err.errMsg.includes('timeout')) {
errorMessage = '请求超时'
} else if (err.errMsg.includes('fail')) {
errorMessage = '网络连接失败'
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 2000,
})
reject(err)
},
}
// 特殊接口处理不需要token的接口
const noTokenUrls = ['/wxLogin', '/user/login']
if (noTokenUrls.includes(url) || options.noToken) {
delete requestOptions.header.Authorization
console.log('跳过token验证的接口:', url)
}
// 处理请求参数
if (options.params && Object.keys(options.params).length > 0) {
// 对于GET请求或明确指定使用查询参数的请求params作为查询参数
if (requestOptions.method === 'GET' || options.useQueryParams) {
const queryString = Object.keys(options.params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(options.params[key])}`)
.join('&')
requestOptions.url += (requestOptions.url.includes('?') ? '&' : '?') + queryString
} else {
// 对于其他请求方法params作为请求体数据
requestOptions.data = { ...options.params }
}
} else if (options.data && Object.keys(options.data).length > 0) {
requestOptions.data = { ...options.data }
} else {
// 如果既没有params也没有data初始化为空对象
requestOptions.data = {}
}
// 自动添加appId到所有请求参数中除非明确指定不添加
try {
if (!options.noAppId && requestOptions.data && !requestOptions.data.appId) {
const appId = getCurrentAppId()
requestOptions.data.appId = appId
console.log('自动添加appId:', appId, '到请求:', requestOptions.url)
}
} catch (error) {
console.error('添加appId时出错:', error)
// 确保即使出错也有appId
if (requestOptions.data) {
requestOptions.data.appId = 1
}
}
// 发起请求
console.log('发起请求:', {
url: requestOptions.url,
method: requestOptions.method,
header: requestOptions.header,
data: requestOptions.data,
timeout: requestOptions.timeout,
baseUrl: BASE_URL,
useQueryParams: options.useQueryParams,
hasParams: !!(options.params && Object.keys(options.params).length > 0),
})
// 显示loading默认显示但减少延迟
if (options.showLoading !== false) {
showLoading(options.loadingText || loadingConfig.loadingText)
}
uni.request(requestOptions)
})
}
/**
* GET请求
* @param {string} url - 请求地址
* @param {Object} params - 查询参数
* @param {Object} options - 请求配置
* @returns {Promise} 返回请求结果
*/
export function get(url, params = {}, options = {}) {
return request({
url,
method: 'GET',
params,
...options,
})
}
/**
* POST请求
* @param {string} url - 请求地址
* @param {Object} data - 请求体数据
* @param {Object} options - 请求配置
* @returns {Promise} 返回请求结果
*/
export function post(url, data = {}, options = {}) {
return request({
url,
method: 'POST',
data,
...options,
})
}
/**
* PUT请求
* @param {string} url - 请求地址
* @param {Object} data - 请求体数据
* @param {Object} options - 请求配置
* @returns {Promise} 返回请求结果
*/
export function put(url, data = {}, options = {}) {
return request({
url,
method: 'PUT',
data,
...options,
})
}
/**
* DELETE请求
* @param {string} url - 请求地址
* @param {Object} options - 请求配置
* @returns {Promise} 返回请求结果
*/
export function del(url, options = {}) {
return request({
url,
method: 'DELETE',
...options,
})
}
/**
* 获取当前环境的appId
* @returns {number} 当前环境的appId
*/
export function getCurrentAppId() {
try {
return config.appId || getAppId() || 1
} catch (error) {
console.error('获取appId失败使用默认值:', error)
return 1
}
}
/**
* 设置请求配置
* @param {Object} newConfig - 新的配置
*/
export function setRequestConfig(newConfig) {
Object.assign(config, newConfig)
console.log('更新请求配置:', config)
}
/**
* 获取请求配置
* @returns {Object} 当前配置
*/
export function getRequestConfig() {
return { ...config }
}
/**
* 清除token
*/
export function clearToken() {
uni.removeStorageSync('token')
console.log('Token已清除')
}
/**
* 设置token
* @param {string} token - token值
*/
export function setToken(token) {
uni.setStorageSync('token', token)
console.log('Token已设置')
}
/**
* 获取token
* @returns {string} token值
*/
export function getToken() {
return uni.getStorageSync('token')
}
// 导出loading相关函数作为统一入口
export {
// 基础loading函数
showLoading,
hideLoading,
forceHideLoading,
// 高级loading函数
showLoadingWithDelay,
hideLoadingWithDelay,
// 状态和配置管理
getLoadingStatus,
setLoadingConfig,
getLoadingConfig,
// 全局初始化
initGlobalLoadingManager,
// 自动loading管理器类
AutoLoadingManager,
}
/**
* 文件上传方法支持POST
* @param {string} url - 上传地址
* @param {string} filePath - 文件路径
* @param {string} name - 文件字段名
* @param {Object} formData - 额外的表单数据
* @param {Object} options - 上传配置
* @returns {Promise} 返回上传结果
*/
export function uploadFile(url, filePath, name = 'file', formData = {}, options = {}) {
return new Promise((resolve, reject) => {
// 获取token
let token = uni.getStorageSync('token')
// 检查token是否存在
if (!token && !options.noToken) {
token = getTempToken()
console.log('使用临时token进行开发测试')
}
// 清理token
token = cleanToken(token)
// 确保URL以/开头
const uploadUrl = url.startsWith('/') ? url : '/' + url
// 构建请求头
let authorization = ''
if (token) {
// #ifdef H5
authorization = `Bearer ${token}`
// #endif
// #ifndef H5
authorization = token
// #endif
}
const header = {
...options.header
}
// 只有在有token时才添加Authorization头部
if (authorization) {
header.Authorization = authorization
}
// 移除Content-Type让浏览器自动设置multipart/form-data
console.log('开始文件上传:', {
url: BASE_URL + uploadUrl,
filePath,
name,
hasToken: !!token,
tokenLength: token ? token.length : 0,
header: header
})
uni.uploadFile({
url: BASE_URL + uploadUrl,
filePath: filePath,
name: name,
formData: formData,
header: header,
timeout: options.timeout || 60000,
method: options.method || 'POST',
success: (res) => {
console.log('文件上传响应:', {
statusCode: res.statusCode,
data: res.data,
header: res.header
})
try {
// 解析响应数据
const data = JSON.parse(res.data)
console.log('解析后的响应数据:', data)
if (res.statusCode === 200 && data.code === 200) {
console.log('文件上传成功:', data)
resolve(data)
} else {
// 服务器返回错误
const errorMsg = data.msg || data.message || '上传失败'
console.error('文件上传失败 - 服务器错误:', {
statusCode: res.statusCode,
code: data.code,
message: errorMsg,
data: data
})
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 3000
})
reject(new Error(errorMsg))
}
} catch (parseError) {
console.error('解析响应数据失败:', {
error: parseError,
rawData: res.data,
statusCode: res.statusCode
})
// 如果状态码不是200可能是服务器错误
if (res.statusCode !== 200) {
const errorMsg = `服务器错误 (${res.statusCode})`
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 3000
})
reject(new Error(errorMsg))
} else {
uni.showToast({
title: '响应数据格式错误',
icon: 'none',
duration: 3000
})
reject(new Error('响应数据格式错误'))
}
}
},
fail: (err) => {
console.error('文件上传失败 - 网络错误:', {
error: err,
errMsg: err.errMsg,
statusCode: err.statusCode
})
let errorMessage = '上传失败'
if (err.errMsg) {
if (err.errMsg.includes('timeout')) {
errorMessage = '上传超时,请重试'
} else if (err.errMsg.includes('fail')) {
errorMessage = '网络连接失败,请检查网络'
} else if (err.errMsg.includes('abort')) {
errorMessage = '上传已取消'
} else {
errorMessage = err.errMsg
}
}
// 如果是HTTP错误状态码
if (err.statusCode) {
switch (err.statusCode) {
case 401:
errorMessage = '登录已过期,请重新登录'
break
case 403:
errorMessage = '没有上传权限'
break
case 413:
errorMessage = '文件太大'
break
case 500:
errorMessage = '服务器内部错误'
break
default:
errorMessage = `服务器错误 (${err.statusCode})`
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
})
reject(err)
}
})
})
}
// Loading管理相关函数已从loading-manager.js导入
// 默认导出request函数方便API文件导入
export default request
/**
* PUT方法文件上传用于头像上传等需要PUT方法的场景
* @param {string} url - 上传地址
* @param {string} filePath - 文件路径
* @param {string} name - 文件字段名
* @param {Object} formData - 额外的表单数据
* @param {Object} options - 上传配置
* @returns {Promise} 返回上传结果
*/
export function uploadFileWithPut(url, filePath, name = 'file', formData = {}, options = {}) {
return new Promise((resolve, reject) => {
// 获取token
let token = uni.getStorageSync('token')
// 检查token是否存在
if (!token && !options.noToken) {
token = getTempToken()
console.log('使用临时token进行开发测试')
}
// 清理token
token = cleanToken(token)
// 确保URL以/开头
const uploadUrl = url.startsWith('/') ? url : '/' + url
// 构建请求头
let authorization = ''
if (token) {
// #ifdef H5
authorization = `Bearer ${token}`
// #endif
// #ifndef H5
authorization = token
// #endif
}
const header = {
...options.header
}
// 只有在有token时才添加Authorization头部
if (authorization) {
header.Authorization = authorization
}
console.log('开始PUT文件上传:', {
url: BASE_URL + uploadUrl,
filePath,
name,
hasToken: !!token,
tokenLength: token ? token.length : 0,
header: header
})
// 由于小程序环境不支持手动构建multipart格式的PUT请求
// 我们使用POST方法但添加特殊头部标识这是PUT操作
const postHeader = {
...header,
'X-HTTP-Method-Override': 'PUT' // 告诉服务器这是PUT操作
}
console.log('使用POST方法模拟PUT上传...')
uni.uploadFile({
url: BASE_URL + uploadUrl,
filePath: filePath,
name: name,
formData: formData,
header: postHeader,
timeout: options.timeout || 60000,
method: 'POST',
success: (res) => {
console.log('POST模拟PUT上传响应:', {
statusCode: res.statusCode,
data: res.data,
header: res.header
})
try {
// 解析响应数据
const data = JSON.parse(res.data)
console.log('解析后的响应数据:', data)
if (res.statusCode === 200 && data.code === 200) {
console.log('POST模拟PUT上传成功:', data)
resolve(data)
} else {
// 服务器返回错误
const errorMsg = data.msg || data.message || '上传失败'
console.error('POST模拟PUT上传失败 - 服务器错误:', {
statusCode: res.statusCode,
code: data.code,
message: errorMsg,
data: data
})
// 如果服务器不支持X-HTTP-Method-Override尝试不带这个头部
console.log('尝试不带X-HTTP-Method-Override的POST方法...')
tryPostWithoutOverride()
}
} catch (parseError) {
console.error('解析响应数据失败:', parseError)
// 如果解析失败尝试不带特殊头部的POST方法
tryPostWithoutOverride()
}
},
fail: (err) => {
console.error('POST模拟PUT上传失败:', err)
// 如果POST方法失败尝试不带特殊头部的POST方法
tryPostWithoutOverride()
}
})
// 尝试不带X-HTTP-Method-Override的POST方法
function tryPostWithoutOverride() {
console.log('尝试不带X-HTTP-Method-Override的POST方法...')
uni.uploadFile({
url: BASE_URL + uploadUrl,
filePath: filePath,
name: name,
formData: formData,
header: header,
timeout: options.timeout || 60000,
method: 'POST',
success: (res) => {
console.log('普通POST上传响应:', {
statusCode: res.statusCode,
data: res.data,
header: res.header
})
try {
const data = JSON.parse(res.data)
console.log('解析后的响应数据:', data)
if (res.statusCode === 200 && data.code === 200) {
console.log('普通POST上传成功:', data)
resolve(data)
} else {
const errorMsg = data.msg || data.message || '上传失败'
console.error('普通POST上传失败:', errorMsg)
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 3000
})
reject(new Error(errorMsg))
}
} catch (parseError) {
console.error('解析响应数据失败:', parseError)
reject(new Error('响应数据格式错误'))
}
},
fail: (err) => {
console.error('普通POST上传失败:', err)
// 最终失败,提示用户联系服务器端
const errorMsg = '上传失败服务器只支持PUT方法但小程序环境不支持PUT文件上传。请联系服务器端同时支持POST方法。'
console.error(errorMsg)
uni.showToast({
title: '上传失败,请联系技术支持',
icon: 'none',
duration: 3000
})
reject(new Error(errorMsg))
}
})
}
})
}