검색 K
밝은/어두운 배경
밝은/어두운 배경
게시물 정보
각 파일의 책임 범위와 수정 시 주의사항을 설명합니다.
index.ts (파이프라인 조율)
│
├── config/_shared.ts [설정값 상수]
│
├── feedRepository.ts [DB 전담]
│ └── @neondatabase/serverless
│
├── fetchService.ts [HTTP 전담]
│
├── parserService.ts [XML 파싱]
│ └── feedItemExtractor.ts [필드 추출 순수 함수]
│ └── fast-xml-parser
│
├── jsonStoreService.ts [JSON 스토어]
│ └── utils/fileUtils.ts
│
├── markdownWriter.ts [Markdown 파일 I/O]
│ └── markdownAssembler.ts [Markdown 문자열 조립]
│
├── cleanupService.ts [고아 파일 정리]
│ └── feedRepository.ts
│
├── dateUtils.ts [시간 표현 통합]
└── logger.ts [로그 버퍼링]index.ts — 파이프라인 조율 전체 실행 흐름을 순서대로 조율합니다. 비즈니스 로직은 갖지 않으며 각 모듈에 위임합니다.
주요 역할
acquireScraperLock / releaseScraperLock)p-limit으로 도메인 단위 병렬 처리 제어new_posts_count.txt 출력수정 시 주의: 병렬 처리 로직(pLimit)과 스토어 캐시(storeCache) 공유 방식을 변경할 때는 동시성 문제에 유의하세요.
⚠ TODO:
processFeed실패 시 반환값이0이나failedCount집계 조건이-1로 되어 있어 실패 카운트가 항상 0으로 집계됩니다. 확인 필요.
feedRepository.ts — DB 전담 registered_feeds와 scraper_lock 테이블에 대한 모든 SQL을 이 파일에서만 작성합니다. index.ts나 다른 모듈에서 직접 SQL을 작성하지 않습니다.
제공 함수
| 함수 | 설명 |
|---|---|
fetchActiveFeeds() | active = true 피드 전체 조회 |
fetchInactiveFeeds() | active = false AND withdrawn_at IS NOT NULL 피드 조회 |
markFeedFailure(url, folderName) | consecutive_failures +1, last_failure_at 갱신 |
markFeedSuccess(url, folderName) | consecutive_failures 0으로 초기화, last_success_at 갱신 |
acquireScraperLock() | scraper_lock.locked = true 설정 |
releaseScraperLock() | scraper_lock.locked = false 설정 |
isScraperLocked() | 현재 락 상태 조회 |
수정 시 주의: DB 스키마 변경 시 rowToConfig() 매핑 함수도 함께 수정해야 합니다.
fetchService.ts — HTTP 전담 외부 RSS 서버에 HTTP GET 요청을 보내고 XML 문자열을 반환합니다. 로그 출력을 하지 않으며, 재시도 이벤트는 콜백(onRetry)으로 호출 측에 위임합니다.
재시도 정책
1회 시도 실패 → 1.5초 대기 → 2회 시도
2회 시도 실패 → 3.0초 대기 → 3회 시도
3회 시도 실패 → 예외 throw수정 시 주의: 쿠키 유지(cookieJar) 로직이 있습니다. 일부 피드 서버는 세션 쿠키를 요구하므로 헤더 수정 시 이 부분을 확인하세요.
parserService.ts — XML 파싱 fast-xml-parser로 RSS/Atom XML을 FeedItem[]으로 변환하고, config.scope에 따라 날짜 필터링을 적용합니다.
수정 시 주의: maxNestedTags: 3000은 복잡한 RSS 피드 대응을 위한 설정입니다. 파서 옵션 변경 시 기존 피드가 깨지지 않는지 확인하세요.
feedItemExtractor.ts — 필드 추출 순수 함수 FeedItem에서 각 필드를 추출하는 함수만 모아 놓은 파일입니다. 외부 의존성이 없으며 단독 테스트가 가능합니다.
제공 함수
| 함수 | 설명 |
|---|---|
extractLink(item) | guid → link 순으로 시도. HTTP로 시작하지 않으면 # 반환 |
extractPubDate(item) | pubDate → published → updated → dc:date 순으로 시도 |
extractAuthor(item) | dc:creator → author.name → author 문자열 순으로 시도 |
extractCategory(item) | 배열이면 ,로 join |
extractTags(item) | tag/tags 콤마 구분 또는 category 배열 사용 |
extractImage(htmlContent) | HTML에서 첫 번째 <img src> 추출 |
extractFullText(item) | contentSnippet → content → description 순. HTML 태그 제거 |
| pageClass: sysDocs |
수정 시 주의: 새로운 RSS 피드 포맷을 지원해야 할 때 이 파일을 수정합니다. 각 함수는 독립적이므로 필요한 함수만 수정하면 됩니다.
jsonStoreService.ts — JSON 스토어 JSON 파일의 읽기·쓰기와 PostRecord 생성을 담당합니다. 정렬은 insertIfNotExists 내에서만 수행합니다.
핵심 동작
load(): 파일이 없으면 빈 FeedStore 반환 (예외 미발생)insertIfNotExists(): post.link 기준 중복 확인 → 삽입 → pubDate 내림차순 정렬createRecord(): sha256(feedUrl:link) 앞 8자로 id 생성수정 시 주의: PostRecord에 필드를 추가하면 createRecord()와 types.ts를 함께 수정해야 합니다.
markdownAssembler.ts — Markdown 문자열 조립 파일 I/O 없이 Markdown 문자열만 생성합니다. 출력 형식을 변경할 때 이 파일을 수정합니다.
제공 함수
| 함수 | 설명 |
|---|---|
buildFrontmatter() | YAML frontmatter + 게시물 정보 블록 |
buildPostBlock() | 포스트 1건의 Markdown 블록 |
buildMarkdownBody() | frontmatter + 로고 + 전체 포스트 블록 조합 |
FOLDER_INDEX_TEMPLATE | index.md 초기 내용 |
YEAR_INDEX_TEMPLATE | 연도별 index.md 초기 내용 |
수정 시 주의: <Img>, :::tip, :::info 등은 VitePress 커스텀 컴포넌트 문법입니다. 이 컴포넌트가 VitePress 설정에 등록되어 있어야 정상 렌더링됩니다.
markdownWriter.ts — Markdown 파일 I/O Markdown 문자열을 파일에 쓰는 작업만 담당합니다. 문자열 생성은 markdownAssembler.ts에 위임합니다.
제공 함수
| 함수 | 설명 |
|---|---|
ensureFolderIndexFile() | src/{folderName}/index.md 없을 때만 생성 |
ensureYearIndexFile() | src/{folderName}/{year}/index.md 없을 때만 생성 |
rebuildMarkdown() | posts가 비어 있으면 no-op. 있으면 전체 재작성 |
cleanupService.ts — 고아 파일 정리 철회된 피드의 Markdown 파일과 JSON 파일을 삭제하고, 빈 디렉토리를 정리합니다.
⚠ TODO:
removeJsonFiles()내부에서srcDir변수를 참조하는 코드가 있으나 해당 함수 스코프에srcDir이 정의되지 않았습니다. 확인 및 수정 필요.
삭제 보호 조건: 동일 RSS URL을 참조하는 active 피드가 하나라도 있으면 JSON 파일은 삭제하지 않습니다.
dateUtils.ts — 시간 표현 통합 UTC_OFFSET_HOURS 하나를 기준으로 모든 시간 문자열을 생성합니다. 시간 관련 코드를 수정할 때는 반드시 이 파일의 함수를 사용하고, new Date()를 직접 포맷하지 마세요.
| 함수 | 출력 예시 | 용도 |
|---|---|---|
nowISO() | 2026-06-03T14:22:01.000+09:00 | JSON 필드 |
nowFrontmatter() | 2026-06-03T14:22:01.000+09:00 | Markdown frontmatter |
nowLog() | 2026-06-03 14:22:01 UTC+09:00 | 로그 출력 |
startTimer() | { elapsedSec: () => "4.2" } | 경과 시간 측정 |
logger.ts — 로그 버퍼링 병렬 처리 시 여러 피드의 로그가 섞이지 않도록 버퍼에 쌓았다가 피드 처리 완료 시 한꺼번에 출력합니다.
로그 태그 의미
| 태그 | 의미 |
|---|---|
[INIT] | index.md 생성 등 초기화 작업 |
[FETCH] | RSS XML 수신 시도 및 결과 |
[STORE] | JSON 스토어 집계 결과 |
[BUILD] | Markdown 파일 생성 결과 |
[SKIP] | 중복 경로 등으로 건너뜀 |
[CLEAN] | 고아 파일 삭제 (glog.msg 직접 출력) |
플러시 형식
── [FEED 3/12] 환경부 공지사항 (www.me.go.kr) ── SUCCESS
[FETCH] → https://...
[FETCH] ✓ 200 OK
[STORE] 파싱 15건 → 당월 8건 → 신규 +3건 (누적 42건)
[BUILD] ✓ src/policy/2026/06/환경부 공지사항.mdtypes.ts — 타입 정의 시스템 전체에서 사용하는 타입을 한 곳에서 관리합니다. 새로운 필드나 타입을 추가할 때 이 파일을 먼저 수정하고, 관련 모듈을 함께 수정합니다.
핵심 타입 목록
| 타입 | 설명 |
|---|---|
RssFeedConfig | DB 행에서 변환된 피드 설정 |
JsonStoreConfig | _shared.ts에서 로드되는 스토어 설정 |
FeedStore | 월별 JSON 파일 최상위 구조 |
PostRecord | 포스트 1건의 전체 데이터 |
ParsedFields | 파서가 추출한 원본 필드 (PostRecord의 서브셋) |
FeedItem | fast-xml-parser 파싱 결과의 raw 타입 |
ScrapScope | 'all' | 'weekly' | '3days' | 'today' |
ContentStatus | 'none' | 'pending' | 'fetched' | 'failed' |