Skip to content

Commit

Permalink
Merge pull request #26 from VOM-Project/feature/webpush
Browse files Browse the repository at this point in the history
feat: FCM push message API
  • Loading branch information
okodeee authored Jun 6, 2024
2 parents 93f7b3f + 1e7b399 commit 0758c76
Show file tree
Hide file tree
Showing 14 changed files with 457 additions and 1 deletion.
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

0 comments on commit 0758c76

Please sign in to comment.