개푸시(GeTui) 푸시 알림 통합 가이드

개푸시 공식 문서: https://docs.getui.com/getui/server/rest_v2/push/

먼저 개푸시 공식 계정을 신청하고, 앱을 등록하여 AppID, AppKey, AppSecret, MasterSecret을 획득합니다.

통합 가이드

  1. 설정 파일 작성

.yml 파일 수정

pushConfig:
  appId: OokKLlwRjU7tJMccVVra72
  appKey: f8C6lK7OGu1115ckOfVxD8
  masterSecret: aTxslPiUJy9kzzZaPONL26
  appSecret: sAoJ9sQ66S13P0PG3c1y02

매핑 파일 작성

PushConfig``` package com.messaging.config;

import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;

@Data @Component @ConfigurationProperties(prefix = "pushConfig") public class PushConfig {

private String appId;
private String appKey;
private String masterSecret;
private String appSecret;

}


2. 유틸리티 클래스 작성

PushNotificationService```
package com.messaging.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.messaging.config.PushConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Component
public class PushNotificationService {

    private static String APP_ID;
    private static String APP_KEY;
    private static String MASTER_SECRET;

    // 생성자에서 설정 초기화
    @Autowired
    public PushNotificationService(PushConfig config) {
        APP_ID = config.getAppId();
        APP_KEY = config.getAppKey();
        MASTER_SECRET = config.getMasterSecret();
    }

    private static final String REDIS_TOKEN_KEY = "GETUI_ACCESS_TOKEN";

    private static RedisTemplate<String, String> redisTemplate;
    private static ObjectMapper objectMapper = new ObjectMapper();

    // RedisTemplate 초기화
    @Autowired
    public void setRedisTemplate(@Qualifier("stringRedisTemplate") RedisTemplate<String, String> redisTemplate) {
        PushNotificationService.redisTemplate = redisTemplate;
    }

    // access_token 가져오기 (Redis 우선)
    public static String getAccessToken() throws Exception {
        String accessToken = redisTemplate.opsForValue().get(REDIS_TOKEN_KEY);
        if (accessToken == null || accessToken.isEmpty()) {
            System.out.println("Redis에서 액세스 토큰을 찾을 수 없습니다. GeTui에서 가져오는 중...");
            accessToken = fetchAccessTokenFromGeTui();
        } else {
            System.out.println("Redis에서 액세스 토큰을 검색했습니다.");
        }
        return accessToken;
    }

    // GeTui에서 access_token 가져오기
    private static String fetchAccessTokenFromGeTui() throws Exception {
        long timestamp = System.currentTimeMillis();
        String signature = generateSHA256(APP_KEY + timestamp + MASTER_SECRET);

        String payload = String.format(
                "{\"signature\": \"%s\", \"timestamp\": \"%s\", \"appKey\": \"%s\"}",
                signature, timestamp, APP_KEY
        );

        String response = executePostRequest("https://restapi.getui.com/v2/" + APP_ID + "/auth", payload);
        String accessToken = extractAccessToken(response);
        long expireTime = extractExpireTime(response);

        // Redis에 토큰 저장 및 만료 시간 설정
        redisTemplate.opsForValue().set(REDIS_TOKEN_KEY, accessToken, expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);

        return accessToken;
    }

    // 고유 request_id 생성 (UUID 사용)
    private static String createRequestId() {
        return UUID.randomUUID().toString().replace("-", "").substring(0, 32);
    }

    // POST 요청 실행 (토큰 포함)
    private static String executePostRequest(String requestUrl, String payload, String token) throws Exception {
        URL url = new URL(requestUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("Authorization", "Bearer " + token);
        connection.setDoOutput(true);

        try (OutputStream os = connection.getOutputStream()) {
            byte[] input = payload.getBytes(StandardCharsets.UTF_8);
            os.write(input, 0, input.length);
        }

        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            System.out.println("푸시 메시지 전송 성공.");
        } else {
            System.out.println("푸시 메시지 전송 실패. 응답 코드: " + responseCode);
        }

        return new String(connection.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
    }

    // POST 요청 실행 (토큰 미포함)
    private static String executePostRequest(String requestUrl, String payload) throws Exception {
        URL url = new URL(requestUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setDoOutput(true);

        try (OutputStream os = connection.getOutputStream()) {
            byte[] input = payload.getBytes(StandardCharsets.UTF_8);
            os.write(input, 0, input.length);
        }

        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            System.out.println("요청 성공.");
        } else {
            System.out.println("요청 실패. 응답 코드: " + responseCode);
        }

        return new String(connection.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
    }

    // access_token 추출
    private static String extractAccessToken(String response) throws Exception {
        JsonNode rootNode = objectMapper.readTree(response);
        return rootNode.path("data").path("token").asText();
    }

    // 만료 시간 추출
    private static long extractExpireTime(String response) throws Exception {
        JsonNode rootNode = objectMapper.readTree(response);
        return rootNode.path("data").path("expire_time").asLong();
    }

    // SHA-256 서명 생성
    private static String generateSHA256(String input) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
        StringBuilder hexString = new StringBuilder();
        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1){
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }

    // CID를 이용한 단일 푸시 메시지 전송
    public static String sendSinglePushByCid(String clientId, String title, String body) throws Exception {
        String token = getAccessToken();
        String requestId = createRequestId();

        String payload = String.format(
                "{\"request_id\": \"%s\", \"target\": {\"cid\": [\"%s\"]}, \"message\": {\"notification\": {\"title\": \"%s\", \"content\": \"%s\", \"action\": \"none\"}}, \"channel\": {\"ios\": {\"type\": \"notify\"}, \"android\": {\"ups\": {\"notification\": {\"title\": \"%s\", \"content\": \"%s\", \"action\": \"none\"}}}}}",
                requestId, clientId, title, body, title, body
        );

        return executePostRequest("https://restapi.getui.com/v2/" + APP_ID + "/push/single/cid", payload, token);
    }

    // 별명(alias)을 이용한 단일 푸시 메시지 전송
    public static void sendSinglePushByAlias(String alias, String title, String body) throws Exception {
        String token = getAccessToken();
        String requestId = createRequestId();

        String payload = String.format(
                "{\"request_id\": \"%s\", \"target\": {\"alias\": [\"%s\"]}, \"message\": {\"notification\": {\"title\": \"%s\", \"content\": \"%s\", \"action\": \"none\"}}, \"channel\": {\"ios\": {\"type\": \"notify\"}, \"android\": {\"ups\": {\"notification\": {\"title\": \"%s\", \"content\": \"%s\", \"action\": \"none\"}}}}}",
                requestId, alias, title, body, title, body
        );

        executePostRequest("https://restapi.getui.com/v2/" + APP_ID + "/push/single/alias", payload, token);
    }

    // CID를 이용한 배치 푸시 메시지 전송
    public static void sendBatchPushByCid(String[] clientIds, String title, String body) throws Exception {
        String token = getAccessToken();
        String requestId = createRequestId();

        String cidList = String.join(",", Arrays.stream(clientIds)
                .map(cid -> "\"" + cid + "\"")
                .toArray(String[]::new));

        String payload = String.format(
                "{\"request_id\": \"%s\", \"target\": {\"cid\": [%s]}, \"message\": {\"notification\": {\"title\": \"%s\", \"content\": \"%s\", \"action\": \"none\"}}, \"channel\": {\"ios\": {\"type\": \"notify\"}, \"android\": {\"ups\": {\"notification\": {\"title\": \"%s\", \"content\": \"%s\", \"action\": \"none\"}}}}}",
                requestId, cidList, title, body, title, body
        );

        executePostRequest("https://restapi.getui.com/v2/" + APP_ID + "/push/list/cid", payload, token);
    }

    // 별명(alias)을 이용한 배치 푸시 메시지 전송
    public static void sendBatchPushByAlias(String[] aliases, String title, String body) throws Exception {
        String token = getAccessToken();
        String requestId = createRequestId();

        String aliasList = String.join(",", Arrays.stream(aliases)
                .map(alias -> "\"" + alias + "\"")
                .toArray(String[]::new));

        String payload = String.format(
                "{\"request_id\": \"%s\", \"target\": {\"alias\": [%s]}, \"message\": {\"notification\": {\"title\": \"%s\", \"content\": \"%s\", \"action\": \"none\"}}, \"channel\": {\"ios\": {\"type\": \"notify\"}, \"android\": {\"ups\": {\"notification\": {\"title\": \"%s\", \"content\": \"%s\", \"action\": \"none\"}}}}}",
                requestId, aliasList, title, body, title, body
        );

        executePostRequest("https://restapi.getui.com/v2/" + APP_ID + "/push/list/alias", payload, token);
    }

    // 모든 사용자에게 푸시 메시지 전송
    public static void sendPushToAll(String title, String body) throws Exception {
        String token = getAccessToken();
        String requestId = createRequestId();

        String payload = String.format(
                "{\"request_id\": \"%s\", \"target\": \"all\", \"message\": {\"notification\": {\"title\": \"%s\", \"content\": \"%s\", \"action\": \"none\"}}, \"channel\": {\"ios\": {\"type\": \"notify\"}, \"android\": {\"ups\": {\"notification\": {\"title\": \"%s\", \"content\": \"%s\", \"action\": \"none\"}}}}}",
                requestId, title, body, title, body
        );

        executePostRequest("https://restapi.getui.com/v2/" + APP_ID + "/push/all", payload, token);
    }

    // 특정 푸시 작업 중지
    public static void stopPushTask(String taskId) throws Exception {
        String token = getAccessToken();
        String requestUrl = "https://restapi.getui.com/v2/" + APP_ID + "/task/" + taskId;
        HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection();
        connection.setRequestMethod("DELETE");
        connection.setRequestProperty("Authorization", "Bearer " + token);
        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            System.out.println("작업이 중지되었습니다.");
        } else {
            System.out.println("작업 중지 실패. 응답 코드: " + responseCode);
        }
    }

    // 예약된 작업 상태 조회
    public static void checkScheduledTask(String taskId) throws Exception {
        String token = getAccessToken();
        String requestUrl = "https://restapi.getui.com/v2/" + APP_ID + "/task/schedule/" + taskId;
        HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("Authorization", "Bearer " + token);
        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            System.out.println("예약 작업 조회 성공.");
        } else {
            System.out.println("예약 작업 조회 실패. 응답 코드: " + responseCode);
        }
    }

    // 예약된 작업 삭제
    public static void removeScheduledTask(String taskId) throws Exception {
        String token = getAccessToken();
        String requestUrl = "https://restapi.getui.com/v2/" + APP_ID + "/task/schedule/" + taskId;
        HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection();
        connection.setRequestMethod("DELETE");
        connection.setRequestProperty("Authorization", "Bearer " + token);
        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            System.out.println("예약 작업이 삭제되었습니다.");
        } else {
            System.out.println("예약 작업 삭제 실패. 응답 코드: " + responseCode);
        }
    }

    // 메시지 상세 정보 조회
    public static void getPushDetails(String cid, String taskId) throws Exception {
        String token = getAccessToken();
        String requestUrl = "https://restapi.getui.com/v2/" + APP_ID + "/task/detail/" + cid + "/" + taskId;
        HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("Authorization", "Bearer " + token);
        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            System.out.println("메시지 상세 정보 조회 성공.");
        } else {
            System.out.println("메시지 상세 정보 조회 실패. 응답 코드: " + responseCode);
        }
    }

    // CID로 별명 조회
    public static String getAliasFromCid(String cid) throws Exception {
        String token = getAccessToken();
        String requestUrl = "https://restapi.getui.com/v2/" + APP_ID + "/user/alias/cid/" + cid;
        HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("Authorization", "Bearer " + token);

        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            String response = new String(connection.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
            JsonNode rootNode = objectMapper.readTree(response);
            String alias = rootNode.path("data").path("alias").asText();
            System.out.println("조회된 별명: " + alias);
            return alias;
        } else {
            System.out.println("CID로 별명 조회 실패. 응답 코드: " + responseCode);
            return null;
        }
    }

    // 별명으로 CID 조회
    public static String[] getCidFromAlias(String alias) throws Exception {
        String token = getAccessToken();
        String requestUrl = "https://restapi.getui.com/v2/" + APP_ID + "/user/cid/alias/" + alias;
        HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("Authorization", "Bearer " + token);

        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            String response = new String(connection.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
            JsonNode rootNode = objectMapper.readTree(response);
            JsonNode cidArray = rootNode.path("data").path("cid");
            String[] cids = objectMapper.convertValue(cidArray, String[].class);
            System.out.println("조회된 CID: " + String.join(", ", cids));
            return cids;
        } else {
            System.out.println("별명으로 CID 조회 실패. 응답 코드: " + responseCode);
            return null;
        }
    }
}
  1. 호출 예제 (안드로이드 단에서 생성된 cid 또는 별명 ID가 필요)

단일 푸시 메시지 전송``` // CID를 이용한 단일 푸시 메시지 전송 @PostMapping("sendSinglePush") public ApiResponse<String> sendSinglePush(@RequestParam("clientId") String clientId, @RequestParam("title") String title, @RequestParam("body") String body) { try { String result = PushNotificationService.sendSinglePushByCid(clientId, title, body); System.out.println(result); return ApiResponse.success("푸시 전송 성공"); } catch (Exception e) { e.printStackTrace(); return ApiResponse.failure("푸시 전송 실패: " + e.getMessage()); } }

// 별명을 이용한 단일 푸시 메시지 전송
@PostMapping("sendSinglePushByAlias")
public ApiResponse<String> sendSinglePushByAlias(@RequestParam("alias") String alias,
                                              @RequestParam("title") String title,
                                              @RequestParam("body") String body) {
    try {
        PushNotificationService.sendSinglePushByAlias(alias, title, body);
        return ApiResponse.success("푸시 전송 성공");
    } catch (Exception e) {
        e.printStackTrace();
        return ApiResponse.failure("푸시 전송 실패: " + e.getMessage());
    }
}

태그: GeTui push-notification java spring-boot Redis

6월 1일 22:26에 게시됨