Skip to content

Commit

Permalink
Merge pull request #46 from Central-MakeUs/feature/33
Browse files Browse the repository at this point in the history
Feature/33: FCM ํ‘ธ์‹œ์•Œ๋ฆผ ๊ตฌํ˜„-2
  • Loading branch information
yxhwxn authored Aug 19, 2024
2 parents c026844 + 0f8330a commit 16cc676
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 104 deletions.
21 changes: 15 additions & 6 deletions src/main/java/com/cmc/suppin/fcm/controller/FcmController.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.cmc.suppin.fcm.controller;

import com.cmc.suppin.fcm.controller.dto.DeviceTokenRequestDTO;
import com.cmc.suppin.fcm.controller.dto.FcmSendDTO;
import com.cmc.suppin.fcm.service.FcmService;
import com.cmc.suppin.global.response.ApiResponse;
import com.cmc.suppin.global.security.reslover.Account;
import com.cmc.suppin.global.security.reslover.CurrentAccount;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
Expand All @@ -16,22 +21,26 @@

@Slf4j
@RestController
@RequiredArgsConstructor
@Tag(name = "FCM", description = "FCM ํ‘ธ์‹œ์•Œ๋ฆผ ๊ด€๋ จ API")
@RequestMapping("/api/v1/fcm")
public class FcmController {

private final FcmService fcmService;

public FcmController(FcmService fcmService) {
this.fcmService = fcmService;
}

// ๋ชจ๋ฐ”์ผ๋กœ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž FCM ํ† ํฐ, ๋ฉ”์‹œ์ง€ ์ œ๋ชฉ, ๋‚ด์šฉ์„ ๋ฐ›์•„์„œ ์„œ๋น„์Šค๋ฅผ ์ฒ˜๋ฆฌ
@PostMapping("/send")
public ResponseEntity<ApiResponse<Object>> pushMessage(@RequestBody @Validated FcmSendDTO fcmSendDto) throws IOException {
log.debug("[+] ํ‘ธ์‹œ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. ");
log.debug("[+] ํ‘ธ์‹œ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.");
int result = fcmService.sendMessageTo(fcmSendDto);

return ResponseEntity.ok(ApiResponse.of(result));
}

@PostMapping("/register")
@Operation(summary = "FCM ๋””๋ฐ”์ด์Šค ํ† ํฐ ๋“ฑ๋ก API", description = "์•ฑ์„ ์‹œ์ž‘ํ•  ๋•Œ, ๋””๋ฐ”์ด์Šค ํ† ํฐ์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.<br><br> DeviceType : ANDROID, IOS, OTHER")
public ResponseEntity<Void> registerDeviceToken(@RequestBody @Validated DeviceTokenRequestDTO request, @CurrentAccount Account account) {
fcmService.registerDeviceToken(account.userId(), request.getToken(), request.getDeviceType());
return ResponseEntity.ok().build();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.cmc.suppin.fcm.controller.dto;

import com.cmc.suppin.fcm.domain.DeviceType;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class DeviceTokenRequestDTO {
private String token;
private DeviceType deviceType; // ANDROID, IOS, OTHER
}
47 changes: 47 additions & 0 deletions src/main/java/com/cmc/suppin/fcm/domain/DeviceToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.cmc.suppin.fcm.domain;

import com.cmc.suppin.member.domain.Member;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;

import java.time.LocalDateTime;

@Entity
@Getter
@Builder
@DynamicInsert
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class DeviceToken {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
@JoinColumn(name = "member_id", nullable = false)
private Member member;

@Column(nullable = false, unique = true)
private String deviceToken;

@Enumerated(EnumType.STRING)
private DeviceType deviceType; // ANDROID, IOS, OTHER

@Column(nullable = false)
private LocalDateTime createdAt;


public void setMember(Member member) {
this.member = member;
}

public void setToken(String token) {
this.deviceToken = token;
}

public void setCreatedAt(LocalDateTime now) {
this.createdAt = now;
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/cmc/suppin/fcm/domain/DeviceType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.cmc.suppin.fcm.domain;

public enum DeviceType {
ANDROID, IOS, OTHER
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.cmc.suppin.fcm.domain.respository;

import com.cmc.suppin.fcm.domain.DeviceToken;
import com.cmc.suppin.member.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface DeviceTokenRepository extends JpaRepository<DeviceToken, Long> {
Optional<DeviceToken> findByDeviceToken(String deviceToken);

List<DeviceToken> findAllByMember(Member member);
}

120 changes: 118 additions & 2 deletions src/main/java/com/cmc/suppin/fcm/service/FcmService.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,128 @@
package com.cmc.suppin.fcm.service;

import com.cmc.suppin.fcm.controller.dto.FcmMessageDTO;
import com.cmc.suppin.fcm.controller.dto.FcmSendDTO;
import com.cmc.suppin.fcm.domain.DeviceToken;
import com.cmc.suppin.fcm.domain.DeviceType;
import com.cmc.suppin.fcm.domain.respository.DeviceTokenRepository;
import com.cmc.suppin.global.enums.UserStatus;
import com.cmc.suppin.member.domain.Member;
import com.cmc.suppin.member.domain.repository.MemberRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auth.oauth2.GoogleCredentials;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.*;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;

@Service
public interface FcmService {
@Slf4j
@RequiredArgsConstructor
public class FcmService {

int sendMessageTo(FcmSendDTO fcmSendDTO) throws IOException;
private final DeviceTokenRepository deviceTokenRepository;
private final MemberRepository memberRepository;

/**
* ํ‘ธ์‹œ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
*
* @param fcmSendDTO ๋ชจ๋ฐ”์ผ์—์„œ ์ „๋‹ฌ๋ฐ›์€ Object
* @return ์„ฑ๊ณต(1), ์‹คํŒจ(0)
*/
public int sendMessageTo(FcmSendDTO fcmSendDTO) throws IOException {

try {
String message = makeMessage(fcmSendDTO);
RestTemplate restTemplate = new RestTemplate();

restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + getAccessToken());

HttpEntity<String> entity = new HttpEntity<>(message, headers);

String API_URL = "https://fcm.googleapis.com/v1/projects/suppin-a5657/messages:send";
ResponseEntity<String> response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, String.class);

if (response.getStatusCode() == HttpStatus.OK) {
return 1;
} else {
log.error("FCM ๋ฉ”์‹œ์ง€ ์ „์†ก ์‹คํŒจ: {}", response.getStatusCode());
return 0;
}
} catch (Exception e) {
log.error("FCM ๋ฉ”์‹œ์ง€ ์ „์†ก ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ", e);
return 0;
}
}

/**
* Firebase Admin SDK์˜ ๋น„๊ณต๊ฐœ ํ‚ค๋ฅผ ์ฐธ์กฐํ•˜์—ฌ Bearer ํ† ํฐ์„ ๋ฐœ๊ธ‰ ๋ฐ›์Šต๋‹ˆ๋‹ค.
*
* @return Bearer token, String
*/
private String getAccessToken() throws IOException {
String firebaseConfigPath = "firebase/suppin-a5657-firebase-adminsdk.json";

GoogleCredentials googleCredentials = GoogleCredentials
.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())
.createScoped(List.of("<https://www.googleapis.com/auth/cloud-platform>"));

googleCredentials.refreshIfExpired();
return googleCredentials.getAccessToken().getTokenValue();
}

/**
* FCM ์ „์†ก ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. (Object -> String)
*
* @param fcmSendDTO, ๋ชจ๋ฐ”์ผ์—์„œ ์ „๋‹ฌ๋ฐ›์€ Object
* @return String
*/
private String makeMessage(FcmSendDTO fcmSendDTO) throws JsonProcessingException {

ObjectMapper om = new ObjectMapper();
FcmMessageDTO fcmMessageDto = FcmMessageDTO.builder()
.message(FcmMessageDTO.Message.builder()
.token(fcmSendDTO.getToken())
.notification(FcmMessageDTO.Notification.builder()
.title(fcmSendDTO.getTitle())
.body(fcmSendDTO.getBody())
.image(null)
.build()
).build()).validateOnly(false).build();

return om.writeValueAsString(fcmMessageDto);
}

/**
* ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ Device Token์„ ์ˆ˜์‹ ํ•˜์—ฌ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
*/
@Transactional
public void registerDeviceToken(String userId, String token, DeviceType deviceType) {
Member member = memberRepository.findByUserIdAndStatusNot(userId, UserStatus.DELETED)
.orElseThrow(() -> new IllegalArgumentException("Member not found"));

deviceTokenRepository.findByDeviceToken(token)
.orElseGet(() -> {
DeviceToken deviceToken = DeviceToken.builder()
.member(member)
.deviceToken(token)
.deviceType(deviceType)
.createdAt(LocalDateTime.now())
.build();
return deviceTokenRepository.save(deviceToken);
});
}
}
96 changes: 0 additions & 96 deletions src/main/java/com/cmc/suppin/fcm/service/FcmServiceImpl.java

This file was deleted.

Loading

0 comments on commit 16cc676

Please sign in to comment.