Compare commits
2 Commits
b84becfbea
...
1576709342
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1576709342 | ||
|
|
9d50ef7847 |
@@ -10,7 +10,7 @@ interface LongPollOptions<T> {
|
||||
enabled: boolean;
|
||||
/** 成功轮询间隔 ms,默认 3000 */
|
||||
intervalMs?: number;
|
||||
/** 连续失败上限,默认 50 */
|
||||
/** 连续失败上限,默认 10 */
|
||||
maxFailures?: number;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function useLongPolling<T>({
|
||||
onData,
|
||||
enabled,
|
||||
intervalMs = 3000,
|
||||
maxFailures = 50,
|
||||
maxFailures = 10,
|
||||
}: LongPollOptions<T>) {
|
||||
const generation = useRef(0);
|
||||
const mountedRef = useRef(true);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { api, requestWithTimeout } from './request';
|
||||
import { api, requestUnlimited } from './request';
|
||||
|
||||
export interface ConsultationSession {
|
||||
id: string;
|
||||
@@ -72,5 +72,5 @@ export async function pollMessages(sessionId: string, afterId?: string) {
|
||||
params.set('timeout', '25');
|
||||
const query = params.toString();
|
||||
const path = `/health/consultation-sessions/${sessionId}/messages/poll${query ? '?' + query : ''}`;
|
||||
return requestWithTimeout<ConsultationMessage[]>('GET', path, undefined, 30000);
|
||||
return requestUnlimited<ConsultationMessage[]>('GET', path, undefined, 30000);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { api, requestWithTimeout } from '../request';
|
||||
import { api, requestUnlimited } from '../request';
|
||||
|
||||
// ── Consultation (doctor view) ─────────────────────
|
||||
|
||||
@@ -71,7 +71,7 @@ export async function pollMessages(sessionId: string, afterId?: string) {
|
||||
params.set('timeout', '25');
|
||||
const query = params.toString();
|
||||
const path = `/health/consultation-sessions/${sessionId}/messages/poll${query ? '?' + query : ''}`;
|
||||
return requestWithTimeout<ConsultationMessage[]>('GET', path, undefined, 30000);
|
||||
return requestUnlimited<ConsultationMessage[]>('GET', path, undefined, 30000);
|
||||
}
|
||||
|
||||
export interface ConsultationStats {
|
||||
|
||||
@@ -56,7 +56,7 @@ class ConcurrencyLimiter {
|
||||
}
|
||||
}
|
||||
|
||||
const limiter = new ConcurrencyLimiter(8);
|
||||
const limiter = new ConcurrencyLimiter(12);
|
||||
|
||||
// --- Response cache + deduplication ---
|
||||
|
||||
@@ -221,13 +221,24 @@ async function doRefresh(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- reLaunch 去重 ---
|
||||
|
||||
let reLaunchPromise: Promise<void> | null = null;
|
||||
|
||||
function safeReLaunch(url: string): void {
|
||||
if (reLaunchPromise) return;
|
||||
reLaunchPromise = Taro.reLaunch({ url }).then(() => {}, () => {}).then(() => {
|
||||
setTimeout(() => { reLaunchPromise = null; }, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Core request ---
|
||||
|
||||
async function request<T>(method: string, path: string, data?: unknown, timeout?: number, signal?: AbortSignal): Promise<T> {
|
||||
async function request<T>(method: string, path: string, data?: unknown, timeout?: number, signal?: AbortSignal, bypassLimiter = false): Promise<T> {
|
||||
let retryCount401 = 0;
|
||||
for (;;) {
|
||||
if (signal?.aborted) throw new Error('请求已取消');
|
||||
await limiter.acquire();
|
||||
if (!bypassLimiter) await limiter.acquire();
|
||||
try {
|
||||
const headers = await getHeaders();
|
||||
const url = `${BASE_URL}${path}`;
|
||||
@@ -262,7 +273,7 @@ async function request<T>(method: string, path: string, data?: unknown, timeout?
|
||||
const pages = Taro.getCurrentPages();
|
||||
const currentPath = pages[pages.length - 1]?.path || '';
|
||||
if (!currentPath.includes('pages/login')) {
|
||||
Taro.reLaunch({ url: '/pages/login/index' });
|
||||
safeReLaunch('/pages/login/index');
|
||||
}
|
||||
}
|
||||
throw new Error('登录已过期');
|
||||
@@ -287,7 +298,7 @@ async function request<T>(method: string, path: string, data?: unknown, timeout?
|
||||
}
|
||||
return body.data as T;
|
||||
} finally {
|
||||
limiter.release();
|
||||
if (!bypassLimiter) limiter.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,6 +360,11 @@ export async function requestWithTimeout<T>(method: string, path: string, data?:
|
||||
return request<T>(method, path, data, timeout);
|
||||
}
|
||||
|
||||
/** 绕过并发限制的请求,用于长轮询等长时间 hang 的请求 */
|
||||
export async function requestUnlimited<T>(method: string, path: string, data?: unknown, timeout?: number): Promise<T> {
|
||||
return request<T>(method, path, data, timeout, undefined, true);
|
||||
}
|
||||
|
||||
/** 重置所有模块级状态,用于测试隔离 */
|
||||
export function resetForTesting(): void {
|
||||
limiter.reset();
|
||||
|
||||
@@ -94,6 +94,8 @@
|
||||
| 小程序轮播图图片 404 | [[erp-health]] 公开端点 | Axum 路由参数 `:id` → `{id}` 语法变更 + URL 拼接缺失 `/api/v1` | **已修复:** 路由改用 `{banner_id}`,新增 `/public/banner-image/{id}` 图片服务端点 |
|
||||
| 前端媒体库图片 401 | [[frontend]] MediaLibrary | `/uploads` 路径需要 JWT 认证 | **已修复:** 新增 `resolveMediaUrl()` 工具函数自动拼接 `?token=`,Vite 代理 `/uploads` |
|
||||
| 后端启动 panic "Path segments must not start with `:`" | [[erp-health]] module.rs | Axum v0.8+ 路由参数语法变更 | 路由定义使用 `{param}` 而非 `:param` |
|
||||
| 小程序分包页 navigateTo:fail timeout | [[miniprogram]] 页面生命周期 | `useEffect` + `usePageData`(useDidShow) 双重初始化 | **已修复:** 合并为 `usePageData` 单次回调,移除独立 `useEffect` |
|
||||
| 小程序/开发者工具卡死无响应 | [[miniprogram]] request.ts 并发控制 | 长轮询占用 ConcurrencyLimiter 槽位 25-30s,叠加 401 形成死锁 | **已修复:** 长轮询走 `requestUnlimited` 独立通道 + 并发上限 8→12 + reLaunch 去重 + maxFailures 50→10 |
|
||||
|
||||
## 模块导航
|
||||
|
||||
|
||||
Reference in New Issue
Block a user