Skip to content

Commit

Permalink
Feat: FCM 푸시알림 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
pingowl committed Nov 11, 2023
1 parent e1c38e2 commit e308f2d
Show file tree
Hide file tree
Showing 14 changed files with 167 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/main/java/igoMoney/BE/common/config/FCMConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class FCMConfig {
private String firebaseJsonFileName;

@Bean
FirebaseMessaging firebaseMessaging() throws IOException {
FirebaseMessaging init() throws IOException {
ClassPathResource resource = new ClassPathResource("firebase/"+firebaseJsonFileName+".json");
InputStream refreshToken = resource.getInputStream();
FirebaseApp firebaseApp = null;
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/igoMoney/BE/common/config/RedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
public class RedisConfig {
Expand All @@ -19,4 +20,11 @@ public class RedisConfig {
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}

@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
1 change: 1 addition & 0 deletions src/main/java/igoMoney/BE/common/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public enum ErrorCode {
ID_TOKEN_INVALID_4(HttpStatus.UNAUTHORIZED, "ID토큰이 유효하지 않습니다.(서명 검증 결과)"),
ID_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "ID토큰이 만료되었습니다."),
AUTH_CODE_INVALID(HttpStatus.UNAUTHORIZED, "Authorization Code가 유효하지 않습니다."),
FCM_TOKEN_INVALID(HttpStatus.UNAUTHORIZED, "FCM토큰이 null입니다."),

// login 예외
LOGIN_CONNECTION_ERROR(HttpStatus.BAD_REQUEST, "로그인 요청 오류"),
Expand Down
1 change: 1 addition & 0 deletions src/main/java/igoMoney/BE/common/jwt/dto/TokenDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ public class TokenDto { // 서버 자체 토큰
private String refreshToken;
private String provider_accessToken;
private Long userId;
private String fcmToken;
}
33 changes: 23 additions & 10 deletions src/main/java/igoMoney/BE/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package igoMoney.BE.controller;

import igoMoney.BE.common.exception.CustomException;
import igoMoney.BE.common.exception.ErrorCode;
import igoMoney.BE.common.jwt.AppleJwtUtils;
import igoMoney.BE.common.jwt.dto.AppleSignOutRequest;
import igoMoney.BE.common.jwt.dto.AppleTokenResponse;
import igoMoney.BE.common.jwt.dto.TokenDto;
import igoMoney.BE.dto.request.FromAppleService;
import igoMoney.BE.dto.request.AuthAppleLoginRequest;
import igoMoney.BE.dto.response.AuthRecreateTokenResponse;
import igoMoney.BE.service.AuthService;
import igoMoney.BE.service.ChallengeService;
import igoMoney.BE.service.RecordService;
import igoMoney.BE.service.RefreshTokenService;
import igoMoney.BE.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
Expand All @@ -33,14 +32,17 @@ public class AuthController {
private final RecordService recordService;
private final AppleJwtUtils appleJwtUtils;
private final RefreshTokenService refreshTokenService;
private final FCMTokenService fcmTokenService;

// 카카오 로그인
@PostMapping("login/kakao")
@ResponseBody
public ResponseEntity<TokenDto> kakaoLogin(@RequestBody TokenDto accessToken) throws IOException {
public ResponseEntity<TokenDto> kakaoLogin(@RequestBody TokenDto token) throws IOException {

TokenDto response = authService.kakaoLogin(accessToken.getAccessToken());
checkFCMToken(token.getFcmToken());
TokenDto response = authService.kakaoLogin(token.getAccessToken());
refreshTokenService.saveRefreshToken(response);
fcmTokenService.saveToken(response.getUserId(), token.getFcmToken());

return ResponseEntity.status(HttpStatus.OK).body(response);
}
Expand Down Expand Up @@ -75,24 +77,26 @@ public String appleLoginPage(ModelMap model) {
// 2. 로그인 후 Identity Token, AuthorizationCode 받기
@PostMapping("login/apple/redirect")
@ResponseBody
public ResponseEntity<TokenDto> getAppleUserIdToken(@RequestBody FromAppleService fromAppleService) throws Exception {
public ResponseEntity<TokenDto> getAppleUserIdToken(@RequestBody AuthAppleLoginRequest authAppleLoginRequest) throws Exception {

checkFCMToken(authAppleLoginRequest.getFCMToken());
// 3. public key 요청하기 (n, e 값 받고 키 생성)
// 4. Identity Token (JWT) 검증하기
// 5. ID토큰 payload 바탕으로 회원가입
List<String> subNemail = appleJwtUtils.checkIdToken(fromAppleService.getId_token());
List<String> subNemail = appleJwtUtils.checkIdToken(authAppleLoginRequest.getId_token());
// DB에 data에서 받아온 정보를 가진 사용자가 있는지 조회 & 회원가입
// 6. 서버에서 직접 JWT 토큰 발급하기 (access & refresh token)
TokenDto response = authService.AppleSignUp(subNemail); // sub, email
refreshTokenService.saveRefreshToken(response);
fcmTokenService.saveToken(response.getUserId(), authAppleLoginRequest.getFCMToken());

return ResponseEntity.status(HttpStatus.OK).body(response);
}

// [애플] 회원탈퇴
@PostMapping("signout/apple")
@ResponseBody
public ResponseEntity<Void> appleSignOut(@RequestBody FromAppleService request) throws IOException {
public ResponseEntity<Void> appleSignOut(@RequestBody AuthAppleLoginRequest request) throws IOException {

// 챌린지 중단 및 패배처리
challengeService.giveUpChallengeSignOut(request.getUserId());
Expand All @@ -111,6 +115,7 @@ public ResponseEntity<Void> appleSignOut(@RequestBody FromAppleService request)
.token(response.getRefresh_token())
.build();
authService.appleSignOut(appleRequest);
fcmTokenService.deleteToken(request.getUserId());

return new ResponseEntity(HttpStatus.OK);
}
Expand All @@ -126,6 +131,7 @@ public ResponseEntity<Void> kakaoSignOut(@PathVariable Long userId){
recordService.deleteAllUserRecords(userId);
// 카카오 연동해제 및 회원정보(User) 삭제
authService.kakaoSignOut(userId);
fcmTokenService.deleteToken(userId);

return new ResponseEntity(HttpStatus.OK);
}
Expand All @@ -145,7 +151,14 @@ public ResponseEntity<AuthRecreateTokenResponse> refreshToken(@RequestBody Map<S
// public ResponseEntity<Void> logout(@RequestBody Map<String, String> accessToken) {
//
// authService.logout(accessToken.get("accessToken"));
// fcmService.deleteToken(userId);
//
// return new ResponseEntity(HttpStatus.OK);
// }

public void checkFCMToken(String fcmToken){
if(fcmToken == null){
throw new CustomException(ErrorCode.FCM_TOKEN_INVALID);
}
}
}
14 changes: 9 additions & 5 deletions src/main/java/igoMoney/BE/dto/request/AuthAppleLoginRequest.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package igoMoney.BE.dto.request;

//import jakarta.validation.constraints.NotNull;
import lombok.*;

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Setter
public class AuthAppleLoginRequest {

private String id;
private String email;
private String nickname;
private String picture ;
private String state;
private String code;
private String id_token;
private String user;
private String refresh_token;
private Long userId;
private String FCMToken;
}
18 changes: 0 additions & 18 deletions src/main/java/igoMoney/BE/dto/request/FromAppleService.java

This file was deleted.

2 changes: 1 addition & 1 deletion src/main/java/igoMoney/BE/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public TokenDto kakaoLogin(String accessToken) throws IOException {
String id = element.getAsJsonObject().get("id").getAsString(); // 카카오 회원번호
String email = "";
String image = "";
//String nickname = "";
//String nickname = ""
email = element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("email").getAsString();
image = element.getAsJsonObject().get("properties").getAsJsonObject().get("profile_image").getAsString();
//nickname = element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("profile").getAsJsonObject().get("nickname").getAsString();
Expand Down
16 changes: 8 additions & 8 deletions src/main/java/igoMoney/BE/service/ChallengeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class ChallengeService {
private final UserRepository userRepository;
private final RecordRepository recordRepository;
private final ChallengeUserRepository challengeUserRepository;
private final NotificationRepository notificationRepository;
private final NotificationService notificationService;
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM월 dd일");

// 시작 안 한 챌린지 목록 조회
Expand Down Expand Up @@ -148,7 +148,7 @@ public void applyChallenge(Long userId, Long challengeId) {
.title("챌린지 현황")
.message(findChallenge.getStartDate().format(dateFormat)+"부터 "+findUser.getNickname()+"님과 챌린지 시작")
.build();
notificationRepository.save(notification);
notificationService.makeNotification(notification);

}

Expand Down Expand Up @@ -200,23 +200,23 @@ public void cancelChallenge(User user, Integer sel) {
.title("챌린지 결과")
.message("상대방 "+ user.getNickname() +"님이 챌린지를 포기했어요.")
.build();
notificationRepository.save(notification);
notificationService.makeNotification(notification);
}
else if (sel==1){
Notification notification = Notification.builder()
.user(user2)
.title("챌린지 결과")
.message(user2.getNickname()+"님! 상대방 "+ user.getNickname() +"님이 3일 연속 미출석으로 패배하셨어요.")
.build();
notificationRepository.save(notification);
notificationService.makeNotification(notification);
}
else if (sel==2){
Notification notification = Notification.builder()
.user(user2)
.title("챌린지 결과")
.message(user2.getNickname()+"님! 상대방 "+ user.getNickname() +"님이 신고 누적으로 패배하셨어요.")
.build();
notificationRepository.save(notification);
notificationService.makeNotification(notification);
}
}

Expand Down Expand Up @@ -292,14 +292,14 @@ else if (((BigDecimal) obj[1]).intValue() < minCost){
.title("챌린지 결과")
.message(u.getNickname()+"님! "+lose.getNickname()+"님과의 챌린지 대결에서 승리하셔서 뱃지를 획득하게 되었어요. \uD83E\uDD47") // 🥇
.build();
notificationRepository.save(notification);
notificationService.makeNotification(notification);
} else{
Notification notification = Notification.builder()
.user(u)
.title("챌린지 결과")
.message(u.getNickname()+"님! "+findWinner.getNickname()+"님과의 챌린지 대결에서 아쉽게 승리하지 못했어요. 새로운 챌린지를 도전해보세요. \uD83D\uDE25") //😥
.build();
notificationRepository.save(notification);
notificationService.makeNotification(notification);
}

}
Expand All @@ -326,7 +326,7 @@ public void checkAttendance() {
.title("챌린지 결과")
.message(u.getNickname()+"님! 지출내역을 3일 동안 인증하지 않아서 해당 챌린지에서 패배하셨어요.")
.build();
notificationRepository.save(absentNotification);
notificationService.makeNotification(absentNotification);
if(check==1){ // 유저 둘 다 미출석
u.deleteBadge();
u.deleteBadge();
Expand Down
49 changes: 49 additions & 0 deletions src/main/java/igoMoney/BE/service/FCMTokenService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package igoMoney.BE.service;

import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message;
import igoMoney.BE.domain.Notification;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class FCMTokenService {

private final StringRedisTemplate tokenRedisTemplate;

public void sendNotification(Notification notification) {
Long userId = notification.getUser().getId();
if(!hasKey(userId)){ return;}
String token = getToken(userId);
Message message = Message.builder()
.putData("title", notification.getTitle())
.putData("content", notification.getMessage())
.putData("userId", String.valueOf(userId))
.setToken(token)
.build();
sendMessage(message);
}

public void saveToken(Long userId, String FCMToken){
tokenRedisTemplate.opsForValue()
.set(String.valueOf(userId), FCMToken);
}

private String getToken(Long userId) {
return tokenRedisTemplate.opsForValue().get(userId);
}

public void deleteToken(Long userId) {
tokenRedisTemplate.delete(String.valueOf(userId));
}

public boolean hasKey(Long userId){
return tokenRedisTemplate.hasKey(String.valueOf(userId));
}

private void sendMessage(Message message) {
FirebaseMessaging.getInstance().sendAsync(message);
}
}
24 changes: 24 additions & 0 deletions src/main/java/igoMoney/BE/service/NotificationService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package igoMoney.BE.service;

import igoMoney.BE.domain.Notification;
import igoMoney.BE.repository.NotificationRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class NotificationService {

private final NotificationRepository notificationRepository;
private final FCMTokenService fcmTokenService;

public void makeNotification(Notification notification){
notificationRepository.save(notification);
fcmTokenService.sendNotification(notification);
}

}
6 changes: 3 additions & 3 deletions src/main/java/igoMoney/BE/service/RecordService.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class RecordService {
private final UserRepository userRepository;
private final ChallengeRepository challengeRepository;
private final UserReportRepository userReportRepository;
private final NotificationRepository notificationRepository;
private final NotificationService notificationService;
private final ImageService imageService;
private final ChallengeService challengeService;

Expand Down Expand Up @@ -153,7 +153,7 @@ public Long reportRecord(RecordReportRequest request) {
.title("챌린지 현황")
.message(offender.getNickname()+"님 지출 내역은 가이드라인 위반으로 인해 삭제 되었어요. 신고 내용 확인 후 조치가 취해질 예정입니다. 신고 사유: "+reportReasons.get(request.getReason()))
.build();
notificationRepository.save(reportNotification);
notificationService.makeNotification(reportNotification);

// hide record
Record record = getRecordOrThrow(request.getRecordId());
Expand All @@ -168,7 +168,7 @@ public Long reportRecord(RecordReportRequest request) {
.title("챌린지 결과")
.message(offender.getNickname()+"님은 신고 누적으로 진행중인 챌린지는 패배처리되고 일주일 동안 챌린지 참여가 제한됩니다. 제한 해제 날짜: " + offender.getBanReleaseDate().format(dateFormat))
.build();
notificationRepository.save(banNotification);
notificationService.makeNotification(banNotification);
challengeService.cancelChallenge(offender, 2);
}
return userReport.getId();
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ spring:
show-sql: false
generate-ddl: true
hibernate:
ddl-auto: create-drop
ddl-auto: update
properties:
hibernate:
format_sql: true
Expand Down
Loading

0 comments on commit e308f2d

Please sign in to comment.