공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 17 / 22

SFTP·FTP 파일 연계

연계 규격, 원자적 전송·마커 파일, 체크섬, 재처리·중복 방지, 키 인증, JSch
섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

3. 코드 예제

코드는 java-src/extension/17_sftp 에 있고 FileTransfer(인터페이스)·LocalTransfer(로컬 폴더 구현체)·Main(데모) 세 파일로 구성됩니다. 외부 jar 는 필요 없습니다.

cmd
cd java-src\extension\17_sftp
javac -encoding UTF-8 *.java && java Main

FileTransfer — 전송 인터페이스와 실패 분류

java
public interface FileTransfer extends AutoCloseable {
    void connect() throws TransferException;
    List<String> list(String remoteDir) throws TransferException;
    void upload(String localPath, String remoteName) throws TransferException;
    void download(String remoteName, String localPath) throws TransferException;
    void rename(String from, String to) throws TransferException;
    void delete(String remoteName) throws TransferException;
    long size(String remoteName) throws TransferException;

    enum Kind { CONNECT, AUTH, PERMISSION, IO }

    class TransferException extends Exception {
        public final Kind kind;
        public TransferException(Kind kind, String message) {
            super(message);
            this.kind = kind;
        }
    }
}

실무 구현체(SFTP·FTP)는 이 인터페이스만 구현하면 배치 로직은 그대로 재사용됩니다. 프로토콜을 바꿔도 Main 의 흐름 코드는 손댈 필요가 없습니다.

LocalTransfer — 로컬 폴더를 원격 서버처럼

java
// 로컬 폴더를 "원격 루트"로 삼아 upload 를 스트리밍 복사로 구현
@Override
public void upload(String localPath, String remoteName) throws TransferException {
    Path src = Paths.get(localPath);
    Path dst = root.resolve(remoteName);
    try (InputStream in = Files.newInputStream(src);
         OutputStream out = Files.newOutputStream(dst)) {
        byte[] buf = new byte[4096];
        long sent = 0;
        int n;
        while ((n = in.read(buf)) != -1) {
            // failAfterBytes 설정 시 중간 끊김을 인위로 재현
            if (failAfterBytes >= 0 && sent + n > failAfterBytes) {
                out.write(buf, 0, (int) Math.max(0, failAfterBytes - sent));
                throw new TransferException(Kind.IO, "전송 중단: " + remoteName);
            }
            out.write(buf, 0, n);
            sent += n;
        }
    } catch (IOException e) {
        throw new TransferException(Kind.IO, "업로드 실패: " + remoteName, e);
    }
}

failAfterBytes 는 데모 전용 훅입니다. 실제 SFTP 구현체에는 없고, 여기서는 중간 끊김 상황을 재현소 검증하기 위해 넣었습니다.

원자적 업로드 — .tmp → 크기 확인 → rename → .ok

java
private static void safeUpload(FileTransfer client, Path local, String remoteName)
        throws FileTransfer.TransferException, IOException {
    String tmpName = remoteName + ".tmp";
    client.upload(local.toString(), tmpName);
    long localSize = Files.size(local);
    long remoteSize = client.size(tmpName);
    if (remoteSize != localSize) {
        throw new FileTransfer.TransferException(FileTransfer.Kind.IO,
                "크기 불일치: local=" + localSize + " remote=" + remoteSize);
    }
    client.rename(tmpName, remoteName);
    Path markerLocal = Files.createTempFile("marker-", ".ok");
    client.upload(markerLocal.toString(), remoteName + ".ok");
    Files.deleteIfExists(markerLocal);
}

이 메서드 하나가 이 레슨의 핵심입니다. SFTP·FTP·로컬 폴더 어떤 구현체를 넣어도 같은 절차로 원자적 전송이 됩니다.

데모 실행 결과

text
=== 1. 안전 업로드 (.tmp -> rename, .ok 마커) ===
업로드 완료: incoming/20260914_KFTC_001.dat (+.ok)

=== 2. 중간 끊김 재현 후 재시도 ===
전송 실패(IO): 전송 중단: 20260914_KFTC_002.dat.tmp
.tmp 잔존=true, 정식 파일 없음=true
재시도 성공: 63 / 63 bytes

=== 3. 수신 측 폴링: .ok 있는 파일만 처리, 중복 수신 건너뜀 ===
처리 완료 -> processed/20260914_KFTC_001.dat
처리 완료 -> processed/20260914_KFTC_002.dat
-- 같은 파일 재전송(중복) 시도 --
건너뜀(이미 처리 이력 있음): 20260914_KFTC_001.dat

=== 4. SHA-256 체크섬 생성·검증 ===
20260914_KFTC_001.dat sha256=53944aaefab8c340... 검증=true

=== 5. 감사 로그 ===
2026-09-14T10:58:39.539444900 UPLOAD user=batch01 file=20260914_KFTC_001.dat bytes=41 result=OK

2단계에서 .tmp 만 남고 정식 파일은 생기지 않는 것이 원자적 전송의 핵심입니다. 재시도는 실패한 파일을 처음부터 다시 올려 성공했습니다. 3단계 재전송은 처리 이력에 남아 있어 건너뛰었습니다.

예제 직접 실행

아래 폴더를 JDK 21 로 컴파일하고 실행합니다. 외부 jar 를 쓰는 레슨은 java-src/lib 를 클래스패스에 넣습니다.

cd java-src\extension\17_sftp
javac -encoding UTF-8 *.java && java Main

:: 외부 jar 가 필요한 레슨
javac -encoding UTF-8 -cp "..\..\lib\*;." *.java && java -cp "..\..\lib\*;." Main
코드 예제
  • FileTransfer — 전송 인터페이스와 실패 분류
  • LocalTransfer — 로컬 폴더를 원격 서버처럼
  • 원자적 업로드 — .tmp → 크기 확인 → rename → .ok
  • 데모 실행 결과
이전 섹션2 핵심 원리3 / 7다음 섹션4 응용 변형 예제