Skip to content

Commit

Permalink
Merge pull request #1394 from innovationacademy-kr/common/feat_web_pu…
Browse files Browse the repository at this point in the history
…sh_alarm

[COMMON] 웹 푸쉬 알림 기능 테스트용
  • Loading branch information
sichoi42 authored Oct 12, 2023
2 parents 8f44151 + 4f51bda commit dd44caf
Show file tree
Hide file tree
Showing 16 changed files with 2,069 additions and 20 deletions.
5 changes: 4 additions & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,7 @@ out/
application*.yml
!application.yml

**/static/docs
**/static/docs

### Firebase ###
*firebase*.json
6 changes: 5 additions & 1 deletion backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,20 @@ dependencies {
implementation 'org.modelmapper:modelmapper:3.1.1'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// querydsl
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"

// Monitoring
// Monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-core:1.11.2'
implementation 'io.micrometer:micrometer-registry-prometheus'

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

// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
Expand Down
9 changes: 9 additions & 0 deletions backend/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@ services:
- '80:80'
volumes:
- ../dev/:/etc/nginx/conf.d/
redis:
image: redis:latest
restart: always
ports:
- "6379:6379"
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
timeout: 20s
retries: 10
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.ftclub.cabinet.firebase;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class FCMInitializer {

@Value("${firebase.messaging.credentials.path}")
private String credentialsPath;

@PostConstruct
public void initialize() throws IOException {
ClassPathResource resource = new ClassPathResource(credentialsPath);

try (InputStream inputStream = resource.getInputStream()) {
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(inputStream))
.build();

if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options);
log.info("Firebase application has been initialized");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.ftclub.cabinet.firebase;

import java.time.Duration;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

import org.ftclub.cabinet.firebase.fcm.service.FCMService;
import org.ftclub.cabinet.redis.service.RedisService;
import org.ftclub.cabinet.utils.overdue.manager.OverdueType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/v4/fcm")
@Log4j2
public class FCMTestController {
private final RedisService redisService;
private final FCMService fcmService;

@PostMapping("test/{token}")
public void test(@PathVariable("token") String token) {
log.info("called test token = {}", token);
redisService.save("sichoi", token, Duration.ofDays(1));
}

@PostMapping("test2/{name}")
public void test2(@PathVariable("name") String name) {
log.info("called test2");
fcmService.sendPushMessage(name, OverdueType.OVERDUE, 1L);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.ftclub.cabinet.firebase.fcm.service;

import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ftclub.cabinet.redis.service.RedisService;
import org.ftclub.cabinet.utils.overdue.manager.OverdueType;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class FCMService {
private final RedisService redisService;


public void sendPushMessage(String name, OverdueType overdueType, Long daysLeftFromExpireDate) {
log.info("called sendPushMessage name = {}, overdueType = {}, daysLeftFromExpireDate = {}", name, overdueType,
daysLeftFromExpireDate);

Optional<String> token = redisService.findByKey(name, String.class);
if (token.isEmpty()) {
log.warn("\"{}\"에 해당하는 디바이스 토큰이 존재하지 않습니다.", name);
return;
}

switch (overdueType) {
case NONE:
log.warn("overdueType이 NONE입니다. name = {}, overdueType = {}, daysLeftFromExpireDate = {}", name, overdueType,
daysLeftFromExpireDate);
break;
case SOON_OVERDUE:
sendSoonOverdueMessage(token.get(), name, daysLeftFromExpireDate);
break;
case OVERDUE:
sendOverdueMessage(token.get(), name, daysLeftFromExpireDate);
break;
}
}

private void sendOverdueMessage(String token, String name, Long daysLeftFromExpireDate) {
log.info(
"called sendOverdueMessage token = {}, name = {}, daysLeftFromExpireDate = {}",
token, name, daysLeftFromExpireDate);
Message message = Message.builder()
.putData("title", "<CABI> 연체 알림")
.putData("content", name + "님, 대여한 사물함이 " + Math.abs(daysLeftFromExpireDate) + "일 연체되었습니다.")
.setToken(token)
.build();

FirebaseMessaging.getInstance().sendAsync(message);
}

private void sendSoonOverdueMessage(String token, String name, Long daysLeftFromExpireDate) {
log.info(
"called sendSoonOverdueMessage token = {}, name = {}, daysLeftFromExpireDate = {}",
token, name, daysLeftFromExpireDate);
if (token.isEmpty()) {
log.warn("\"{}\"에 해당하는 디바이스 토큰이 존재하지 않습니다.", name);
return;
}
Message message = Message.builder()
.putData("title", "<CABI> 연체 예정 알림")
.putData("content", "대여한 사물함이 " + daysLeftFromExpireDate + "일 후 연체됩니다.")
.setToken(token)
.build();

FirebaseMessaging.getInstance().sendAsync(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.ftclub.cabinet.redis.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
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;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Value("${spring.redis.host}")
private String redisHost;

@Value("${spring.redis.port}")
private int redisPort;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}

@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());

redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());

return redisTemplate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.ftclub.cabinet.redis.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Duration;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class FCMTokenRedisService implements RedisService {

private static final String KEY_PREFIX = "cabinet-fcm-token:";

private final StringRedisTemplate redisTemplate;
private final ObjectMapper objectMapper;


/**
* @param key 조회할 키
* @param type 조회할 값의 타입
* @return 조회된 값
*/
@Override
public <T> Optional<T> findByKey(String key, Class<T> type) {
String serializedValue = redisTemplate.opsForValue().get(KEY_PREFIX + key);
if (serializedValue == null) {
return Optional.empty();
}
try {
return Optional.of(objectMapper.readValue(serializedValue, type));
} catch (Exception e) {
log.error("Redis findByKey error", e);
}
return Optional.empty();
}

/**
* @param key 저장할 키
* @param data 저장할 값
* @param duration 저장할 기간
*/
@Override
public <T> void save(String key, T data, Duration duration) {
try {
String serializedValue = objectMapper.writeValueAsString(data);
redisTemplate.opsForValue().set(KEY_PREFIX + key, serializedValue, duration);
} catch (Exception e) {
log.error("Redis save error", e);
}
}

/**
* @param key 삭제할 키
*/
@Override
public boolean delete(String key) {
return Boolean.TRUE.equals(redisTemplate.delete(KEY_PREFIX + key));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.ftclub.cabinet.redis.service;

import java.time.Duration;
import java.util.Optional;

public interface RedisService {

<T> Optional<T> findByKey(String key, Class<T> type);

<T> void save(String key, T data, Duration duration);

boolean delete(String key);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.ftclub.cabinet.cabinet.service.CabinetService;
import org.ftclub.cabinet.config.MailOverdueProperties;
import org.ftclub.cabinet.dto.ActiveLentHistoryDto;
import org.ftclub.cabinet.firebase.fcm.service.FCMService;
import org.ftclub.cabinet.utils.mail.EmailSender;
import org.springframework.stereotype.Component;

Expand All @@ -21,6 +22,7 @@
public class OverdueManager {

private final EmailSender emailSender;
private final FCMService fcmService;
private final CabinetService cabinetService;
private final MailOverdueProperties mailOverdueProperties;

Expand Down Expand Up @@ -67,6 +69,7 @@ public void handleOverdue(ActiveLentHistoryDto activeLent) {
try {
emailSender.sendMail(activeLent.getName(), activeLent.getEmail(), subject,
template);
fcmService.sendPushMessage(activeLent.getName(), overdueType, activeLent.getDaysLeftFromExpireDate());
} catch (Exception e) {
e.printStackTrace();
}
Expand Down
2 changes: 1 addition & 1 deletion config
Loading

0 comments on commit dd44caf

Please sign in to comment.