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 push message API #26

Merged
merged 10 commits into from
Jun 6, 2024
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,8 @@ application.yml
### yml file ###
src/main/**/application-local.yml

### Firebase Admin SDK ###
src/main/resources/firebase

### data.sql ###
src/main/**/data.sql
src/main/**/data.sql
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.google.firebase:firebase-admin:9.2.0' // Google Firebase Admin
implementation 'com.fasterxml.jackson.core:jackson-core:2.16.1' // Jackson Data Bind
implementation 'org.springframework.boot:spring-boot-starter-json' //webPush
implementation 'org.springframework.boot:spring-boot-starter-websocket' //webrtc
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
compileOnly 'org.projectlombok:lombok'
Expand Down
46 changes: 46 additions & 0 deletions src/main/java/vom/spring/domain/webpush/ApiResponseWrapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package vom.spring.domain.webpush;

import lombok.Getter;

@Getter
public class ApiResponseWrapper<T> {
private int resultCode;
private String resultMsg;
private T result;

// Builder 클래스를 사용하여 객체를 더 쉽게 생성할 수 있도록 합니다
public static <T> Builder<T> builder() {
return new Builder<>();
}

public static class Builder<T> {
private int resultCode;
private String resultMsg;
private T result;

public Builder<T> resultCode(int resultCode) {
this.resultCode = resultCode;
return this;
}

public Builder<T> resultMsg(String resultMsg) {
this.resultMsg = resultMsg;
return this;
}

public Builder<T> result(T result) {
this.result = result;
return this;
}

public ApiResponseWrapper<T> build() {
ApiResponseWrapper<T> response = new ApiResponseWrapper<>();
response.resultCode = this.resultCode;
response.resultMsg = this.resultMsg;
response.result = this.result;
return response;
}
}

// Getter 및 Setter 메소드는 필요에 따라 추가합니다.
}
37 changes: 37 additions & 0 deletions src/main/java/vom/spring/domain/webpush/FcmController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package vom.spring.domain.webpush;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
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;

import java.io.IOException;

@Slf4j
@RestController
@RequestMapping("/api/v1/fcm")
public class FcmController {
private final FcmService fcmService;

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

@PostMapping("/send")
public ResponseEntity<ApiResponseWrapper<Object>> pushMessage(@RequestBody @Validated FcmSendDto fcmSendDto) throws IOException, IOException {
log.debug("[+] 푸시 메시지를 전송합니다. ");
int result = fcmService.sendMessageTo(fcmSendDto);

ApiResponseWrapper<Object> arw = ApiResponseWrapper
.builder()
.result(result)
.resultCode(SuccessCode.SELECT_SUCCESS.getStatus())
.resultMsg(SuccessCode.SELECT_SUCCESS.getMessage())
.build();
return new ResponseEntity<>(arw, HttpStatus.OK);
}
}
29 changes: 29 additions & 0 deletions src/main/java/vom/spring/domain/webpush/FcmMessageDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package vom.spring.domain.webpush;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class FcmMessageDto {
private boolean validateOnly;
private FcmMessageDto.Message message;

@Builder
@AllArgsConstructor
@Getter
public static class Message {
private FcmMessageDto.Notification notification;
private String token;
}

@Builder
@AllArgsConstructor
@Getter
public static class Notification {
private String title;
private String body;
private String image;
}
}
21 changes: 21 additions & 0 deletions src/main/java/vom/spring/domain/webpush/FcmSendDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package vom.spring.domain.webpush;

import lombok.*;

@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FcmSendDto {
private String token;

private String title;

private String body;

@Builder(toBuilder = true)
public FcmSendDto(String token, String title, String body) {
this.token = token;
this.title = title;
this.body = body;
}
}
12 changes: 12 additions & 0 deletions src/main/java/vom/spring/domain/webpush/FcmService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package vom.spring.domain.webpush;

import org.springframework.stereotype.Service;

import java.io.IOException;

@Service
public class FcmService {
int sendMessageTo(FcmSendDto fcmSendDto) throws IOException {
return 0;
}
}
82 changes: 82 additions & 0 deletions src/main/java/vom/spring/domain/webpush/FcmServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package vom.spring.domain.webpush;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auth.oauth2.GoogleCredentials;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.List;

public class FcmServiceImpl extends FcmService {
/**
* 푸시 메시지 처리를 수행하는 비즈니스 로직
*
* @param fcmSendDto 모바일에서 전달받은 Object
* @return 성공(1), 실패(0)
*/
@Override
public int sendMessageTo(FcmSendDto fcmSendDto) throws IOException {

String message = makeMessage(fcmSendDto);
RestTemplate restTemplate = new RestTemplate();

HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
headers.set("Authorization", "Bearer " + getAccessToken());

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

String API_URL = "https://fcm.googleapis.com/v1/projects/vomvom-fd09b/messages:send";
// String API_URL = "https://fcm.googleapis.com/fcm/send";

ResponseEntity response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, String.class);

return response.getStatusCode() == HttpStatus.OK ? 1 : 0;
}

/**
* Firebase Admin SDK의 비공개 키를 참조하여 Bearer 토큰을 발급 받습니다.
*
* playground에서 발급받은 토큰
*
* @return Bearer token
*/
private String getAccessToken() throws IOException {
String firebaseConfigPath = "firebase/vomvom-fd09b-firebase-adminsdk-ghtjs-0070b39a4e.json";

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

googleCredentials.refreshIfExpired();

System.out.println(googleCredentials.getAccessToken());

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

/**
* FCM 전송 정보를 기반으로 메시지를 구성합니다. (Object -> String)
*
* @param fcmSendDto FcmSendDto
* @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);
}
}
23 changes: 23 additions & 0 deletions src/main/java/vom/spring/domain/webpush/SuccessCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package vom.spring.domain.webpush;

public enum SuccessCode {
SELECT_SUCCESS(200, "데이터 조회 성공"),
UPDATE_SUCCESS(200, "데이터 업데이트 성공"),
CREATE_SUCCESS(201, "데이터 생성 성공");

private final int status;
private final String message;

SuccessCode(int status, String message) {
this.status = status;
this.message = message;
}

public int getStatus() {
return status;
}

public String getMessage() {
return message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package vom.spring.domain.webpush.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import vom.spring.domain.webpush.dto.WebpushDto;
import vom.spring.domain.webpush.service.WebpushService;

import java.util.List;

@RestController
@Controller
public class WebpushController {
private WebpushService webpushService;
@Autowired
public WebpushController(WebpushService webpushService) {
this.webpushService = webpushService;
}

@GetMapping(value = "/api/webpush/{member-id}")
public ResponseEntity<List<WebpushDto>> getWebpushes(
@PathVariable("member-id") Long memberId
) {
return new ResponseEntity<>(webpushService.getWebpushes(memberId), HttpStatus.OK);
}

// @PostMapping("/api/webpush/send")
// public String sendNotification(
// @RequestBody NotificationRequest request)
// {
// webpushService.sendNotification(request.getTargetToken(), request.getTitle(), request.getBody());
// return "Notification sent successfully!";
// }
//
// @Getter
// public static class NotificationRequest {
// private String targetToken;
// private String title;
// private String body;
//
// // Getters and Setters
// }

}
38 changes: 38 additions & 0 deletions src/main/java/vom/spring/domain/webpush/domain/Webpush.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package vom.spring.domain.webpush.domain;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import vom.spring.domain.member.domain.Member;
import vom.spring.domain.webcam.domain.Status;
import vom.spring.domain.webcam.domain.Webcam;

import java.time.LocalDateTime;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Webpush {

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

@Column(nullable = false)
private LocalDateTime createdAt;

@ManyToOne
@JoinColumn(referencedColumnName = "id", name = "from_member_id", nullable = false)
private Member fromMember;

@ManyToOne
@JoinColumn(referencedColumnName = "id", name = "to_member_id", nullable = false)
private Member toMember;

// @OneToOne
// @JoinColumn(referencedColumnName = "id", name = "webcam_id", nullable = false)
// private Webcam webcam;
}
18 changes: 18 additions & 0 deletions src/main/java/vom/spring/domain/webpush/dto/WebpushDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package vom.spring.domain.webpush.dto;

import lombok.Getter;

import java.time.LocalDateTime;

@Getter
public class WebpushDto {
private Long fromMemberId;
private LocalDateTime createdAt;
// private Long webcamId;

public WebpushDto(Long fromMemberId, LocalDateTime createdAt) {
this.fromMemberId = fromMemberId;
this.createdAt = createdAt;
// this.webcamId = webcamId;
}
}
Loading
Loading