docs: Redis bi-gram 추천 검색어 시스템 + 오늘 작업 요약 추가
- 섹션 14: 추천 검색어 아키텍처, API, Redis 구조, 프론트엔드 UI - 섹션 15: 2026-01-11 커밋 히스토리 및 주요 변경 사항
This commit is contained in:
parent
5a5e601f63
commit
3d7e8e1c2f
1 changed files with 167 additions and 0 deletions
|
|
@ -521,3 +521,170 @@ useEffect(() => {
|
||||||
{/* 내용 */}
|
{/* 내용 */}
|
||||||
</motion.div>
|
</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. **카테고리 보호**: 시스템 기본 카테고리 삭제 방지
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue