| | |
| | | ]; |
| | | |
| | | /** |
| | | * 检测单个服务健康状态 |
| | | * 带超时控制的 fetch 请求封装 |
| | | * @param {string} url 请求地址 |
| | | * @param {Object} options fetch 配置 |
| | | * @param {number} timeout 超时时间(毫秒),默认 3000ms |
| | | * @returns {Promise<Response>} 请求响应 |
| | | */ |
| | | const fetchWithTimeout = async (url, options = {}, timeout = 3000) => { |
| | | // 1. 创建中止控制器,用于超时取消请求 |
| | | const controller = new AbortController(); |
| | | const signal = controller.signal; |
| | | |
| | | // 2. 超时定时器:到时间后中止请求并抛出错误 |
| | | const timeoutTimer = setTimeout(() => { |
| | | controller.abort(); |
| | | throw new Error(`请求超时(${timeout}ms)`); |
| | | }, timeout); |
| | | |
| | | try { |
| | | // 3. 绑定中止信号到 fetch 请求 |
| | | const response = await fetch(url, { |
| | | ...options, |
| | | signal, // 关键:关联中止控制器 |
| | | }); |
| | | clearTimeout(timeoutTimer); // 请求成功,清除超时定时器 |
| | | return response; |
| | | } catch (error) { |
| | | clearTimeout(timeoutTimer); // 出错/超时,清除定时器 |
| | | // 区分中止错误(超时)和其他错误 |
| | | if (error.name === 'AbortError') { |
| | | throw new Error(`请求超时:${url} 超过 ${timeout}ms 未响应`); |
| | | } |
| | | throw error; // 抛出其他错误(如网络错误) |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * 检测单个服务健康状态(新增超时控制) |
| | | * @param {Object} service - 服务配置(baseUrl + healthPath) |
| | | * @returns {Promise<String|null>} 可用的 baseUrl,失败返回 null |
| | | */ |
| | | export const checkServiceHealth = async (service) => { |
| | | const { baseUrl, healthPath } = service; |
| | | if (!baseUrl) return null; |
| | | return new Promise(async (resolve, reject) => { |
| | | try { |
| | | // 健康检查请求(超时 3 秒,不携带 Token,避免未登录拦截) |
| | | const response = await fetch(`${baseUrl}${healthPath}`, { |
| | | |
| | | try { |
| | | // 健康检查请求(使用封装的带超时的 fetch,3秒超时) |
| | | const response = await fetchWithTimeout( |
| | | `${baseUrl}${healthPath}`, |
| | | { |
| | | method: "GET", |
| | | timeout: 3000, |
| | | headers: { |
| | | "Content-Type": "application/json", |
| | | }, |
| | | }); |
| | | if (response.ok) { |
| | | console.log(`服务 ${service.name} 健康,地址:${baseUrl}`); |
| | | resolve(baseUrl) |
| | | // return baseUrl; |
| | | } |
| | | } catch (error) { |
| | | console.error(`服务 ${service.name} 连接失败:`, error.message); |
| | | resolve(null) |
| | | // return null; |
| | | } |
| | | }) |
| | | }, |
| | | 3000 // 明确配置 3 秒超时 |
| | | ); |
| | | |
| | | if (response.ok) { |
| | | console.log(`服务 ${service.name} 健康,地址:${baseUrl}`); |
| | | return baseUrl; |
| | | } else { |
| | | // 响应状态码非 2xx,视为服务不可用 |
| | | console.warn(`服务 ${service.name} 响应异常,状态码:${response.status}`); |
| | | return null; |
| | | } |
| | | } catch (error) { |
| | | console.error(`服务 ${service.name} 连接失败:`, error.message); |
| | | return null; |
| | | } |
| | | }; |
| | | |
| | | /** |
| | |
| | | */ |
| | | export const findAvailableService = async () => { |
| | | // 并行检测所有服务(提高效率) |
| | | const healthCheckPromises = serviceList.map((item) => { |
| | | return checkServiceHealth(item) |
| | | }); |
| | | const healthResult = await Promise.any(healthCheckPromises); // 检测到健康的链接就立刻返回 |
| | | // 筛选可用的 baseUrl |
| | | const availableBaseUrl = healthResult |
| | | try { |
| | | const healthCheckPromises = serviceList.map((item) => { |
| | | return checkServiceHealth(item); |
| | | }); |
| | | const healthResult = await Promise.all(healthCheckPromises); |
| | | // 筛选可用的 baseUrl |
| | | console.log("健康检查结果:", healthResult); |
| | | const availableBaseUrl = healthResult.filter((item) => item != null)[0]; |
| | | |
| | | if (availableBaseUrl) { |
| | | return availableBaseUrl; |
| | | } else { |
| | | // 所有服务均不可用,抛出异常(后续在初始化时捕获) |
| | | throw new Error("所有服务健康检查失败,请检查服务状态或网络配置"); |
| | | if (availableBaseUrl) { |
| | | return availableBaseUrl; |
| | | } else { |
| | | // 所有服务均不可用,抛出异常 |
| | | throw new Error("所有服务健康检查失败,请检查服务状态或网络配置"); |
| | | } |
| | | } catch (err) { |
| | | throw err; |
| | | } |
| | | }; |