개푸시 공식 문서: https://docs.getui.com/getui/server/rest_v2/push/
먼저 개푸시 공식 계정을 신청하고, 앱을 등록하여 AppID, AppKey, AppSecret, MasterSecret을 획득합니다.
통합 가이드
- 설정 파일 작성
.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;
}
}
}
- 호출 예제 (안드로이드 단에서 생성된 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());
}
}