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

♻️ refactor: noti-service 지역 변환 & 가중치 적용 #118 #122

Merged
merged 13 commits into from
Jul 1, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public ApiResponse<?> deleteNotification(@AuthUser String email, @RequestParam("

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Builder;

@Builder
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 = " 위도(latitude) 값은 필수입니다.")
@DecimalMax(value = "43.0", inclusive = true, message = "대한민국 내에서만 가능합니다. (33~43)")
@DecimalMin(value = "33.0", inclusive = true, message = "대한민국 내에서만 가능합니다. (33~43)")
double latitude,

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


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.waither.notiservice.api.response;

import lombok.Builder;

import java.util.List;

@Builder
public record MainWeatherResponse(

//현재 강수 확률
String pop,
//현재 온도
String temp,
//최저 온도
String tempMin,
//최고 온도
String tempMax,
//현재 습도
String humidity,
//풍향
String windVector,
//풍
String windDegree,
// 예상 기온
List<String> expectedTemp,
// 예상 강수량
List<String> expectedRain,
// 예상 강수 형태
List<String> expectedPty,
// 예상 하늘 상태
List<String> expectedSky
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@
import java.time.LocalDateTime;

@Builder
@Getter
@AllArgsConstructor
public class NotificationResponse {

public String id;
public LocalDateTime time;
public String message;

public record NotificationResponse(
String id,
LocalDateTime time,
String message
) {
public static NotificationResponse of(Notification notification) {
return NotificationResponse.builder()
.id(notification.getId())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +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 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);
}
}
//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 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
Expand Up @@ -94,24 +94,6 @@ private ConsumerFactory<String, KafkaDto.UserMedianDto> userMedianConsumerFactor
}



@Bean("firebaseTokenKafkaListenerContainerFactory")
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, KafkaDto.TokenDto> firebaseTokenConsumerFactory() {
Map<String, Object> props = dtoSettings();
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(KafkaDto.TokenDto.class));
}



@Bean("userSettingsKafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, KafkaDto.UserSettingsDto> userSettingsConcurrentKafkaListenerContainerFactory(){
ConcurrentKafkaListenerContainerFactory<String, KafkaDto.UserSettingsDto> factory = new ConcurrentKafkaListenerContainerFactory<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void updateToken(String email, TokenDto tokenDto) {

public void sendSingleAlarm(String email, String title, String message) {
String token = String.valueOf(redisUtils.get(email));
fireBaseUtils.sendSingleMessage(token, title, message);
// fireBaseUtils.sendSingleMessage(token, title, message);
notificationRepository.save(Notification.builder()
.email(email)
.title(title)
Expand All @@ -44,7 +44,7 @@ public void sendAlarms(List<String> userEmails, String title, String message) {
log.info("[ 푸시알림 ] Email ---> {}", userEmails);
log.info("[ 푸시알림 ] message ---> {}", message);

fireBaseUtils.sendAllMessages(tokens,title, message);
// fireBaseUtils.sendAllMessages(tokens,title, message);

List<Notification> notifications = userEmails.stream()
.map(email -> Notification.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
import com.waither.notiservice.repository.jpa.UserMedianRepository;
import com.waither.notiservice.repository.redis.NotificationRecordRepository;
import com.waither.notiservice.utils.RedisUtils;
import com.waither.notiservice.utils.TemperatureUtils;
import com.waither.notiservice.utils.WeatherMessageUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

Expand All @@ -38,7 +39,7 @@ public class KafkaConsumer {
@KafkaListener(topics = "${spring.kafka.template.user-median-topic}", containerFactory = "userMedianKafkaListenerContainerFactory")
public void consumeUserMedian(KafkaDto.UserMedianDto userMedianDto) {

Season currentSeason = TemperatureUtils.getCurrentSeason();
Season currentSeason = WeatherMessageUtils.getCurrentSeason();
log.info("[ Kafka Listener ] 사용자 중앙값 데이터 동기화");
log.info("[ Kafka Listener ] Season : -- {} ", currentSeason.name());
log.info("[ Kafka Listener ] Email : --> {}", userMedianDto.email());
Expand All @@ -49,8 +50,6 @@ public void consumeUserMedian(KafkaDto.UserMedianDto userMedianDto) {
//User Median 이미 있을 경우
UserMedian userMedian = optionalUserMedian.get();
userMedian.setLevel(userMedianDto);
userMedianRepository.save(userMedian);

} else {
//User Median 없을 경우 생성
//TODO : 계절당 초기값 받아야 함
Expand All @@ -65,22 +64,6 @@ public void consumeUserMedian(KafkaDto.UserMedianDto userMedianDto) {
}


/**
* Firebase Token Listener
* */
@Transactional
@KafkaListener(topics = "firebase-token", containerFactory = "firebaseTokenKafkaListenerContainerFactory")
public void consumeFirebaseToken(KafkaDto.TokenDto tokenDto) {

log.info("[ Kafka Listener ] Firebase Token 동기화");
log.info("[ Kafka Listener ] Email : --> {}", tokenDto.email());
log.info("[ Kafka Listener ] Token : --> {}", tokenDto.token());

//토큰 Redis 저장
redisUtils.save(tokenDto.email(), tokenDto.token());
}


/**
* User Settings Listener
* */
Expand All @@ -96,7 +79,6 @@ public void consumeUserSettings(KafkaDto.UserSettingsDto userSettingsDto) {
Optional<UserData> userData = userDataRepository.findByEmail(userSettingsDto.email());
if (userData.isPresent()) {
userData.get().updateValue(userSettingsDto.key(), userSettingsDto.value());
userDataRepository.save(userData.get());
} else {
log.warn("[ Kafka Listener ] User Data 초기값이 없었습니다.");
UserData newUserData = UserData.builder()
Expand Down Expand Up @@ -165,7 +147,7 @@ public void consumeWindAlarm(KafkaDto.WeatherDto weatherDto) {


/**
* 강설 정보 알림 Listener <br>
* 강수 정보 알림 Listener <br>
* 기상청 기준 <br>
* 약한 비 1~3mm <br>
* 보통 비 3~15mm <br>
Expand All @@ -174,8 +156,8 @@ public void consumeWindAlarm(KafkaDto.WeatherDto weatherDto) {
* <a href="https://www.kma.go.kr/kma/biz/forecast05.jsp">참고</a>
*/
@Transactional
@KafkaListener(topics = "alarm-snow", containerFactory = "weatherKafkaListenerContainerFactory")
public void consumeSnow(KafkaDto.WeatherDto weatherDto) {
@KafkaListener(topics = "alarm-rain", containerFactory = "weatherKafkaListenerContainerFactory")
public void consumeRain(KafkaDto.WeatherDto weatherDto) {

int currentHour = LocalDateTime.now().getHour();
// 22:00 ~ 07:00 는 알림을 전송하지 않음
Expand All @@ -184,25 +166,31 @@ public void consumeSnow(KafkaDto.WeatherDto weatherDto) {
}

String title = "Waither 강수 정보 알림";
List<UserData> userData = userDataRepository.findAllBySnowAlertIsTrue();
StringBuilder sb = new StringBuilder();

String region = weatherDto.region();
Double prediction = Double.valueOf(weatherDto.message()); //강수량

//지역
String region = weatherDto.region();
log.info("[ Kafka Listener ] 강수량 지역 --> {}", region);
log.info("[ Kafka Listener ] 걍수량 --> {}", prediction);
String message = weatherDto.message();

List<UserData> userData = userDataRepository.findAllBySnowAlertIsTrue();
//1시간 뒤, 2시간 뒤, 3시간 뒤, 4시간 뒤, 5시간 뒤, 6시간 뒤
List<Double> predictions = Arrays.stream(message.split(","))
.map(String::trim) //공백 제거
.map(s -> s.equals("강수없음") ? "0" : s)
.map(Double::parseDouble)
.toList();
String rainMessage = WeatherMessageUtils.getRainPredictions(predictions);

//예시 : 현재 서울특별시 지역에 2mm의 약한 비가 내릴 예정입니다.
//TODO: 언제 내리는지? 확인 필요
sb.append("현재 ").append(region).append(" 지역에 ").append(prediction).append("mm의 ")
.append(getExpression(prediction)).append("가 내릴 예정입니다.");
if (rainMessage == null) {
//6시간 동안 강수 정보 없음
return;
}

sb.append("현재 ").append(region).append(" 지역에 ").append(rainMessage);
//알림 보낼 사용자 이메일
List<String> userEmails = filterRegionAndRainAlarm(region, userData, currentHour);


System.out.println("[ 푸시알림 ] 강수량 알림");
alarmService.sendAlarms(userEmails, title, sb.toString());

Expand All @@ -215,6 +203,7 @@ public void consumeSnow(KafkaDto.WeatherDto weatherDto) {

}


/**
* 기상 특보 알림 Listener
* */
Expand Down Expand Up @@ -249,6 +238,7 @@ public void consumeClimateAlarm(KafkaDto.WeatherDto weatherDto) {


//지역 필터링 & 알림 규칙 검사

private List<String> filterRegionAndWindAlarm(String region, List<UserData> userData, int currentHour) {
return userData.stream()
.filter(data -> {
Expand All @@ -261,8 +251,8 @@ private List<String> filterRegionAndWindAlarm(String region, List<UserData> user
.map(UserData::getEmail)
.toList();
}

//지역 필터링 & 알림 규칙 검사

private List<String> filterRegionAndRainAlarm(String region, List<UserData> userData, int currentHour) {
return userData.stream()
.filter(data -> {
Expand All @@ -275,7 +265,6 @@ private List<String> filterRegionAndRainAlarm(String region, List<UserData> user
.map(UserData::getEmail)
.toList();
}

private List<String> filterRegion(String region, List<UserData> userData) {
return userData.stream()
.filter(data -> {
Expand All @@ -286,20 +275,6 @@ private List<String> filterRegion(String region, List<UserData> userData) {
.toList();
}

//강수 표현
private String getExpression(double prediction) {
//1~3mm : 약한 비
if (prediction > 1 && prediction < 3) {
return "약한 비";
//3~15mm : 비
} else if (prediction >=3 && prediction < 15) {
return "비";
//15~30mm 강한 비
} else if (prediction >= 15 &&prediction <= 30) {
return "강한 비";
//30mm~ 매우 강한 비
} else if (prediction >= 30) {
return "매우 강한 비";
} else return "비";
}


}
Loading
Loading