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: noti-service FCM 푸시알림 환경 구성, Kafka연결, 인가 과정 처리 (#98) #109

Merged
merged 24 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3ce2c14
📝docs : kafka yml
DDonghyeo May 31, 2024
3827249
♻️kafka : User-service Kafka 동기화
DDonghyeo May 31, 2024
4bf4540
♻️kafka : Kafka Dto 로직 변경
DDonghyeo May 31, 2024
54051cf
♻️kafka : Kafka Dto Builder
DDonghyeo May 31, 2024
aa6510b
♻️refactor : @AuthUser 인가과정 적용
DDonghyeo May 31, 2024
e82198e
♻️refactor : 유저 식별자 email로 변경
DDonghyeo May 31, 2024
ee0e4d7
♻️refactor : OAuthController Swagger hidden
DDonghyeo May 31, 2024
cf988ac
♻️refactor : OAuthController Swagger hidden
DDonghyeo May 31, 2024
3b128f0
♻️refactor : 유저 식별자 Email로 변경
DDonghyeo May 31, 2024
7839016
♻️refactor : 유저 식별자 Email로 변경
DDonghyeo May 31, 2024
ae443f9
♻️refactor : Test 케이스 재 작성
DDonghyeo May 31, 2024
e423ea0
♻️refactor : Test 케이스 수정
DDonghyeo May 31, 2024
5e23d8e
♻️refactor : Topic 주석 Dto 수정
DDonghyeo May 31, 2024
cd11b5a
♻️refactor : 위도, 경도 표현식 변경
DDonghyeo May 31, 2024
bc6f48c
♻️refactor : ApiResponse
DDonghyeo May 31, 2024
7aa4b65
♻️refactor : Kafka Dto 수정
DDonghyeo Jun 7, 2024
d44b9ea
♻️refactor : FCM 토큰 환경 구성
DDonghyeo Jun 18, 2024
67746d4
✨feat : 토큰 최신화 API
DDonghyeo Jun 18, 2024
10a11f4
✨feat : 토큰 최신화 API
DDonghyeo Jun 18, 2024
110cb35
♻️refactor : 알림 로직 변경 & 레코드 적용
DDonghyeo Jun 18, 2024
824988c
♻️refactor : Kafka ConsumerConfig & 로직 변경
DDonghyeo Jun 18, 2024
b0005ab
♻️refactor : Kafka Consumer 수정
DDonghyeo Jun 19, 2024
62989de
♻️refactor : Token controller 네이밍 수정
DDonghyeo Jun 19, 2024
49b2fd3
♻️refactor : FireBaseUtils Error 수정
DDonghyeo Jun 22, 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
3 changes: 3 additions & 0 deletions noti-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ dependencies {
//JUnit + AssertJ
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
testImplementation 'org.assertj:assertj-core:3.23.1'

//firebase
implementation 'com.google.firebase:firebase-admin:9.2.0'
}

//openApi {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.waither.notiservice.api;

import com.waither.notiservice.api.request.LocationDto;
import com.waither.notiservice.global.annotation.AuthUser;
import com.waither.notiservice.global.response.ApiResponse;
import com.waither.notiservice.service.NotificationService;
import com.waither.notiservice.utils.RedisUtils;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -17,31 +17,32 @@ public class NotificationController {

private final NotificationService notificationService;

private final RedisUtils redisUtils;

@Operation(summary = "Get notification", description = "알림 목록 조회하기")
@GetMapping("")
public ApiResponse<?> getNotifications(Long userId) {
return ApiResponse.onSuccess(notificationService.getNotifications(userId));
public ApiResponse<?> getNotifications(@AuthUser String email) {
return ApiResponse.onSuccess(notificationService.getNotifications(email));
}

@Operation(summary = "Delete notification", description = "알림 삭제하기")
@DeleteMapping("")
public ApiResponse<?> deleteNotification(@RequestParam("id") String notificationId) {
notificationService.deleteNotification(notificationId);
public ApiResponse<?> deleteNotification(@AuthUser String email, @RequestParam("id") String notificationId) {
notificationService.deleteNotification(email, notificationId);
return ApiResponse.onSuccess(HttpStatus.OK);
}

@Operation(summary = "Send Go Out Alarm", description = "외출 알림 전송하기")
@PostMapping("/goOut")
public void sendGoOutAlarm(Long userId) {
notificationService.sendGoOutAlarm(userId);
public ApiResponse<?> sendGoOutAlarm(@AuthUser String email) {
notificationService.sendGoOutAlarm(email);
return ApiResponse.onSuccess(HttpStatus.OK);
}

@Operation(summary = "Current Location", description = "현재 위치 전송")
@PostMapping("/location")
public void checkCurrentAlarm(@RequestBody @Valid LocationDto locationDto) {
notificationService.checkCurrentAlarm(locationDto);
public ApiResponse<?> updateLocation(@AuthUser String email, @RequestBody @Valid LocationDto locationDto) {
notificationService.updateLocation(email, locationDto);
return ApiResponse.onSuccess(HttpStatus.OK);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.waither.notiservice.api;

import com.waither.notiservice.api.request.TokenDto;
import com.waither.notiservice.global.annotation.AuthUser;
import com.waither.notiservice.global.response.ApiResponse;
import com.waither.notiservice.service.AlarmService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RequestMapping("/noti")
@RestController
public class TokenContoller {

private final AlarmService alarmService;

@Operation(summary = "Firebase Token 업데이트", description = "Request Body에 발급한 FCM토큰 값을 넣어서 주시면 됩니다.")
@PostMapping("/token")
public ApiResponse<?> updateToken(@AuthUser String email, @RequestBody TokenDto tokenDto) {
alarmService.updateToken(email, tokenDto);
return ApiResponse.onSuccess("토큰 업로드가 완료되었습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
package com.waither.notiservice.api.request;

import jakarta.validation.Valid;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@AllArgsConstructor
@NoArgsConstructor
public class LocationDto {
public record LocationDto (
@NotBlank(message = " 위도(lat) 값은 필수입니다.")
@DecimalMax(value = "132.0", inclusive = true, message = "위도(lat)는 대한민국 내에서만 가능합니다.")
@DecimalMin(value = "124.0", inclusive = true, message = "위도(lat)는 대한민국 내에서만 가능합니다.")
double lat,

@NotBlank(message = " 경도(y) 값은 필수입니다.")
@DecimalMax(value = "43.0", inclusive = true, message = "경도(lon)는 대한민국 내에서만 가능합니다.")
@DecimalMin(value = "33.0", inclusive = true, message = "경도(lon)는 대한민국 내에서만 가능합니다.")
double lon
) {

@NotBlank(message = " 위도(x) 값은 필수입니다.")
@DecimalMax(value = "132.0", inclusive = true, message = "위도(x)는 대한민국 내에서만 가능합니다.")
@DecimalMin(value = "124.0", inclusive = true, message = "위도(x)는 대한민국 내에서만 가능합니다.")
public double x;

@NotBlank(message = " 경도(y) 값은 필수입니다.")
@DecimalMax(value = "43.0", inclusive = true, message = "경도(y)는 대한민국 내에서만 가능합니다.")
@DecimalMin(value = "33.0", inclusive = true, message = "경도(y)는 대한민국 내에서만 가능합니다.")
public double y;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.waither.notiservice.api.request;

public record TokenDto(
String token

) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.waither.notiservice.config;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import java.io.FileInputStream;
import java.io.IOException;

@Configuration
@RequiredArgsConstructor
public class FirebaseConfig {

@Value("${firebase.key.path}")
private final String keyPath;

@PostConstruct
public void initializeApp() throws IOException {
FileInputStream serviceAccount =
new FileInputStream(keyPath);

FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build();

FirebaseApp.initializeApp(options);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.waither.notiservice.config;

import com.waither.notiservice.dto.kafka.TokenDto;
import com.waither.notiservice.dto.kafka.UserMedianDto;
import com.waither.notiservice.dto.kafka.UserSettingsDto;
import com.waither.notiservice.dto.kafka.KafkaDto;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -81,52 +79,83 @@ public ConsumerFactory<String, String> stringConsumerFactory() {


@Bean("userMedianKafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, UserMedianDto> userMedianDtoConcurrentKafkaListenerContainerFactory(){
ConcurrentKafkaListenerContainerFactory<String, UserMedianDto> factory = new ConcurrentKafkaListenerContainerFactory<>();
public ConcurrentKafkaListenerContainerFactory<String, KafkaDto.UserMedianDto> userMedianDtoConcurrentKafkaListenerContainerFactory(){
ConcurrentKafkaListenerContainerFactory<String, KafkaDto.UserMedianDto> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(userMedianConsumerFactory());
factory.setConcurrency(3);
factory.setBatchListener(true);
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.BATCH);
return factory;
}

private ConsumerFactory<String, UserMedianDto> userMedianConsumerFactory() {
private ConsumerFactory<String, KafkaDto.UserMedianDto> userMedianConsumerFactory() {
Map<String, Object> props = dtoSettings();
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(UserMedianDto.class));
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(KafkaDto.UserMedianDto.class));
}



@Bean("firebaseTokenKafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, TokenDto> firebaseTokenConcurrentKafkaListenerContainerFactory(){
ConcurrentKafkaListenerContainerFactory<String, TokenDto> factory = new ConcurrentKafkaListenerContainerFactory<>();
public ConcurrentKafkaListenerContainerFactory<String, KafkaDto.TokenDto> firebaseTokenConcurrentKafkaListenerContainerFactory(){
ConcurrentKafkaListenerContainerFactory<String, KafkaDto.TokenDto> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(firebaseTokenConsumerFactory());
factory.setConcurrency(3);
factory.setBatchListener(true);
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.BATCH);
return factory;
}

private ConsumerFactory<String, TokenDto> firebaseTokenConsumerFactory() {
private ConsumerFactory<String, KafkaDto.TokenDto> firebaseTokenConsumerFactory() {
Map<String, Object> props = dtoSettings();
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(TokenDto.class));
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(KafkaDto.TokenDto.class));
}



@Bean("userSettingsKafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, UserSettingsDto> userSettingsConcurrentKafkaListenerContainerFactory(){
ConcurrentKafkaListenerContainerFactory<String, UserSettingsDto> factory = new ConcurrentKafkaListenerContainerFactory<>();
public ConcurrentKafkaListenerContainerFactory<String, KafkaDto.UserSettingsDto> userSettingsConcurrentKafkaListenerContainerFactory(){
ConcurrentKafkaListenerContainerFactory<String, KafkaDto.UserSettingsDto> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(userSettingsConsumerFactory());
factory.setConcurrency(3);
factory.setBatchListener(true);
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.BATCH);
return factory;
}

private ConsumerFactory<String, UserSettingsDto> userSettingsConsumerFactory() {
private ConsumerFactory<String, KafkaDto.UserSettingsDto> userSettingsConsumerFactory() {
Map<String, Object> props = dtoSettings();
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(UserSettingsDto.class));
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(KafkaDto.UserSettingsDto.class));
}


@Bean("initialDataKafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, KafkaDto.InitialDataDto> initialDataConcurrentKafkaListenerContainerFactory(){
ConcurrentKafkaListenerContainerFactory<String, KafkaDto.InitialDataDto> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(initialDataConsumerFactory());
factory.setConcurrency(3);
factory.setBatchListener(true);
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.BATCH);
return factory;
}

private ConsumerFactory<String, KafkaDto.InitialDataDto> initialDataConsumerFactory() {
Map<String, Object> props = dtoSettings();
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(KafkaDto.InitialDataDto.class));
}

@Bean("weatherKafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, KafkaDto.WeatherDto> weatherConcurrentKafkaListenerContainerFactory(){
ConcurrentKafkaListenerContainerFactory<String, KafkaDto.WeatherDto> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(weatherConsumerFactory());
factory.setConcurrency(3);
factory.setBatchListener(true);
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.BATCH);
return factory;
}

private ConsumerFactory<String, KafkaDto.WeatherDto> weatherConsumerFactory() {
Map<String, Object> props = dtoSettings();
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(KafkaDto.WeatherDto.class));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public KafkaAdmin kafkaAdmin() {
* <h2>userMedian 동기화 토픽</h2>
* @Producer : user-service
* @Consumer : noti-service
* @MessageObject : {@link com.waither.notiservice.dto.kafka.UserMedianDto}
* @MessageObject : {@link com.waither.notiservice.dto.kafka.KafkaDto.UserMedianDto}
* @Description : noti-service의 userMedian 테이블의 데이터를 동기화 하기 위해 사용합니다.
* 계절은 자동으로 계산합니다.
* <br>
Expand All @@ -48,7 +48,7 @@ public NewTopic userMedianTopic(){
* <h2>Firebase Token 동기화 토픽</h2>
* @Producer : user-service
* @Consumer : noti-service
* @MessageObject : {@link com.waither.notiservice.dto.kafka.TokenDto}
* @MessageObject : {@link com.waither.notiservice.dto.kafka.KafkaDto.TokenDto}
* @Description : noti-service의 firebase 토큰을 저장을 위해 사용됩니다.
*
*/
Expand All @@ -64,7 +64,7 @@ public NewTopic fireBaseTokenTopic(){
* <h2>User Settings 동기화 토픽</h2>
* @Producer : user-service
* @Consumer : noti-service
* @MessageObject : {@link com.waither.notiservice.dto.kafka.UserSettingsDto}
* @MessageObject : {@link com.waither.notiservice.dto.kafka.KafkaDto.UserSettingsDto}
* @Description : noti-service의 User Data 데이터 동기화를 위해 사용됩니다.
*
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
public class FireBaseToken {

@Id
private Long userId;
private String email;

private String token;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public class Notification extends BaseEntity {

private String content;

private Long userId;
private String email;

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.waither.notiservice.domain;

import com.waither.notiservice.domain.type.Season;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.DynamicInsert;

@Builder
Expand All @@ -16,7 +14,7 @@
public class UserData {

@Id
private Long userId;
private String email;

private String nickName;

Expand All @@ -38,6 +36,9 @@ public class UserData {
// 직장 지역 레포트 알림 받기
private boolean regionReport;

//가중치
private Double weight;

public void updateValue(String key, String value) {
switch (key) {
case "nickName" -> nickName = value;
Expand All @@ -47,6 +48,7 @@ public void updateValue(String key, String value) {
case "windAlert" -> windAlert = Boolean.parseBoolean(value);
case "regionReport" -> regionReport = Boolean.parseBoolean(value);
case "windDegree" -> windDegree = Integer.valueOf(value);
case "weight" -> weight = Double.valueOf(value);

}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.waither.notiservice.domain;

import com.waither.notiservice.domain.type.Season;
import com.waither.notiservice.enums.Season;
import com.waither.notiservice.dto.kafka.KafkaDto;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;
Expand All @@ -15,7 +16,7 @@
public class UserMedian {

@Id
private Long userId;
private String email;
private Double medianOf1And2;
private Double medianOf2And3;
private Double medianOf3And4;
Expand All @@ -24,12 +25,21 @@ public class UserMedian {
@Enumerated(value = EnumType.STRING)
public Season season;

public void setLevel(int level, double value) {
public void setLevel(int level, Double value) {
switch (level) {
case 1 -> medianOf1And2 = value;
case 2 -> medianOf2And3 = value;
case 3 -> medianOf3And4 = value;
case 4 -> medianOf4And5 = value;
}
}

public void setLevel(KafkaDto.UserMedianDto userMedianDto) {
KafkaDto.SeasonData seasonData = userMedianDto.seasonData();
medianOf1And2 = seasonData.medianOf1And2();
medianOf2And3 = seasonData.medianOf2And3();
medianOf3And4 = seasonData.medianOf3And4();
medianOf4And5 = seasonData.medianOf4And5();

}
}
Loading
Loading