필요한 일이 생겨서 간단히 프로토타이핑한 구글 수익보고서 다운로드하는 java프로그램

 

필요 라이브러리(아래 google-api-services-storage외에도 import된 라이브러리 추가 필요)

<dependency>
    <groupId>com.google.apis</groupId>
    <artifactId>google-api-services-storage</artifactId>
    <version>v1-rev171-1.25.0</version>
</dependency>

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.DataStoreFactory;
import com.google.api.services.storage.Storage;
import com.google.api.services.storage.StorageScopes;
import com.google.api.services.storage.model.Objects;
import com.google.api.services.storage.model.StorageObject;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;

/**
 * 구글 재무보고서 중에서 '수익'보고서를 다운로드하는 샘플 프로그램
 *  - 참고: https://support.google.com/googleplay/android-developer/answer/6135870#zippy=%2C%EC%88%98%EC%9D%B5
 *  - 2021년 11월 기준 구글스토어에서 제공하는 재무보고서는 3가지가 존재
 *    1. 예상 판매실적
 *    2. 수익
 *    3. 대한민국 Play 잔액 차감
 *
 *  - 추가 참고사항
 *  1) 수익 보고서를 사용하여 판매 대금과 거래 내역를 파악할 수 있습니다.
 *     보고서의 각 행은 거래 유형(예: 고객에게 대금을 청구하거나 Google에 수수료를 지급하는 시기)과 함께 원래 금액 및 변환된 금액을 나타냅니다.
 *  2) 수익 보고서에는 이전 달에 발생한 인보이스가 포함됩니다. 최소 지급액에 도달하면 인보이스를 받게 됩니다. 수익 보고서를 사용할 수 있게 되면 몇 주 후에판매 대금을 수령할 수 있습니다.
 *  3) 수익 보고서는 한 달에 한 번 생성되며 일반적으로 다음 달 5일에 제공됩니다.
 *  	경우에 따라 Google에서 계산 오류를 바로잡기 위해 수익을 조정할 수 있습니다.
 *  	이 경우 Google에서 문제에 관해 알려 드리며 개발자 기록용으로 조정된 거래만 포함되어 있는 추가 수입 파일을 생성합니다.
 *	4) Google은 유럽 경제 지역(EEA)의 사용자에게 판매되는 상품의 등록된 판매자이므로, 영향을 받는 국가에서 이루어진 판매가 주문당 한 줄씩 표시됩니다.
 *		또한 거래 유형은 '청구'로 표시됩니다. 다른 국가에서 이루어진 판매에는 'Google 수수료' 거래 유형도 포함됩니다.
 * 	5) 수익 보고서에는 지불 거절이 포함되지 않습니다
 *
 * @author eomsh
 */
@Slf4j
public class GetEarningsReport {

	final static String encStoredCredential = "입력필요";
	final static String encClientSecretJson = "입력필요";

	//다운로드 저장 디렉토리 경로
	final static String saveDirStr = "c:\\temp\\google_earnigns";

	//버킷
	final static String BUCKET_NAME = "pubsite_prod_rev_블라블라"; //버킷명(Google Play Console -> 보고서 다운로드 -> 재무 -> Cloud Storage URI복사를 통해서 확인 가능)

	//수익보고서 object name
	final static String OBJECT_NAME = "earnings/earnings_블라블라.zip"; //object name 샘플. earnings/earnings_202110_블라블라숫자-숫자.zip

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

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

		HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
		JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();

		//@formatter:off
			Credential credential = authorize(
				httpTransport,
				jsonFactory,
				new CustomByteDataStoreFactory(GoogleAuthUtils.getDecStoredCredential(encStoredCredential)),
				GoogleClientSecrets.load(jsonFactory,
				new StringReader(GoogleAuthUtils.getDecClientSecretsJson(encClientSecretJson)))
			);

		//@formatter:on

		final String surfixAppVer = ".0.0.1"; //ApplicationName을 관리할때 버전을 추가하여 관리하기 위함.
		final String applicationName = "블라블라" + surfixAppVer;

		Storage storage = new Storage.Builder(httpTransport, jsonFactory, credential).setApplicationName(applicationName).build();
		Storage.Objects.Get getObject = storage.objects().get(BUCKET_NAME, OBJECT_NAME);

		final StorageObject storageObject = getObject.execute();
		log.info("storageObject 정보 ==>\n\t{}", storageObject.toPrettyString());

		FileUtils.forceMkdir(new File(saveDirStr)); //디렉토리 생성
		log.info("저장 디렉토리:{}", saveDirStr);

		String objectNameSplitArray[] = StringUtils.split(OBJECT_NAME, "/");
		String saveFileFullPathStr = saveDirStr + File.separator + objectNameSplitArray[1];

		log.info("저장 파일 full path:{}", saveFileFullPathStr);

		final File saveFile = new File(saveFileFullPathStr);
		FileUtils.deleteQuietly(saveFile); //기존 다운로드된 파일이 존재할 수 있어서 선 삭제 진행

		FileOutputStream out = new FileOutputStream(saveFile);
		getObject.getMediaHttpDownloader().setDirectDownloadEnabled(true); //true 설정 필요
		getObject.executeMediaAndDownloadTo(out);

		LocalDateTime end = LocalDateTime.now();
		log.info("\n\n==== End: {} ==== 소요시간: {}(second)", end, Duration.between(start, end).getSeconds());
	}
    
    /**
	 * Storage의 object들 중에서, name prefix로 필터링하여 가져옴
	 *
	 * @param storage
	 * @param bucketName
	 * @param namePrefix
	 * @return
	 * @throws IOException
	 */
	public static Iterable<StorageObject> listObject(Storage storage, String bucketName, String namePrefix) throws IOException {

		List<List<StorageObject>> pagedList = Lists.newArrayList();
		Storage.Objects.List listObjects = storage.objects().list(bucketName).setPrefix(namePrefix);
		Objects objects;
		do {
			objects = listObjects.execute();
			List<StorageObject> items = objects.getItems();
			if (items != null) {
				pagedList.add(objects.getItems());
			}
			listObjects.setPageToken(objects.getNextPageToken());
		} while (objects.getNextPageToken() != null);

		return Iterables.concat(pagedList);
	}


	/**
	 * 구글 인증
	 *
	 * @param httpTransport
	 * @param jsonFactory
	 * @param dataStoreFactory
	 * @param clientSecrets
	 * @return
	 * @throws Exception
	 */
	public static Credential authorize(
		//@formatter:off
		HttpTransport httpTransport,
		JsonFactory jsonFactory,
		DataStoreFactory dataStoreFactory,
		GoogleClientSecrets clientSecrets
		//@formatter:on
	) throws Exception {

		if (clientSecrets.getDetails().getClientId() == null || clientSecrets.getDetails().getClientSecret() == null) {
			throw new Exception("client_secrets not well formed.");
		}

		//@formatter:off
		GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow
			.Builder(
				httpTransport,
				jsonFactory,
				clientSecrets,
				Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL)
			).setDataStoreFactory(dataStoreFactory)
				.build();
		//@formatter:on

		return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
	}
}

+ Recent posts