[251031] feat: implement sequential pagination for complete review data collection
- Add fetchAllReviewsWithSequentialPaging function for TV app optimization - Sequential page fetching to collect all reviews (memory-efficient) - Pagination logic: continue while receivedCount >= pageSize AND totalCollected < totalReviews - Automatically handles both ALL and filtered review requests - Consolidates all pages into single Redux dispatch with complete data Implementation details: - pageSize = 100 (maximum) to minimize pagination rounds - Checks rvwListCnt < pageSize OR totalCollected >= totRvwCnt to determine end - Preserves reviewDetail from first page (totRvwCnt, totRvwAvg, etc) - Properly combines all reviews from multiple pages - Includes comprehensive logging for debugging pagination flow TV app optimization: - Sequential processing reduces memory footprint - Minimizes server load with efficient page requests - Single final Redux dispatch reduces state updates - Error handling with detailed logging 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -309,123 +309,184 @@ export const getVideoIndicatorFocus = (focused) => (dispatch) => {
|
||||
});
|
||||
};
|
||||
|
||||
// User Review List 추가 조회 IF-LGSP-101
|
||||
export const getUserReviewList = (requestParams) => (dispatch, getState) => {
|
||||
// 순차 페이징으로 모든 리뷰 데이터를 수집하는 함수 (TV 앱 성능 최적화)
|
||||
// Option 2: 순차 페칭 (메모리 효율, 서버 부하 감소)
|
||||
const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestParams) => {
|
||||
const {
|
||||
prdtId,
|
||||
patnrId,
|
||||
filterTpCd = 'ALL',
|
||||
filterTpVal,
|
||||
pageSize = 5,
|
||||
pageNo = 1
|
||||
pageSize = 100, // 최대값으로 설정하여 페이징 횟수 최소화
|
||||
} = requestParams;
|
||||
|
||||
const params = {
|
||||
console.log('[fetchAllReviewsWithSequentialPaging] 🚀 순차 페이징 시작:', {
|
||||
prdtId,
|
||||
patnrId,
|
||||
filterTpCd,
|
||||
pageSize,
|
||||
pageNo,
|
||||
// 우선순위 1: cntryCd 기본값 'US' 설정 (TV 환경에서는 자동으로 header로 전달됨)
|
||||
cntryCd: 'US'
|
||||
};
|
||||
filterTpVal,
|
||||
pageSize
|
||||
});
|
||||
|
||||
// 우선순위 2: filterTpVal 조건부 필수 검증 강화
|
||||
if (filterTpCd !== 'ALL') {
|
||||
if (!filterTpVal) {
|
||||
console.warn('[UserReviewList] ⚠️ filterTpCd가 ALL이 아니면 filterTpVal은 필수입니다', {
|
||||
let allReviews = [];
|
||||
let currentReviewDetail = null;
|
||||
let pageNo = 1;
|
||||
let hasMore = true;
|
||||
|
||||
try {
|
||||
while (hasMore) {
|
||||
// 1. 현재 페이지 요청 파라미터 생성
|
||||
const params = {
|
||||
prdtId,
|
||||
patnrId,
|
||||
filterTpCd,
|
||||
filterTpVal,
|
||||
message: 'filterTpVal이 제공되지 않았습니다'
|
||||
pageSize,
|
||||
pageNo,
|
||||
cntryCd: 'US'
|
||||
};
|
||||
|
||||
// filterTpCd가 'ALL'이 아니면 filterTpVal 추가
|
||||
if (filterTpCd !== 'ALL') {
|
||||
if (!filterTpVal) {
|
||||
console.warn('[fetchAllReviewsWithSequentialPaging] ⚠️ filterTpCd가 ALL이 아니면 filterTpVal은 필수입니다');
|
||||
}
|
||||
params.filterTpVal = filterTpVal;
|
||||
}
|
||||
|
||||
// 2. API 호출 (Promise 기반으로 변경하여 에러 처리 가능하게 함)
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
const onSuccess = (res) => resolve(res);
|
||||
const onFail = (err) => reject(err);
|
||||
TAxios(dispatch, getState, 'get', URLS.GET_USER_REVIEW_LIST, params, {}, onSuccess, onFail);
|
||||
});
|
||||
|
||||
console.log(`[fetchAllReviewsWithSequentialPaging] 📄 페이지 ${pageNo} 응답:`, {
|
||||
pageNo,
|
||||
reviewListLength: response.data?.data?.reviewDetail?.reviewList?.length || 0,
|
||||
totRvwCnt: response.data?.data?.reviewDetail?.totRvwCnt,
|
||||
rvwListCnt: response.data?.data?.reviewDetail?.rvwListCnt
|
||||
});
|
||||
|
||||
// 3. 응답 데이터 추출
|
||||
const reviewData = extractReviewListApiData(response.data);
|
||||
|
||||
if (!reviewData || !reviewData.reviewList) {
|
||||
console.warn('[fetchAllReviewsWithSequentialPaging] ⚠️ 리뷰 데이터 추출 실패, 페이징 종료');
|
||||
break;
|
||||
}
|
||||
|
||||
// 4. 첫 페이지에서 reviewDetail 저장 (totRvwCnt, totRvwAvg 등 포함)
|
||||
if (pageNo === 1) {
|
||||
currentReviewDetail = reviewData.reviewDetail;
|
||||
}
|
||||
|
||||
// 5. 현재 페이지의 리뷰들을 전체 리스트에 추가
|
||||
allReviews = allReviews.concat(reviewData.reviewList);
|
||||
|
||||
console.log(`[fetchAllReviewsWithSequentialPaging] ✅ 페이지 ${pageNo} 수집 완료:`, {
|
||||
pageNo,
|
||||
currentPageCount: reviewData.reviewList.length,
|
||||
totalCollected: allReviews.length,
|
||||
totRvwCnt: currentReviewDetail?.totRvwCnt
|
||||
});
|
||||
|
||||
// 6. 페이징 종료 조건 확인
|
||||
// rvwListCnt < pageSize이면 마지막 페이지
|
||||
// 또는 받은 리뷰 수가 총 리뷰 수와 같으면 종료
|
||||
const receivedCount = reviewData.reviewList.length;
|
||||
const totalReviews = currentReviewDetail?.totRvwCnt || 0;
|
||||
|
||||
if (receivedCount < pageSize || allReviews.length >= totalReviews) {
|
||||
hasMore = false;
|
||||
console.log('[fetchAllReviewsWithSequentialPaging] 📊 페이징 종료:', {
|
||||
reason: receivedCount < pageSize ? '받은 개수 < pageSize' : '수집된 개수 >= 총 개수',
|
||||
receivedCount,
|
||||
pageSize,
|
||||
totalCollected: allReviews.length,
|
||||
totalReviews
|
||||
});
|
||||
} else {
|
||||
pageNo++;
|
||||
}
|
||||
}
|
||||
params.filterTpVal = filterTpVal;
|
||||
|
||||
// 7. 모든 리뷰 수집 완료, Redux에 디스패치
|
||||
console.log('[fetchAllReviewsWithSequentialPaging] 🎉 모든 리뷰 수집 완료:', {
|
||||
totalCollected: allReviews.length,
|
||||
totRvwCnt: currentReviewDetail?.totRvwCnt,
|
||||
pages: pageNo - 1
|
||||
});
|
||||
|
||||
// Redux 디스패치를 위한 최종 데이터 구성
|
||||
const isAllFilter = filterTpCd === 'ALL';
|
||||
const actionType = isAllFilter ? types.GET_USER_REVIEW_LIST : types.GET_FILTERED_REVIEW_LIST;
|
||||
|
||||
const finalPayload = {
|
||||
reviewList: allReviews,
|
||||
reviewDetail: currentReviewDetail,
|
||||
prdtId,
|
||||
...(isAllFilter ? {} : { filterTpCd, filterTpVal })
|
||||
};
|
||||
|
||||
const action = {
|
||||
type: actionType,
|
||||
payload: finalPayload
|
||||
};
|
||||
|
||||
console.log('[fetchAllReviewsWithSequentialPaging] 📦 Redux 디스패치:', {
|
||||
actionType,
|
||||
totalReviews: allReviews.length,
|
||||
totRvwCnt: currentReviewDetail?.totRvwCnt,
|
||||
prdtId
|
||||
});
|
||||
|
||||
dispatch(action);
|
||||
|
||||
return finalPayload;
|
||||
} catch (error) {
|
||||
console.error('[fetchAllReviewsWithSequentialPaging] ❌ 에러 발생:', {
|
||||
error: error.message,
|
||||
prdtId,
|
||||
pageNo,
|
||||
currentCollected: allReviews.length
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const body = {};
|
||||
// User Review List 추가 조회 IF-LGSP-101 (순차 페이징으로 모든 데이터 수집)
|
||||
export const getUserReviewList = (requestParams) => async (dispatch, getState) => {
|
||||
const {
|
||||
prdtId,
|
||||
patnrId,
|
||||
filterTpCd = 'ALL',
|
||||
filterTpVal
|
||||
} = requestParams;
|
||||
|
||||
console.log('[UserReviewList] 🚀 API 요청 시작:', {
|
||||
requestParams,
|
||||
params,
|
||||
body,
|
||||
url: URLS.GET_USER_REVIEW_LIST,
|
||||
console.log('[getUserReviewList] 🚀 getUserReviewList 호출됨 (순차 페이징 사용):', {
|
||||
prdtId,
|
||||
patnrId,
|
||||
filterTpCd,
|
||||
filterTpVal,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log('[UserReviewList] ✅ API 성공 응답:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
retCode: response.data && response.data.retCode,
|
||||
retMsg: response.data && response.data.retMsg,
|
||||
hasData: !!(response.data && response.data.data),
|
||||
fullResponse: response.data,
|
||||
try {
|
||||
// fetchAllReviewsWithSequentialPaging 함수를 호출하여 모든 리뷰 수집
|
||||
const result = await fetchAllReviewsWithSequentialPaging(dispatch, getState, requestParams);
|
||||
|
||||
console.log('[getUserReviewList] ✅ 모든 리뷰 수집 완료:', {
|
||||
totalReviews: result.reviewList.length,
|
||||
totRvwCnt: result.reviewDetail?.totRvwCnt,
|
||||
prdtId
|
||||
});
|
||||
|
||||
const reviewData = extractReviewListApiData(response.data);
|
||||
|
||||
console.log('[UserReviewList] 📊 추출된 리뷰 데이터:', {
|
||||
hasData: !!reviewData,
|
||||
reviewListLength: reviewData && reviewData.reviewList ? reviewData.reviewList.length : 0,
|
||||
reviewDetail: reviewData && reviewData.reviewDetail,
|
||||
totRvwCnt: reviewData && reviewData.reviewDetail && reviewData.reviewDetail.totRvwCnt,
|
||||
totRvwAvg: reviewData && reviewData.reviewDetail && reviewData.reviewDetail.totRvwAvg,
|
||||
} catch (error) {
|
||||
console.error('[getUserReviewList] ❌ 순차 페이징 중 에러 발생:', {
|
||||
error: error.message,
|
||||
prdtId,
|
||||
patnrId
|
||||
});
|
||||
|
||||
if (reviewData) {
|
||||
// filterTpCd가 'ALL'이면 GET_USER_REVIEW_LIST, 아니면 GET_FILTERED_REVIEW_LIST 디스패치
|
||||
const isAllFilter = filterTpCd === 'ALL';
|
||||
const actionType = isAllFilter ? types.GET_USER_REVIEW_LIST : types.GET_FILTERED_REVIEW_LIST;
|
||||
|
||||
console.log('[UserReviewList] 🔴 dispatch 직전 상태:', {
|
||||
actionType: actionType,
|
||||
typeValue: isAllFilter ? 'GET_USER_REVIEW_LIST' : 'GET_FILTERED_REVIEW_LIST',
|
||||
filterTpCd,
|
||||
filterTpVal,
|
||||
reviewListLength: reviewData.reviewList.length,
|
||||
prdtId: prdtId
|
||||
});
|
||||
|
||||
const action = {
|
||||
type: actionType,
|
||||
payload: {
|
||||
...reviewData, // reviewList + reviewDetail 모두 포함
|
||||
prdtId: prdtId,
|
||||
// GET_FILTERED_REVIEW_LIST인 경우 filterTpCd, filterTpVal 포함
|
||||
...(isAllFilter ? {} : { filterTpCd, filterTpVal })
|
||||
},
|
||||
};
|
||||
|
||||
console.log('[UserReviewList] 🟡 dispatch할 액션:', JSON.stringify(action, null, 2));
|
||||
|
||||
dispatch(action);
|
||||
|
||||
console.log('[UserReviewList] 📦 데이터 디스패치 완료:', {
|
||||
reviewListLength: reviewData.reviewList.length,
|
||||
reviewDetail: reviewData.reviewDetail,
|
||||
prdtId,
|
||||
actionType,
|
||||
filterInfo: isAllFilter ? '(ALL 필터)' : `(${filterTpCd}: ${filterTpVal})`
|
||||
});
|
||||
} else {
|
||||
console.warn('[UserReviewList] ⚠️ 리뷰 데이터 추출 실패');
|
||||
}
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error('[UserReviewList] ❌ API 실패:', {
|
||||
message: error.message,
|
||||
status: error.response && error.response.status,
|
||||
statusText: error.response && error.response.statusText,
|
||||
responseData: error.response && error.response.data,
|
||||
requestParams: requestParams,
|
||||
params: params,
|
||||
url: URLS.GET_USER_REVIEW_LIST,
|
||||
fullError: error,
|
||||
});
|
||||
};
|
||||
|
||||
TAxios(dispatch, getState, 'get', URLS.GET_USER_REVIEW_LIST, params, body, onSuccess, onFail);
|
||||
}
|
||||
};
|
||||
|
||||
// Review Filters 추출 함수 (IF-LGSP-100)
|
||||
|
||||
Reference in New Issue
Block a user