import Taro from '@tarojs/taro' import tokenManager from '../utils/tokenManager' import envConfig, { isDevelopment, getEnvInfo } from '../config/env' // 请求方法类型 export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' // 请求配置接口 export interface RequestConfig { url: string method?: HttpMethod data?: any params?: Record headers?: Record needAuth?: boolean // 是否需要token认证 showLoading?: boolean // 是否显示加载提示 loadingText?: string // 加载提示文本 showToast?: boolean // 是否显示toast } // 响应数据接口 export interface ApiResponse { code: number data: T message: string success: boolean } // HTTP状态码常量 export const HTTP_STATUS = { SUCCESS: 200, UNAUTHORIZED: 401, FORBIDDEN: 403, NOT_FOUND: 404, SERVER_ERROR: 500 } class HttpService { private baseURL: string private timeout: number private enableLog: boolean constructor() { // 使用环境配置 this.baseURL = `${envConfig.apiBaseURL}/api/` this.timeout = envConfig.timeout this.enableLog = envConfig.enableLog // 在开发环境下输出配置信息 if (isDevelopment()) { console.log('🌍 HTTP服务初始化:', { baseURL: this.baseURL, timeout: this.timeout, envInfo: getEnvInfo() }) } } // 构建完整URL private buildUrl(url: string, params?: Record): string { const fullUrl = url.startsWith('http') ? url : `${this.baseURL}${url.startsWith('/') ? url.slice(1) : url}` if (params) { const searchParams = new URLSearchParams() Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { searchParams.append(key, String(value)) } }) const queryString = searchParams.toString() return queryString ? `${fullUrl}?${queryString}` : fullUrl } return fullUrl } // 构建请求头 private buildHeaders(config: RequestConfig): Record { const headers: Record = { 'Content-Type': 'application/json', ...config.headers } // 如果需要认证,添加token if (config.needAuth !== false) { const authHeader = tokenManager.getAuthHeader() Object.assign(headers, authHeader) } return headers } // 日志输出 private log(level: 'info' | 'warn' | 'error', message: string, data?: any) { if (!this.enableLog) return const logMethod = console[level] || console.log const timestamp = new Date().toLocaleTimeString() if (data) { logMethod(`[${timestamp}] HTTP ${level.toUpperCase()}: ${message}`, data) } else { logMethod(`[${timestamp}] HTTP ${level.toUpperCase()}: ${message}`) } } // 处理响应 private handleResponse(response: any): Promise> { return new Promise((resolve, reject) => { const { statusCode, data } = response this.log('info', `响应状态码: ${statusCode}`, { data }) // HTTP状态码检查 if (statusCode !== HTTP_STATUS.SUCCESS) { this.handleHttpError(statusCode) reject(new Error(`HTTP Error: ${statusCode}`)) return } // 业务状态码检查 if (data && typeof data === 'object') { if (data.success === false || (data.code && data.code !== 0 && data.code !== 200)) { this.handleBusinessError(data) reject(new Error(data.message || '请求失败')) return } } resolve(data) }) } // 处理HTTP错误 private handleHttpError(statusCode: number): void { let message = '网络请求失败' switch (statusCode) { case HTTP_STATUS.UNAUTHORIZED: message = '登录已过期,请重新登录' tokenManager.clearTokens() // 可以在这里跳转到登录页面 break case HTTP_STATUS.FORBIDDEN: message = '没有权限访问该资源' break case HTTP_STATUS.NOT_FOUND: message = '请求的资源不存在' break case HTTP_STATUS.SERVER_ERROR: message = '服务器内部错误' break default: message = `请求失败 (${statusCode})` } this.log('error', `HTTP错误 ${statusCode}: ${message}`) Taro.showToast({ title: message, icon: 'none', duration: 2000 }) } // 处理业务错误 private handleBusinessError(data: any): void { const message = data.message || '操作失败' this.log('error', `业务错误: ${message}`, data) Taro.showToast({ title: message, icon: 'none', duration: 2000 }) } getHashParam(key) { const hash = window.location.hash; const queryString = hash.split('?')[1] || ''; const params = new URLSearchParams(queryString); return params.get(key); } // 统一请求方法 async request(config: RequestConfig): Promise> { const { url, method = 'GET', data, params, showLoading = false, loadingText = '请求中...' } = config let fullUrl = this.buildUrl(url, method === 'GET' ? params : undefined) // 后门id,用于调试 let userid = this.getHashParam("userIdTest") if (userid) { if (fullUrl.indexOf("?") > -1) { fullUrl += `&userIdTest45=${userid}` } else { fullUrl += `?userIdTest45=${userid}` } } this.log('info', `发起请求: ${method} ${fullUrl}`, { data: method !== 'GET' ? data : undefined, params: method === 'GET' ? params : undefined }) // 检查token(如果需要认证) if (config.needAuth === true && !tokenManager.hasValidToken()) { Taro.showToast({ title: '请先登录', icon: 'none' }) throw new Error('Token无效或已过期') } // 显示加载提示 if (showLoading) { Taro.showLoading({ title: loadingText, mask: true }) } try { const reqHeader = this.buildHeaders(config) this.log('info', 'HTTP REQ HEADER: ', reqHeader) const requestConfig = { url: fullUrl, method: method, data: method !== 'GET' ? data : undefined, header: reqHeader, timeout: this.timeout } const response = await Taro.request(requestConfig) return this.handleResponse(response) } catch (error) { this.log('error', '请求失败', error) // 在模拟模式下返回模拟数据 if (envConfig.enableMock && isDevelopment()) { this.log('info', '使用模拟数据') return this.getMockResponse(url, method) } Taro.showToast({ title: '网络连接失败', icon: 'none' }) throw error } finally { // 隐藏加载提示 if (showLoading) { Taro.hideLoading() } } } // 获取模拟数据 private getMockResponse(url: string, method: string): ApiResponse { this.log('info', `返回模拟数据: ${method} ${url}`) return { code: 200, success: true, message: '模拟请求成功', data: { mockData: true, url, method, timestamp: new Date().toISOString() } as T } } // GET请求 get(url: string, params?: Record, config?: Partial): Promise> { return this.request({ url, method: 'GET', params, ...config }) } // POST请求 post(url: string, data?: any, config?: Partial): Promise> { return this.request({ url, method: 'POST', data, ...config }) } uploadFile(){ } // PUT请求 put(url: string, data?: any, config?: Partial): Promise> { return this.request({ url, method: 'PUT', data, ...config }) } // DELETE请求 delete(url: string, params?: Record, config?: Partial): Promise> { return this.request({ url, method: 'DELETE', params, ...config }) } // PATCH请求 patch(url: string, data?: any, config?: Partial): Promise> { return this.request({ url, method: 'PATCH', data, ...config }) } // 获取当前环境信息 getEnvInfo() { return { baseURL: this.baseURL, timeout: this.timeout, enableLog: this.enableLog, ...getEnvInfo() } } } // 导出HTTP服务实例 export default new HttpService()