FTPDJ — 대외기관 파일 수신 어댑터 (인수인계 문서)
관련 가이드:
DJERP_1000_AP_BAP(FTP)개발가이드_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에 직접 커밋되어 있습니다.
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과 조합됩니다.
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/
동작상 유의점:
- 치환이 발생하면 계산된 일자가 배치 헤더의 기준일자(BaseDate)로 함께 설정됩니다. 로그·모니터링의 기준일자가 이 값을 따릅니다.
- 치환 중 예외가 나도 작업은 중단되지 않고 어댑터 로그에만 기록됩니다. 이 경우 경로에
#YYYYMMDD#문자열이 그대로 남아 원격 경로 조회 단계에서 실패합니다. - 경로 끝의
/는 없으면 자동으로 붙습니다.
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 단위 표기
문자열을 그대로 파싱하지 않고 치환 후 정수 변환합니다.
값.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 CVE-2026-34197 |
activemq-broker-5.15.13.jar activemq-client-5.15.13.jar activemq-console-5.15.13.jar activemq-spring-5.15.13.jar |
5.15.13 → 5.19.11 교체. 이 CVE의 최소 수정 버전은 5.15.16이며, 현재 활성 라인 최신인 5.19.11을 적용했습니다. |
| CVE-2023-48795 | jsch-0.1.55.jar | com.jcraft:jsch 0.1.55 → com.github.mwiede:jsch 2.28.7 교체. Terrapin은 0.2.15에서 strict KEX 지원으로 수정되었습니다. |
| CVE-2023-20863 | spring-core-5.3.27.jar 등 spring 계열 14건 |
현재 버전 5.3.27이 바로 이 CVE의 수정 버전입니다. 영향 범위는 5.3.0–5.3.26 및 6.0.0–6.0.7이고, 수정 릴리스가 5.3.27 / 6.0.8입니다. ** 조치 불필요. 스캔 도구가 영향 범위 상한을 잘못 잡은 것으로 보이며, 판정 근거 재확인을 요청드립니다 |
| CVE-2026-22740 | spring-core-5.3.27.jar 등 spring 계열 14건 |
이 결함은 WebFlux 서버 애플리케이션 전용입니다 (멀티파트 임시파일 미삭제). 본 애플리케이션은 spring-webmvc 기반이며spring-webflux jar 자체가 배포물에 없습니다. |