서버 알림목록 로딩이슈
This commit is contained in:
@@ -3,9 +3,9 @@
|
|||||||
*
|
*
|
||||||
* 차트를 왼쪽으로 스크롤할 때 과거 캔들 데이터를 추가로 불러오는 훅.
|
* 차트를 왼쪽으로 스크롤할 때 과거 캔들 데이터를 추가로 불러오는 훅.
|
||||||
*
|
*
|
||||||
* 데이터 소스 우선순위:
|
* 데이터 소스:
|
||||||
* 1. 백엔드 GET /api/candles/history (Ta4jStorage + 업비트 REST 프록시 + RSI 포함)
|
* 백엔드 GET /api/candles/history (Ta4jStorage + 업비트 REST 프록시 + RSI 포함)
|
||||||
* 2. 백엔드 호출 실패 시 → 업비트 REST 직접 폴백
|
* 백엔드 실패 시 → 추가 로드 중단 (Upbit 직접 호출 없음)
|
||||||
*
|
*
|
||||||
* 중복 방지 락(Lock):
|
* 중복 방지 락(Lock):
|
||||||
* loadingRef = true 인 동안에는 동일 타임스탬프 중복 요청을 원천 차단.
|
* loadingRef = true 인 동안에는 동일 타임스탬프 중복 요청을 원천 차단.
|
||||||
@@ -20,7 +20,6 @@ import type { RefObject, MutableRefObject } from 'react';
|
|||||||
import type { ChartManager } from '../utils/ChartManager';
|
import type { ChartManager } from '../utils/ChartManager';
|
||||||
import { toHistoryApiIso } from '../utils/analysisChartData';
|
import { toHistoryApiIso } from '../utils/analysisChartData';
|
||||||
import { timeframeToCandleType } from '../utils/chartCandleType';
|
import { timeframeToCandleType } from '../utils/chartCandleType';
|
||||||
import { fetchUpbitCandlesBeforeCached } from '../utils/requestCache';
|
|
||||||
import type { OHLCVBar, Timeframe } from '../types';
|
import type { OHLCVBar, Timeframe } from '../types';
|
||||||
|
|
||||||
/** 이 값(바 인덱스)보다 왼쪽에 가시 범위의 시작이 오면 추가 로드 트리거 */
|
/** 이 값(바 인덱스)보다 왼쪽에 가시 범위의 시작이 오면 추가 로드 트리거 */
|
||||||
@@ -147,18 +146,15 @@ export function useHistoryLoader({
|
|||||||
setIsLoadingMore(true);
|
setIsLoadingMore(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 백엔드 /api/candles/history 에서만 로드 — Upbit 직접 호출 없음
|
||||||
let olderBars: OHLCVBar[];
|
let olderBars: OHLCVBar[];
|
||||||
|
|
||||||
// ── 1. 백엔드 /api/candles/history 우선 시도 ──────────────────────────
|
|
||||||
try {
|
try {
|
||||||
olderBars = await fetchHistoryFromBackend(symbol, candleType, beforeIso, LOAD_BATCH);
|
olderBars = await fetchHistoryFromBackend(symbol, candleType, beforeIso, LOAD_BATCH);
|
||||||
console.debug(`[HistoryLoader] 백엔드에서 ${olderBars.length}개 수신 (${candleType})`);
|
console.debug(`[HistoryLoader] 백엔드에서 ${olderBars.length}개 수신 (${candleType})`);
|
||||||
} catch (backendErr) {
|
} catch (backendErr) {
|
||||||
// ── 2. 백엔드 실패 시 업비트 REST 직접 폴백 ───────────────────────
|
console.warn('[HistoryLoader] 백엔드 실패, 추가 로드 건너뜀:', backendErr);
|
||||||
console.warn('[HistoryLoader] 백엔드 실패, 업비트 직접 폴백:', backendErr);
|
hasMoreRef.current = false;
|
||||||
olderBars = await fetchUpbitCandlesBeforeCached(
|
return;
|
||||||
symbol, timeframe, LOAD_BATCH, beforeIso,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (olderBars.length === 0) {
|
if (olderBars.length === 0) {
|
||||||
|
|||||||
@@ -10,14 +10,6 @@ import { getCandleStart, TF_SECONDS } from '../utils/upbitApi';
|
|||||||
import { DISPLAY_COUNT, WARMUP_COUNT, OBV_DEEP_HISTORY } from './useUpbitData';
|
import { DISPLAY_COUNT, WARMUP_COUNT, OBV_DEEP_HISTORY } from './useUpbitData';
|
||||||
import type { WsStatus } from './useUpbitData';
|
import type { WsStatus } from './useUpbitData';
|
||||||
import { API_BASE } from '../utils/backendApi';
|
import { API_BASE } from '../utils/backendApi';
|
||||||
import { fetchUpbitCandlesCached } from '../utils/requestCache';
|
|
||||||
|
|
||||||
/** 초기 렌더 최소 봉 수 — 미만이면 업비트 REST 폴백 */
|
|
||||||
const MIN_INITIAL_BARS = Math.min(50, Math.floor(DISPLAY_COUNT / 4));
|
|
||||||
|
|
||||||
function barsHaveVolume(bars: OHLCVBar[]): boolean {
|
|
||||||
return bars.some(b => (Number(b.volume) || 0) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 백엔드 BarBuilder 는 틱마다 1m 만 STOMP 발행 — 차트 분봉은 클라이언트에서 집계 */
|
/** 백엔드 BarBuilder 는 틱마다 1m 만 STOMP 발행 — 차트 분봉은 클라이언트에서 집계 */
|
||||||
const STOMP_TICK_CANDLE_TYPE = '1m';
|
const STOMP_TICK_CANDLE_TYPE = '1m';
|
||||||
@@ -110,52 +102,24 @@ async function fetchInitialBarsWithRetry(
|
|||||||
throw lastErr ?? new Error('history fetch failed');
|
throw lastErr ?? new Error('history fetch failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 백엔드 history → 부족·거래량 0·실패 시 업비트 REST 폴백 */
|
/**
|
||||||
|
* 백엔드 /api/candles/history 에서만 초기 캔들을 로드한다.
|
||||||
|
* Upbit REST 직접 호출을 하지 않음 — 백엔드가 데이터를 아직 쌓지 못한 경우
|
||||||
|
* 빈 배열을 반환하고, 호출부에서 STOMP bootstrap 경로로 처리한다.
|
||||||
|
*/
|
||||||
async function loadInitialBars(
|
async function loadInitialBars(
|
||||||
market: string,
|
market: string,
|
||||||
timeframe: Timeframe,
|
timeframe: Timeframe,
|
||||||
count: number,
|
count: number,
|
||||||
marketChanged: boolean,
|
|
||||||
preferUpbit = false,
|
|
||||||
): Promise<OHLCVBar[]> {
|
): Promise<OHLCVBar[]> {
|
||||||
const candleType = timeframeToCandleType(timeframe);
|
const candleType = timeframeToCandleType(timeframe);
|
||||||
const upbitCount = Math.min(count, OBV_DEEP_HISTORY);
|
|
||||||
|
|
||||||
if (preferUpbit) {
|
|
||||||
try {
|
try {
|
||||||
const upbit = await fetchUpbitCandlesCached(market, timeframe, upbitCount, marketChanged);
|
return await fetchInitialBarsWithRetry(market, candleType, count);
|
||||||
if (upbit.length >= MIN_INITIAL_BARS && barsHaveVolume(upbit)) return upbit;
|
|
||||||
} catch {
|
} catch {
|
||||||
/* Upbit 실패 시 backend 시도 */
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bars: OHLCVBar[] = [];
|
|
||||||
try {
|
|
||||||
bars = await fetchInitialBarsWithRetry(market, candleType, count);
|
|
||||||
} catch {
|
|
||||||
bars = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const needsUpbitFallback = bars.length < MIN_INITIAL_BARS || !barsHaveVolume(bars);
|
|
||||||
if (needsUpbitFallback) {
|
|
||||||
try {
|
|
||||||
const fallback = await fetchUpbitCandlesCached(
|
|
||||||
market,
|
|
||||||
timeframe,
|
|
||||||
upbitCount,
|
|
||||||
marketChanged,
|
|
||||||
);
|
|
||||||
if (fallback.length > bars.length || (barsHaveVolume(fallback) && !barsHaveVolume(bars))) {
|
|
||||||
bars = fallback;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* 폴백 실패 시 backend 결과 유지 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bars;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useStompChartData(
|
export function useStompChartData(
|
||||||
market: string,
|
market: string,
|
||||||
timeframe: Timeframe,
|
timeframe: Timeframe,
|
||||||
@@ -180,8 +144,6 @@ export function useStompChartData(
|
|||||||
const historyReadyRef = useRef(false);
|
const historyReadyRef = useRef(false);
|
||||||
/** 백엔드 history 비어 있을 때 STOMP 누적 → React bars 동기화 */
|
/** 백엔드 history 비어 있을 때 STOMP 누적 → React bars 동기화 */
|
||||||
const bootstrapFromStompRef = useRef(false);
|
const bootstrapFromStompRef = useRef(false);
|
||||||
const prevMarketRef = useRef('');
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (opts.enabled === false) return;
|
if (opts.enabled === false) return;
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
@@ -196,13 +158,10 @@ export function useStompChartData(
|
|||||||
last1mTimeRef.current = 0;
|
last1mTimeRef.current = 0;
|
||||||
lastTimeRef.current = 0;
|
lastTimeRef.current = 0;
|
||||||
|
|
||||||
const marketChanged = prevMarketRef.current !== '' && prevMarketRef.current !== market;
|
|
||||||
prevMarketRef.current = market;
|
|
||||||
|
|
||||||
const count = opts.deepObvHistory
|
const count = opts.deepObvHistory
|
||||||
? OBV_DEEP_HISTORY
|
? OBV_DEEP_HISTORY
|
||||||
: DISPLAY_COUNT + WARMUP_COUNT;
|
: DISPLAY_COUNT + WARMUP_COUNT;
|
||||||
loadInitialBars(market, timeframe, count, marketChanged, opts.deepObvHistory === true)
|
loadInitialBars(market, timeframe, count)
|
||||||
.then(newBars => {
|
.then(newBars => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
barsRef.current = newBars;
|
barsRef.current = newBars;
|
||||||
|
|||||||
Reference in New Issue
Block a user