diff --git a/src/main/java/com/hyundai/app/config/SecurityConfig.java b/src/main/java/com/hyundai/app/config/SecurityConfig.java index 166241d..77661a8 100644 --- a/src/main/java/com/hyundai/app/config/SecurityConfig.java +++ b/src/main/java/com/hyundai/app/config/SecurityConfig.java @@ -38,7 +38,7 @@ public void configure(WebSecurity web) { web.ignoring().antMatchers( "/", "/resources/**", "/v2/api-docs", "/swagger-resources/**", "/swagger-ui/index.html", "/swagger-ui.html","/webjars/**", "/swagger/**", // swagger - "/api/v1/auth/**", "/api/v1/admin/**", "/api/v1/fcm-push/**", "/api/v1/heendy-guide/**", "/websocket/**"); + "/api/v1/auth/**", "/api/v1/admin/**", "/api/v1/fcm-push/random-spot/**", "/api/v1/heendy-guide/**", "/websocket/**"); } @Override @@ -62,7 +62,8 @@ public void configure(HttpSecurity httpSecurity) throws Exception { .authorizeRequests() .antMatchers("/api/v1/auth/**").permitAll() .antMatchers("/api/v1/admin/**").permitAll() - .antMatchers("/api/v1/fcm/**").permitAll() + .antMatchers("/api/v1/fcm-push/**").permitAll() + .antMatchers("/api/v1/fcm-push/**").permitAll() .antMatchers("/api/v1/stores/**").authenticated() .antMatchers("/api/v1/members/**").authenticated() .anyRequest().permitAll() diff --git a/src/main/java/com/hyundai/app/fcm/FcmController.java b/src/main/java/com/hyundai/app/fcm/FcmController.java index 5182622..7609d82 100644 --- a/src/main/java/com/hyundai/app/fcm/FcmController.java +++ b/src/main/java/com/hyundai/app/fcm/FcmController.java @@ -1,6 +1,7 @@ package com.hyundai.app.fcm; import com.hyundai.app.fcm.dto.PushReqDto; +import com.hyundai.app.security.methodparam.MemberId; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; @@ -8,6 +9,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import springfox.documentation.annotations.ApiIgnore; import java.time.LocalDateTime; import java.util.concurrent.ExecutionException; @@ -18,7 +20,7 @@ * FCM 테스트용 컨트롤러 */ @Log4j -@Api("FCM 테스트용 API") +@Api("FCM 푸시 알림 API") @RestController @RequestMapping("/api/v1/fcm-push") @RequiredArgsConstructor @@ -26,18 +28,6 @@ public class FcmController { private final FcmPushService fcmPushService; - /** - * @author 황수영 - * @since 2024/02/20 - * FCM 푸시 알림 테스트용 - 바로 FCM 알림 발송 - */ - @ApiOperation("FCM 테스트용 API") - @PostMapping("/test/{deviceToken}") - public ResponseEntity testPush(@PathVariable String deviceToken) throws ExecutionException, InterruptedException { - fcmPushService.testPush(deviceToken); - return new ResponseEntity<>(HttpStatus.OK); - } - /** * @author 황수영 * @since 2024/02/21 @@ -51,4 +41,34 @@ public ResponseEntity pushRandomSpot(@RequestBody PushReqDto pushReqDto) { fcmPushService.createRandomSpotPushSchedule(pushReqDto); return new ResponseEntity<>(HttpStatus.OK); } + + /** + * @author 황수영 + * @since 2024/02/20 + * FCM 푸시 알림 테스트용 - 디바이스 토큰만 + */ + @ApiOperation("FCM 테스트용 API : 디바이스 토큰으로 푸시알림 발송") + @PostMapping("/random-spot/device-token") + public ResponseEntity pushByDeviceToken( + @RequestBody PushReqDto pushReqDto) throws ExecutionException, InterruptedException + { + log.debug("랜덤 스팟 FCM 푸시 알림 바로 전송 => device token : " + pushReqDto.getDeviceToken()); + fcmPushService.testPush(pushReqDto.getDeviceToken()); + return new ResponseEntity<>(HttpStatus.OK); + } + + /** + * @author 황수영 + * @since 2024/02/20 + * FCM 푸시 알림 시연용 - 로그인한 유저에게 바로 FCM 알림 발송 + */ + @ApiOperation("FCM 시연용 API : 호출 시, 로그인한 유저에게 푸시알림 발송") + @GetMapping + public ResponseEntity pushByMemberId( + @ApiIgnore @MemberId String memberId + ) throws ExecutionException, InterruptedException { + log.debug("랜덤 스팟 FCM 푸시 알림 바로 전송 => memberId : " + memberId); + fcmPushService.createRandomSpotPushByMemberId(memberId); + return new ResponseEntity<>(HttpStatus.OK); + } } \ No newline at end of file diff --git a/src/main/java/com/hyundai/app/fcm/FcmPushService.java b/src/main/java/com/hyundai/app/fcm/FcmPushService.java index 299332e..46e63a2 100644 --- a/src/main/java/com/hyundai/app/fcm/FcmPushService.java +++ b/src/main/java/com/hyundai/app/fcm/FcmPushService.java @@ -2,7 +2,12 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.messaging.FirebaseMessaging; +import com.hyundai.app.exception.AdventureOfHeendyException; +import com.hyundai.app.exception.ErrorCode; +import com.hyundai.app.fcm.dto.PushMessageDto; import com.hyundai.app.fcm.dto.PushReqDto; +import com.hyundai.app.member.domain.Member; +import com.hyundai.app.member.mapper.MemberMapper; import com.hyundai.app.scheduler.PushScheduler; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j; @@ -26,6 +31,12 @@ public class FcmPushService { private FirebaseApp firebaseApp; @Autowired private PushScheduler pushScheduler; + @Autowired + private MemberMapper memberMapper; + + @Autowired + private PushAlarmMapper pushAlarmMapper; + /** * @author 황수영 @@ -34,7 +45,7 @@ public class FcmPushService { */ public void testPush(String deviceToken) throws ExecutionException, InterruptedException { log.debug("알림 테스트 시작"); - Notification notification = PushType.createNotification(PushType.WELCOME); + Notification notification = PushType.createNotification(createPushMessage(PushType.RANDOM_SPOT.getId())); Message message = PushType.createMessage(notification, deviceToken); FirebaseMessaging.getInstance(firebaseApp).sendAsync(message).get(); } @@ -48,4 +59,32 @@ public void createRandomSpotPushSchedule(PushReqDto pushReqDto) { log.debug("createRandomSpotPushSchedule => 랜덤 스팟 푸시 알림"); pushScheduler.schedulePushForRandomSpot(pushReqDto); } + + /** + * @author 황수영 + * @since 2024/02/20 + * 랜덤 스팟 FCM 푸시 알림 전송 + */ + public void createRandomSpotPushByMemberId(String memberId) throws ExecutionException, InterruptedException{ + log.debug("createRandomSpotPushSchedule => 랜덤 스팟 푸시 알림"); + Notification notification = PushType.createNotification(createPushMessage(PushType.RANDOM_SPOT.getId())); + + Member member = memberMapper.findById(memberId); + if (member == null) { + log.debug("존재하는 회원 없습니다. memberId : " + memberId); + throw new AdventureOfHeendyException(ErrorCode.MEMBER_NOT_EXIST); + } + Message message = PushType.createMessage(notification, member.getDeviceToken()); + FirebaseMessaging.getInstance(firebaseApp).sendAsync(message).get(); + } + + /** + * @author 황수영 + * @since 2024/03/01 + * 랜덤 스팟 FCM 푸시 알림 메시지 생성용 + */ + public PushMessageDto createPushMessage(int id){ + PushAlarm pushAlarm = pushAlarmMapper.getPushAlarmById(id); + return PushMessageDto.of(pushAlarm.getTitle(), pushAlarm.getContent(), pushAlarm.getImage()); + } } \ No newline at end of file diff --git a/src/main/java/com/hyundai/app/fcm/PushAlarm.java b/src/main/java/com/hyundai/app/fcm/PushAlarm.java new file mode 100644 index 0000000..5963159 --- /dev/null +++ b/src/main/java/com/hyundai/app/fcm/PushAlarm.java @@ -0,0 +1,19 @@ +package com.hyundai.app.fcm; + +import lombok.*; + +/** + * @author 황수영 + * @since 2024/03/02 + * 푸시 알림 도메인 (이벤트별로 메시지 설정용) + */ +@Getter +@Builder +@ToString +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class PushAlarm { + private String title; + private String content; + private String image; +} diff --git a/src/main/java/com/hyundai/app/fcm/PushAlarmMapper.java b/src/main/java/com/hyundai/app/fcm/PushAlarmMapper.java new file mode 100644 index 0000000..2429039 --- /dev/null +++ b/src/main/java/com/hyundai/app/fcm/PushAlarmMapper.java @@ -0,0 +1,10 @@ +package com.hyundai.app.fcm; + +/** + * @author 황수영 + * @since 2024/03/02 + * 푸시 알림 매퍼 + */ +public interface PushAlarmMapper { + PushAlarm getPushAlarmById(int id); +} \ No newline at end of file diff --git a/src/main/java/com/hyundai/app/fcm/PushType.java b/src/main/java/com/hyundai/app/fcm/PushType.java index 5e19d90..9cdda4c 100644 --- a/src/main/java/com/hyundai/app/fcm/PushType.java +++ b/src/main/java/com/hyundai/app/fcm/PushType.java @@ -14,13 +14,15 @@ @Getter @AllArgsConstructor public enum PushType { - RANDOM_SPOT("흰디의 모험에 온 걸 환영해🎉", - "나는 대장 흰디야! 반가워", - "https://avatars.githubusercontent.com/u/158237286?s=400&u=db03152b8b64ca04183e918814f02316a5e8c4d9&v=4"), - WELCOME("'흰디의 모험' 랜덤 스팟이 열렸어🎁", + WELCOME(1, "'흰디의 모험' 랜덤 스팟이 열렸어🎁", "랜덤 스팟에서의 이벤트를 확인해봐", + "https://avatars.githubusercontent.com/u/158237286?s=400&u=db03152b8b64ca04183e918814f02316a5e8c4d9&v=4"), + RANDOM_SPOT(2, "흰디의 모험에 온 걸 환영해🎉", + "나는 대장 흰디야! 반가워", "https://avatars.githubusercontent.com/u/158237286?s=400&u=db03152b8b64ca04183e918814f02316a5e8c4d9&v=4"); + + private final int id; private final String title; private final String content; private final String image; @@ -30,8 +32,7 @@ public enum PushType { * @since 2024/02/20 * Notification 생성 */ - public static Notification createNotification(PushType pushType) { - PushMessageDto pushMessageDto = PushMessageDto.of(pushType); + public static Notification createNotification(PushMessageDto pushMessageDto) { return Notification.builder() .setTitle(pushMessageDto.getTitle()) diff --git a/src/main/java/com/hyundai/app/fcm/dto/PushMessageDto.java b/src/main/java/com/hyundai/app/fcm/dto/PushMessageDto.java index 7480bfe..b6ecc11 100644 --- a/src/main/java/com/hyundai/app/fcm/dto/PushMessageDto.java +++ b/src/main/java/com/hyundai/app/fcm/dto/PushMessageDto.java @@ -1,6 +1,7 @@ package com.hyundai.app.fcm.dto; import com.hyundai.app.fcm.PushType; +import lombok.AllArgsConstructor; import lombok.Getter; /** @@ -10,16 +11,17 @@ */ @Getter +@AllArgsConstructor public class PushMessageDto { private String title; private String content; private String image; - public static PushMessageDto of(PushType pushType) { - PushMessageDto pushMessageDto = new PushMessageDto(); - pushMessageDto.content = pushType.getContent(); - pushMessageDto.title = pushType.getTitle(); - pushMessageDto.image = pushType.getImage(); - return pushMessageDto; + public static PushMessageDto from(PushType pushType) { + return new PushMessageDto(pushType.getTitle(), pushType.getContent(), pushType.getImage()); + } + + public static PushMessageDto of(String title, String content, String image) { + return new PushMessageDto(title, content, image); } } \ No newline at end of file diff --git a/src/main/java/com/hyundai/app/member/controller/MemberController.java b/src/main/java/com/hyundai/app/member/controller/MemberController.java index e00c7e0..325a3cc 100644 --- a/src/main/java/com/hyundai/app/member/controller/MemberController.java +++ b/src/main/java/com/hyundai/app/member/controller/MemberController.java @@ -1,12 +1,12 @@ package com.hyundai.app.member.controller; import com.hyundai.app.common.AdventureOfHeendyResponse; +import com.hyundai.app.member.dto.DeviceTokenDto; import com.hyundai.app.member.dto.MemberResDto; import com.hyundai.app.member.service.MemberService; import com.hyundai.app.security.methodparam.MemberId; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -22,7 +22,6 @@ */ @Log4j @Api("회원 관련 API") -@RequiredArgsConstructor @RestController @RequestMapping("/api/v1/members") public class MemberController { @@ -44,6 +43,22 @@ public ResponseEntity login(@ApiIgnore @MemberId String memberId) return new ResponseEntity<>(memberResDto, HttpStatus.ACCEPTED); } + /** + * @author 황수영 + * @since 2024/02/14 + * FCM 디바이스 토큰 업데이트 + */ + @PostMapping("/token") + @ApiOperation("FCM 디바이스 토큰 업데이트 API") + public ResponseEntity updateDeviceToken( + @ApiIgnore @MemberId String memberId, + @RequestBody DeviceTokenDto deviceTokenDto + ) { + log.debug("FCM 디바이스 토큰 업데이트 : " + memberId + " token : " + deviceTokenDto.getToken()); + memberService.updateDeviceToken(memberId, deviceTokenDto); + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + /** * @author 엄상은 * @since 2024/02/26 diff --git a/src/main/java/com/hyundai/app/member/domain/Member.java b/src/main/java/com/hyundai/app/member/domain/Member.java index cfc16ff..688c1cc 100644 --- a/src/main/java/com/hyundai/app/member/domain/Member.java +++ b/src/main/java/com/hyundai/app/member/domain/Member.java @@ -21,6 +21,7 @@ public class Member extends BaseEntity { private String nickname; private Role role; private String refreshToken; + private String deviceToken; private String oauthId; private String imgUrl; private String qrUrl; diff --git a/src/main/java/com/hyundai/app/member/dto/DeviceTokenDto.java b/src/main/java/com/hyundai/app/member/dto/DeviceTokenDto.java new file mode 100644 index 0000000..82ed405 --- /dev/null +++ b/src/main/java/com/hyundai/app/member/dto/DeviceTokenDto.java @@ -0,0 +1,14 @@ +package com.hyundai.app.member.dto; + +import lombok.Getter; + +/** + * @author 황수영 + * @since 2024/03/02 + * FCM 디바이스 토큰 값 업데이트 + */ +@Getter +public class DeviceTokenDto { + private String platform; // FCM + private String token; +} \ No newline at end of file diff --git a/src/main/java/com/hyundai/app/member/mapper/MemberMapper.java b/src/main/java/com/hyundai/app/member/mapper/MemberMapper.java index 4cd9f10..f62dc2b 100644 --- a/src/main/java/com/hyundai/app/member/mapper/MemberMapper.java +++ b/src/main/java/com/hyundai/app/member/mapper/MemberMapper.java @@ -1,6 +1,7 @@ package com.hyundai.app.member.mapper; import com.hyundai.app.member.domain.Member; +import org.apache.ibatis.annotations.Param; /** * @author 황수영 @@ -13,4 +14,5 @@ public interface MemberMapper { Member findById(String id); Member findByOauthId(String oauthId); void updateQrUrl(Member member); + void updateDeviceToken(@Param("id") String id, @Param("token")String token); } diff --git a/src/main/java/com/hyundai/app/member/service/MemberService.java b/src/main/java/com/hyundai/app/member/service/MemberService.java index fd7b4d7..325d24b 100644 --- a/src/main/java/com/hyundai/app/member/service/MemberService.java +++ b/src/main/java/com/hyundai/app/member/service/MemberService.java @@ -1,5 +1,6 @@ package com.hyundai.app.member.service; +import com.hyundai.app.member.dto.DeviceTokenDto; import com.hyundai.app.member.dto.LoginReqDto; import com.hyundai.app.member.dto.LoginResDto; import com.hyundai.app.member.dto.MemberResDto; @@ -13,6 +14,7 @@ public interface MemberService { MemberResDto getMemberInfo(String id); + void updateDeviceToken(String memberId, DeviceTokenDto deviceTokenDto); LoginResDto login(LoginReqDto loginReqDto); diff --git a/src/main/java/com/hyundai/app/member/service/MemberServiceImpl.java b/src/main/java/com/hyundai/app/member/service/MemberServiceImpl.java index d05128d..f1ec3b9 100644 --- a/src/main/java/com/hyundai/app/member/service/MemberServiceImpl.java +++ b/src/main/java/com/hyundai/app/member/service/MemberServiceImpl.java @@ -3,6 +3,7 @@ import com.hyundai.app.exception.AdventureOfHeendyException; import com.hyundai.app.exception.ErrorCode; import com.hyundai.app.member.domain.Member; +import com.hyundai.app.member.dto.DeviceTokenDto; import com.hyundai.app.member.dto.LoginReqDto; import com.hyundai.app.member.dto.LoginResDto; import com.hyundai.app.member.dto.MemberResDto; @@ -84,7 +85,7 @@ private LoginResDto getUpdatedToken(Member member) { /** * @author 황수영 - * @since 2024/02/13 + * @since 2024/02/13mvn clean package * oauth id값으로 회원가입 */ public LoginResDto joinByOauthId(String email, OauthType oauthType) { @@ -120,7 +121,23 @@ public MemberResDto getMemberInfo(String id) { } return MemberResDto.of(member); } - + + /** + * @author 황수영 + * @since 2024/02/13 + * FCM 디바이스 토큰 업데이트 + */ + @Override + public void updateDeviceToken(String memberId, DeviceTokenDto deviceTokenDto) { + log.debug("FCM 디바이스 토큰 업데이트 : " + memberId); + Member member = memberMapper.findById(memberId); + if (member == null) { + log.error("회원 id가 존재하지 않습니다. : " + memberId); + throw new AdventureOfHeendyException(ErrorCode.MEMBER_NOT_EXIST); + } + memberMapper.updateDeviceToken(memberId, deviceTokenDto.getToken()); + } + /** * @author 엄상은 * @since 2024/02/26 diff --git a/src/main/java/com/hyundai/app/scheduler/job/RandomSpotPushJob.java b/src/main/java/com/hyundai/app/scheduler/job/RandomSpotPushJob.java index cc3709a..f8fc0f1 100644 --- a/src/main/java/com/hyundai/app/scheduler/job/RandomSpotPushJob.java +++ b/src/main/java/com/hyundai/app/scheduler/job/RandomSpotPushJob.java @@ -7,6 +7,7 @@ import com.hyundai.app.exception.AdventureOfHeendyException; import com.hyundai.app.fcm.FcmPushService; import com.hyundai.app.fcm.PushType; +import com.hyundai.app.fcm.dto.PushMessageDto; import lombok.extern.log4j.Log4j; import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; @@ -46,7 +47,7 @@ protected void executeInternal(JobExecutionContext context) throws JobExecutionE private void createRandomSpotPushNotification(JobDataMap dataMap) { String deviceToken = dataMap.getString("deviceToken"); - Notification notification = PushType.createNotification(PushType.RANDOM_SPOT); + Notification notification = PushType.createNotification(PushMessageDto.from(PushType.RANDOM_SPOT)); Message message = PushType.createMessage(notification, deviceToken); try { FirebaseMessaging.getInstance(firebaseApp).sendAsync(message).get(); diff --git a/src/main/resources/mapper/MemberMapper.xml b/src/main/resources/mapper/MemberMapper.xml index eb5e1af..22a544a 100644 --- a/src/main/resources/mapper/MemberMapper.xml +++ b/src/main/resources/mapper/MemberMapper.xml @@ -32,6 +32,7 @@ , nickname AS nickname , role AS role , refresh_token AS refreshToken + , device_token AS deviceToken , oauth_id AS OAuthId , img_url AS imgUrl , qr_url AS qrUrl @@ -45,8 +46,18 @@ , nickname AS nickname , role AS role , refresh_token AS refreshToken - , oauth_id AS oauthId + , device_token AS deviceToken + , oauth_id AS OAuthId + , img_url AS imgUrl + , qr_url AS qrUrl FROM MEMBER WHERE oauth_id = #{oauthId} + + + UPDATE member + SET device_token = #{token} + WHERE id = #{id} + + diff --git a/src/main/resources/mapper/PushAlarmMapper.xml b/src/main/resources/mapper/PushAlarmMapper.xml new file mode 100644 index 0000000..5abc3d7 --- /dev/null +++ b/src/main/resources/mapper/PushAlarmMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file