ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
전국 공공도서관 통합 검색 도구입니다. 자주 가는 도서관을 미리 골라 두면, 읽고 싶은 책이 그중 어디에 있는지 한 번의 검색으로 알려 줍니다. 상세 정보와 대출 신청은 해당 도서관 페이지로 넘깁니다. 읽고 싶은 책을 정해 두고도 도서관 홈페이지를 하나씩 열어서 따로 검색해야 하는 상황을 없애려고 만들었습니다. 한 번 방문해서 여러 권을 함께 빌리려 하면 확인할 것이 권수와 도서관 수를 곱한 만큼 늘어나기 때문에, 그 확인 과정이 사실상 매번 포기하게 되는 지점입니다. 두 가지는 의도적으로 하지 않습니다. 성능이나 구현 난이도 때문이 아니라 제품 결정입니다.README 전문 보기 ↗
전국 공공도서관 통합 검색 도구입니다. 자주 가는 도서관을 미리 골라 두면, 읽고 싶은 책이 그중 어디에 있는지 한 번의 검색으로 알려 줍니다. 상세 정보와 대출 신청은 해당 도서관 페이지로 넘깁니다.
읽고 싶은 책을 정해 두고도 도서관 홈페이지를 하나씩 열어서 따로 검색해야 하는 상황을 없애려고 만들었습니다. 한 번 방문해서 여러 권을 함께 빌리려 하면 확인할 것이 권수와 도서관 수를 곱한 만큼 늘어나기 때문에, 그 확인 과정이 사실상 매번 포기하게 되는 지점입니다.
두 가지는 의도적으로 하지 않습니다. 성능이나 구현 난이도 때문이 아니라 제품 결정입니다.
대출 가능 여부를 목록에 미리 달지 않습니다. 제공되는 대출 상태 데이터는 조회일 전날 기준이라 실시간이 아닙니다. 부정확한 상태를 믿고 헛걸음하는 것이 이런 도구를 못 쓰게 만드는 가장 큰 요인입니다. 그래서 「대출 상태 확인」을 눌렀을 때만 물어보고, 답에는 언제 기준인지(어제 날짜)를 항상 함께 적습니다. 한 권 검색은 소장 도서관마다 단추가 있고, 여러 권 검색은 도서관 줄에서 그 도서관에 있는 책 전부의 대출 상태를 한 번에 물어보는데, 그것도 누를 때만, 그 도서관에 있는 책 수만큼만 부릅니다.
"미소장"과 "확인 불가"를 섞지 않습니다. 소장 조회는 외부 호출이라 실패할 수 있는데, 그 실패를 미소장으로 표시하면 실제로 있는 책을 없다고 답하게 됩니다. 화면에서 두 상태는 문구도 배경색도 다르고, 확인하지 못한 책을 미소장 집계에 넣지 않습니다.
실제로 배포되어 돌고 있습니다. 인증키가 승인되었고, Google Compute Engine 의 무료 등급 VM 한 대에 API 서버를 올려 HTTPS 까지 붙였습니다. 화면은 Vercel 에 있습니다.
서지 응답의 실제 모양도 확인했습니다. authors 는 가정대로 지음 ; 옮김 형태로 오지만,
구분 기호 주변의 공백이 제각각이었습니다. 같은 책의 다른 판이 칼 세이건 지음 ;홍승수 옮김
으로도 칼 세이건 지음;홍승수 옮김 으로도 옵니다. 그래서 구분 기호를 " : " 처럼 양쪽
공백으로 찾으면 안 되고, 한쪽에만 공백이 있어도 잡아야 합니다. 실제로 받은 문자열은
RealResponseShapeTest 가 그대로 고정해 두었습니다.
키가 활성화되기 전에 받았던 오류 응답 하나가 설계를 무너뜨릴 뻔한 것을 드러냈습니다. 정보나루는 오류도 HTTP 200
으로 돌려주는데, 그 본문에는 lib 도 doc 도 없습니다. 상태 코드만 보고 넘기면 빈 목록이
되고 화면은 그것을 "미소장"으로 그립니다. 실행해 보지 않으면 절대 드러나지 않는
종류였습니다.
무엇이 검증되었고 무엇이 아직 추측인지는 docs/가정과-검증상태.md
에 정리해 두었습니다. API 응답을 본 적 없이 만든 부분이 있으므로 그것을 확인된 사실처럼
다루면 안 됩니다.
| 영역 | 상태 |
|---|---|
| 도서관 선택 화면 (지역 계층, 이름 검색, 내 주변 5km, 고른 목록에서 한 곳씩 해제, 많이 고르면 지역으로 묶어 보기) | 실제 브라우저에서 동작 확인 |
| 세 상태 체크박스, URL 공유, 브라우저 저장 | 동작 확인 |
| 한 권 검색 화면과 네 가지 소장 상태 표시 | 실제 응답으로 동작 확인 |
| 띄어쓰기가 다른 검색어의 결과 통일 (「레미제라블」과 「레 미제라블」) | 실제 응답으로 확인. 두 검색이 같은 135개 ISBN 을 같은 순서로 돌려줌 |
| 소장 조회의 지역을 정보나루 소속으로 묻기 | 실제 응답으로 확인. 주소에서 시도를 못 읽던 고양시립 2곳이 소장 조회에 잡힘 |
| 대출 상태 (누를 때만, 전날 기준 날짜와 함께) | 실제 응답으로 동작 확인 (한 권 검색). 여러 권 검색 쪽은 같은 부품을 쓰며 미검증 |
| 여러 권 검색 (줄 해석, 도서관별 결과, 전부 가진 도서관 표시) | 실제 응답으로 한 줄 확인. 첫 후보가 한 권 검색의 첫 저작과 같았습니다(코스모스, 사이언스북스 판). 서른 줄짜리 실제 목록은 미검증 — 호출을 몇 번 쓰고 얼마나 걸리는지 재 본 적이 없습니다 |
| 서가 (청구기호 순으로 세운 원목 서가, 초성 색인, 이 책 주변 서가, 제목·저자로 자리 찾기, 갈래로 구역 이동) | 가짜 장서 1,200권으로 화면과 정렬 확인. 실제 도서관으로 세워 본 적은 없습니다 — pageSize 상한, pageNo 깊이, callNumbers 가 실제로 얼마나 차는지가 미검증입니다 |
| 서가 순서 규칙 (자료실 → 별치 → 분류번호 → 도서기호 → 권차 → 복본) | 단위 시험 28개 통과. 열쇠와 비교자가 같은 순서를 내는지 섞어 가며 200번 견줍니다 |
| 서가 안에서 책 찾기 (제목·저자로 그 책이 선 자리로 이동) | 실제 도서관 장서 12,582권으로 확인. 「토지」가 대하소설 1·2·3권을 나란한 자리로, 띄어쓰기가 달라도 같은 결과. 정보나루 호출 0건 |
| 갈래로 서가 구역 이동 (분류명으로 그 갈래가 선 자리로) | 실제 도서관 장서 7,072권으로 확인. 갈래 63가지의 권수가 실제 조각과 하나도 어긋나지 않았고, 걸어갈 만한 29가지만 목록에 올립니다. 정보나루 호출 0건 |
| 자리를 옮길 때 미끄러지는 움직임 (거리에 따라 280~1,100ms) | 브라우저에서 스크롤을 40ms 간격으로 지켜봐 되돌아간 프레임 0 확인. prefers-reduced-motion 은 건너뜁니다 |
| 서지 정규화, 저작 묶기, 색인 생성과 검증 | 테스트 통과 (실제 서지로는 미검증) |
| DB 스키마와 색인 전환 | 실제 PostgreSQL 에 적용해 제약까지 확인 |
| 도서관 마스터 조회 | 실제로 전국 목록을 받아 화면에 그림 |
| 오류 응답 처리 | 실제로 받은 본문으로 확인 (HTTP 200 오류, XML·JSON 양쪽) |
| 배포 | 실제로 돌고 있음. GCE 무료 등급 VM + Caddy(HTTPS), 프론트는 Vercel |
| 서지 응답의 필드 모양 | 실제 응답으로 확인. 구분 기호 주변 공백이 제각각이라 한쪽만 있어도 잡습니다 |
| 주소별 호출 제한 (분당·하루, IPv6 는 /64 로 묶음) | 테스트 통과. 실제 트래픽으로는 미검증 |
| 공유 링크 미리보기(OG) 와 개인정보처리방침 페이지 | 만들어 붙였습니다. 미리보기는 실제 공유로 미검증 |
1차 범위에서는 정보나루 인증키 하나만 있으면 됩니다. 매뉴얼을 확인한 결과 도서관 마스터(위경도 포함), 서지, 소장 도서관을 정보나루 하나가 모두 제공하기 때문에, 국립중앙도서관 서지정보 API 와 공공데이터포털은 나중에 서지 품질을 보강할 때 붙이면 됩니다.
cp .env.example .env
# .env 의 D4L_AUTH_KEY 를 채웁니다. 이 파일은 커밋하지 않습니다.
인증키 신청 절차는 docs/0단계-체크리스트.md 에 있습니다.
인증키를 받는 것과 서버 IP 를 등록하는 것은 한 묶음으로 처리해야 합니다. 등록하지
않으면 1일 호출 한도가 30,000건이 아니라 500건으로 조용히 적용됩니다.
cd backend
set -a; . ../.env; set +a # .env 를 환경 변수로 올립니다
./gradlew bootRun # http://localhost:8080
D4L_AUTH_KEY 가 비어 있으면 서버가 그 사실을 말하고 뜨지 않습니다. 키 없이 조용히
떠서 모든 조회가 실패하는 것보다 낫기 때문입니다.
cd frontend
npm install
npm run dev # http://localhost:5173
API 서버가 없어도 화면은 뜹니다. 이때는 샘플 도서관 24곳으로 선택 화면만 동작하고, 샘플이라는 사실을 화면 위쪽에 밝힙니다. 책 검색은 서버가 있어야 합니다.
cd backend && ./gradlew test # 정규화, 군집화, 색인, 어댑터, 정보나루 응답 파싱, 호출 제한
cd frontend && npm test # 선택 상태 계산, URL 인코딩, 사이트 주소 일관성
./scripts/verify-schema.sh # 마이그레이션과 스키마 제약 (루트가 아닌 계정으로)
카카오톡이나 슬랙에 링크를 붙였을 때 보이는 그림(frontend/public/og.png)입니다.
cd frontend && node tools/make-og-image.mjs
원본은 frontend/tools/og-image.html 이고, 위 명령이 헤드리스 크로미움으로 그립니다.
크로미움을 직접 부르지 마세요. 요청한 창 높이보다 85px 적게 그리면서 남는 자리를
배경색으로 채우기 때문에, 아래쪽에 둔 것이 잘린 줄도 모르고 사라집니다. 그 보정이
스크립트에 들어 있습니다. 한글 글꼴이 없는 기계에서 만들면 글자가 네모로 나오므로 만든
뒤에는 눈으로 확인하세요.
서지 → BibNormalizer → WorkMatcher → WorkClusterer → SearchDocBuilder → IndexVerifier
↓
검색어 → 저작 → isbn13_list 전체 → libSrchByBook (시도별) → 소장 도서관 → 고른 곳과 교집합
| 위치 | 내용 |
|---|---|
backend/src/main/java/kr/wimb/bib | 정규화, ISBN 처리, 저자 대조, 판정 규칙(R0~R7), 군집화, work_id 승계 |
backend/src/main/java/kr/wimb/index | 색인 문서 생성과 전환 전 검증 |
backend/src/main/java/kr/wimb/holdings | 소장 조회. 미소장과 확인 불가를 타입에서부터 구분합니다 |
backend/src/main/java/kr/wimb/data4library | 외부 API 형식을 아는 유일한 곳입니다 |
backend/src/main/java/kr/wimb/ingest | 어댑터 계약, 호출 예산 원장, HTTP 재시도와 간격 제어 |
backend/src/main/java/kr/wimb/api | Spring Boot 컨트롤러와 검색 서비스 |
backend/src/main/resources/db/migration | PostgreSQL 스키마 |
backend/src/main/java/kr/wimb/query | 붙여넣은 목록의 줄 해석. 외부 호출이 없습니다 |
backend/src/main/java/kr/wimb/shelf | 서가. 청구기호 조립과 정렬, 장서 수집, 언제 세울지 |
frontend/src | 도서관 선택, 한 권 검색, 여러 권 검색, 서가 화면 |
프론트(Vercel). 앱 밖의 정적 파일은 자바스크립트가 돌지 않아도 열립니다.
| 주소 | 무엇 |
|---|---|
/ | 앱. 고른 도서관이 ?libs= 로 붙어 그대로 공유됩니다 |
/privacy | 개인정보처리방침. vercel.json 이 /privacy.html 로 넘깁니다 |
/robots.txt, /sitemap.xml | 크롤러 규칙과 주소 목록 |
/og.png | 공유 미리보기 그림. 원본은 frontend/tools/og-image.html |
API. 소장은 /api/holdings 한 곳에서만 답하고 검색은 소장을 부르지 않습니다.
그 이유는 CLAUDE.md 에 있습니다.
| 주소 | 방식 | 무엇 |
|---|---|---|
/api/libraries | GET | 도서관 마스터. 위경도까지 함께 |
/api/search | GET | 책 검색(제목·저자·출판사·ISBN). 소장은 답하지 않습니다 |
/api/check/resolve | POST | 붙여 넣은 목록의 줄 해석과 책 확정 |
/api/holdings | POST | 저작 하나의 소장 도서관. 판본 전체를 함께 보냅니다 |
/api/loan | GET | 그 도서관의 대출 상태. 어제 기준입니다 |
/api/go/{도서관부호} | GET | 도서관 페이지로 넘김 |
/api/version | GET | 커밋 해시와 빌드 시각 |
/api/status | GET | 도서관 수, 오늘 쓴 호출, 캐시 항목 수, 세고 있는 주소 수 |
/api/diagnose | GET | 나가는 IP 와 정보나루 응답. 열 때마다 호출을 하나 씁니다 |
/robots.txt | GET | 크롤러가 /api/go/… 를 따라다니지 못하게 막습니다 |
주소마다 매긴 호출 무게는 여기에 적지 않습니다. RateLimitInterceptor.costOf 하나가
정본이고, 숫자를 베껴 두면 그것이 바뀔 때 이 표만 조용히 거짓이 됩니다. 이 저장소는 같은
일을 이미 두 번 겪었습니다.
「내 주변」은 반경 5km 안을 가까운 순으로 보여 줍니다. 도서관은 멀리 다니지 않으므로 좁게 잡았습니다. 3km 로 내리면 서울 도심 말고는 대부분 0~1곳이 나오는데, 그러면 아래의 안내가 기본 상태가 되어 아무도 읽지 않게 됩니다.
반경 안에 3곳도 없으면 더 먼 곳까지 보여 주되, 넓혔다는 사실을 화면에 밝힙니다.
빈 목록을 주면 사용자는 그것을 「우리 동네에 도서관이 없다」가 아니라 「이 도구가
고장났다」로 읽습니다. 반대로 잠자코 넓히면 30km 떨어진 곳을 「내 주변」으로 읽고
다녀와서야 멀다는 것을 압니다. 반경은 frontend/src/domain/selection.ts 의
NEARBY_RADIUS_KM 한 곳에 있습니다.
위경도가 없는 도서관은 거리를 잴 수 없어 이 탭에 나오지 않습니다. 목록에서 사라지는 것이 아니라 이름 검색과 지역 계층에서는 그대로 찾힙니다.
서지는 미리 색인하고, 소장은 조회할 때 가져와 캐시에 쌓습니다. 이 두 경로를 혼동하지 마세요. 서지 색인을 두는 이유는 호출량 절감이 아니라 검색 품질 통제이고, 한 저작에 묶인 ISBN 전체를 알아야 소장 조회가 정확해지기 때문입니다. 판본 하나만 조회하면 도서관이 다른 판을 가지고 있어도 미소장으로 나옵니다.
소장 데이터를 미리 적재하지 않는 이유는 1,619개관 벌크 적재가 1억 2천만 행에 이르는 데 비해, 조회 시점 호출이 호출 한도 안에서 충분히 감당되기 때문입니다.
이것이 이 도구를 실제로 쓰게 만드는 화면입니다. 한 번 방문해서 여러 권을 빌리는 것이 실제 행동이고, 그 확인을 권수와 도서관 수를 곱한 만큼 반복하는 것이 이 도구를 만든 이유입니다.
두 단계로 나뉘어 있습니다.
POST /api/check/resolve 가 줄을 해석하고 책을 확정합니다. 줄 해석 자체에는 외부
호출이 없으므로 목록을 붙여넣은 직후에 화면이 그려집니다.POST /api/holdings 를 책마다 따로 부르고, 답이 오는 대로 그 줄만 채웁니다.
도서관별 결과는 도착할 때마다 다시 계산되며, 확인이 끝나지 않았다는 사실을 함께
표시합니다.화면은 자료가 흐르는 순서대로 위에서 아래로 내려갑니다. ① 목록을 넣으면 ② 줄마다 어떤 책으로 읽었는지 카드로 보여 주고(자동으로 고른 후보에도 선택 표시가 붙고, 「이 책이 아닌가요?」로 바꾸면 그 줄만 다시 묻습니다) ③ 그 책들이 어느 도서관에 있는지 도서관 줄로 보여 줍니다. 전부 가진 도서관에는 「전부 있음」이 붙고, 줄을 펼치면 있는 책과 없는 책, 그 도서관에 있는 책 전부의 대출 상태를 한 번에 묻는 단추, 홈페이지 링크가 나옵니다. 도서관에 관한 단추는 거기 한 곳에만 있습니다.
줄 확정은 여섯 줄씩 겹쳐서 돌립니다. 줄마다 정보나루를 두세 번 부르고 그 응답이 한 번에 3~5초라, 차례로 돌리면 서른 줄에 몇 분이 걸리기 때문입니다. 여러 권 검색이 줄마다 첫 후보 하나의 소장을 묻는다는 점은 알아 두어야 합니다. 후보는 한 권 검색과 같은 순서(제목 일치도와 대출건수로 묶음을 세우고, 낱권 묶음 안은 권차 순)로 최대 100개까지 주고, 첫 후보가 가장 잘 맞는 묶음의 첫 권입니다. 다른 판을 찾는다면 「이 책이 아닌가요?」에서 고를 수 있습니다.
캐시가 빈 상태로 30권을 확인하면 수십 초가 걸리는데, 그동안 빈 화면을 보여 주면 사용자는 이 도구를 다시 쓰지 않습니다. 그래서 이 순서를 지킵니다.
한 권 검색도 같은 방식입니다. 처음에는 /api/search 가 저작마다 소장을 조회해서 한꺼번에
답했는데, 저작이 200개면 호출도 200번이고 요청 간격이 120ms 라 그것만으로 24초가 걸렸습니다.
사용자에게는 그냥 「검색이 안 되는 것」으로 보입니다. 지금은 책 목록을 먼저 그리고 소장을
한 권씩 채웁니다.
그리고 검색 결과는 제목 일치도로 다시 세우고, 같은 등급 안에서는 대출건수 순으로 둡니다. 정보나루가 주는 순서를 그대로 쓰면 「코스모스」를 찾았는데 「미크로코스모스 입문」이 1등으로 나옵니다. 낱권은 여기서 한 번 더 묶습니다. 대출건수만으로 세우면 「레미제라블」의 민음사 낱권이 1권, 5권, 2권, 3권, 4권 순으로 흩어지고 그 사이에 다른 출판사가 끼어듭니다. 그래서 권차를 뗀 표제와 출판사가 같은 것을 한 묶음으로 보고, 묶음 사이는 대출건수로, 묶음 안은 권차 순으로 세웁니다. 권차는 받은 표기를 그대로 적어 「1」이나 「상」이 되고, 상·중·하로 나뉜 책도 그 순서로 세웁니다. 권차 없는 세트와 합본은 낱권 뒤에 둡니다. 서버는 위에서 100개까지 돌려주고 화면은 20개씩 펼치는데, 펼친 만큼만 소장을 물어보므로 그 숫자가 곧 소장을 물어보는 횟수입니다. 한 묶음의 확인이 끝나면 소장 → 미소장 → 확인 불가 순으로 한 번 세워서 그리고, 그때까지는 진행 막대가 어디까지 왔는지 보여 줍니다.
띄어쓰기는 어느 쪽으로 넣어도 같은 책이 나옵니다. 정보나루는 어절 단위로 그대로 찾아 「레미제라블」과 「레 미제라블」이 서로 다른 검색인데, 서버가 두 표기와 찾은 책의 저자로 함께 찾아 합칩니다. 함께 찾은 표기는 화면에 밝힙니다. 저자도 마찬가지입니다. 「칼 세이건」과 「칼세이건」이 서로 다른 검색이라, 띄어쓰기를 뺀 표기와 제목만으로 되찾은 것, 저자만 붙여 썼으면 띄어쓰기 자리를 옮긴 표기까지 함께 찾아 저자가 맞는 것만 더합니다.
줄 해석 규칙(kr.wimb.query.LineParser)에서 알아 둘 것:
집계에서는 확인하지 못한 책을 소장에도 미소장에도 넣지 않습니다. 「고른 도서관에 없는 책」과 「확인하지 못한 책」은 화면에서도, 「결과 복사」의 글에서도 따로 적힙니다. 앞은 서점으로 넘어가야 하는 책이고 뒤는 다시 확인하면 있을 수 있는 책이라 행동이 다르기 때문입니다.
BookSearchService 는 검색할 때마다 정보나루에서 서지를 받아 그 자리에서 묶습니다.
최종 설계는 서지를 미리 적재해 색인에서 검색하는 것인데, 그 적재를 아직 돌리지 않았습니다.
정규화와 저작 묶기는 최종 설계와 완전히 같은 코드를 쓰므로, 적재를 시작하면 이 클래스의
fetchBooks 만 색인 조회로 갈아 끼우면 됩니다.
도서관 링크는 지금 홈페이지로만 갑니다. 상세 → 검색 결과 → 홈페이지 순으로 내려가는
길이 다 놓여 있고 규칙도 315줄 들어 있지만, 그 규칙이 실제 OPAC 에서 맞는지 확인하지 못해
wimb.opac.links-enabled 를 꺼 두었습니다. 틀린 규칙은 링크가 깨지는 것보다 나쁩니다.
그 도서관이 HTTP 200 을 주면서 결과만 0건이 되어 사용자에게는 「소장한다더니 그 책이 없네」로
보이고, 그러면 소장 정보 자체를 믿지 않게 됩니다. 깨진 링크는 눈에 보이지만 이것은 보이지
않습니다. 검증하지 않은 색인을 전환하지 않는 것과 같은 판단입니다.
규칙 표는 그대로 읽어 둡니다. 보완의 기반이고, /api/status 의 opacRuleLibraries 가
「꺼서 0」인지 「파일을 잃어서 0」인지 가르는 근거이며(opacLinksEnabled 가 함께 나갑니다),
/api/diagnose/opac 이 스위치를 건너뛰어 배포된 서버에서 한 줄씩 확인할 수 있게 해
줍니다. 서버가 그 OPAC 에 닿는지는 나가는 IP 에 달려 있어 로컬에서 확인한 것으로는 알 수
없기 때문입니다. 확인이 끝나면 환경 변수 하나로 켭니다.
켜면 이렇게 동작합니다. 상세 주소는 대부분 도서관 내부 키를 써서 미리 만들 수 없으므로,
누를 때 서버가 ISBN 검색 결과를 받아 그 안의 상세 링크를 뽑아 보냅니다(DetailResolver).
어떤 링크를 뽑을지는 OPAC 시스템마다 정규식 한 줄
(backend/src/main/resources/opac/detail-patterns.csv)입니다. 링크에 넣는 ISBN 은 저작의 첫
판이 아니라 그 도서관이 실제로 가진 판입니다. 어느 단계의 링크인지는 화면에 밝히고
있습니다. 조용히 홈페이지로 보내면 사용자는 검색 결과 자체가 틀렸다고 생각하기 때문입니다.
지금은 그 단계가 전부 홈페이지라, 화면이 목록 아래에 한 번 그렇게 적습니다.
프론트만 Vercel 에 올리고 API 서버는 따로 둡니다. Vercel 프로젝트의 Root Directory 는
frontend 이고, API 주소는 frontend/.env.production 에 들어 있습니다. 이 값은 비밀이
아니라 설정입니다(어차피 번들에 박혀 브라우저에 보입니다). VITE_* 는 빌드할 때 코드에
박히므로 값을 바꾸면 반드시 다시 배포해야 합니다.
API 서버는 backend/Dockerfile 하나로 올립니다. 처음 만드는 절차는
docs/구글-서버-만들기.md 에 화면 단위로 적어 두었고, 전체 구성과
왜 그렇게 했는지는 docs/배포.md 에 있습니다.
아무것도 안 하셔도 됩니다. 푸시하면 GitHub Actions 가 테스트를 돌리고 이미지를 만들어
올리고, VM 이 2분마다 확인해서 새 것이 있으면 갈아 끼웁니다. 켜는 절차와 원리는
scripts/vm/README.md 에 있습니다.
이미지를 VM 에서 만들지 않는 것이 핵심입니다. 무료 등급 e2-micro 는 기본 CPU 가 코어의
0.25개이고 표준 영구 디스크가 30GB 에서 쓰기 45 IOPS 라, 자바를 컴파일하면 1015분이
걸렸습니다. 파일 하나 복사하는 데 26초가 걸린 적도 있습니다. 러너에서는 같은 일이 23분입니다.
프론트가 안 바뀌었을 때는 미리보기와 운영을 갈라서 보세요. Vercel 은 브랜치마다 미리보기 배포를 만들고, 운영 주소는 Production 브랜치 하나만 따라갑니다. 그래서 미리보기가 계속 잘 만들어지고 있어도 그 브랜치에 올리지 않으면 운영 화면은 예전 그대로입니다. 배포 목록에서 Production 배지가 붙은 줄을 봐야 합니다. 그리고 대시보드의 「Redeploy」는 그 배포가 만들어졌던 옛 커밋을 다시 빌드합니다. 최신 커밋이 올라가지 않아서 누른 것이라면 같은 화면이 한 번 더 나올 뿐입니다. 실제로 그렇게 같은 커밋을 두 번 배포했습니다.
키는 서버의 ~/wimb.env 에 한 번만 넣어 둡니다(chmod 600). 이 파일은 VM 에만 있고
이미지에도 저장소에도 들어가지 않습니다.
손으로 돌려야 할 때는 이 한 줄입니다.
~/where-is-my-book*/scripts/vm/deploy.sh
떴는지는 이 한 줄로 확인합니다.
curl -s localhost:8080/api/status; echo
librariesLoaded 가 0이 아니면 정상입니다. 서버가 뜰 때 도서관 목록을 미리 받아 두므로,
아무도 부르지 않았는데 이미 들어와 있어야 맞습니다. -p 127.0.0.1:8080:8080 은 8080 을
바깥에 열지 않는다는 뜻이고, 바깥과는 같은 VM 의 Caddy 가 이야기합니다.
API 서버를 서버리스에 올리면 안 됩니다. 정보나루에 등록할 고정 IP 가 API 서버에 붙어야 하는데, 서버리스는 나가는 IP 가 고정되지 않아 등록 효과를 받지 못합니다. 그러면 조용히 1일 500건 한도가 적용되고, 평소에는 멀쩡하다가 확인 두어 번에 한도가 소진되어 "확인 불가"가 쏟아지는 형태로 나타납니다. 같은 이유로 GitHub Actions 에서 정보나루를 호출하지 않습니다.
| 문서 | 내용 |
|---|---|
CLAUDE.md | 반복해서 어기기 쉬운 제약만 모은 것 |
docs/구현계획.md | 전체 설계 |
docs/가정과-검증상태.md | 무엇이 검증되었고 무엇이 추측인지 |
docs/0단계-체크리스트.md | 인증키 신청과 사전 검증 |
docs/배포.md | 배포 구성과 그렇게 정한 이유 |
docs/구글-서버-만들기.md | GCE 에 서버를 처음 만드는 절차 |
docs/도서관-주소-규칙-채우기.md | OPAC 주소 규칙을 손으로 채우는 절차 |
docs/로컬에서-규칙-자동으로-채우기.md | 국내 회선의 로컬 세션에 넘겨 상세 패턴까지 채우게 하는 말 |
docs/reference/ | 정보나루 Open API Manual |
backend/src/test/resources/bib/golden-pairs.tsv 가 「같은 책」의 실질적인 명세입니다.
실제 데이터에서 틀린 사례를 만날 때마다 여기에 한 줄씩 추가하면 됩니다.
문서를 「이렇게 하기로 했다」로만 두면 언젠가 어겨집니다. 그래서 되풀이해서 어긴 것 셋은
.claude/settings.json 의 훅으로 옮겨 놓았고, 실제 스크립트는 scripts/hooks/ 에 있습니다.
| 훅 | 언제 | 하는 일 |
|---|---|---|
protect-plan.sh | 파일을 고치거나 명령을 돌리기 직전 | 계획 문서를 고치려는 시도를 막습니다 |
turn-start.sh | 턴이 시작할 때 | 그 시점의 커밋을 적어 둡니다 |
on-stop.sh | 턴이 끝날 때 | 자바가 바뀌었으면 Spotless 를 돌리고, 소스만 바뀌고 문서가 그대로면 알리기만 합니다 |
Write/Edit 뿐 아니라 Bash 명령까지 봅니다. 도구만 막으면 heredoc 이나 sed -i 로 그냥
지나가는데, 실제로 이 저장소의 문서는 대부분 Bash 로 고쳐졌습니다. 다만 경로가 쓰기의
대상 자리에 있을 때만 막습니다. 그 문서를 인용하는 다른 문서를 고칠 때 걸리면 안 되기
때문입니다. 읽는 것도 막지 않습니다. 명령 문자열을 보는 방식이라, 경로를 변수에 담거나
docs/*.md 로 뭉뚱그리면 놓칩니다. 실수로 고치는 것을 막는 장치이지 우회를 막는 장치가
아닙니다.
바뀐 파일은 「고친 도구」로 세지 않고 턴 시작 시점의 커밋과 비교해서 알아냅니다. 도구로 고치든 Bash 로 고치든, 턴 안에서 이미 커밋했든 안 했든 똑같이 잡힙니다.
계획 문서만 막고 CLAUDE.md 와 README.md 는 열어 둔 것이 의도적입니다. 앞은 그때의 판단을
남기는 기록이고, 뒤 둘은 사실이 달라질 때마다 고쳐야 하는 문서입니다. 실제로 이 저장소에서
값을 한 대부분의 수정이 뒤 둘에 대한 것이었습니다.
문서 알림은 막지 않고 알리기만 합니다. 오탈자 하나를 고칠 때마다 문서를 요구하면 알림 자체를 무시하게 되고, 그러면 정작 제약이 달라졌을 때에도 그냥 지나가게 됩니다.
Spotless 는 턴이 끝난 뒤에 도므로, 이미 커밋한 다음이면 포매팅 결과가 커밋 밖에 남습니다.
그래서 무언가를 고쳤을 때에는 그 사실을 화면에 알립니다. git diff 로 확인하고 함께
커밋하면 됩니다.
templates.csv)은 315줄
채워졌고 해석기도 붙어 있지만 실제 OPAC 에서 맞는지 확인하지 못해 꺼 두었습니다
(wimb.opac.links-enabled). 국내 회선에서 scripts/opac-browse.py 로 규칙을 확인하고
상세 패턴(detail-patterns.csv)을 채운 뒤, 배포된 서버에서 /api/diagnose/opac 으로
그 OPAC 에 닿는지까지 보고 켭니다(docs/로컬에서-규칙-자동으로-채우기.md).
확인한 것만 켜세요. 한꺼번에 켜면 어느 규칙이 틀렸는지 알 방법이 없습니다.BookSearchService.fetchBooks 를 색인 조회로 바꿉니다.golden-pairs.tsv 를 실제 데이터에서 만난 사례로 200쌍까지 채웁니다.InMemoryApiBudget 이라
컨테이너가 새로 뜨면 그날 쓴 건수가 0으로 돌아갑니다. 남은 건수를 근거로 삼을 수 없고,
한도의 80% 에서 배경 갱신을 멈추는 보호도 걸리지 않습니다.출처: 도서관 정보나루 (국립중앙도서관)
정보나루는 국립중앙도서관이 운영하는 서비스이지 별개의 출처가 아닙니다. 가운뎃점으로 이어 두 곳처럼 적으면 쓰지도 않는 출처를 하나 더 얹는 셈이 되고, 그러면 이 표시 자체를 믿을 수 없게 됩니다.
화면에도 같은 문구를 표시합니다. 이용 조건상의 의무이면서, 소장 정보를 어디까지 믿을 수 있는지 알려 주는 표시이기도 합니다. 소장 정보에는 조회 시각을 항상 함께 표시합니다. 사용자가 "미소장"을 "확실히 없다"로 읽을지 "한 달 전 기준이다"로 읽을지가 그 표시 하나에 달려 있기 때문입니다.
크롤러 규칙(robots.txt, sitemap.xml) | 사이트와 API 양쪽에 있고 테스트가 붙어 있습니다. 검색엔진 등록은 아직 안 했습니다 |
| 서지 적재 배치 | 아직 없음 (검색할 때마다 조회해서 그 자리에서 묶습니다) |
| 도서관 상세 페이지 링크 | 지금은 꺼 두어 모든 도서관이 홈페이지로 갑니다(wimb.opac.links-enabled). 검색 규칙 315줄(도서관 307곳)과 상세 패턴 네 줄, 누를 때 검색 결과에서 상세 링크를 뽑는 해석기까지 들어 있지만, 실제 OPAC 에서 정확도를 확인하지 못했습니다. 틀린 규칙은 HTTP 200 을 주면서 결과만 0건이라 눈에 띄지 않은 채로 「소장한다더니 그 책이 없네」가 됩니다. 확인한 만큼씩 켤 예정이고, 확인은 /api/diagnose/opac 이 스위치를 건너뛰어 도와줍니다 |