|
@@ -28,314 +28,203 @@ import type { UniRequestConfig, UniResponse } from '@dcloudio/uni-app'
|
|
|
|
|
|
// 定义基础响应类型
|
|
|
export interface HttpResponse<T = any> {
|
|
|
- code : number
|
|
|
- message : string
|
|
|
- data : T
|
|
|
- [key : string] : any
|
|
|
+ code: number
|
|
|
+ message: string
|
|
|
+ data: T
|
|
|
+ [key: string]: any
|
|
|
}
|
|
|
|
|
|
// 请求配置类型
|
|
|
export interface RequestConfig extends UniRequestConfig {
|
|
|
- baseURL ?: string
|
|
|
- retry ?: number // 重试次数
|
|
|
- retryDelay ?: number // 重试延迟时间(ms)
|
|
|
- loading ?: boolean // 是否显示加载状态
|
|
|
- headers ?: Record<string, string>
|
|
|
- timeout ?: number
|
|
|
- skipAuth ?: boolean // 是否跳过认证检查
|
|
|
-}
|
|
|
-
|
|
|
-// 定义 Token 类型
|
|
|
-export interface TokenData {
|
|
|
- value : string
|
|
|
- expire : number
|
|
|
-}
|
|
|
-
|
|
|
-// 拦截器类型
|
|
|
-interface Interceptor<V> {
|
|
|
- fulfilled : (value : V) => V | Promise<V>
|
|
|
- rejected ?: (error : any) => any
|
|
|
+ baseURL?: string
|
|
|
+ retry?: number // 重试次数
|
|
|
+ retryDelay?: number // 重试延迟时间(ms)
|
|
|
+ loading?: boolean // 是否显示加载状态
|
|
|
+ headers?: Record<string, string>
|
|
|
+ timeout?: number
|
|
|
+ skipAuth?: boolean // 是否跳过认证检查
|
|
|
}
|
|
|
|
|
|
export class HttpClient {
|
|
|
- private defaults : RequestConfig
|
|
|
- private interceptors : {
|
|
|
- request : Interceptor<RequestConfig>[]
|
|
|
- response : Interceptor<UniResponse<HttpResponse>>[]
|
|
|
- }
|
|
|
- private pendingRequests : Map<string, RequestTask>
|
|
|
-
|
|
|
- // 新增刷新相关属性
|
|
|
- private isRefreshing = false
|
|
|
- private refreshSubscribers : ((token : string) => void)[] = []
|
|
|
-
|
|
|
- constructor(config : RequestConfig = {}) {
|
|
|
- this.defaults = {
|
|
|
- baseURL: '',
|
|
|
- timeout: 10000,
|
|
|
- retry: 0,
|
|
|
- retryDelay: 1000,
|
|
|
- loading: false,
|
|
|
- ...config
|
|
|
- }
|
|
|
-
|
|
|
- this.interceptors = {
|
|
|
- request: [],
|
|
|
- response: []
|
|
|
- }
|
|
|
- this.pendingRequests = new Map()
|
|
|
- }
|
|
|
-
|
|
|
- //token检查方法
|
|
|
- static checkTokenValidity(token : any) : string | null {
|
|
|
- let tokenValue : string | null = null
|
|
|
- let tokenExpire : number | null = null
|
|
|
- try {
|
|
|
- if (token) {
|
|
|
- // 处理字符串类型的token(可能是JSON或原始字符串)
|
|
|
- if (typeof token === 'string') {
|
|
|
- try {
|
|
|
- // 尝试解析为JSON对象
|
|
|
- const parsed = JSON.parse(token)
|
|
|
- if (parsed && typeof parsed.value === 'string') {
|
|
|
- tokenValue = parsed.value
|
|
|
- tokenExpire = parsed.expire
|
|
|
- } else {
|
|
|
- // 不是有效JSON对象,当作原始token字符串
|
|
|
- tokenValue = token
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- // JSON解析失败,当作原始token字符串
|
|
|
- tokenValue = token
|
|
|
- }
|
|
|
- }
|
|
|
- else if (typeof token === 'object' && token.value) {
|
|
|
- tokenValue = token.value
|
|
|
- tokenExpire = token.expire
|
|
|
- }
|
|
|
- if (tokenValue) {
|
|
|
- const now = Date.now() + 10000 // 10秒缓冲
|
|
|
- // 如果有过期时间则检查,否则认为有效
|
|
|
- if (tokenExpire) {
|
|
|
- return tokenExpire > now ? tokenValue : null
|
|
|
- }
|
|
|
- return tokenValue
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error('Token 有效性检查失败:', e)
|
|
|
- }
|
|
|
- return null
|
|
|
- }
|
|
|
-
|
|
|
- private createRequestKey(config : RequestConfig) : string {
|
|
|
- return `${config.method}-${config.url}-${JSON.stringify(config.data)}`
|
|
|
- }
|
|
|
-
|
|
|
- // token刷新
|
|
|
- private async refreshToken() : Promise<void> {
|
|
|
- try {
|
|
|
- const refreshToken = uni.getStorageSync('refreshToken');
|
|
|
- const tokenValue = HttpClient.checkTokenValidity(refreshToken);
|
|
|
-
|
|
|
- const response = await this.request<{
|
|
|
- accessToken : string;
|
|
|
- refreshToken : string;
|
|
|
- }>({
|
|
|
- url: `/member/auth/refresh-token?refreshToken=${tokenValue}`,
|
|
|
- method: 'POST',
|
|
|
- data: '',
|
|
|
- skipAuth: true
|
|
|
- });
|
|
|
-
|
|
|
- uni.setStorageSync('token', response.accessToken);
|
|
|
- uni.setStorageSync('refreshToken', response.refreshToken);
|
|
|
-
|
|
|
- this.refreshSubscribers.forEach(callback => callback(response.accessToken));
|
|
|
- this.refreshSubscribers = [];
|
|
|
- } catch (error) {
|
|
|
- console.error('刷新Token失败:', error);
|
|
|
- uni.removeStorageSync('token');
|
|
|
- uni.removeStorageSync('refreshToken');
|
|
|
- uni.reLaunch({ url: '/pages/login/selectLogin' });
|
|
|
- throw error;
|
|
|
- } finally {
|
|
|
- this.isRefreshing = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 核心方法增加无感刷新逻辑
|
|
|
- async request<T = any>(config : RequestConfig) : Promise<T> {
|
|
|
- let mergedConfig : RequestConfig = { ...this.defaults, ...config }
|
|
|
- // 执行请求拦截器
|
|
|
- for (const interceptor of this.interceptors.request) {
|
|
|
- try {
|
|
|
- mergedConfig = await interceptor.fulfilled(mergedConfig)
|
|
|
- } catch (error) {
|
|
|
- interceptor.rejected?.(error)
|
|
|
- throw error
|
|
|
- }
|
|
|
- }
|
|
|
- const requestKey = this.createRequestKey(mergedConfig)
|
|
|
- // 取消重复请求
|
|
|
- if (this.pendingRequests.has(requestKey)) {
|
|
|
- this.pendingRequests.get(requestKey)?.abort()
|
|
|
- }
|
|
|
-
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const sendRequest = async (retryCount = 0) => {
|
|
|
- if (mergedConfig.loading) {
|
|
|
- uni.showLoading({ title: '加载中...', mask: true })
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- const task = uni.request({
|
|
|
- ...mergedConfig,
|
|
|
- url: mergedConfig.baseURL + mergedConfig.url,
|
|
|
- success: async (response) => {
|
|
|
- const res = response.data as HttpResponse;
|
|
|
- if (res.code === 401 && !mergedConfig.skipAuth) {
|
|
|
- const retryOriginalRequest = new Promise<T>((resolve) => {
|
|
|
- this.refreshSubscribers.push((newToken) => {
|
|
|
- // 使用新Token重试请求
|
|
|
- const retryConfig = {
|
|
|
- ...mergedConfig,
|
|
|
- header: {
|
|
|
- ...mergedConfig.header,
|
|
|
- Authorization: `Bearer ${newToken}`
|
|
|
- }
|
|
|
- }
|
|
|
- this.request(retryConfig).then(resolve)
|
|
|
- })
|
|
|
- })
|
|
|
- if (!this.isRefreshing) {
|
|
|
- this.isRefreshing = true
|
|
|
- this.refreshToken().catch((error) => {
|
|
|
- this.refreshSubscribers = []
|
|
|
- reject(error)
|
|
|
- })
|
|
|
- }
|
|
|
- return resolve(await retryOriginalRequest)
|
|
|
- }
|
|
|
- // 执行响应拦截器
|
|
|
- let processedResponse = response
|
|
|
- for (const interceptor of this.interceptors.response) {
|
|
|
- try {
|
|
|
- processedResponse = await interceptor.fulfilled(processedResponse)
|
|
|
- } catch (error) {
|
|
|
- interceptor.rejected?.(error)
|
|
|
- reject(error)
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- const resData = processedResponse.data as HttpResponse<T>
|
|
|
- if (resData.code === 0) {
|
|
|
- resolve(resData.data)
|
|
|
- } else {
|
|
|
- uni.showToast({
|
|
|
- title: resData.msg || '请求失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- reject(resData)
|
|
|
- }
|
|
|
- },
|
|
|
- fail: async (error) => {
|
|
|
- // 重试逻辑
|
|
|
- if (retryCount < mergedConfig.retry!) {
|
|
|
- setTimeout(() => {
|
|
|
- sendRequest(retryCount + 1)
|
|
|
- }, mergedConfig.retryDelay)
|
|
|
- } else {
|
|
|
- reject(error)
|
|
|
- }
|
|
|
- },
|
|
|
- complete: () => {
|
|
|
- this.pendingRequests.delete(requestKey)
|
|
|
- if (mergedConfig.loading) {
|
|
|
- uni.hideLoading()
|
|
|
- }
|
|
|
- }
|
|
|
- }) as RequestTask
|
|
|
-
|
|
|
- this.pendingRequests.set(requestKey, task)
|
|
|
- } catch (error) {
|
|
|
- reject(error)
|
|
|
- }
|
|
|
- }
|
|
|
- sendRequest()
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- // 添加请求拦截器
|
|
|
- useRequestInterceptor(interceptor : Interceptor<RequestConfig>) {
|
|
|
- this.interceptors.request.push(interceptor)
|
|
|
- }
|
|
|
-
|
|
|
- // 添加响应拦截器
|
|
|
- useResponseInterceptor(interceptor : Interceptor<UniResponse<HttpResponse>>) {
|
|
|
- this.interceptors.response.push(interceptor)
|
|
|
- }
|
|
|
-
|
|
|
- // 快捷方法
|
|
|
- get<T = any>(url : string, config ?: RequestConfig) {
|
|
|
- return this.request<T>({ ...config, url, method: 'GET' })
|
|
|
- }
|
|
|
-
|
|
|
- post<T = any>(url : string, data ?: any, config ?: RequestConfig) {
|
|
|
- return this.request<T>({ ...config, url, data, method: 'POST' })
|
|
|
- }
|
|
|
-
|
|
|
- delete<T = any>(url : string, data ?: any, config ?: RequestConfig) {
|
|
|
- return this.request<T>({ ...config, url, data, method: 'DELETE' })
|
|
|
- }
|
|
|
-
|
|
|
- put<T = any>(url : string, data ?: any, config ?: RequestConfig) {
|
|
|
- return this.request<T>({ ...config, url, data, method: 'PUT' })
|
|
|
- }
|
|
|
+ private defaults: RequestConfig
|
|
|
+ private pendingRequests: Map<string, RequestTask>
|
|
|
+
|
|
|
+ constructor(config: RequestConfig = {}) {
|
|
|
+ this.defaults = {
|
|
|
+ baseURL: '',
|
|
|
+ timeout: 10000,
|
|
|
+ retry: 0,
|
|
|
+ retryDelay: 1000,
|
|
|
+ loading: false,
|
|
|
+ ...config
|
|
|
+ }
|
|
|
+
|
|
|
+ this.pendingRequests = new Map()
|
|
|
+ }
|
|
|
+
|
|
|
+ // token检查方法
|
|
|
+ static checkTokenValidity(token: any): string | null {
|
|
|
+ let tokenValue: string | null = null
|
|
|
+ try {
|
|
|
+ if (token) {
|
|
|
+ if (typeof token === 'string') {
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(token)
|
|
|
+ if (parsed && typeof parsed.value === 'string') {
|
|
|
+ tokenValue = parsed.value
|
|
|
+ } else {
|
|
|
+ tokenValue = token
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ tokenValue = token
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (typeof token === 'object' && token.value) {
|
|
|
+ tokenValue = token.value
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('Token 有效性检查失败:', e)
|
|
|
+ }
|
|
|
+ return tokenValue
|
|
|
+ }
|
|
|
+
|
|
|
+ private createRequestKey(config: RequestConfig): string {
|
|
|
+ return `${config.method}-${config.url}-${JSON.stringify(config.data)}`
|
|
|
+ }
|
|
|
+
|
|
|
+ async request<T = any>(config: RequestConfig): Promise<T> {
|
|
|
+ let mergedConfig: RequestConfig = { ...this.defaults, ...config }
|
|
|
+ const requestKey = this.createRequestKey(mergedConfig)
|
|
|
+
|
|
|
+ // 取消重复请求
|
|
|
+ if (this.pendingRequests.has(requestKey)) {
|
|
|
+ this.pendingRequests.get(requestKey)?.abort()
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const sendRequest = (retryCount = 0) => {
|
|
|
+ if (mergedConfig.loading) {
|
|
|
+ uni.showLoading({ title: '加载中...', mask: true })
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const task = uni.request({
|
|
|
+ ...mergedConfig,
|
|
|
+ url: mergedConfig.baseURL + mergedConfig.url,
|
|
|
+ success: (response) => {
|
|
|
+ const res = response.data as HttpResponse
|
|
|
+ if (res.code === 200) {
|
|
|
+ resolve(res as T)
|
|
|
+ } else {
|
|
|
+ uni.showToast({
|
|
|
+ title: res.message || '请求失败',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ reject(res)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ // 重试逻辑
|
|
|
+ if (retryCount < mergedConfig.retry!) {
|
|
|
+ setTimeout(() => {
|
|
|
+ sendRequest(retryCount + 1)
|
|
|
+ }, mergedConfig.retryDelay)
|
|
|
+ } else {
|
|
|
+ uni.showToast({
|
|
|
+ title: '网络错误,请重试',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ reject(error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ complete: () => {
|
|
|
+ this.pendingRequests.delete(requestKey)
|
|
|
+ if (mergedConfig.loading) {
|
|
|
+ uni.hideLoading()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }) as RequestTask
|
|
|
+
|
|
|
+ this.pendingRequests.set(requestKey, task)
|
|
|
+ } catch (error) {
|
|
|
+ reject(error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sendRequest()
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 快捷方法
|
|
|
+ get<T = any>(url: string, config?: RequestConfig) {
|
|
|
+ return this.request<T>({ ...config, url, method: 'GET' })
|
|
|
+ }
|
|
|
+
|
|
|
+ post<T = any>(url: string, data?: any, config?: RequestConfig) {
|
|
|
+ return this.request<T>({ ...config, url, data, method: 'POST' })
|
|
|
+ }
|
|
|
+
|
|
|
+ delete<T = any>(url: string, data?: any, config?: RequestConfig) {
|
|
|
+ return this.request<T>({ ...config, url, data, method: 'DELETE' })
|
|
|
+ }
|
|
|
+
|
|
|
+ put<T = any>(url: string, data?: any, config?: RequestConfig) {
|
|
|
+ return this.request<T>({ ...config, url, data, method: 'PUT' })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 创建实例
|
|
|
export const http = new HttpClient({
|
|
|
- baseURL: 'http://qhmall.natapp1.cc/app-api',
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'application/json'
|
|
|
- }
|
|
|
+ baseURL: 'http://192.168.1.34:8080/jeecg-boot/app',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
-// 添加请求拦截器 - 处理鉴权
|
|
|
-http.useRequestInterceptor({
|
|
|
- fulfilled: (config) => {
|
|
|
- // 跳过不需要认证的请求
|
|
|
- if (config.skipAuth) return config
|
|
|
- const tokenStorage = uni.getStorageSync('token')
|
|
|
- // 检查 token 有效性并提取值
|
|
|
- const tokenValue = HttpClient.checkTokenValidity(tokenStorage)
|
|
|
- if (tokenValue) {
|
|
|
- config.header = config.header || {}
|
|
|
- config.header.Authorization = `Bearer ${tokenValue}`
|
|
|
- } else {
|
|
|
- console.log('[请求拦截器] 无有效Token')
|
|
|
- }
|
|
|
- return config
|
|
|
- }
|
|
|
+// 使用UniApp原生请求拦截器
|
|
|
+uni.addInterceptor('request', {
|
|
|
+ invoke(args: UniApp.RequestOptions) {
|
|
|
+ // 跳过不需要认证的请求
|
|
|
+ if (args.skipAuth) return
|
|
|
+ const tokenStorage = uni.getStorageSync('TOKEN')
|
|
|
+ const tokenValue = HttpClient.checkTokenValidity(tokenStorage)
|
|
|
+
|
|
|
+ if (tokenValue) {
|
|
|
+ args.header = args.header || {}
|
|
|
+ args.header['x-access-token'] = tokenValue
|
|
|
+ } else {
|
|
|
+ console.log('[请求拦截器] 无有效Token')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail(err) {
|
|
|
+ console.error('请求拦截器失败:', err)
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
-// 添加响应拦截器 - 错误处理
|
|
|
-http.useResponseInterceptor({
|
|
|
- fulfilled: (response) => {
|
|
|
- const resData = response.data as HttpResponse;
|
|
|
- if (resData.code !== 0) {
|
|
|
- return Promise.reject(response)
|
|
|
- }
|
|
|
- return response
|
|
|
- },
|
|
|
- rejected: (error) => {
|
|
|
- console.log(error.data, 'err');
|
|
|
- uni.showToast({
|
|
|
- title: error.data.msg || '请求失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- return Promise.reject(error)
|
|
|
- }
|
|
|
+// 使用UniApp原生响应拦截器
|
|
|
+uni.addInterceptor('request', {
|
|
|
+ success: (res) => {
|
|
|
+ const data = res.data as HttpResponse
|
|
|
+ if (data.code !== 200) {
|
|
|
+ uni.showToast({
|
|
|
+ title: data.message || '请求失败',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ // 返回自定义错误对象,触发fail回调
|
|
|
+ return {
|
|
|
+ ...res,
|
|
|
+ errMsg: `业务错误: ${data.message}`,
|
|
|
+ data: null
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return res
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('响应拦截器捕获错误:', err)
|
|
|
+ uni.showToast({
|
|
|
+ title: '网络错误,请重试',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ return err
|
|
|
+ }
|
|
|
})
|