Files
bapweb/docs/README.md
T
2026-09-09 19:30:24 +09:00

307 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FTPDJ — 대외기관 파일 수신 어댑터 (인수인계 문서)
> **대상 소스**: [`src/com/eactive/eai/adapter/ftp/FTPDJ.java`](../src/com/eactive/eai/adapter/ftp/FTPDJ.java)
> **관련 가이드**: [`DJERP_1000_AP_BAP(FTP)개발가이드_v0.9_20260603.pptx`](./DJERP_1000_AP_BAP%28FTP%29개발가이드_v0.9_20260603.pptx) — EMS 등록 절차·화면 조작 중심
> **최종 갱신**: 2026-09-09
## 0. 이 문서에 대하여
FTPDJ를 **넘겨받아 유지보수할 개발자**를 위한 문서입니다. PPT 가이드가 "화면에서 어떻게 등록하는가"를 다룬다면, 이 문서는 **"등록한 값이 코드에서 어떻게 해석되어 동작하는가"** 를 다룹니다.
특히 FTPDJ의 동작은 4장~10장이 그 내용이며 이 문서의 핵심입니다.
값 검증이 없어 **오타가 조용히 다른 동작으로 이어지는 지점**이 몇 군데 있습니다. 6장(`protocol`)과 7장(`io.bandwidth`)은 반드시 읽고 넘어가십시오.
---
## 1. 저장소 · 빌드 · 배포
### 1.1 Git 저장소
```
origin https://git.eactive.synology.me:8090/eapim/djbank/bapweb.git
```
| 브랜치 | 용도 |
|---|---|
| `master` | 주 브랜치 |
| `soxor` | 작업 브랜치 (FTPDJ 개발이 진행된 곳) |
FTPDJ 관련 주요 커밋:
| 커밋 | 일자 | 내용 |
|---|---|---|
| `adeec70` | 2026-06-05 | `FileFetchRequestHandler` 구현 (HTTP 요청 진입점 신규) |
| `7f98f97` | 2026-09-07 | 취약점 patch version (의존 jar 교체) |
### 1.2 빌드
Ant 기반입니다. Maven/Gradle 설정은 없으며 **jar는 `WebContent/WEB-INF/lib`에 직접 커밋**되어 있습니다.
```bash
ant war # clean → compile → dist/BAPWeb.war 생성
ant build # 컴파일만 (build/classes)
```
| 항목 | 값 |
|---|---|
| Java source/target | **17** (`build.xml`) |
| 컴파일 classpath | `WebContent/WEB-INF/lib/*.jar` (단 `ezgatormodule2.jar` 제외) + `libs/*.jar` |
| 산출물 | `dist/BAPWeb.war` |
| WAS | WebLogic, context-root `/BAPWeb` (`WebContent/WEB-INF/weblogic.xml`) |
> `WebContent/WEB-INF/oldlib`은 취약점 조치로 교체된 **구버전 jar 보관용**입니다. 클래스패스에도 WAR에도 포함되지 않습니다(`build.xml`의 war 태스크에서 제외 처리). 되돌릴 일이 없다면 삭제해도 무방합니다.
---
## 2. FTPDJ는 무엇인가
EMS(관리자포탈)의 **대외기관 등록 → 송수신구분코드**에서 `FTPDJ`를 선택하면 이 클래스가 전송 어댑터로 동작합니다.
DJBank 공용 SFTP 프록시를 경유해 **대외기관의 파일을 내려받는(Download) 전용 어댑터**입니다.
| 항목 | 내용 |
|---|---|
| 클래스 | `com.eactive.eai.adapter.ftp.FTPDJ` |
| 상속 / 구현 | `Transfer` 상속, `SocketService` 구현 |
| 지원 동작 | **수신(`recvFile`)만 지원** |
| 미지원 | `sendFile()` 호출 시 `UnsupportedOperationException` |
| 전송 라이브러리 | JSch 기반 (`FTPUtil.listFilesJSCH`, `FTPUtil.retrieve`) |
`SocketService`의 나머지 메서드(`connect`, `isActive`, `getCurrentSocket` 등)는 인터페이스 계약을 채우기 위한 빈 구현입니다. **실제 로직은 `recvFile()` 한 곳에서 시작합니다.** 소스를 처음 읽는다면 여기부터 보십시오.
---
## 3. 전체 동작 흐름
```
[1] HTTP 요청 (action=DOWNLOAD, remoteCode, remoteFile)
│ FileFetchRequestHandler
[2] Job_Queue 테이블에 작업 등록 → 즉시 응답 (status=QUEUED, jobId=UUID)
│ 스케줄러가 매분 0초 / 30초에 큐를 집어감
[3] FTPDJ.recvFile(batchDoc) ← 이 문서의 범위
├─ FtpEnv.init() : 접속정보(DB) + 프로퍼티(DB)로 실행 환경 조립 ← 핵심
├─ listFilesJSCH() : 원격 디렉터리 목록 조회 후 대상 파일 존재 확인
├─ fileDownProcess() : 다운로드 → RECVREAL 수신 → RECVARCH 이동 → check 파일 생성
└─ callbackProcess() : callback.url 이 있으면 BAT에 완료 통보 후 응답코드 검증
```
**비동기 구조**입니다. HTTP 요청은 큐에 넣고 바로 `QUEUED`를 반환하며, 실제 전송은 최대 30초 뒤 스케줄러가 시작합니다. 요청자는 `action=STATUS``jobId`를 조회해 진행 상태를 확인합니다. 요청/응답 JSON 규격은 PPT 가이드 4장을 참조하십시오.
---
## 4. 프로퍼티 목록 (`TelegramInfo{업무구분_대외기관}`)
FTPDJ가 **실제로 읽는** 프로퍼티는 아래 8개가 전부입니다.
| 키 | 기본값 | 용도 | 비고 |
|---|---|---|---|
| `remote.path` | `""` | 원격지 기준 경로 | `#YYYYMMDD#` 치환 지원. 요청이 절대경로면 **무시됨** (5장) |
| `protocol` | `""``sftp_passwd` | 전송 프로토콜 | **값 매핑 주의** (6장) |
| `proxy.server` | `""` | SFTP 프록시 서버 | `IP:PORT` 형식. 비우면 직접 접속 |
| `connection.timeout` | `30` | 접속 타임아웃(초) | 숫자만. 문자 포함 시 `NumberFormatException` |
| `read.timeout` | `30` | 읽기 타임아웃(초) | 동일 |
| `io.bandwidth` | `0` | 전송 대역 제한 | `0`=무제한. `K`/`M` 표기 지원 (7장) |
| `ftp.transfer.type` | `BINARY` | FTP 전송 모드 | `ASCII`가 필요한 경우에만 지정 |
| `callback.url` | `""` | 수신 완료 통보 URL | 비우면 콜백 생략 (10장) |
접속 정보(IP·포트·사용자 ID·비밀번호/SSH Key)와 수신 대상 파일명은 **프로퍼티가 아니라 대외기관 연결 정보 및 HTTP 요청**에서 배치 헤더로 전달됩니다. 프로퍼티에서 찾지 마십시오.
---
## 5. `remote.path` 조립 규칙
원격 경로는 **프로퍼티 단독으로 결정되지 않고** 요청으로 들어온 `remoteFile`과 조합됩니다.
```java
remotePath = 프로퍼티 remote.path
requestPath = remoteFile 디렉터리 부분
remoteFile = remoteFile 파일명 부분
remotePath = Paths.get(remotePath).resolve(requestPath).normalize() + "/"
```
### 5.1 요청이 절대경로면 `remote.path`는 무시된다
Java `Path.resolve()`는 인자가 절대경로일 때 **기준 경로를 버리고 인자를 그대로 반환**합니다. PPT 가이드의 *"remote.path : 요청시 full path 이면 무시됨"* 이 이 동작입니다.
| `remote.path` | 요청 `remoteFile` | 최종 원격 경로 | 파일명 |
|---|---|---|---|
| `/data/recv` | `test.txt` | `/data/recv/` | `test.txt` |
| `/data/recv` | `sub/test.txt` | `/data/recv/sub/` | `test.txt` |
| `/data/recv` | `/home/soxor/sftp/svr/test.txt` | `/home/soxor/sftp/svr/`**remote.path 무시** | `test.txt` |
| `""` (미등록) | `/home/soxor/sftp/svr/test.txt` | `/home/soxor/sftp/svr/` | `test.txt` |
> 요청자가 절대경로를 보내는 방식이면 `remote.path`는 등록하지 않아도 됩니다. 반대로 `remote.path`로 접근 경로를 **통제하려는 의도라면 이 구조로는 불가능합니다** — 요청자가 절대경로를 보내면 어디든 접근됩니다. 경로 제한이 요건이라면 별도 검증 로직을 추가해야 합니다.
### 5.2 날짜 토큰 치환
조합이 끝난 경로에서 `#YYYYMMDD#` 패턴을 찾아 실제 일자로 치환합니다.
```
정규식: #YYYYMMDD([+-]\d+)?#
```
| 표기 | 의미 | 예 (오늘이 2026-09-09) |
|---|---|---|
| `#YYYYMMDD#` | 당일 | `20260909` |
| `#YYYYMMDD-1#` | 전일 | `20260908` |
| `#YYYYMMDD-7#` | 7일 전 | `20260902` |
| `#YYYYMMDD+1#` | 익일 | `20260910` |
등록 예:
```
remote.path = /data/recv/#YYYYMMDD# → /data/recv/20260909/
remote.path = /data/recv/#YYYYMMDD-1# → /data/recv/20260908/
```
동작상 유의점:
1. 치환이 발생하면 계산된 일자가 **배치 헤더의 기준일자(BaseDate)로 함께 설정**됩니다. 로그·모니터링의 기준일자가 이 값을 따릅니다.
2. 치환 중 예외가 나도 **작업은 중단되지 않고** 어댑터 로그에만 기록됩니다. 이 경우 경로에 `#YYYYMMDD#` 문자열이 그대로 남아 원격 경로 조회 단계에서 실패합니다.
3. 경로 끝의 `/`는 없으면 자동으로 붙습니다.
---
## 6. `protocol` 값 매핑 (주의)
**가장 오해하기 쉬운 항목입니다.** 매칭은 열거형 이름 기준이고, **매칭에 실패해도 오류 없이 조용히 기본값으로 처리**됩니다.
| 등록값(대소문자 무관) | 해석 | 내부 코드 | 전송 방식 |
|---|---|---|---|
| `ftp` | FTP | `0` | 평문 FTP |
| `sftp_passwd` | SFTP + 비밀번호 | `1` | SFTP |
| `sftp_key` | SFTP + SSH Key | `2` | SFTP |
| **미등록 / 공백 / 그 외 모든 값** | **SFTP + 비밀번호** | `1` | SFTP |
즉 아래가 모두 **동일하게 `sftp_passwd`로 동작**합니다.
```
protocol = (미등록)
protocol = sftp ← 가이드 표기지만 매핑 대상이 아님
protocol = SFTP
protocol = sftp_password ← 오타
```
지켜야 할 규칙은 두 가지입니다.
- **FTP를 쓰려면 반드시 `ftp`로 정확히** 등록해야 합니다. 오타 시 SFTP로 접속을 시도해 원인 파악이 어려운 실패가 납니다.
- **SSH Key 인증을 쓰려면 반드시 `sftp_key`로** 등록해야 합니다. 비워두거나 `sftp`로 두면 비밀번호 인증으로 동작합니다.
> 오타가 로그에 남지 않으므로 접속 실패 시 프로퍼티 오타를 우선 의심하십시오. **개선 후보**: 매칭 실패 시 경고 로그를 남기거나 예외를 던지도록 `Protocol.get()`을 수정하는 것이 안전합니다.
---
## 7. `io.bandwidth` 단위 표기
문자열을 그대로 파싱하지 않고 **치환 후 정수 변환**합니다.
```java
.replace("K", "000").replace("M", "000000").replace(",", "")
```
| 등록값 | 실제 적용값 | 설명 |
|---|---|---|
| `0` 또는 미등록 | `0` | 제한 없음 |
| `500K` | `500000` | |
| `10M` | `10000000` | |
| `1,024` | `1024` | 콤마 제거 |
| `500k` | **예외 발생** | 소문자 `k`는 치환되지 않음 → `NumberFormatException` |
| `500KB` | **예외 발생** | `B`가 남음 |
**대문자 `K` / `M`만 사용하십시오.** 표기가 잘못되면 `FtpEnv.init()` 단계에서 예외가 나 다운로드가 시작되지 않습니다.
---
## 8. 파일 저장 위치 프로퍼티 (`ResponseRcvDirInfo`)
수신 파일이 놓이는 위치는 대외기관 프로퍼티가 아니라 **공통 그룹 `ResponseRcvDirInfo`** 에서 결정됩니다.
(EMS 메뉴: 환경정보 → 공통코드관리 → 프라퍼티 관리)
| 키 | 용도 | FTPDJ에서의 역할 |
|---|---|---|
| `rcv.real.directory` | 수신 중 임시 디렉터리 | 다운로드가 **먼저 떨어지는 곳** |
| `rcv.arch.directory` | 수신 완료 보관 디렉터리 | 완료 후 **이동되는 최종 위치**. check 파일도 여기 |
| `rcv.error.directory` | 오류 파일 디렉터리 | 실패 시 받다 만 파일을 이동 |
| `rcv.root.directory` | 빈 완료표시 파일 위치 | 0바이트 파일 생성 |
| `rcv.root.ext` | check 파일 확장자 | **미등록 시 `.chk`** |
| `retension.day.*` | 폴더별 보존일수 | 값(일) 경과 시 자동 삭제. 공백/비정수면 삭제 안 함 |
### 8.1 경로 조립과 파일 생성 순서
작업 등록 시 수신 디렉터리는 `rcv.real.directory/업무구분코드/대외기관코드` 로 설정됩니다.
```
① 다운로드 {rcv.real.directory}/{업무구분}/{대외기관}/{파일명}
② 이동 {rcv.arch.directory}/{업무구분}/{대외기관}/{파일명}
③ check 파일 {rcv.arch.directory}/{업무구분}/{대외기관}/{파일명}{rcv.root.ext}
④ 빈 표시 파일 {rcv.root.directory}/{업무구분}/{대외기관}/{파일명}
```
- ①→② 이동은 `REPLACE_EXISTING`이므로 **같은 이름의 기존 파일은 덮어씁니다.**
- ③, ④는 생성 전에 기존 파일이 있으면 **먼저 삭제**하고 새로 만듭니다.
- 다운로드 중 실패하면 ①의 파일을 `rcv.error.directory`로 옮기고, 이동조차 실패하면 조용히 무시합니다.
> **순서를 바꾸지 마십시오.** check 파일(③)은 반드시 본 파일 이동(②) **이후**에 생성됩니다. 수신측(BAT)이 check 파일을 폴링하는 구조이므로, check 파일이 보이는 시점에는 본 파일이 이미 최종 위치에 있어야 합니다. 순서를 뒤집으면 BAT가 미완성 파일을 집어갈 수 있습니다.
---
## 9. check 파일 포맷
`rcv.arch.directory` 아래 `{파일명}.chk`로 생성되며, 기존 EAI(BAT)가 폴링으로 읽어갑니다.
```
%-30s %8s %010d %c %-100s @@
```
| 위치(1-based) | 길이 | 내용 |
|---|---|---|
| 1 30 | 30 | InterfaceID (거래구분코드 `bizCode`), 좌측정렬 공백 패딩 |
| 31 38 | 8 | 기준일자 `YYYYMMDD` (생성 시점의 현재일자) |
| 39 48 | 10 | 파일 건수, 0 패딩 (**항상 `0000000001`**) |
| **49** | 1 | **상태값** — 생성 시 `'0'` 고정 |
| 50 149 | 100 | 파일명, 좌측정렬 공백 패딩 |
| 150 151 | 2 | 종료 표시 `@@` |
총 **151바이트**입니다. 49번째 바이트의 상태값은 콜백 응답 검증에서 다시 쓰입니다(10장). 상수 `BAT_RET_CODE_IDX = 49`가 이 위치를 가리킵니다.
> **BAT와의 인터페이스 규격입니다.** 포맷을 바꾸면 수신측 파싱이 깨집니다. 변경 시 반드시 BAT 담당자와 합의하십시오.
---
## 10. `callback.url` 동작
`callback.url`**비어 있으면 콜백 단계 자체를 건너뜁니다.** 등록되어 있으면 다음을 호출합니다.
```
GET {callback.url}?eaibatMsg={check 파일과 동일한 151바이트 문자열}
```
응답 검증 조건은 세 가지이며, **하나라도 어긋나면 작업 전체가 실패 처리**됩니다.
| 검증 | 실패 시 메시지 |
|---|---|
| HTTP 상태코드 `200` | `Response code is not 200.(코드)` |
| 응답 본문 길이 ≥ 49바이트 | `BAT return message is too short.` |
| 응답 본문 **49번째 바이트 = `'1'`** | `BAT return code is not '1'.(값)` |
응답 인코딩은 `Content-Type` 헤더의 charset을 따르고, 없으면 UTF-8로 처리합니다.
> **알아둘 것:** 파일 수신과 저장이 모두 성공해도 콜백 검증에서 실패하면 작업은 오류로 기록됩니다.
---
## 11. 의존 라이브러리 취약점 조치 내역
| 취약점 번호 | 해당 jar 파일 | 조치 내용 |
|:-------------------|:-----------------------------|:------------------------------------|
| CVE-2023-46604<br>CVE-2026-34197 | activemq-broker-5.15.13.jar<br>activemq-client-5.15.13.jar<br>activemq-console-5.15.13.jar<br>activemq-spring-5.15.13.jar | **5.15.13 → 5.19.11** 교체.<br>이 CVE의 최소 수정 버전은 5.15.16이며,<br>현재 활성 라인 최신인 5.19.11을 적용했습니다. |
| CVE-2023-48795 | jsch-0.1.55.jar | **com.jcraft:jsch 0.1.55<br>→ com.github.mwiede:jsch 2.28.7** 교체.<br>Terrapin은 0.2.15에서<br>strict KEX 지원으로 수정되었습니다. |
| CVE-2023-20863 | spring-core-5.3.27.jar 등<br>spring 계열 14건 | **현재 버전 5.3.27이 바로 이 CVE의 수정 버전입니다.**<br>영향 범위는 5.3.05.3.26 및 6.0.06.0.7이고,<br>수정 릴리스가 5.3.27 / 6.0.8입니다.<br>\*\* 조치 불필요. 스캔 도구가 영향 범위 상한을<br>잘못 잡은 것으로 보이며, 판정 근거 재확인을 요청드립니다 |
| CVE-2026-22740 | spring-core-5.3.27.jar 등<br>spring 계열 14건 | 이 결함은 **WebFlux 서버 애플리케이션 전용**입니다<br>(멀티파트 임시파일 미삭제).<br>본 애플리케이션은 `spring-webmvc` 기반이며<br>`spring-webflux` jar 자체가 배포물에 없습니다. |