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>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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