구글에서는 개발자에게 재무보고서를 제공하고 있습니다.

 

'예상판매실적 보고서', '수익 보고서', '대한민국 Play 잔액 차감 보고서'를 제공하는데, 이 중 실제 회계처리는 수익 보고서를 이용해야합니다.

 

그런데 수익 보고서 CSV파일을 분석해보니 조금 귀찮은 문제(타임존 처리, 필드 포맷 등)가 있습니다.

이에 간단히 프로그램을 만들었습니다.

 

프로그램은 한국 개발자에게 익숙한 java로 만들었습니다.

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * 구글 재무보고서 중에서 '수익'보고서 CSV파일을 읽어서 파싱
 *  - 구글 '재무보고서 수익'에 대한 설명: https://support.google.com/googleplay/android-developer/answer/6135870#zippy=%2C%EC%88%98%EC%9D%B5
 *  - apache commons-csv를 이용해서 CSV파일을 파싱( https://mvnrepository.com/artifact/org.apache.commons/commons-csv/1.9.0 )
 *
 * @author eomsh
 */
@Slf4j
public class EarningsReportFileParser {

	private static String targetFilePath = "파싱할 CSV파일 경로"; //파싱할 CSV파일

	private static DateTimeFormatter parseFMT = DateTimeFormatter.ofPattern("MMM d, yyyy hh:mm:ss a z", Locale.ENGLISH); //CSV 일시 파싱을 위한 포맷(예. Sep 1, 2021 12:00:20 AM PST)

	//private static ZoneId kstZoneId = ZoneId.of("Asia/Seoul");
	private static ZoneId pstZoneId = ZoneId.of("America/Los_Angeles"); //PST는 ZoneId가 'America/Los_Angeles' 임
	private static ZoneId pdtZoneId = ZoneId.of("GMT-07:00"); //PDT는 ZoneId가 'GMT-07:00' 임.

	private static Map<String, Integer> csvHeadMap = null; //CSV 헤더 맵

	/**
	 * 구글 개발자콘솔에서 다운로드한 재무보고서-수익 CSV파일을 파싱하는 프로그램
	 *
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {

		LocalDateTime start = LocalDateTime.now();
		log.info("\n\n==== Start: {} ====", start);

		List<CSVRecord> records = getCsvRecords(new File(targetFilePath)); //파싱하여 데이터 records를 얻음

		log.info("CSV 데이터 records 수: {}", records.size());
		//log.debug("records 0번째 데이터:{}", records.get(0));

		for (CSVRecord record : records) {

			//Transaction 일시를 유닉스타임스탬프로 변환(CSV문서의 데이터는 PDT, PST 등의 일치하지 않는 타임존 정보로 되어 있기에 내부 처리의 용이함을 위해 유닉스타임스탬프로 변환
			getUnixTs(record.get("Transaction Date"), record.get("Transaction Time"));
		}

		LocalDateTime end = LocalDateTime.now();
		log.info("\n\n==== End: {} ==== 소요시간: {}(second)", end, Duration.between(start, end).getSeconds()); //로컬 PC에서 50만건 대략 10초 소요됨

	}

	/**
	 * 수익 CSV파일을 파싱하여 List데이를 얻음
	 *
	 * @param targetFile
	 * @return
	 * @throws IOException
	 */
	private static List<CSVRecord> getCsvRecords(File targetFile) throws IOException {

		//구글 개발자콘솔의 재무보고서 기능을 통해서 내려받은 '수익.csv파일'

		int sampleDataRow = 0; //샘플 데이터 row번호
		try (BufferedReader bufferedReader = new BufferedReader(new FileReader(targetFile))) {

			
            //구글 수익보고서를 분석하여 필요에 맞게 적절하게 CSV포맷 지정
			//@formatter:off
			CSVFormat csvFormat = CSVFormat.EXCEL.builder() //기본 엑셀타입 포맷
				.setHeader().setSkipHeaderRecord(true) //헤더 포함&skip
				.setQuote('"') //쌍따옴표 escape처리
				.setNullString("") //empty는 null처리
				.build();
			//@formatter:on
			CSVParser parser = csvFormat.parse(bufferedReader); //파서 처리
            
			//CSVParser parser = CSVFormat.EXCEL.withFirstRecordAsHeader().withQuote('"').parse(bufferedReader); //엑셀타입 & 쌍따옴표 escape처리
			List<CSVRecord> records = parser.getRecords();

			csvHeadMap = parser.getHeaderMap();

			// @formatter:off
			log.debug("\n"
					+ "CSV 데이터 records 수: {}\n"
					+ "CSV 헤더 정보\n\t"
						+ "CSV헤더 필드수: {}\n\t"
						+ "헤더 필드리스트: {}\n"
					+ "{}번째 row 데이터 정보\n\t"
						+ "데이터 필드수: {}\n\t"
						+ "데이터: {}\n",
						records.size(),
						parser.getHeaderMap().size(),
						parser.getHeaderMap(),
						sampleDataRow,
						records.get(sampleDataRow).size(),
						records.get(sampleDataRow)
			);

			// @formatter:on

			return records;
		}
	}

	/**
	 * transaction 일시 정보를 유닉스타임스탬프로 변환하여 리턴
	 *
	 * @param transactionDate CSV의 Transaction Date 필드의 값
	 * @param transactionTime CSV의 Transaction Time 필드의 값
	 * @return
	 */
	private static long getUnixTs(String transactionDate, String transactionTime) {

		String timeField = transactionTime;

		String timeArray[] = StringUtils.split(transactionTime, ":");
		if (timeArray[0].length() != 2) { //1:01:30 AM PDT 와 같이 시간필드에 0이 누락된 경우는 0을 붙여줘서 파싱 포맷에 맞게 변환
			timeField = "0" + timeField;
		}

		final String targetStr = transactionDate + " " + timeField; // 예) Sep 1, 2021 12:00:20 AM PDT

		if (StringUtils.contains(transactionTime, "PST")) {

			LocalDateTime targetDT = LocalDateTime.parse(targetStr, parseFMT); //파싱
			ZonedDateTime pstZDT = targetDT.atZone(pstZoneId);
			LocalDateTime pstDT = pstZDT.withZoneSameInstant(pstZoneId).toLocalDateTime();

			return pstDT.toEpochSecond(pstZoneId.getRules().getOffset(pstDT));

		} else if (StringUtils.contains(transactionTime, "PDT")) {

			LocalDateTime targetDT = LocalDateTime.parse(targetStr, parseFMT); //파싱
			ZonedDateTime pdtZDT = targetDT.atZone(pdtZoneId);
			LocalDateTime pdtDT = pdtZDT.withZoneSameInstant(pdtZoneId).toLocalDateTime();

			return pdtDT.toEpochSecond(pdtZoneId.getRules().getOffset(pdtDT));
		}

		throw new IllegalStateException(String.format("Not supported timezone data. transactionDate:'%s' | transactionTime:'%s'", transactionDate, transactionTime));
	}
}

java 8버전 이상 필요


import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

/**
 * 타임존 변환 샘플 소스
 *
 * @author
 */
public class TimeZoneConvert {

	public static void main(String[] args) {
		pstData();
	}

	/**
	 * PST로 변환 샘플
	 *  - 참고: mysql 기준 쿼리 샘플 SELECT CONVERT_TZ( NOW(), 'UTC', 'Asia/Seoul') AS kst, CONVERT_TZ(NOW(), 'UTC', 'America/Los_Angeles') AS PST
	 */
	public static void pstData() {

		DateTimeFormatter strFMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); //문자 출력용 포맷

		//PST(Pacific Standard Time). 태평양표준시로 UTC-8. PST는 흔히 LA타임으로 불리며, ZoneID는 'America/Los_Angeles' 임
		//PST와 KST는 17시간 차이가 남(PST가 17시간 늦음). KST->PST는 17시간 빼면됨
		//String targetStrPST = "Mar 1, 2021 11:42:23 PM PST"; //KST 기준으로 2021-03-02 16:42:23
		String targetStrPST = "Mar 4, 2021 05:32:33 PM PST"; //KST 기준으로 2021-03-02 16:42:23

		DateTimeFormatter targetFMT = DateTimeFormatter.ofPattern("MMM d, yyyy hh:mm:ss a z", Locale.ENGLISH); //대상 문자 PST의 포맷
		LocalDateTime targetPstDT = LocalDateTime.parse(targetStrPST, targetFMT);

		ZonedDateTime pstZDT = targetPstDT.atZone(ZoneId.of("America/Los_Angeles")); //PST는 ZoneId가 'America/Los_Angeles' 임
		System.out.println("PST ymdt=> " + pstZDT.toLocalDateTime().format(strFMT));

		ZoneId kstZoneId = ZoneId.of("Asia/Seoul");
		LocalDateTime kstDT = pstZDT.withZoneSameInstant(kstZoneId).toLocalDateTime(); //KST로 변환
		String kstStr = kstDT.format(strFMT);

		System.out.println("KST ymdt => " + kstStr);
		System.out.println("KST ymdt toEpochSecond(유닉스 타임스탬프) => " + kstDT.toEpochSecond(kstZoneId.getRules().getOffset(kstDT)));

	}
    
    /**
	 * PDT로 변환 샘플
	 */
	public static void pdtData() {

		final ZoneId pdtZoneId = ZoneId.of("GMT-07:00"); //PDT는 GMT-07:00
		final DateTimeFormatter strFMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); //문자 출력용 포맷

		//참고링크: https://savvytime.com/converter/pdt-to-kst-utc/aug-1-2021/3am
		String targetStrPDT = "Aug 1, 2021 12:03:00 AM PDT"; //KST 기준으로 2021-08-01 16:03:00이고, UTC기준으로는 2021-08-01 07:03:00

		DateTimeFormatter targetFMT = DateTimeFormatter.ofPattern("MMM d, yyyy hh:mm:ss a z", Locale.ENGLISH); //대상 문자 포맷
		LocalDateTime targetPdtDT = LocalDateTime.parse(targetStrPDT, targetFMT); //PDT의 LocalDateTime 객체가 생성됨

		// @formatter:off
		System.out.println(
			String.format(
				"PDT 테스트 문자열: %s\n"
				+ "PDT LocalDateTime: %s\n"
				+ "PDT EpochSecond: %s\n"
				+ "PDT -> UTC LocalDateTime: %s\n"
				+ "PDT -> KST LocalDateTime: %s",
				targetStrPDT,
				targetPdtDT.format(strFMT),
				targetPdtDT.atZone(pdtZoneId).toEpochSecond(),
				targetPdtDT.atZone(pdtZoneId).withZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime().format(strFMT),
				targetPdtDT.atZone(pdtZoneId).withZoneSameInstant(ZoneId.of("Asia/Seoul")).toLocalDateTime().format(strFMT)
			)
		);

	}
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

/**
 * CSV파일을 읽어서 파싱하여 DB(Mysql)에 저장하는 프로그램 샘플
 *  - apache commons-csv를 이용해서 CSV파일을 파싱(참고: https://mvnrepository.com/artifact/org.apache.commons/commons-csv/1.9.0 )
 *
 * @author
 */
@Slf4j
public class CsvFileParseSaveToDB {

	private static String targetFilePath = "CSV파일 전체 경로";

	public static void main(String[] args) {

		//한글 깨지 방지를 위해서 characterEncoding=UTF-8 처리
		final String jdbcURL = "jdbc:mysql://DB주소:3306/논리DB명?characterEncoding=UTF-8";
		final String username = "DB ID";
		final String password = "DB 암호";

		final int batchSize = 2_000; //bulk insert시 커밋 갯수

		Connection connection = null;

		try {

			connection = DriverManager.getConnection(jdbcURL, username, password);
			connection.setAutoCommit(false);

			String sql = "insert  into `temp_test`(`name_1`,`name_2`) " + "VALUES (?, ??)";

			PreparedStatement statement = connection.prepareStatement(sql);

			int columnSize = 2; //CSV 데이터 필드 컬럼 갯수

			List<CSVRecord> records = getCsvRecords();
			for (int row = 0; row < records.size(); row++) {

				CSVRecord data = records.get(row);
				for (int fieldIndex = 0; fieldIndex < columnSize; fieldIndex++) {
					statement.setString(fieldIndex + 1, data.get(fieldIndex));
				}

				statement.addBatch();
				if (row % batchSize == 0) {
					statement.executeBatch();
					System.out.println(String.format("statement.executeBatch ing row ==> %s", row));
					connection.commit(); //DB서버 부하분산을 원하는 대용량 처리시 중간중간 커밋

					sleep(1); //부하 분산
				}

			}

			//남아있는 데이터 처리
			System.out.println("나머지 데이터도 executeBatch ");
			statement.executeBatch();
			connection.commit();

			connection.close();

		} catch (IOException ex) {
			System.err.println(ex);
		} catch (SQLException ex) {
			ex.printStackTrace();

			try {
				connection.rollback();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}

	}

	private static void sleep(long millis) {

		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private static List<CSVRecord> getCsvRecords() throws IOException {

		File targetFile = new File(targetFilePath);

		int sampleDataRow = 0; //샘플 데이터 row번호
		try (BufferedReader bufferedReader = new BufferedReader(new FileReader(targetFile))) {

			CSVParser parser = CSVFormat.EXCEL.withFirstRecordAsHeader().withQuote('"').parse(bufferedReader); //엑셀타입 & 쌍따옴표 escape처리
			List<CSVRecord> records = parser.getRecords();

			log.debug("\nCSV 헤더\n\t{}\n데이터 샘플\n\t{}\n", parser.getHeaderMap(), records.get(sampleDataRow));
			log.info("\n\t헤더 필드 갯수 :{}\n\t데이터 갯수 :{}\n\t{}번째 row의 데이터 필드 갯수:{}\n\n", parser.getHeaderMap().size(), records.size(), sampleDataRow,
				records.get(sampleDataRow).size());

			return records;
		}
	}

}

1. 필요사항(maven dependency추가)

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-csv -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-csv</artifactId>
            <version>1.9.0</version>
        </dependency>

 

2. 간단한 java CSV파일 파싱 샘플 소스

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;

/**
 * CSV파일을 읽어서 파싱
 *  - apache commons-csv를 이용해서 CSV파일을 파싱: https://mvnrepository.com/artifact/org.apache.commons/commons-csv/1.9.0
 *
 * @author 엄승하
 */
@Slf4j
public class CsvFileParser {

	public static void main(String[] args) throws IOException {

		//파싱할 CSV파일
		File targetFile = new File("CSV파일 경로");

		int sampleDataRow = 0; //샘플 데이터 row번호
		try (BufferedReader bufferedReader = new BufferedReader(new FileReader(targetFile))) {

			CSVParser parser = CSVFormat.EXCEL.withFirstRecordAsHeader().withQuote('"').parse(bufferedReader); //엑셀타입 & 쌍따옴표 escape처리
			List<CSVRecord> records = parser.getRecords();

			log.debug("\nCSV 헤더\n\t{}\n데이터 샘플\n\t{}", parser.getHeaderMap(), records.get(sampleDataRow));

			//샘플 데이터의 필드 데이터를 개행하여 출력
			//			for (String field : records.get(sampleDataRow)) {
			//				System.out.println(field);
			//			}

			log.info("헤더 필드 갯수 :{} | {}번째 row의 데이터 필드 갯수:{}", parser.getHeaderMap().size(), sampleDataRow, records.get(sampleDataRow).size());
			log.info("헤더: {} ", parser.getHeaderMap());

		}

	}
}

 

간단한 Testcase로 확인용 작성

	@Test
	void 두글자국가코드_및_국가명_리스트조회() {

		String[] countries = Locale.getISOCountries();
		//Arrays.stream(countries).forEach(System.out::println); //2글자 국가코드 리스트 확인

		for (String country : countries) {

			Locale l = new Locale("en", country);
			System.out.println(String.format("2글자 국가코드(ISO 3166-1 alpha-2): %s | 영문 국가명: %s | 한글 국가명: %s ", country, l.getDisplayCountry(new Locale("en")),
				l.getDisplayCountry(new Locale("ko"))));

		}

		System.out.println("countries 갯수: " + countries.length);

		Assertions.assertNotNull(countries);
		Assertions.assertTrue(countries.length >= 200); //2021년기준 249개국이 존재.(200개 국가 이상을 assert 체크 기준으로 함)
	}

메모 목적의 글로써 생략되는 내용이 많이 있을 수 있는점을 감안해주세요

(우아콘 2020참고)

 

  1. querydsl exist 사용 금지
    1. 기본
      1. sql의 exist는 조건을 만족하는 1번째 row를 만나면 쿼리가 바로 종료
      2. count의 경우는 모든 row를 scan해야하기 때문에 성능이 exist보다 안 좋음
    2. qeurydsl의 exist는 count 쿼리를 이용해서 수행되기 때문에 성능이 안 좋음
    3. 직접 구현
      1. limit 1을 추가함(예전에 mysql 생쿼리로 개발할때도 이렇게 했었음..)
        1. 다만, 조회결과가 없으면 0이 아니라 null을 반환하니 null 체크처리 주의
  2. cross join 회피
    1. 나올수 있는 모든 경우의 수를 대상으로 하기때문에 성능이 안좋은 cross join. 피하는게 좋음
    2. querydsl은 묵시적 join사용할때 cross join발생할 수 있음
    3. 명시적 join으로 회피
      1. 예) innerJoin 메소드로 명시적 처리
  3. Entity 보다는 DTO를 우선 사용
    1. entity 조회시
      1. 실시간으로 Entity 변경이 필요한 경우에는 장점 -> 다만 회사 프로덕션 환경에서 이럴일은 없음
      2. hibernate 캐시 불필요
      3. 불필요한 컬럼 조회
      4. OneToOne N+1  쿼리 등
      5. 단순 조회 기능에서는 성능 이슈 요소가 많음
    2. DTO 조회시
      1. 고강도 성능 개선, 대량의 데이터 조회가 필요한 경우
      2. 조회 컬럼 최소화하기
        1. as 표현식으로 대체하면 DB에실행되는쿼리에서 as컬럼은 제외됨
          1. 예) Expressions.asNumber(bookNo).as("bookNo")
      3. Select 컬럼에 Entity 자제
        1. 불필요 신규 Entity의 모든 컬럼이 조회될 수 있음
        2. OntToOne 관계에 대해서 매건마다 조회됨(N+1무조건 발생하게 됨)
  4. Group by 최적화
    1. mysql에서는 order by null 을 이용하면 file sort가 발생하지 않는 기능이 존재(개인적으로도 많이 사용)
    2. querydsl에서는 order by null을 지원하지 않기 때문에 직접 구현해서 사용
      1. 정렬이 필요하더라도 조회결과가 100건 이하라면 어플리케이션에서 정렬하는걸 고려(페이징이 아닐때만)
        1. was는 scale out이 가능하지만 DB는 어려움
  5. 커버링 인덱스 사용
    1. 개인적으로도 대용량 게시판 서비스의 페이징 개념 만들때 사용함
    2. jpql은 from절의 서브쿼리를 지원하지 않기 때문에 우회처리가 필요
      1. 쿼리를 2개로 나눠서 실행
        1. Cluster Key(PK와 같은)를 커버링 인덱스로 빠르게 조회하고
        2. 조회된 Key로 select쿼리를 in 쿼리로 실행
  6. update 최적화
    1. 무분별한 DirtyChecking을 꼭 확인해야함
      1. 실시간 비지니스 처리, 실시간 단건 처리시
        1. 하이버네이트 캐시는 일괄 업데이트시 캐시 갱신이 안되기때문에
      2. Querydsl.update
        1. 대량의 데이터를 일괄로 Update 처리시
  7. bulk insert
    1. JPA에서는 auto_increment일때는 insert합치기가 적용되지 않는 문제가 있음
    2. jdbcTemplate롤 bulk insert는 처리 가능하나, 컴파일 체크, Type-safe 개발이 어려움
      1. 문자열로 쿼리를 작성해야해서
    3. 따로 개발해서 진행할수도 있지만... 개인적으로 고민좀 되는 부분이 있음

내부 신입 개발자 등을 위해서 간단히 프로토타이핑한 java stream filter 중복제거 소스입니다.

package stream;

import lombok.Data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 내부 개발자 교육을 위한 간단한 java Stream 샘플 소스
 *  - 중복제거
 *
 * @author 엄승하
 */
public class StreamDistinct {

	public static void main(String[] args) {

		//테스트 데이터 생성
		int cnt = 10_000;
		List<TestVO> list = new ArrayList<>(cnt);
		for (int i = 1; i <= cnt; i++) {
			list.add(getTestVO(1, i)); //usreId는 동일하게 생성
		}

		//중복되지 않는 userId 추가 생성
		list.add(getTestVO(2, 10));
		list.add(getTestVO(3, 11));
		list.add(getTestVO(4, 12));

		System.out.println(String.format("\n테스트 list의 데이터 갯수:%d\n", list.size()));

		long start = System.currentTimeMillis();
		List<String> result = list.stream().filter(distinctByKey(m -> m.getUserId())).map(TestVO::getUserId).collect(Collectors.toList()); //java stream을 이용해서 유니크한 userId리스트만 모으기
		long end = System.currentTimeMillis();

		System.out.println("== Start: 중복 제거된 userId 리스트");

		for (String userId : result) {
			System.out.println(userId);
		}
		System.out.println("== End: 중복 제거된 userId 리스트");

		System.out.println("\n중복제거 stream filter 소요시간(millis): " + (end - start));

	}

	private static TestVO getTestVO(int suffixUserId, int suffixBookName) {

		TestVO vo = new TestVO();
		vo.setUserId("eom_" + suffixUserId);
		vo.setBookName("book_" + suffixBookName);

		return vo;
	}

	/**
	 * 특정 키로 중복제거
	 *
	 * @param keyExtractor
	 * @param <T>
	 * @return
	 */
	private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
		Map<Object, Boolean> map = new HashMap<>();
		return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
	}

	@Data
	private static class TestVO {

		private String userId;
		private String bookName;

	}
}

 

실행하면 아래와 같은 결과가 나옵니다.

테스트 list의 데이터 갯수:10003

== Start: 중복 제거된 userId 리스트
eom_1
eom_2
eom_3
eom_4
== End: 중복 제거된 userId 리스트

중복제거 stream filter 소요시간(millis): 2

사설 인증키를 blob으로 DB에 저장하고,

admin 시스템 만들어서 관리 자동화 하는 기능을 만드는데 가끔 잊어버립니다.(여러 랭귀지를 쓰다보니...)

 

잊어버릴때 찾기 위해서 필요한 부분을 메모 목적으로 blog에 작성해보겠습니다.

 

/**
    * private key 내용을 얻어옴
    *  -  -----BEGIN PRIVATE KEY----- 또는 -----END PRIVATE KEY----- 와 같은 가이드라인 줄은 제외하고 실제 사용하는 부분만 파일에서 가져옴
    *
    * @param privateKeyFile
    * @return
    */
private String getPrivateKeyBody(MultipartFile privateKeyFile) {

    try (BufferedReader br = new BufferedReader(new InputStreamReader(privateKeyFile.getInputStream()))) {

        String line;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            if (line.contains("PRIVATE KEY")) { //guard line은 pass
                continue;
            }
            sb.append(line);
        }

        return sb.toString();

    } catch (Exception e) {
        log.error(e.getMessage(), e);
        throw new RuntimeException(e.getMessage(), e);
    }
}

+ Recent posts