Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: FCM을 통해 푸쉬 알림 구현 #605

Merged
merged 63 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
816bc5b
feat: Member fcmToken 필드 추가 및 로그인 필드 추가
helenason Oct 16, 2024
c30da47
feat: 공모 참여 시 총대에게 알림 전송
helenason Oct 16, 2024
871f31f
feat: 공모 참여 취소 시 총대에게 알림 전송
helenason Oct 16, 2024
71fabe2
feat: 댓글방 상태 변경 시 참여자에게 알림 전송
helenason Oct 17, 2024
87f7b6c
feat: 댓글 작성 시 작성자 제외 참여자에게 알림 전송
helenason Oct 17, 2024
68afe3d
refactor: FcmMessageManager 메시지 생성 로직 추출
helenason Oct 18, 2024
3e4fb84
chore: FCM key 관리
helenason Oct 18, 2024
1847365
style: .gitignore EOF
helenason Oct 18, 2024
0a56f86
chore: FCM key path 관리
helenason Oct 18, 2024
8e37b6d
chore: dev CI/CD script 수정
helenason Oct 18, 2024
232a9e8
Merge branch 'develop' into feature/587-fcm
helenason Oct 18, 2024
56ed9ed
chore: dev CI/CD script 트리거 수정
helenason Oct 18, 2024
7bdac86
chore: dev CI/CD script 트리거 수정
helenason Oct 18, 2024
b5343c1
chore: dev CI/CD script 수정
helenason Oct 18, 2024
d52123c
chore: fcm key 빈 파일 생성
helenason Oct 18, 2024
59395fa
chore: properties 파일 수정
helenason Oct 18, 2024
243a543
chore: yml 파일 수정
helenason Oct 18, 2024
2fbc056
chore: dev CI/CD 파일 수정
helenason Oct 18, 2024
e9ae177
chore: test fcm key 파일 추가
helenason Oct 18, 2024
fb58174
chore: dev CI/CD 스크립트 수정
helenason Oct 18, 2024
345e26b
chore: dev CI/CD 스크립트 수정
helenason Oct 18, 2024
a54c98e
chore: dev CI/CD 스크립트 수정
helenason Oct 18, 2024
67b60cc
chore: dev CI/CD 스크립트 수정
helenason Oct 18, 2024
7411239
chore: dev CI/CD 스크립트 수정
helenason Oct 18, 2024
96dd81a
chore: dev CI/CD 스크립트 수정
helenason Oct 18, 2024
a9a108b
chore: dev CI/CD 스크립트 수정
helenason Oct 18, 2024
3260191
chore: dev CI/CD 스크립트 수정
helenason Oct 18, 2024
de6ca3b
feat: notification 방식에서 data 방식으로 변경
helenason Oct 20, 2024
6bc9632
feat: 전달 데이터에 offering_id 추가
helenason Oct 20, 2024
cfd043d
Merge branch 'develop' into feature/587-fcm
helenason Oct 21, 2024
508a0d2
chore: 파일 읽는 방식 변경
helenason Oct 21, 2024
68dba79
fix: 토픽 이름 변경
helenason Oct 21, 2024
d2fed6d
refactor: QA 위한 로그
helenason Oct 21, 2024
100e58b
refactor: 로그인 시 fcmToken 비교 후 다를 경우 갱신
helenason Oct 21, 2024
0366b0f
refactor: 거래 상태 알림 문구 변경
helenason Oct 21, 2024
a0a9ba1
feat: 안드로이드 리다이렉트를 위한 필드 추가
helenason Oct 21, 2024
062cd3e
refactor: 필드명 변경 및 패키지 정리
helenason Oct 21, 2024
c512f4f
refactor: FcmTopic 구현
helenason Oct 21, 2024
81b712c
refactor: 토큰 갱신 여부 로깅
helenason Oct 21, 2024
3943410
feat: 공모 작성 시 본인 제외 broadcasting
helenason Oct 21, 2024
ea7b88f
refactor: 공모 작성 시 본인 제외 broadcasting 주제 구독 방식
helenason Oct 21, 2024
c85389f
refactor: FcmCondition, FcmTopic 도메인 추출
helenason Oct 21, 2024
858a368
refactor: notification 상수 정리
helenason Oct 21, 2024
d949417
refactor: 로그인 요청시 fcmToken 필드 비어있는지 검증
helenason Oct 21, 2024
8ac44a5
refactor: offering_member 토픽 이름 변경
helenason Oct 21, 2024
1f54e5f
feat: 유효하지 않은 토큰을 가진 사용자에 대한 예외 처리
helenason Oct 21, 2024
116661a
refactor: 개발 환경과 로컬 환경 분리
helenason Oct 21, 2024
ee4e255
chore: CI/CD 스크립트 정리
helenason Oct 21, 2024
5876b95
refactor: notificationService에서 repository 의존성 제거
helenason Oct 21, 2024
e3a0f94
refactor: MessageManager 계층 도메인에서 서비스로 이동
helenason Oct 21, 2024
6aec78f
chore: dev CI/CD 스크립트 트리거 수정
helenason Oct 22, 2024
1108ade
fix: 오래된 토큰을 가진 사용자에 대한 알림 전송 무시
helenason Oct 23, 2024
4272d0f
chore: dev CI/CD 트리거 변경
helenason Oct 23, 2024
47872ab
refactor: yml 중복 필드 제거
helenason Oct 23, 2024
beccb98
refactor: 불필요한 어노테이션 제거
helenason Oct 23, 2024
bca5963
refactor: 민감 정보 로깅 제거
helenason Oct 23, 2024
9ade0e7
refactor: fcmToken null 처리
helenason Oct 23, 2024
3c9868d
refactor: 방상태 변경 시 토픽 변경
helenason Oct 23, 2024
52adfe6
refactor: FcmMessageCreator 빈 등록
helenason Oct 23, 2024
73b416e
refactor: FcmNotificationService 코드 순서 변경
helenason Oct 23, 2024
810486a
refactor: NotificationService 반환값 변경
helenason Oct 23, 2024
90edf36
test: 리팩터링 변경 사항 반영
helenason Oct 23, 2024
bdce767
chore: dev CI/CD 복구
helenason Oct 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions .github/workflows/backend-dev-ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ on:
- "backend/**"
- ".github/workflows/backend-dev-ci-cd.yml"
- "Dockerfile"
# pull_request:
# branches: [ "develop" ]
# paths:
# - "backend/**"
# - ".github/workflows/backend-dev-ci-cd.yml"
# - "Dockerfile"
# pull_request:
# branches: [ "develop" ]
# paths:
# - "backend/**"
# - ".github/workflows/backend-dev-ci-cd.yml"
# - "Dockerfile"

jobs:

Expand Down Expand Up @@ -44,6 +44,10 @@ jobs:
- name: Set Application yml for dev
run: |
echo "${{ secrets.APPLICATION_PROPERTIES_DEV }}" > src/main/resources/application.properties
mkdir -p src/main/resources/fcm
echo '${{ secrets.FCM_SECRET_KEY }}' > src/main/resources/fcm/chongdaemarket-fcm-key.json
mkdir -p src/test/resources/fcm
echo '${{ secrets.FCM_SECRET_KEY }}' > src/test/resources/fcm/chongdaemarket-fcm-key.json
working-directory: ./backend

- name: Build with Gradle Wrapper
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/backend-prod-ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ jobs:
- name: Set Application yml for prod
run: |
echo "${{ secrets.APPLICATION_PROPERTIES_PROD }}" > src/main/resources/application.properties
mkdir -p src/main/resources/fcm
echo '${{ secrets.FCM_SECRET_KEY }}' > src/main/resources/fcm/chongdaemarket-fcm-key.json
mkdir -p src/test/resources/fcm
echo '${{ secrets.FCM_SECRET_KEY }}' > src/test/resources/fcm/chongdaemarket-fcm-key.json
working-directory: ./backend

- name: Build with Gradle Wrapper
Expand Down
2 changes: 1 addition & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ openapi3.yaml
.idea

### FCM ###
/src/main/resources/fcm
/src/main/resources/fcm
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,33 @@
import com.zzang.chongdae.member.repository.MemberRepository;
import com.zzang.chongdae.member.repository.entity.MemberEntity;
import com.zzang.chongdae.member.service.NicknameGenerator;
import com.zzang.chongdae.notification.service.FcmNotificationService;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@RequiredArgsConstructor
@Service
public class AuthService {

private final FcmNotificationService notificationService;
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
private final NicknameGenerator nickNameGenerator;
private final AuthClient authClient;

@WriterDatabase
@Transactional
public AuthInfoDto kakaoLogin(KakaoLoginRequest request) {
String loginId = authClient.getKakaoUserInfo(request.accessToken());
AuthProvider provider = AuthProvider.KAKAO;
MemberEntity member = memberRepository.findByLoginId(loginId)
.orElseGet(() -> signup(provider, loginId, request.fcmToken()));
return login(member);
return login(member, request.fcmToken());
Copy link
Member

@ChooSeoyeon ChooSeoyeon Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전 버전 API 를 사용하는 앱에 대응하기 위해서 fcmToken이 null일 때 처리 추가합시다!

Copy link
Contributor Author

@helenason helenason Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위 방식을 통해 현재 DB 내 default 값("invalid" + UUID.randomUUID())으로 설정되도록 하였습니다!

}

private MemberEntity signup(AuthProvider provider, String loginId, String fcmToken) {
Expand All @@ -40,12 +46,21 @@ private MemberEntity signup(AuthProvider provider, String loginId, String fcmTok
return memberRepository.save(member);
}

private AuthInfoDto login(MemberEntity member) {
private AuthInfoDto login(MemberEntity member, String fcmToken) {
AuthMemberDto authMember = new AuthMemberDto(member);
AuthTokenDto authToken = jwtTokenProvider.createAuthToken(member.getId().toString());
checkFcmToken(member, fcmToken);
notificationService.login(member);
return new AuthInfoDto(authMember, authToken);
}

private void checkFcmToken(MemberEntity member, String fcmToken) {
if (!memberRepository.existsByIdAndFcmToken(member.getId(), fcmToken)) {
log.info("토큰 갱신 사용자 id: {}", member.getId());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 로그는 빼는게 어떨까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위에서 포케의 리뷰에 작성해둔 부분 참고해주세요 :-) 감사합니다

member.updateFcmToken(fcmToken);
}
}

public AuthTokenDto refresh(String refreshToken) {
Long memberId = jwtTokenProvider.getMemberIdByRefreshToken(refreshToken);
return jwtTokenProvider.createAuthToken(memberId.toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.zzang.chongdae.auth.service.dto;

import javax.annotation.Nullable;
import jakarta.validation.constraints.NotEmpty;

public record KakaoLoginRequest(String accessToken,

@Nullable
@NotEmpty
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

회의 때 Nullable에서 NotEmpty로 바뀐 배경이 궁금했어요 😄

이전 버전을 사용하는 사용자들이 fcmToken을 전달하지 않으면 어떻게 되나요?
전달하지 않으면 fcmtoken 값은 어떻게 처리되나요?

기존에 fcmtoken을 전달하지 않은 사용자들은 어떻게 처리되나요? checkFcmToken으로 이미 값이 없으니 갱신하는 로직이 생략되는 걸까요~

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkFcmToken을 통해 DB에 저장된 토큰과 요청된 토큰이 다를 경우 갱신합니다. 로그인의 토큰 갱신과 비슷한 개념이라고 생각하시면 돼요.

@NotEmpty로 설정한 이유는, fcm 서버에 알림을 send할 때 빈 값의 토큰에 대해 알림을 전송하지 않도록 하기 위함이었어요. 빈 문자열 토큰에 대해 알림을 전송하면 예외가 터져서요. 따라서 fcmToken은 클라이언트 측으로부터 비지 않은 상태로 들어와야 한다고 생각했습니다.

String fcmToken) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public Long saveComment(CommentSaveRequest request, MemberEntity member) {
CommentEntity comment = new CommentEntity(member, offering, request.content());
CommentEntity savedComment = commentRepository.save(comment);

List<OfferingMemberEntity> members = offeringMemberRepository.findAllByOffering(offering);
notificationService.saveComment(savedComment, members);
List<OfferingMemberEntity> offeringMembers = offeringMemberRepository.findAllByOffering(offering);
notificationService.saveComment(savedComment, offeringMembers);
return savedComment.getId();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
boolean existsByNickname(String nickname);

Optional<MemberEntity> findByLoginId(String loginId);

boolean existsByIdAndFcmToken(Long id, String fcmToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ public MemberEntity(String nickname, AuthProvider provider, String loginId, Stri
public boolean isSame(MemberEntity other) {
return this.equals(other);
}

public void updateFcmToken(String fcmToken) {
this.fcmToken = fcmToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.zzang.chongdae.global.exception.MarketException;
import com.zzang.chongdae.notification.exception.NotificationErrorCode;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.io.InputStream;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -29,23 +24,16 @@ public void initialize() {
return;
}
try {
URL url = this.getClass().getResource(secretKeyPath);
if (url == null) {
throw new MarketException(NotificationErrorCode.CANNOT_FIND_URL);
}
File secretKeyFile = new File(url.toURI());
FileInputStream secretKey = new FileInputStream(secretKeyFile);
InputStream secretKey = this.getClass().getResourceAsStream(secretKeyPath);
FirebaseApp.initializeApp(fcmOptions(secretKey));
log.info("성공적으로 FCM 앱을 초기화하였습니다.");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (URISyntaxException e) { // todo
throw new RuntimeException(e);
}
}

private FirebaseOptions fcmOptions(FileInputStream secretKey) throws IOException {
private FirebaseOptions fcmOptions(InputStream secretKey) throws IOException {
return FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(secretKey))
.build();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
package com.zzang.chongdae.notification.domain;

import com.zzang.chongdae.offering.repository.entity.OfferingEntity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 어노테이션은 FcmCondition에서 사용하나요? 사용하지 않으면 제거 부탁드려요~

@Getter
@RequiredArgsConstructor
public class FcmCondition {

private static final String CONDITION_FORMAT_TRUE_AND_FALSE = "'%s' in topics && !('%s' in topics)";

private final String value;

public static FcmCondition roomStatusCondition(OfferingEntity offering) {
FcmTopic offeringTopic = FcmTopic.offeringMemberTopic(offering);
FcmTopic proposerTopic = FcmTopic.proposerTopic(offering);
String value = CONDITION_FORMAT_TRUE_AND_FALSE.formatted(offeringTopic.getValue(), proposerTopic.getValue());
return new FcmCondition(value);
}

public static FcmCondition offeringCondition(OfferingEntity offering) {
FcmTopic memberTopic = FcmTopic.memberTopic();
FcmTopic proposerTopic = FcmTopic.proposerTopic(offering);
String value = CONDITION_FORMAT_TRUE_AND_FALSE.formatted(memberTopic.getValue(), proposerTopic.getValue());
return new FcmCondition(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.zzang.chongdae.notification.domain;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FcmData {

private final Map<String, String> data = new HashMap<>();

public void addData(String key, Object value) {
data.put(key, value.toString());
}

public Map<String, String> getData() {
data.forEach((key, value) -> log.info("{} : {}", key, value));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 로그도 빼는 건 어떨까요?

return Collections.unmodifiableMap(data);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.zzang.chongdae.notification.domain;

import com.zzang.chongdae.member.repository.entity.MemberEntity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class FcmToken {

private final String value;

public FcmToken(MemberEntity member) {
this.value = member.getFcmToken();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ private FcmTokens(List<FcmToken> tokens) {

public static FcmTokens from(List<MemberEntity> members) {
List<FcmToken> tokens = members.stream()
.map(member -> new FcmToken(member.getFcmToken()))
.map(FcmToken::new)
.toList();
return new FcmTokens(tokens);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.zzang.chongdae.notification.domain;

import com.zzang.chongdae.offering.repository.entity.OfferingEntity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정적팩터리 메서드를 사용하려고 설정하셨을까요? 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정확합니다!

public class FcmTopic {

private static final String TOPIC_FORMAT_MEMBER = "member";
private static final String TOPIC_FORMAT_OFFERING_MEMBER = "member_of_offering_%d";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분도 회의 때 논한대로 participant_of_offering_%d 로 변경합시다!

private static final String TOPIC_FORMAT_OFFERING_PROPOSER = "proposer_of_offering_%d";

private final String value;

public static FcmTopic offeringMemberTopic(OfferingEntity offering) {
String value = TOPIC_FORMAT_OFFERING_MEMBER.formatted(offering.getId());
return new FcmTopic(value);
}

public static FcmTopic proposerTopic(OfferingEntity offering) {
String value = TOPIC_FORMAT_OFFERING_PROPOSER.formatted(offering.getId());
return new FcmTopic(value);
}

public static FcmTopic memberTopic() {
return new FcmTopic(TOPIC_FORMAT_MEMBER);
}
}

This file was deleted.

Loading