Skip to content

Commit

Permalink
✨ Feature/#65 - FCM 푸시알림 기능 구현 (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
dongkyeomjang authored Nov 22, 2024
1 parent a056c83 commit a6f2618
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 242 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ http/http-client.private.env.json
### ignore application.yml ###
src/main/resources/application-dev.yml
src/main/resources/application-local.yml
src/main/resources/firebase/firebase_service_key.json
src/main/resources/firebase/firebase-key.json
.gradle/*
build/*

Expand Down
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ dependencies {
implementation 'org.springframework.cloud:spring-cloud-aws-context:2.2.6.RELEASE'
implementation 'org.springframework.cloud:spring-cloud-aws-autoconfigure:2.2.6.RELEASE'

// Firebase
implementation 'com.google.firebase:firebase-admin:6.8.1'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.2.2'

// Secheduler
implementation 'org.springframework.boot:spring-boot-starter-quartz'

Expand Down
99 changes: 0 additions & 99 deletions src/main/java/com/daon/onjung/core/utility/CookieUtil.java

This file was deleted.

83 changes: 83 additions & 0 deletions src/main/java/com/daon/onjung/core/utility/FirebaseUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.daon.onjung.core.utility;

import com.daon.onjung.core.exception.error.ErrorCode;
import com.daon.onjung.core.exception.type.CommonException;
import com.google.auth.oauth2.GoogleCredentials;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.List;
import java.util.Map;

@Component
@RequiredArgsConstructor
@Slf4j
public class FirebaseUtil {

@Value("${google.firebase.config.path}")
private String firebaseConfigPath;

@Value("${google.access-token.request.url}")
private String GOOGLE_ACCESS_TOKEN_URL;

@Value("${google.notification.url}")
private String GOOGLE_NOTIFICATION_URL;

private static final String BEARER_PREFIX = "Bearer " ;
private static final String JSON_BODY_KEY_TITLE = "title";
private static final String JSON_BODY_KEY_BODY = "body";
private static final String JSON_BODY_KEY_TOKEN = "token";
private static final String JSON_BODY_KEY_NOTIFICATION = "notification";
private static final String JSON_BODY_KEY_MESSAGE = "message";
private static final String JSON_BODY_KEY_VALIDATE_ONLY = "validate_only";

public String createFirebaseRequestUrl() {
return UriComponentsBuilder.fromHttpUrl(GOOGLE_NOTIFICATION_URL)
.toUriString();
}

public HttpHeaders createFirebaseRequestHeaders() {

String accessToken = null;

try {
GoogleCredentials credentials = GoogleCredentials
.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())
.createScoped(List.of(GOOGLE_ACCESS_TOKEN_URL));

credentials.refreshIfExpired();

accessToken = BEARER_PREFIX + credentials.getAccessToken().getTokenValue();
} catch (Exception e) {
log.error("Google Access Token을 가져오는데 실패했습니다.", e);
throw new CommonException(ErrorCode.EXTERNAL_SERVER_ERROR); }

HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
headers.add("Authorization", accessToken);
return headers;
}

public String createFirebaseRequestBody(String deviceToken, String storeName) {
JSONObject messageBody = new JSONObject();

JSONObject notificationJson = new JSONObject();
notificationJson.put(JSON_BODY_KEY_TITLE, storeName + " 가게의 식권에 당첨됐어요!");
notificationJson.put(JSON_BODY_KEY_BODY, "축하합니다! " + storeName + " 가게의 식권에 당첨되셨습니다. 지금 바로 확인해보세요!");

JSONObject messageJson = new JSONObject();
messageJson.put(JSON_BODY_KEY_TOKEN, deviceToken);
messageJson.put(JSON_BODY_KEY_NOTIFICATION, notificationJson);

messageBody.put(JSON_BODY_KEY_MESSAGE, messageJson);
messageBody.put(JSON_BODY_KEY_VALIDATE_ONLY, false);

return messageBody.toJSONString();
}
}
9 changes: 0 additions & 9 deletions src/main/java/com/daon/onjung/core/utility/HeaderUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,4 @@ public static Optional<String> refineHeader(HttpServletRequest request, String h
return Optional.of(unpreparedToken.substring(prefix.length()));
}

// public static Optional<String> refineHeader(StompHeaderAccessor request, String header, String prefix) {
// String unpreparedToken = request.getFirstNativeHeader(header);
//
// if (!StringUtils.hasText(unpreparedToken) || !unpreparedToken.startsWith(prefix)) {
// return Optional.empty();
// }
//
// return Optional.of(unpreparedToken.substring(prefix.length()));
// }
}
65 changes: 0 additions & 65 deletions src/main/java/com/daon/onjung/core/utility/HttpServletUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,73 +17,8 @@
@RequiredArgsConstructor
public class HttpServletUtil {

@Value("${web-engine.client-url}")
private String clientUrl;

@Value("${web-engine.cookie-domain}")
private String cookieDomain;

@Value("${json-web-token.refresh-token-expire-period}")
private Long refreshTokenExpirePeriod;

private final ObjectMapper objectMapper;

public void onSuccessRedirectResponseWithJWTCookie(
HttpServletResponse response,
DefaultJsonWebTokenDto tokenDto
) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpStatus.CREATED.value());

CookieUtil.addCookie(
response,
cookieDomain,
Constants.ACCESS_TOKEN,
tokenDto.getAccessToken()
);
CookieUtil.addSecureCookie(
response,
cookieDomain,
Constants.REFRESH_TOKEN,
tokenDto.getRefreshToken(),
(int) (refreshTokenExpirePeriod / 1000L)
);

response.sendRedirect(String.format("%s/%s", clientUrl, "profile"));
}

public void onSuccessBodyResponseWithJWTCookie(
HttpServletResponse response,
DefaultJsonWebTokenDto tokenDto
) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpStatus.CREATED.value());

CookieUtil.addCookie(
response,
cookieDomain,
Constants.ACCESS_TOKEN,
tokenDto.getAccessToken()
);
CookieUtil.addSecureCookie(
response,
cookieDomain,
Constants.REFRESH_TOKEN,
tokenDto.getRefreshToken(),
(int) (refreshTokenExpirePeriod / 1000L)
);

Map<String, Object> result = new HashMap<>();

result.put("success", true);
result.put("data", null);
result.put("error", null);

response.getWriter().write(objectMapper.writeValueAsString(result));
}

public void onSuccessBodyResponseWithJWTBody(
HttpServletResponse response,
DefaultJsonWebTokenDto tokenDto
Expand Down
52 changes: 0 additions & 52 deletions src/main/java/com/daon/onjung/core/utility/PasswordUtil.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.daon.onjung.core.exception.error.ErrorCode;
import com.daon.onjung.core.exception.type.CommonException;
import com.daon.onjung.core.utility.BankUtil;
import com.daon.onjung.core.utility.FirebaseUtil;
import com.daon.onjung.core.utility.RestClientUtil;
import com.daon.onjung.event.application.usecase.ProcessCompletedEventUseCase;
import com.daon.onjung.event.domain.Event;
Expand Down Expand Up @@ -44,6 +45,7 @@ public class ProcessCompletedEventService implements ProcessCompletedEventUseCas

private final RestClientUtil restClientUtil;
private final BankUtil bankUtil;
private final FirebaseUtil firebaseUtil;

private final ApplicationEventPublisher applicationEventPublisher;

Expand Down Expand Up @@ -89,8 +91,8 @@ public void execute(Long eventId) {
applicationEventPublisher.publishEvent(
EventScheduled.builder()
.eventId(newEvent.getId())
.scheduledTime(newEvent.getEndDate().plusDays(1).atStartOfDay())
// .scheduledTime(LocalDateTime.now().plusMinutes(1)) // 테스트용 1분 뒤
// .scheduledTime(newEvent.getEndDate().plusDays(1).atStartOfDay())
.scheduledTime(LocalDateTime.now().plusMinutes(1)) // 테스트용 1분 뒤
.build()
);

Expand Down Expand Up @@ -154,6 +156,16 @@ public void execute(Long eventId) {
);
ticketRepository.save(ticket);

// 푸시 알림 발송
url = firebaseUtil.createFirebaseRequestUrl();
headers = firebaseUtil.createFirebaseRequestHeaders();
String deviceToken = userRepository.findById(userId).
orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_RESOURCE)).getDeviceToken();
String storeName = store.getName();
String firebaseRequestBody = firebaseUtil.createFirebaseRequestBody(deviceToken, storeName);

restClientUtil.sendPostMethod(url, headers, firebaseRequestBody);

// 발급된 유저 기록 및 발급된 티켓 수 증가
issuedUserIds.add(userId);
issuedTickets++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ public void execute(MultipartFile logo,
applicationEventPublisher.publishEvent(
EventScheduled.builder()
.eventId(event.getId())
.scheduledTime(event.getEndDate().plusDays(1).atStartOfDay())
// .scheduledTime(LocalDateTime.now().plusMinutes(1)) // 테스트용 1분 뒤
// .scheduledTime(event.getEndDate().plusDays(1).atStartOfDay())
.scheduledTime(LocalDateTime.now().plusMinutes(1)) // 테스트용 1분 뒤
.build()
);

Expand Down
Loading

0 comments on commit a6f2618

Please sign in to comment.