docs: Redis bi-gram 추천 검색어 시스템 + 오늘 작업 요약 추가

- 섹션 14: 추천 검색어 아키텍처, API, Redis 구조, 프론트엔드 UI
- 섹션 15: 2026-01-11 커밋 히스토리 및 주요 변경 사항
This commit is contained in:
caadiq 2026-01-11 23:22:24 +09:00
parent 5a5e601f63
commit 3d7e8e1c2f

View file

@ -521,3 +521,170 @@ useEffect(() => {
{/* 내용 */}
</motion.div>
```
---
## 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 && (
<div className="absolute top-full left-0 right-0 bg-white border rounded-lg shadow-lg z-50">
{suggestions.map((s, i) => (
<button
key={i}
onClick={() => handleSuggestionClick(s)}
className="w-full px-4 py-2 text-left hover:bg-gray-100"
>
{s}
</button>
))}
</div>
);
}
```
#### 모바일 - 유튜브 스타일 리스트
```jsx
// 검색창 아래 전체 화면 리스트
{
showSuggestions && suggestions.length > 0 && (
<div className="absolute inset-x-0 top-12 bottom-0 bg-white z-50">
{suggestions.map((s, i) => (
<button
key={i}
onClick={() => handleSuggestionClick(s)}
className="w-full px-4 py-3 flex items-center gap-3 border-b"
>
<Search size={16} className="text-gray-400" />
<span>{s}</span>
</button>
))}
</div>
);
}
```
### 키워드 일괄 추출 스크립트
```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. **카테고리 보호**: 시스템 기본 카테고리 삭제 방지