Initial commit: IT센터 출입자관리시스템 (ACS)
방문자 사전신청·승인, 입·출입 체크인/아웃, QR 배지, 재실현황, 블랙리스트, 대시보드 통계, 방문 리포트(엑셀)까지 7단계 전 기능 구현. - backend: Spring Boot 3.4.5 / Java 21 (JDK 26 빌드), 세션 인증, JPA, H2/PostgreSQL, POI, ZXing, Flyway - frontend: React 19 / Vite 6 / TypeScript - infra: Docker Compose (db·app·web nginx), Flyway V1__init, Python 사용자 시드 - docs: 워크플로우 / 시퀀스 다이어그램(Mermaid) / 이슈·유의사항 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
11
docs/README.md
Normal file
11
docs/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# ACS 문서
|
||||
|
||||
IT센터 출입자관리시스템 관련 문서 모음.
|
||||
|
||||
| 문서 | 내용 |
|
||||
|------|------|
|
||||
| [workflow.md](workflow.md) | 전체 업무 워크플로우 (서술형) — 역할, 상태 머신, 신청→승인→입·출입→통계 흐름, 배포/운영 |
|
||||
| [workflow-sequence.md](workflow-sequence.md) | 워크플로우 시퀀스/상태 다이어그램 (Mermaid) |
|
||||
| [issues-and-guidelines.md](issues-and-guidelines.md) | 기획·개발·테스트·수정 단계 이슈 정리 및 유의사항(규칙) |
|
||||
|
||||
> 프로젝트 개요·실행 방법·API 요약은 상위 [README.md](../README.md) 참조.
|
||||
175
docs/issues-and-guidelines.md
Normal file
175
docs/issues-and-guidelines.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# ACS 개발 이슈 정리 및 유의사항(규칙)
|
||||
|
||||
> 문서 작성일: 2026-07-03
|
||||
> 대상: IT센터 출입자관리시스템 (`C:\ai-dev\workspace\access-control-system`)
|
||||
> 목적: 기획·개발·테스트·수정 단계에서 실제로 겪은 이슈를 정리하고, 재발 방지를 위한 **규칙**으로 제안한다.
|
||||
> 표기: 각 항목은 **[이슈] → [규칙]** 형태. 규칙 요약은 문서 끝 §6 체크리스트 참조.
|
||||
|
||||
---
|
||||
|
||||
## 1. 기획 단계
|
||||
|
||||
### 1-1. 출입 하드웨어 미확정
|
||||
- **[이슈]** 실제 출입통제 게이트 장비가 확정되지 않은 상태에서 개발을 시작해야 했다.
|
||||
- **[규칙]** 외부 의존(장비·SMS·인증서 등)은 **인터페이스로 추상화**하고 Mock 구현을 기본 제공한다. ACS는 `AccessControlGateway` + `MockAccessControlGateway`로 이 원칙을 지켰다. 실장비는 구현체 교체만으로 붙일 수 있어야 한다.
|
||||
|
||||
### 1-2. 상태(라이프사이클) 정의를 코드보다 먼저
|
||||
- **[이슈]** 방문 신청의 상태(승인 대기/승인/반려/취소/만료)가 모호하면 서비스 곳곳의 분기가 흐트러진다.
|
||||
- **[규칙]** 도메인 상태 머신을 **먼저 확정**하고 enum(`VisitStatus`)으로 고정한다. 상태 전이 규칙(예: `PENDING`만 승인 가능, 방문일 경과 시 `EXPIRED`)을 문서/주석에 남긴다.
|
||||
|
||||
### 1-3. 역할·권한 경계
|
||||
- **[이슈]** ADMIN/SECURITY/HOST의 화면·API 접근 범위가 불명확하면 권한 누수가 생긴다.
|
||||
- **[규칙]** 역할별 접근 표를 기획 단계에서 확정하고, 프론트 라우팅 가드(`Protected roles=[...]`)와 백엔드 인가(`SecurityConfig`)에서 **이중으로** 강제한다. 프론트 가드만 믿지 않는다.
|
||||
|
||||
### 1-4. 방문자 개인정보·공개 링크
|
||||
- **[이슈]** 방문자에게 보내는 출입증 링크(`/pass/:token`)는 비로그인 공개 페이지다.
|
||||
- **[규칙]** 공개 식별자는 **추측 불가능한 토큰**(UUID `qrToken`)만 사용하고, 순번 ID를 공개 URL에 노출하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 개발 단계 — 빌드/환경
|
||||
|
||||
> 이 프로젝트는 JDK 26 + Maven + 사내 보안환경(EDR·선택적 SSL 인스펙션)이라는 특수 조합에서 개발되었다. 아래는 재현성이 확인된 함정들이다.
|
||||
|
||||
### 2-1. JDK 26에서 Lombok이 조용히 동작 안 함
|
||||
- **[이슈]** `cannot find symbol: method getX/setX` 수백 개. Lombok 애너테이션 프로세서가 침묵 실패.
|
||||
- **[규칙]** 두 가지를 **모두** 적용한다 (하나만으론 부족):
|
||||
1. `pom.xml <properties>`에 `<lombok.version>1.18.46</lombok.version>` (Spring Boot가 핀한 1.18.38은 JDK 26 미지원).
|
||||
2. `maven-compiler-plugin`에 명시적 `annotationProcessorPaths`로 lombok 등록 (JDK 23+는 classpath 자동 발견을 하지 않음).
|
||||
- 참고: `backend/pom.xml`이 정상 레퍼런스.
|
||||
|
||||
### 2-2. 콜드 Maven 빌드가 `.lastUpdated` 파일잠금으로 죽음
|
||||
- **[이슈]** 처음 빌드 시 `FileSystemException: ....lastUpdated: 다른 프로세스가 파일 사용 중` → reactor 전체 중단. 원인은 사내 EDR의 실시간 파일 잠금.
|
||||
- **[규칙]**
|
||||
- `MAVEN_OPTS`에 `-Dmaven.legacyLocalRepo=true` (env.cmd에 반영됨). 직접 mvn 실행 시에도 포함.
|
||||
- 콜드 빌드는 실패하면 `find .m2 -name '*.lastUpdated' -delete` 후 **재시도 루프**로 repo를 데운다. 한번 캐시가 따뜻해지면 이후엔 깨끗하게 빌드된다.
|
||||
- 검증 실행은 `mvn package`로 fat jar를 만들어 **`java -jar`로 구동**하는 것이 가장 안전(런타임에 Maven 불필요 → 잠금 회피).
|
||||
|
||||
### 2-3. 편집 중 잔여 `.tmp.*` 파일
|
||||
- **[이슈]** 소스 곳곳에 `*.java.tmp.PID.hash`, `*.tsx.tmp...` 같은 잔여 파일이 남아 있다(현재도 7개 존재). 에디터/툴이 원자적 교체를 하는 중 EDR 잠금으로 임시본이 정리되지 못한 흔적.
|
||||
- **[규칙]**
|
||||
- 커밋/빌드 전 `find . -name "*.tmp.*" -not -path '*/target/*' -delete`로 정리.
|
||||
- `.gitignore`에 `*.tmp.*` 패턴을 추가해 저장소 오염을 막는다.
|
||||
|
||||
### 2-4. 사내 SSL 인스펙션 (선택적)
|
||||
- **[이슈]** 명시적 프록시는 없지만 일부 도메인은 사내 장비가 인증서를 재서명(Bank of Korea CA). curl/git은 폐기검사 hard-fail(`CRYPT_E_NO_REVOCATION_CHECK`)로 끊김.
|
||||
- **[규칙]**
|
||||
- **검증을 끄지 않는다.** `verify=False`, 사내 CA 단독 번들 교체(`REQUESTS_CA_BUNDLE`=사내단독) 금지 — 공인 CA 호스트(npm/pypi/github)가 깨진다.
|
||||
- **추가형/저장소형 신뢰**만 사용: Windows 저장소(공인+사내 CA)를 쓰고, 폐기검사만 건너뛴다(curl `ssl-no-revoke`, git `schannelCheckRevoke=false`).
|
||||
- TLS/인증서 오류가 나면 임의 대응 대신 **`corporate-cert-fix` 스킬**을 사용한다.
|
||||
|
||||
### 2-5. `.cmd` 스크립트 인코딩
|
||||
- **[이슈]** `.cmd` 편집 시 LF 혼입 → cmd.exe 파싱 깨짐. 한글 주석이 든 .cmd는 CP949 콘솔에서 바이트 desync로 줄이 깨져 엉뚱한 명령 실행.
|
||||
- **[규칙]** `scripts\*.cmd`는 **ASCII 전용 + CRLF 줄바꿈**. 한글 설명은 `.cmd`가 아니라 별도 `readme\`·`data\` md에 둔다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 개발 단계 — 도메인 로직
|
||||
|
||||
### 3-1. 승인 시점에 qrToken 발급
|
||||
- **[이슈]** 출입증 QR·공개 링크·문자 발송이 모두 하나의 토큰에 의존한다.
|
||||
- **[규칙]** `qrToken`(UUID)은 **승인(`APPROVED`) 시점에만** 발급한다(`ApprovalService`). 신청/반려 단계에서는 발급하지 않는다.
|
||||
|
||||
### 3-2. 알림 발송 실패가 승인을 롤백하면 안 됨
|
||||
- **[이슈]** 문자 발송(외부 API)이 실패하면 승인 트랜잭션까지 롤백될 위험.
|
||||
- **[규칙]** 부수효과(알림)의 실패는 **catch & log**로 흡수하고 주 트랜잭션(승인)은 커밋한다(`ApprovalService.notifyVisitor`). 외부 I/O 실패로 핵심 업무가 무효화되지 않게 한다.
|
||||
|
||||
### 3-3. 체크인 검증 순서 고정
|
||||
- **[이슈]** 검증 순서가 흐트러지면 만료·차단·중복입장이 잘못된 우선순위로 처리될 수 있다.
|
||||
- **[규칙]** `AccessService.checkIn`의 검증 순서를 유지한다:
|
||||
1. 상태 `APPROVED` 확인
|
||||
2. 방문 종료일 경과 → `EXPIRED` 전이 + 거부
|
||||
3. 방문 시작일 이전 → 거부
|
||||
4. 블랙리스트 매칭 → 403 차단
|
||||
5. 이미 재실 중 → 409 중복입장
|
||||
6. 금일 퇴장 완료 → 재입장 불가
|
||||
- 상태 값·거부 사유 메시지를 응답에 명확히 담는다.
|
||||
|
||||
### 3-4. "일자" 기준 판정 (늦은 도착 허용)
|
||||
- **[이슈]** 시각까지 엄격히 보면 몇 분 늦은 방문자가 입장 거부된다.
|
||||
- **[규칙]** 입장 허용은 **날짜(LocalDate) 기준**으로 판정하고, 실제 입출입 시각은 신청 시각과 **분리하여** `access_events.event_at`에 기록한다.
|
||||
|
||||
### 3-5. 재실 판정은 마지막 이벤트로
|
||||
- **[이슈]** 입장/퇴장을 별도 플래그로 관리하면 정합성이 깨진다.
|
||||
- **[규칙]** "현재 재실 중"은 별도 상태 컬럼이 아니라 **해당 방문의 마지막 `AccessEvent` 방향이 `IN`인가**로 판정한다(`isInside`). 단일 진실원본(이벤트 로그)을 유지한다.
|
||||
|
||||
### 3-6. 웹캠 QR 스캔은 secure context 필수
|
||||
- **[이슈]** 출입콘솔/키오스크의 웹캠 QR 스캔이 `http://내부IP` 접속 시 카메라 차단으로 동작 안 함.
|
||||
- **[규칙]** 카메라 기능은 **HTTPS 또는 localhost**에서만 동작함을 전제로 배포한다. QR 스캔이 필요한 단말은 HTTPS 도메인 또는 localhost로 접속시킨다(대안: 이름 검색 체크인).
|
||||
|
||||
### 3-7. 문자 링크 주소(`ACS_PUBLIC_BASE_URL`)
|
||||
- **[이슈]** 문자에 담긴 출입증 링크가 `localhost`면 방문자 휴대폰에서 열리지 않는다.
|
||||
- **[규칙]** `ACS_PUBLIC_BASE_URL`은 **방문자 휴대폰에서 실제 접속 가능한 외부 URL**로 설정한다. localhost 금지. 실발송(`hanbank`) 전 메시지 API 도달성(`nc -vz`)을 확인한다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 테스트 단계
|
||||
|
||||
### 4-1. 런타임 end-to-end 검증
|
||||
- **[이슈]** 컴파일 성공 ≠ 동작. 특히 QR 이미지·엑셀·권한 가드는 실제 응답을 봐야 안다.
|
||||
- **[규칙]** 핵심 플로우는 **curl 실서버 검증**으로 확인한다(로그인→신청→승인→체크인→재실→체크아웃, 역할 가드 403 포함). ACS는 1~7단계 전부 런타임 검증됨: QR PNG 240x240 유효/없으면 404, 블랙리스트 add→체크인 403→해제→200, stats 정확, 리포트 xlsx 셀 내용·역할 가드·잘못된 기간 400 확인.
|
||||
- API 스모크 테스트는 `backend/test-api.http`에 유지한다.
|
||||
|
||||
### 4-2. Flyway 마이그레이션 검증 자동화
|
||||
- **[이슈]** prod 스키마(Flyway `V1__init.sql`)와 JPA 엔티티가 어긋나면 운영 기동 시 터진다.
|
||||
- **[규칙]** Flyway 스크립트를 **JUnit 회귀 테스트**로 검증한다. ACS는 `FlywayValidationTest`(`@ActiveProfiles("fwtest")`, H2 PostgreSQL-mode + `ddl-auto=validate`)로 V1을 검증한다. 스키마를 바꿀 때 이 테스트를 반드시 통과시킨다.
|
||||
|
||||
### 4-3. 경계·예외 케이스
|
||||
- **[이슈]** 정상 경로만 보면 중복입장/만료/차단/권한 없음이 방치된다.
|
||||
- **[규칙]** 각 API의 **거부·오류 경로**(403/404/409/400)를 정상 경로와 함께 테스트한다. 상태 코드와 사유 메시지를 검증한다.
|
||||
|
||||
### 4-4. Docker 실기동은 별도 확인 필요
|
||||
- **[이슈]** 검증 시 Docker 데몬이 꺼져 있어 `docker compose up` 실기동은 미검증으로 남았다.
|
||||
- **[규칙]** 컨테이너 빌드/기동은 **환경 가용 시 별도 검증** 항목으로 명시하고, 미검증이면 "미검증"으로 솔직히 남긴다. 특히 사내망 `docker build`는 SSL 인스펙션에 걸리면 사내 CA 주입/내부 미러가 필요할 수 있다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 수정/유지보수 단계
|
||||
|
||||
### 5-1. 버전 관리 시작
|
||||
- **[이슈]** 현재 저장소에 **git 커밋 이력이 없다**(작업이 커밋되지 않음). 잔여 `.tmp` 파일도 함께 방치.
|
||||
- **[규칙]** 의미 있는 단위로 **즉시 커밋**한다. 커밋 전 `.tmp.*` 정리 + 빌드/핵심 테스트 통과 확인. `.gitignore`에 `target/`, `node_modules/`, `*.tmp.*`, `.env`, `sms-outbox/` 등을 등록해 산출물·비밀·임시파일이 커밋되지 않게 한다.
|
||||
|
||||
### 5-2. dev 시드/비밀번호가 prod로 새면 안 됨
|
||||
- **[이슈]** `DataSeeder`의 admin/security/host + `ChangeMe123!`는 개발 편의용이다.
|
||||
- **[규칙]** 시드 계정은 `@Profile("!prod")`로 **prod에서 비활성**한다(적용됨). prod 사용자는 별도 스크립트(`scripts/seed-load.py`)로 적재하고, 최초 로그인 시 비밀번호 변경(`mustChangePassword`)을 강제한다. 기본 비밀번호를 문서/코드에 그대로 두지 않는다.
|
||||
|
||||
### 5-3. 상태 전이 로직 수정 시 파급 확인
|
||||
- **[이슈]** `VisitStatus` 전이나 체크인 검증을 고치면 승인·재실·리포트·통계가 연쇄 영향을 받는다.
|
||||
- **[규칙]** 상태/검증 로직을 수정하면 §3-3 검증 순서와 §4의 end-to-end·Flyway 테스트를 **재실행**한다. 상태 값을 추가/변경하면 프론트 표시 문자열과 통계 집계도 함께 갱신한다.
|
||||
|
||||
### 5-4. 비밀·설정은 `.env`로 분리
|
||||
- **[이슈]** DB 비밀번호, SMS provider, 공개 URL 등이 코드에 박히면 환경 이전이 위험해진다.
|
||||
- **[규칙]** 환경 의존 값은 전부 `.env`(예: `POSTGRES_PASSWORD`, `WEB_PORT`, `ACS_SMS_PROVIDER`, `ACS_PUBLIC_BASE_URL`, `ACS_SMS_API_URL`)로 분리하고 `.env.example`만 커밋한다. 실제 `.env`는 커밋 금지.
|
||||
|
||||
### 5-5. HTTPS/장비 연동 등 미완 항목 추적
|
||||
- **[이슈]** HTTPS(카메라), 실 출입장비, 사내망 Docker 빌드는 도메인/인증서/장비 확정 후 진행할 항목으로 남아 있다.
|
||||
- **[규칙]** 미완/보류 항목은 README·이 문서에 **명시적으로 남기고**, 전제 조건(도메인·인증서·장비 스펙)이 갖춰지면 반영한다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 규칙 요약 체크리스트
|
||||
|
||||
**환경/빌드**
|
||||
- [ ] Lombok 1.18.46 + `annotationProcessorPaths` 유지 (JDK 26)
|
||||
- [ ] `MAVEN_OPTS`에 `-Dmaven.legacyLocalRepo=true`, 콜드빌드는 retry, 검증은 `java -jar`
|
||||
- [ ] 커밋 전 `*.tmp.*` 정리, `.gitignore` 등록
|
||||
- [ ] TLS 오류 시 검증 끄지 말 것 → `corporate-cert-fix` 스킬 (추가형 신뢰)
|
||||
- [ ] `scripts\*.cmd`는 ASCII + CRLF, 한글 설명은 md로 분리
|
||||
|
||||
**도메인 로직**
|
||||
- [ ] `qrToken`은 승인 시점에만 발급
|
||||
- [ ] 알림 실패는 catch & log (승인 롤백 금지)
|
||||
- [ ] 체크인 검증 순서 6단계 고정, 날짜 기준 판정
|
||||
- [ ] 재실 판정은 마지막 이벤트 방향으로
|
||||
- [ ] 카메라 QR = secure context, 문자 URL은 외부 접속 가능 주소
|
||||
|
||||
**테스트**
|
||||
- [ ] 정상+거부(403/404/409/400) 경로 모두 curl 검증
|
||||
- [ ] Flyway `FlywayValidationTest` 통과
|
||||
- [ ] Docker 실기동은 가용 시 별도 검증, 미검증이면 명시
|
||||
|
||||
**보안/운영**
|
||||
- [ ] 시드 계정 `@Profile("!prod")`, 기본 비밀번호 강제 변경
|
||||
- [ ] 비밀/설정은 `.env` 분리, `.env.example`만 커밋
|
||||
- [ ] 의미 단위 즉시 커밋, 미완 항목은 문서에 추적
|
||||
163
docs/workflow-sequence.md
Normal file
163
docs/workflow-sequence.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# ACS 워크플로우 — 시퀀스/상태 다이어그램 (Mermaid)
|
||||
|
||||
> 대상: IT센터 출입자관리시스템 · 작성일 2026-07-03
|
||||
> GitHub·VS Code·mermaid.live 등에서 렌더링됨. 서술형 문서는 [workflow.md](workflow.md) 참조.
|
||||
|
||||
---
|
||||
|
||||
## 1. 방문 신청 상태 머신
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> DRAFT: 신청 초안
|
||||
DRAFT --> PENDING: 제출
|
||||
PENDING --> APPROVED: 승인 (qrToken 발급)
|
||||
PENDING --> REJECTED: 반려
|
||||
PENDING --> CANCELLED: 취소
|
||||
APPROVED --> CANCELLED: 취소
|
||||
APPROVED --> EXPIRED: 방문 종료일 경과 후 체크인 시도
|
||||
APPROVED --> [*]: 출입 완료
|
||||
REJECTED --> [*]
|
||||
CANCELLED --> [*]
|
||||
EXPIRED --> [*]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 전체 흐름 (신청 → 승인 → 발송 → 입·출입)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Host as HOST/담당자
|
||||
actor Admin as ADMIN
|
||||
participant API as ACS 백엔드
|
||||
participant SMS as PassNotifier(SMS)
|
||||
actor Visitor as 방문자
|
||||
actor Sec as SECURITY/게이트
|
||||
participant GW as AccessControlGateway
|
||||
|
||||
Host->>API: ① 방문 사전신청 (POST /api/visit-requests)
|
||||
note right of API: 상태 = PENDING (개별 or 엑셀 업로드)
|
||||
|
||||
Admin->>API: ② 승인함 조회 (GET /api/visit-requests/pending)
|
||||
Admin->>API: ③ 승인 (POST /api/approvals/{id}/approve)
|
||||
note right of API: 상태 = APPROVED, qrToken(UUID) 발급, Approval 기록
|
||||
API->>SMS: ④ 출입증(QR PNG) 발송 요청
|
||||
note right of SMS: 발송 실패는 catch&log — 승인은 롤백 안 함
|
||||
SMS-->>Visitor: 출입증 링크 문자 (ACS_PUBLIC_BASE_URL/pass/:token)
|
||||
|
||||
Visitor->>API: ⑤ 공개 출입증 열람 (GET /pass/:token, 비로그인)
|
||||
API-->>Visitor: QR / 방문정보 표시
|
||||
|
||||
Visitor->>Sec: ⑥ 방문일 현장 도착
|
||||
Sec->>API: ⑦ 체크인 (POST /api/access/check-in, QR/이름)
|
||||
note right of API: 상태·일자·블랙리스트·중복·재입장 검증 (아래 §3)
|
||||
API->>GW: openGate(gateId)
|
||||
GW-->>API: accepted
|
||||
API-->>Sec: 입장 처리 (AccessEvent IN 기록)
|
||||
|
||||
Sec->>API: ⑧ 재실현황 (GET /api/access/inside)
|
||||
API-->>Sec: 재실 방문자 목록
|
||||
|
||||
Visitor->>Sec: ⑨ 퇴장
|
||||
Sec->>API: ⑩ 체크아웃 (POST /api/access/check-out)
|
||||
API-->>Sec: 퇴장 처리 (AccessEvent OUT 기록)
|
||||
|
||||
Admin->>API: ⑪ 대시보드 통계 (GET /api/stats/summary)
|
||||
Sec->>API: ⑫ 방문 리포트 (GET /api/reports/visits.xlsx)
|
||||
API-->>Sec: 엑셀(POI) 다운로드
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 체크인 검증 로직 (AccessService.checkIn)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Sec as SECURITY / 키오스크
|
||||
participant AS as AccessService
|
||||
participant BL as BlacklistService
|
||||
participant AE as AccessEventRepo
|
||||
participant GW as AccessControlGateway
|
||||
|
||||
Sec->>AS: check-in (qrToken 또는 visitRequestId)
|
||||
AS->>AS: 방문신청 resolve
|
||||
|
||||
alt 상태 ≠ APPROVED
|
||||
AS-->>Sec: 400 승인되지 않은 방문
|
||||
else 방문 종료일 경과
|
||||
AS->>AS: 상태 = EXPIRED
|
||||
AS-->>Sec: 400 신청일 경과 — 재신청 필요
|
||||
else 방문 시작일 이전
|
||||
AS-->>Sec: 400 아직 방문일 아님
|
||||
else
|
||||
AS->>BL: blockReason(name, contact)
|
||||
alt 블랙리스트 매칭
|
||||
BL-->>AS: 사유
|
||||
AS-->>Sec: 403 차단된 방문자
|
||||
else 통과
|
||||
AS->>AE: isInside? / hasExitedToday?
|
||||
alt 이미 재실 중
|
||||
AS-->>Sec: 409 중복 입장
|
||||
else 금일 퇴장 완료
|
||||
AS-->>Sec: 400 재입장 불가
|
||||
else 정상
|
||||
AS->>AE: AccessEvent(IN) 저장
|
||||
AS->>GW: openGate(gateId)
|
||||
GW-->>AS: accepted
|
||||
AS-->>Sec: 입장 처리 완료
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 시스템 컴포넌트 개요
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph FE[프론트엔드 React/Vite]
|
||||
L[로그인] --> DB1[대시보드]
|
||||
VR[방문신청] --> AQ[승인함 ADMIN]
|
||||
AC[출입콘솔] --> KIOSK[키오스크 공개]
|
||||
RPT[리포트] & BLK[블랙리스트 ADMIN]
|
||||
PP[공개 출입증 /pass/:token]
|
||||
end
|
||||
|
||||
subgraph BE[백엔드 Spring Boot]
|
||||
CTRL[Controllers] --> SVC[Services]
|
||||
SVC --> REPO[(JPA Repositories)]
|
||||
SVC --> QR[QrService ZXing]
|
||||
SVC --> NOTI[PassNotifier SMS]
|
||||
SVC --> GWY[AccessControlGateway Mock/실장비]
|
||||
SVC --> POI[Report/Excel POI]
|
||||
end
|
||||
|
||||
subgraph DATA[저장소]
|
||||
DBH[(H2 dev)]
|
||||
DBP[(PostgreSQL prod + Flyway)]
|
||||
end
|
||||
|
||||
FE -->|/api 세션 인증| CTRL
|
||||
REPO --> DBH
|
||||
REPO --> DBP
|
||||
NOTI -->|hanbank| MSG[사내 메시지 API]
|
||||
PP -.공개 링크.-> FE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 배포 파이프라인 (운영, Docker)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
ENV[.env 설정] --> BUILD[docker compose build]
|
||||
BUILD --> UP[compose up -d: db + app + web]
|
||||
UP --> FW[app 기동 시 Flyway V1__init 적용]
|
||||
FW --> SEED[compose run seed: 사용자 적재]
|
||||
SEED --> READY[서비스 준비: nginx :80 → /api → app :8080]
|
||||
READY --> DBV[(db_data 볼륨 영속)]
|
||||
```
|
||||
232
docs/workflow.md
Normal file
232
docs/workflow.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# IT센터 출입자관리시스템(ACS) 워크플로우
|
||||
|
||||
> 문서 작성일: 2026-07-03
|
||||
> 대상: `C:\ai-dev\workspace\access-control-system` (Spring Boot 3.4.5 / Java 21 · React 19 · Vite 6)
|
||||
> 목적: 방문자 사전신청 → 승인 → 출입증 발송 → 입·출입 체크 → 재실현황/리포트까지의 전체 업무 흐름 정리
|
||||
|
||||
---
|
||||
|
||||
## 1. 시스템 개요
|
||||
|
||||
IT센터를 방문하는 외부 방문자의 **사전신청·승인·출입·통계**를 관리하는 웹 시스템.
|
||||
출입통제 하드웨어(게이트)는 `AccessControlGateway` 인터페이스로 추상화되어 있어 현재는 Mock으로 동작하고, 추후 실제 장비 연동이 가능하다.
|
||||
|
||||
| 구분 | 내용 |
|
||||
|------|------|
|
||||
| 백엔드 | Spring Boot 3.4.5 / Java 21 · Spring Security(세션) · JPA · H2(dev)/PostgreSQL(prod) · POI · ZXing · Flyway |
|
||||
| 프론트엔드 | React 19 · Vite 6 · TypeScript · react-router 7 |
|
||||
| 포트 | API `8080`, 웹 `5173`(dev) / nginx `80`(prod) |
|
||||
| 인증 | 세션 기반, 최초 로그인 시 비밀번호 강제 변경 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 역할(Role)과 접근 범위
|
||||
|
||||
| 역할 | 주요 권한 | 접근 화면 |
|
||||
|------|-----------|-----------|
|
||||
| `ADMIN` | 전체 관리, 모든 승인/반려, 사용자·블랙리스트·리포트 | 대시보드, 방문신청, 승인함, 출입콘솔, 블랙리스트, 리포트 |
|
||||
| `SECURITY` | 입·출입 콘솔(체크인/아웃), 재실현황, 배지 발급, 리포트 | 대시보드, 방문신청, 출입콘솔, 리포트 |
|
||||
| `HOST` | 방문 사전신청 등록, 담당 방문 확인 | 대시보드, 방문신청, 출입콘솔 |
|
||||
| (비로그인) | 방문자 공개 출입증(`/pass/:token`), 키오스크(`/kiosk`) | 공개 페이지 |
|
||||
|
||||
> 라우팅 기준: `frontend/src/App.tsx`. `승인함(/approvals)`·`블랙리스트(/blacklist)`는 ADMIN 전용, `리포트(/reports)`는 SECURITY·ADMIN.
|
||||
|
||||
---
|
||||
|
||||
## 3. 방문 신청 생명주기 (상태 머신)
|
||||
|
||||
`VisitStatus` (backend/entity/VisitStatus.java) 기준 상태 전이:
|
||||
|
||||
```
|
||||
신청 등록 승인
|
||||
[DRAFT] ───────────────▶ [PENDING] ───────────────▶ [APPROVED] ──▶ 출입 가능
|
||||
│ │
|
||||
│ 반려 │ 방문일 경과(체크인 시도)
|
||||
▼ ▼
|
||||
[REJECTED] [EXPIRED]
|
||||
|
||||
[PENDING] 또는 [APPROVED] ──── 취소 ────▶ [CANCELLED]
|
||||
```
|
||||
|
||||
| 상태 | 의미 | 진입 조건 |
|
||||
|------|------|-----------|
|
||||
| `DRAFT` | 임시 저장 | 신청 초안 |
|
||||
| `PENDING` | 승인 대기 | 신청 제출 |
|
||||
| `APPROVED` | 승인됨(출입 가능, qrToken 발급) | 관리자 승인 |
|
||||
| `REJECTED` | 반려됨 | 관리자 반려 |
|
||||
| `CANCELLED` | 신청 취소됨 | 신청자/관리자 취소 |
|
||||
| `EXPIRED` | 방문일 경과로 만료 | 방문 종료일 이후 체크인 시도 시 자동 전이 |
|
||||
|
||||
> 핵심 규칙: **승인 시점(ApprovalService.approve)** 에 `qrToken = UUID`가 발급되고, 이 토큰으로 출입증(QR)·공개 페이지·문자 발송이 이루어진다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 전체 업무 워크플로우 (End-to-End)
|
||||
|
||||
```
|
||||
[HOST/담당자] [ADMIN] [방문자] [SECURITY/게이트]
|
||||
│ │ │ │
|
||||
①방문 사전신청 ───────────▶ ②승인함 검토 │ │
|
||||
(개별 or 엑셀 업로드) │ │ │
|
||||
│ ③승인 / 반려 │ │
|
||||
│ │ (승인 시 qrToken) │ │
|
||||
│ ├── ④출입증 문자발송 ─▶ 휴대폰 링크 수신 │
|
||||
│ │ ⑤공개 출입증(/pass/:token) │
|
||||
│ │ │ QR 확인 │
|
||||
│ │ │ │
|
||||
│ │ └── ⑥방문일 현장 도착 ────▶ ⑦체크인(QR/이름)
|
||||
│ │ │ ├ 블랙리스트 검증
|
||||
│ │ │ ├ 상태/일자 검증
|
||||
│ │ │ └ 게이트 오픈 + 입장기록
|
||||
│ │ ⑧재실현황 표시
|
||||
│ │ └── ⑨퇴장 ───────────────▶ ⑩체크아웃(OUT 기록)
|
||||
│ │ │
|
||||
└───────────────── ⑪대시보드 통계 / ⑫방문 리포트(엑셀) ────────────────────┘
|
||||
```
|
||||
|
||||
### 단계별 상세
|
||||
|
||||
**① 방문 사전신청 (HOST/ADMIN/SECURITY)** — `POST /api/visit-requests`
|
||||
- 방문자 정보(이름·연락처·회사), 방문 구역(Zone), 방문 기간(visitFrom~visitTo), 담당 호스트 지정.
|
||||
- 개별 등록 또는 **엑셀 일괄 업로드**(`POST /api/visit-requests/upload`, `ExcelImportService`).
|
||||
- 상태 → `PENDING`.
|
||||
|
||||
**② ③ 승인/반려 (ADMIN)** — `GET /api/visit-requests/pending` → `POST /api/approvals/{id}/approve|reject`
|
||||
- `PENDING` 상태만 처리 가능(이미 처리된 건은 409 conflict).
|
||||
- 승인: 상태 → `APPROVED`, **`qrToken` 발급**, `Approval` 기록 저장.
|
||||
- 반려: 상태 → `REJECTED`, 사유(comment) 기록.
|
||||
|
||||
**④ 출입증 발송 (자동)** — `PassNotifier`
|
||||
- 승인 직후 QR PNG(240px)를 생성하여 방문자에게 발송.
|
||||
- 발송 실패는 승인 트랜잭션을 롤백하지 않음 (catch & log — 승인은 정상 처리).
|
||||
- Provider: `dev`(로그만, `LoggingPassNotifier`) / `hanbank`(사내 메시지 API로 LMS 실발송, `HanbankMessagePassNotifier`).
|
||||
- 문자에는 `ACS_PUBLIC_BASE_URL` 기반 공개 출입증 링크 포함.
|
||||
|
||||
**⑤ 공개 출입증 (방문자, 비로그인)** — `GET /pass/:token` → `PublicPassController`
|
||||
- 방문자가 휴대폰에서 링크 접속 → QR/방문정보 확인.
|
||||
- secure context(HTTPS/localhost)에서만 카메라·안전 접속 보장.
|
||||
|
||||
**⑥ ⑦ 입장 체크인 (SECURITY/게이트 or 키오스크)** — `POST /api/access/check-in`
|
||||
- 식별: QR 토큰 스캔 또는 방문자 이름 검색(`GET /api/access/search?q=`).
|
||||
- **검증 순서** (`AccessService.checkIn`):
|
||||
1. 상태 == `APPROVED` 아니면 거부.
|
||||
2. 방문 종료일 경과 → 상태 `EXPIRED` 전이 + 거부("재신청 필요").
|
||||
3. 방문 시작일 이전 → 거부("아직 방문일 아님").
|
||||
4. 블랙리스트 매칭(이름+연락처) → 403 차단.
|
||||
5. 이미 입장 중 → 409 중복입장.
|
||||
6. 금일 이미 퇴장 완료 → 재입장 불가.
|
||||
- 통과 시: `AccessEvent(IN)` 기록 + `gateway.openGate()` 호출(게이트 오픈).
|
||||
- **늦은 도착 허용**: 같은 '일자'면 시간은 엄격히 보지 않음. 실제 입장 시각은 `access_events.event_at`에 별도 기록.
|
||||
|
||||
**⑧ 재실현황 (SECURITY/ADMIN)** — `GET /api/access/inside`
|
||||
- 마지막 이벤트가 `IN`인 방문자 = 현재 재실 중. 이름·회사·구역·호스트·입장시각 표시.
|
||||
- 금일 전체 출입기록: `AccessService.listTodayRecords` (입장/퇴장/재실여부 포함).
|
||||
|
||||
**⑨ ⑩ 퇴장 체크아웃** — `POST /api/access/check-out`
|
||||
- 입장 기록이 없으면 409(퇴장 불가).
|
||||
- `AccessEvent(OUT)` 기록. 재실현황에서 제외됨.
|
||||
|
||||
**⑪ 대시보드 통계** — `GET /api/stats/summary` → `StatsController`
|
||||
- 오늘의 방문/승인대기/재실 인원 등 요약 집계.
|
||||
|
||||
**⑫ 방문 리포트 (SECURITY/ADMIN)** — `GET /api/reports/visits.xlsx?from=&to=`
|
||||
- 기간별 방문 내역을 Apache POI로 엑셀 생성·다운로드(`ReportService`).
|
||||
|
||||
---
|
||||
|
||||
## 5. 키오스크 셀프 체크인 흐름 (비로그인)
|
||||
|
||||
`GET /kiosk` → `KioskPage` — 입구 무인 단말에서 방문자가 직접 QR을 스캔하여 셀프 체크인/아웃.
|
||||
- 백엔드는 동일한 `check-in`/`check-out` API 사용하되 operator = null(셀프서비스).
|
||||
- 웹캠 QR 스캔은 secure context 필요(`useQrScanner.ts`).
|
||||
|
||||
---
|
||||
|
||||
## 6. 블랙리스트 흐름 (ADMIN)
|
||||
|
||||
`GET/POST /api/blacklist`, `DELETE /api/blacklist/{id}` → `BlacklistController`
|
||||
- 이름+연락처 기준 차단 명단 관리.
|
||||
- **체크인 시 자동 검증**: `BlacklistService.blockReason()`이 매칭되면 입장 403 차단(사유 표시).
|
||||
|
||||
---
|
||||
|
||||
## 7. 컴포넌트 데이터 흐름 (백엔드 계층)
|
||||
|
||||
```
|
||||
Controller → Service → Repository(JPA) → DB(H2/PostgreSQL)
|
||||
│ │
|
||||
│ ├─ ApprovalService ─▶ QrService(ZXing) ─▶ PassNotifier(SMS)
|
||||
│ ├─ AccessService ───▶ AccessControlGateway(Mock/실장비)
|
||||
│ │ └─ BlacklistService
|
||||
│ └─ ReportService(POI) / StatsService / ExcelImportService(POI)
|
||||
│
|
||||
└─ 공통: GlobalExceptionHandler(예외→ApiResponse), ApiResponse(응답 래퍼), SecurityConfig(세션 인가)
|
||||
```
|
||||
|
||||
주요 엔티티: `User`, `Visitor`, `VisitRequest`, `Approval`, `AccessEvent`, `Zone`, `Blacklist`
|
||||
(공통 `BaseEntity` — JPA auditing으로 생성/수정 시각 자동 기록)
|
||||
|
||||
---
|
||||
|
||||
## 8. 배포/운영 워크플로우
|
||||
|
||||
### 로컬 개발 (H2)
|
||||
```cmd
|
||||
run-backend.cmd :: http://localhost:8080 (env.cmd로 포터블 JDK/Maven 로드)
|
||||
run-frontend.cmd :: http://localhost:5173
|
||||
```
|
||||
초기 계정(DataSeeder, dev 전용): `admin` / `security` / `host` — 비밀번호 `ChangeMe123!` (최초 로그인 시 변경 요구).
|
||||
|
||||
### 운영 (Docker, PostgreSQL)
|
||||
```bash
|
||||
cd infra
|
||||
cp .env.example .env # POSTGRES_PASSWORD, WEB_PORT, ACS_SMS_PROVIDER, ACS_PUBLIC_BASE_URL 설정
|
||||
docker compose up -d --build # db + app(:8080) + web(nginx :80)
|
||||
docker compose run --rm seed # 사용자 시드 적재
|
||||
docker compose logs -f app # Flyway 마이그레이션/기동 로그
|
||||
```
|
||||
- `prod` 프로파일 + Flyway `V1__init.sql`로 스키마 관리, DB는 named volume(`db_data`)에 영속.
|
||||
- nginx가 정적 파일 서빙 + `/api` 리버스 프록시.
|
||||
|
||||
### 문자 실발송(hanbank) 전제
|
||||
1. 서버→메시지 API 도달 확인: `nc -vz 210.104.132.59 8000`
|
||||
2. `ACS_SMS_PROVIDER=hanbank`, `ACS_PUBLIC_BASE_URL`=외부 접속 가능한 실제 URL(localhost 금지) 설정 후 재기동.
|
||||
|
||||
---
|
||||
|
||||
## 9. 구현 현황 (7단계)
|
||||
|
||||
| 단계 | 내용 | 상태 |
|
||||
|------|------|------|
|
||||
| 1 | 스캐폴딩 (pom, 설정, 공통 클래스, 전역 예외/응답 래퍼, JPA auditing) | ✅ |
|
||||
| 2 | 인증·인가 (세션 로그인, 역할 기반 권한, 시드) | ✅ |
|
||||
| 3 | 방문 사전신청 + 승인 (신청/취소/엑셀 업로드, 승인/반려 시 qrToken 발급) | ✅ |
|
||||
| 4 | 입·출입 체크인/체크아웃 + 재실현황 (AccessEvent, Gateway+Mock, 중복입장·만료 검증) | ✅ |
|
||||
| 5 | QR/배지 발급 (ZXing PNG, 배지 인쇄 화면) | ✅ |
|
||||
| 6 | 블랙리스트(체크인 차단) + 대시보드 통계 + 방문 리포트 엑셀(POI) | ✅ |
|
||||
| 7 | Flyway V1__init.sql(prod) + Docker Compose(db·app·web nginx) + Python 사용자 시드 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 10. 주요 API 요약
|
||||
|
||||
| 분류 | 엔드포인트 |
|
||||
|------|-----------|
|
||||
| 인증 | `POST /api/auth/login` · `/logout` · `GET /api/auth/me` · `POST /api/auth/change-password` |
|
||||
| 구역 | `GET /api/zones` |
|
||||
| 방문신청 | `GET/POST /api/visit-requests` · `GET .../pending` · `POST .../{id}/cancel` · `POST .../upload`(엑셀) |
|
||||
| 승인 | `POST /api/approvals/{id}/approve` · `/reject` |
|
||||
| 출입 | `POST /api/access/check-in` · `/check-out` · `GET /api/access/inside` · `/search?q=` |
|
||||
| 출입증 | `GET /api/passes/{id}` · `/qr.png` · 공개 `/pass/:token` |
|
||||
| 통계/리포트 | `GET /api/stats/summary` · `GET /api/reports/visits.xlsx?from=&to=` |
|
||||
| 블랙리스트 | `GET/POST /api/blacklist` · `DELETE /api/blacklist/{id}` (ADMIN) |
|
||||
|
||||
> API 스모크 테스트: `backend/test-api.http`
|
||||
|
||||
---
|
||||
|
||||
## 11. 향후 연동 포인트
|
||||
|
||||
- **실 출입장비 연동**: `AccessControlGateway` 구현체를 Mock → 실장비 드라이버로 교체.
|
||||
- **HTTPS/카메라 스캔**: 사내 도메인·인증서 확보 후 nginx 443 TLS 블록 + `ACS_PUBLIC_BASE_URL` https 설정 (웹캠 QR 스캔은 secure context 필수).
|
||||
- **사내망 Docker 빌드**: SSL 인스펙션 대응(사내 CA 주입 또는 내부 미러) 필요 시 Dockerfile 반영.
|
||||
Reference in New Issue
Block a user