diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index 17f2265..411cfef 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -521,3 +521,170 @@ useEffect(() => { {/* 내용 */} ``` + +--- + +## 14. Redis 기반 Bi-gram 추천 검색어 시스템 + +### 아키텍처 개요 + +```mermaid +graph LR + User[사용자 검색] --> API[/api/schedules/suggestions] + API --> Redis[(Redis)] + Redis --> |bi-gram 매칭| Results[추천 검색어] + + Admin[관리자 일정 CRUD] --> Extract[키워드 추출] + Extract --> Redis +``` + +### 주요 파일 + +| 파일 | 설명 | +| ----------------------------------------------- | ---------------------------------------------- | +| `backend/routes/schedules.js` | 추천 검색어 API (`/api/schedules/suggestions`) | +| `backend/scripts/extract-keywords.js` | 기존 일정에서 키워드 일괄 추출 스크립트 | +| `frontend/src/pages/pc/admin/Schedule.jsx` | 관리자 검색창 드롭다운 | +| `frontend/src/pages/pc/public/Schedule.jsx` | PC 검색 추천 UI | +| `frontend/src/pages/mobile/public/Schedule.jsx` | 모바일 유튜브 스타일 추천 리스트 | + +### Redis 데이터 구조 + +``` +fromis9:search:suggestions (Sorted Set) +├── "쇼케이스" → score: 15 +├── "팬미팅" → score: 12 +├── "라디오" → score: 8 +└── ... + +fromis9:search:bigrams (Hash) +├── "쇼케" → "쇼케이스,쇼케이스투어" +├── "케이" → "쇼케이스,케이팝" +└── ... +``` + +### API 엔드포인트 + +```javascript +// GET /api/schedules/suggestions?q=쇼케 +// Response: ["쇼케이스", "쇼케이스 투어", ...] + +router.get("/suggestions", async (req, res) => { + const query = req.query.q?.trim(); + if (!query || query.length < 2) return res.json([]); + + // bi-gram 매칭 + const bigram = query.slice(0, 2); + const cached = await redis.hget("fromis9:search:bigrams", bigram); + + if (cached) { + const keywords = cached + .split(",") + .filter((k) => k.toLowerCase().includes(query.toLowerCase())) + .slice(0, 10); + return res.json(keywords); + } + + res.json([]); +}); +``` + +### 키워드 추출 로직 (일정 저장 시) + +```javascript +// admin.js - 일정 저장 시 키워드 추출 +const extractKeywords = (title) => { + // 특수문자 제거, 공백으로 분리 + const words = title.replace(/[^\w\s가-힣]/g, " ").split(/\s+/); + return words.filter((w) => w.length >= 2); +}; + +// Redis에 저장 +for (const keyword of keywords) { + await redis.zincrby("fromis9:search:suggestions", 1, keyword); + + // bi-gram 인덱스 + for (let i = 0; i < keyword.length - 1; i++) { + const bigram = keyword.slice(i, i + 2); + const existing = await redis.hget("fromis9:search:bigrams", bigram); + const set = new Set(existing ? existing.split(",") : []); + set.add(keyword); + await redis.hset("fromis9:search:bigrams", bigram, [...set].join(",")); + } +} +``` + +### 프론트엔드 UI + +#### PC 관리자/공개 페이지 - 드롭다운 + +```jsx +// 검색창 아래 드롭다운 +{ + suggestions.length > 0 && ( +
+ {suggestions.map((s, i) => ( + + ))} +
+ ); +} +``` + +#### 모바일 - 유튜브 스타일 리스트 + +```jsx +// 검색창 아래 전체 화면 리스트 +{ + showSuggestions && suggestions.length > 0 && ( +
+ {suggestions.map((s, i) => ( + + ))} +
+ ); +} +``` + +### 키워드 일괄 추출 스크립트 + +```bash +# 기존 일정에서 키워드 추출하여 Redis에 저장 +cd /docker/fromis_9/backend +node scripts/extract-keywords.js +``` + +--- + +## 15. 오늘 작업 요약 (2026-01-11) + +### 커밋 히스토리 + +| 커밋 | 설명 | +| --------- | ------------------------------------------------------- | +| `727b05f` | Redis 기반 bi-gram 추천 검색어 시스템 구현 | +| `9c2ff74` | Admin Schedule 추천 검색어 연동 + 빈 상태 드롭다운 숨김 | +| `2ad5341` | Mobile Schedule 추천 검색어 API 연동 | +| `8e3cab9` | 기본 카테고리 보호 및 ID 재정렬 | +| `de2e02f` | 모바일 앨범 상세 UI 개선 | +| `d6bc8d7` | 모바일 앨범 갤러리 UI 대폭 개선 (Swiper, 뒤로가기 등) | + +### 주요 변경 사항 + +1. **추천 검색어 시스템**: Redis bi-gram 기반 자동완성 +2. **모바일 앨범 갤러리**: Swiper ViewPager + LightboxIndicator +3. **뒤로가기 처리**: History API로 모달/라이트박스 닫기 +4. **카테고리 보호**: 시스템 기본 카테고리 삭제 방지