From 610528998c3a4377d15d39b2dcdc6a3a8c93889a Mon Sep 17 00:00:00 2001 From: nahowo Date: Thu, 1 May 2025 12:13:48 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20Discord=20service/controller=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/controller/DiscordController.java | 32 +--- .../backend/controller/SlackController.java | 3 +- .../backend/controller/SnsController.java | 4 +- .../backend/service/DiscordService.java | 173 +++++++++++------- .../Alchive/backend/service/SnsService.java | 5 +- 5 files changed, 114 insertions(+), 103 deletions(-) diff --git a/src/main/java/com/Alchive/backend/controller/DiscordController.java b/src/main/java/com/Alchive/backend/controller/DiscordController.java index 2e849ec..bd7bf5f 100644 --- a/src/main/java/com/Alchive/backend/controller/DiscordController.java +++ b/src/main/java/com/Alchive/backend/controller/DiscordController.java @@ -29,40 +29,16 @@ public class DiscordController { @Operation(summary = "디스코드 봇 연결", description = "디스코드 액세스 토큰을 요청하고 DM 채널을 연결하는 api입니다. ") @GetMapping("/dm/open") - public ResponseEntity openDiscordDm(@AuthenticationPrincipal User user, @RequestParam String code) { - // Access Token 요청 - String accessToken = discordService.getAccessToken(code); - log.info("Access Token 반환 완료: " + accessToken); - - // 사용자 DISCORD USER ID 요청 - String discordUserId = discordService.getDiscordUserIdFromAccessToken(accessToken); - log.info("Discord User Id 반환 완료: " + discordUserId); - - // DM 채널 생성 요청 - String channelId = discordService.getDmChannel(discordUserId); - log.info("DM 채널 생성 완료"); - - // Discord SNS 정보 저장 - SnsCreateRequest snsCreateRequest = SnsCreateRequest.builder() - .category(SnsCategory.DISCORD) - .sns_id(discordUserId) // Discord User Id - .channel_id(channelId) // Discord Channel Id - .time("0 0 18 ? * MON") - .build(); - snsService.createSns(user, snsCreateRequest); - log.info("SNS 정보 저장 완료"); - - // DM 전송 요청 - discordService.sendDm(channelId, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); - + public ResponseEntity initializeDiscordChannelAndSendWelcomeDM(@AuthenticationPrincipal User user, @RequestParam String code) { + discordService.initializeDiscordChannleAndSaveSnsInfo(user, code); + discordService.sendDm(user, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); return ResponseEntity.ok(ResultResponse.of(DISCORD_DM_SEND_SUCCESS)); } @Operation(summary = "디스코드 DM 전송", description = "디스코드 DM으로 메시지를 전송하는 api입니다. ") @PostMapping("dm/send") public ResponseEntity sendDiscordDm(@AuthenticationPrincipal User user, @RequestParam String message) { - Sns discordInfo = discordService.getDiscordInfo(user); - discordService.sendDmJda(discordInfo.getSns_id(), message); + discordService.sendDmJda(user, message); return ResponseEntity.ok(ResultResponse.of(DISCORD_DM_SEND_SUCCESS)); } } diff --git a/src/main/java/com/Alchive/backend/controller/SlackController.java b/src/main/java/com/Alchive/backend/controller/SlackController.java index d3d9083..25e202b 100644 --- a/src/main/java/com/Alchive/backend/controller/SlackController.java +++ b/src/main/java/com/Alchive/backend/controller/SlackController.java @@ -33,7 +33,8 @@ public ResponseEntity openSlackDm(@AuthenticationPrincipal User log.info("사용자 slack 정보를 불러왔습니다. "); // Slack SNS 정보 저장 - snsService.createSns(user, snsCreateRequest); + Sns slackSns = Sns.of(user, snsCreateRequest); + snsService.createSns(slackSns); log.info("사용자 slack 정보를 저장했습니다. "); // DM 전송 요청 diff --git a/src/main/java/com/Alchive/backend/controller/SnsController.java b/src/main/java/com/Alchive/backend/controller/SnsController.java index c11ed3b..0a9c41d 100644 --- a/src/main/java/com/Alchive/backend/controller/SnsController.java +++ b/src/main/java/com/Alchive/backend/controller/SnsController.java @@ -1,6 +1,7 @@ package com.Alchive.backend.controller; import com.Alchive.backend.config.result.ResultResponse; +import com.Alchive.backend.domain.sns.Sns; import com.Alchive.backend.domain.user.User; import com.Alchive.backend.dto.request.SnsCreateRequest; import com.Alchive.backend.dto.response.SnsResponseDTO; @@ -32,7 +33,8 @@ public ResponseEntity getSns(@PathVariable Long snsId) { @Operation(summary = "소셜 정보 생성", description = "소셜 정보를 생성하는 메서드입니다. ") @PostMapping("") public ResponseEntity createSns(@AuthenticationPrincipal User user, SnsCreateRequest request) { - snsService.createSns(user, request); + Sns sns = Sns.of(user, request); + snsService.createSns(sns); return ResponseEntity.ok(ResultResponse.of(SNS_CREATE_SUCCESS)); } } diff --git a/src/main/java/com/Alchive/backend/service/DiscordService.java b/src/main/java/com/Alchive/backend/service/DiscordService.java index a3a0ce9..af11466 100644 --- a/src/main/java/com/Alchive/backend/service/DiscordService.java +++ b/src/main/java/com/Alchive/backend/service/DiscordService.java @@ -6,6 +6,7 @@ import com.Alchive.backend.domain.board.Board; import com.Alchive.backend.domain.sns.Sns; import com.Alchive.backend.domain.sns.SnsCategory; +import com.Alchive.backend.dto.request.SnsCreateRequest; import com.Alchive.backend.repository.BoardRepository; import com.Alchive.backend.repository.SnsReporitory; import lombok.RequiredArgsConstructor; @@ -29,11 +30,14 @@ @RequiredArgsConstructor @EnableScheduling @Slf4j -public class DiscordService { +public class DiscordService{ + private final static String DISCORD_USER_INFO_REQUEST_URL = "https://discord.com/api/v10/users/@me"; + private final static String DISCORD_ACCESS_TOKEN_REQUEST_URL = "https://discord.com/api/oauth2/token"; + private final static String DISCORD_DM_CHANNEL_REQUEST_URL = "https://discord.com/api/v10/users/@me/channels"; private final JDA jda; private final BoardRepository boardRepository; private final SnsReporitory snsReporitory; - + private final SnsService snsService; @Value("${DISCORD_CLIENT_ID}") private String clientId; @@ -47,94 +51,83 @@ public class DiscordService { private String discordBotToken; private RestTemplate restTemplate = new RestTemplate(); - public String getAccessToken(String code) { - String getTokenUrl = "https://discord.com/api/oauth2/token"; + public void initializeDiscordChannleAndSaveSnsInfo(com.Alchive.backend.domain.user.User user, String code) { + String accessToken = getAccessToken(code); + String discordUserId = getDiscordUserIdFromAccessToken(accessToken); + String channelId = getDmChannel(discordUserId); + + SnsCreateRequest snsCreateRequest = SnsCreateRequest.builder() + .category(SnsCategory.DISCORD) + .sns_id(discordUserId) // Discord User Id + .channel_id(channelId) // Discord Channel Id + .time("0 0 18 ? * MON") + .build(); + Sns discordInfo = Sns.of(user, snsCreateRequest); + saveDiscordInfo(discordInfo); + } - HttpHeaders getTokenHeaders = new HttpHeaders(); - getTokenHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + private String getAccessToken(String code) { + HttpHeaders httpHeader = new HttpHeaders(); + httpHeader.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap httpBody = new LinkedMultiValueMap<>(); + httpBody.add("client_id", clientId); + httpBody.add("client_secret", clientSecret); + httpBody.add("grant_type", "authorization_code"); + httpBody.add("redirect_uri", redirectUri); + httpBody.add("code", code); + + HttpEntity> request = new HttpEntity<>(httpBody, httpHeader); + ResponseEntity response = sendRestTemplateExchange(DISCORD_ACCESS_TOKEN_REQUEST_URL, HttpMethod.POST, request); + String accessToken = parseResponse(response, "access_token"); + return accessToken; + } - MultiValueMap getTokenParams = new LinkedMultiValueMap<>(); - getTokenParams.add("client_id", clientId); - getTokenParams.add("client_secret", clientSecret); - getTokenParams.add("code", code); - getTokenParams.add("grant_type", "authorization_code"); - getTokenParams.add("redirect_uri", redirectUri); + private ResponseEntity sendRestTemplateExchange(String requestUrl, HttpMethod method, HttpEntity request) { + ResponseEntity response = restTemplate.exchange(requestUrl, method, request, Map.class); + return response; + } - HttpEntity> request = new HttpEntity<>(getTokenParams, getTokenHeaders); - ResponseEntity response = restTemplate.postForEntity(getTokenUrl, request, Map.class); + private String parseResponse(ResponseEntity response, String targetParam) { Map responseBody = response.getBody(); + checkInvalidGrant(responseBody); + String targetResult = (String) responseBody.get(targetParam); + return targetResult; + } + private void checkInvalidGrant(Map responseBody) { if (responseBody.containsKey("error") && responseBody.get("error") == "invalid_grant" ) { throw new InvalidGrantException(); } - String accessToken = (String) responseBody.get("access_token"); - return accessToken; } - public String getDiscordUserIdFromAccessToken(String accessToken) { - String getUserInfoUrl = "https://discord.com/api/v10/users/@me"; + private String getDiscordUserIdFromAccessToken(String accessToken) { HttpHeaders accessTokenHeaders = new HttpHeaders(); accessTokenHeaders.setBearerAuth(accessToken); - - HttpEntity authRequest = new HttpEntity<>(accessTokenHeaders); - ResponseEntity userInfoResponse = restTemplate.exchange(getUserInfoUrl, HttpMethod.GET, authRequest, Map.class); - Map userInfo = userInfoResponse.getBody(); - - String discordUserId = (String) userInfo.get("id"); + HttpEntity request = new HttpEntity<>(accessTokenHeaders); + ResponseEntity response = sendRestTemplateExchange(DISCORD_USER_INFO_REQUEST_URL, HttpMethod.GET, request); + String discordUserId = parseResponse(response, "id"); return discordUserId; } - public Sns getDiscordInfo(com.Alchive.backend.domain.user.User user) { - Long userId = user.getId(); - Sns discordInfo = snsReporitory.findByUser_IdAndCategory(userId, SnsCategory.DISCORD) - .orElseThrow(NoSuchSnsIdException::new); - return discordInfo; - } - - public void sendDm(String channelId, String message) { - String sendMessageUrl = "https://discord.com/api/v10/channels/" + channelId + "/messages"; - HttpHeaders sendDmHeaders = new HttpHeaders(); - sendDmHeaders.set("Authorization", "Bot " + discordBotToken); - sendDmHeaders.setContentType(MediaType.APPLICATION_JSON); - - Map sendDmParams = new HashMap<>(); - sendDmParams.put("content", message); - - HttpEntity> sendMessageRequest = new HttpEntity<>(sendDmParams, sendDmHeaders); - restTemplate.postForEntity(sendMessageUrl, sendMessageRequest, Map.class); - } - - public String getDmChannel(String discordUserId) { - String getDMChannelUrl = "https://discord.com/api/v10/users/@me/channels"; - Map getDmParams = new HashMap<>(); - getDmParams.put("recipient_id", discordUserId); + private String getDmChannel(String discordUserId) { + Map httpBody = new HashMap<>(); + httpBody.put("recipient_id", discordUserId); + HttpHeaders httpHeader = new HttpHeaders(); + httpHeader.setContentType(MediaType.APPLICATION_JSON); + httpHeader.set("Authorization", "Bot " + discordBotToken); + HttpEntity> request = new HttpEntity<>(httpBody, httpHeader); - HttpHeaders botTokenHeader = new HttpHeaders(); - botTokenHeader.setContentType(MediaType.APPLICATION_JSON); - botTokenHeader.set("Authorization", "Bot " + discordBotToken); - - HttpEntity> createDmRequest = new HttpEntity<>(getDmParams, botTokenHeader); - ResponseEntity dmResponse = restTemplate.postForEntity(getDMChannelUrl, createDmRequest, Map.class); - Map dmResponseBody = dmResponse.getBody(); - - String channelId = (String) dmResponseBody.get("id"); + ResponseEntity response = sendRestTemplateExchange(DISCORD_DM_CHANNEL_REQUEST_URL, HttpMethod.POST, request); + String channelId = parseResponse(response, "id"); return channelId; } - // JDA 사용 메서드 - public void sendDmJda(String discordUserId, String message) { - User user = jda.retrieveUserById(discordUserId).complete(); - if (user != null) { - user.openPrivateChannel().queue(channel -> - channel.sendMessage(message).queue() - ); - } else { - throw new NoSuchDiscordUserException(); - } + private void saveDiscordInfo(Sns discordInfo) { + snsService.createSns(discordInfo); } -// @Scheduled(cron = "0 */1 * * * *") // todo: Quartz로 동적 스케줄링 작성하기 - public void sendMessageReminderBoard(com.Alchive.backend.domain.user.User user) { + // @Scheduled(cron = "0 */1 * * * *") // todo: Quartz로 동적 스케줄링 작성하기 + private void sendMessageReminderBoard(com.Alchive.backend.domain.user.User user) { LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(1); Board unSolvedBoard = boardRepository.findUnsolvedBoardAddedBefore(threeDaysAgo, user.getId()); @@ -146,9 +139,49 @@ public void sendMessageReminderBoard(com.Alchive.backend.domain.user.User user) unSolvedBoard.getProblem().getUrl()); Sns discordInfo = getDiscordInfo(user); - sendDmJda(discordInfo.getSns_id(), message); + sendDmJda(user, message); } else { log.info("풀지 못한 문제가 존재하지 않습니다. "); } } + + public void sendDm(com.Alchive.backend.domain.user.User user, String message) { + String channelId = getUserChannelId(user); + String sendMessageUrl = "https://discord.com/api/v10/channels/" + channelId + "/messages"; + + HttpHeaders sendDmHeaders = new HttpHeaders(); + sendDmHeaders.set("Authorization", "Bot " + discordBotToken); + sendDmHeaders.setContentType(MediaType.APPLICATION_JSON); + Map sendDmParams = new HashMap<>(); + sendDmParams.put("content", message); + HttpEntity> request = new HttpEntity<>(sendDmParams, sendDmHeaders); + + sendRestTemplateExchange(sendMessageUrl, HttpMethod.POST, request); + } + + private String getUserChannelId(com.Alchive.backend.domain.user.User user) { + return getDiscordInfo(user).getChannel_id(); + } + + private Sns getDiscordInfo(com.Alchive.backend.domain.user.User user) { + Long userId = user.getId(); + Sns discordInfo = snsReporitory.findByUser_IdAndCategory(userId, SnsCategory.DISCORD) + .orElseThrow(NoSuchSnsIdException::new); + return discordInfo; + } + + // JDA 사용 메서드 + public void sendDmJda(com.Alchive.backend.domain.user.User user, String message) { + String discordUserId = getDiscordInfo(user).getSns_id(); + User jdaUser = jda.retrieveUserById(discordUserId).complete(); + checkUserNull(jdaUser); + jdaUser.openPrivateChannel().queue(channel -> + channel.sendMessage(message).queue()); + } + + private void checkUserNull(User user) { + if (user == null) { + throw new NoSuchDiscordUserException(); + } + } } diff --git a/src/main/java/com/Alchive/backend/service/SnsService.java b/src/main/java/com/Alchive/backend/service/SnsService.java index 8dce77d..c9d990e 100644 --- a/src/main/java/com/Alchive/backend/service/SnsService.java +++ b/src/main/java/com/Alchive/backend/service/SnsService.java @@ -14,7 +14,7 @@ @RequiredArgsConstructor @Service @Slf4j -public class SnsService { +public class SnsService{ private final SnsReporitory snsReporitory; public SnsResponseDTO getSns(Long snsId) { @@ -23,8 +23,7 @@ public SnsResponseDTO getSns(Long snsId) { } @Transactional - public void createSns(User user, SnsCreateRequest request) { - Sns sns = Sns.of(user, request); + public void createSns(Sns sns) { snsReporitory.save(sns); } } From f8ae8407c3cd0c34347438b42114fbd49c37de81 Mon Sep 17 00:00:00 2001 From: nahowo Date: Thu, 1 May 2025 14:58:55 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Slack=20controller/service=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 8196 -> 0 bytes .gitignore | 3 + .../backend/controller/BoardController.java | 1 - .../backend/controller/DiscordController.java | 8 +- .../backend/controller/SlackController.java | 23 +---- .../backend/service/DiscordService.java | 51 +++------- .../Alchive/backend/service/SlackService.java | 96 +++++++++--------- .../Alchive/backend/service/SnsService.java | 31 +++++- 8 files changed, 94 insertions(+), 119 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index eea3851c23924cf9eadef3f6ecd408209a777f21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMF=!M)6#bLC^$sH@*mzgDg_uH6QUt_mJuiaTh$(H%-i5n^J2!Y2QUqkNva|_y zAr=-E7M6Bes9$w-&j9obgsbEjT-r`Cp8LbzPe!(-~8So5v20R0vf&Ya8ytBF0OWylN z>qpOkXW&ROz|V&eon>TYYNUR2V9-YZ$SRg~!+f46kfD{4m8p@?pebVtX-t(pVkl#d z_0ZxXD^nwlIVpSiP}Z}uClsZ<m{RSJU+U8a#lkxUd_(U;xG`+0lM7Gx z-f#>BR31q=O2k7q^LXyjDs^c{Ya*Vd<6kmQ8N!XHS^fI*+6N~d2bITDjuP=K?Q%Q; z_!%IUHCh$1s88Rd){SH5_SxlaQya&Al;kl?M+pDHXX8IcT?XKyEBs%4a$ft-w66c? z8JRK++OdLjM3}Eq+|CY1Mj21`(+#E$FQ3Z!Y-6yMV9pZ z%>HY+c@m}DmrcEeKbPq;jL4NM8GX zsy&2vt8w%D-n_K8b4%ajnKrLsQ7dzbIHx%pWA9GzH=w(Sc>K32&eTM-V^pzI%!xLO zC{a1}x~X&0_HIs2Jt~L>S1+lS)ry<~y(c*@-#i0{fq{xiqQU3?@VY*ZLuUUl#5}aR$ja16Xwd%ohXC`N O&A initializeDiscordChannelAndSendWelcomeDM(@AuthenticationPrincipal User user, @RequestParam String code) { discordService.initializeDiscordChannleAndSaveSnsInfo(user, code); - discordService.sendDm(user, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); + discordService.sendDiscordDm(user, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); return ResponseEntity.ok(ResultResponse.of(DISCORD_DM_SEND_SUCCESS)); } @Operation(summary = "디스코드 DM 전송", description = "디스코드 DM으로 메시지를 전송하는 api입니다. ") @PostMapping("dm/send") public ResponseEntity sendDiscordDm(@AuthenticationPrincipal User user, @RequestParam String message) { - discordService.sendDmJda(user, message); + discordService.sendJdaDm(user, message); return ResponseEntity.ok(ResultResponse.of(DISCORD_DM_SEND_SUCCESS)); } } diff --git a/src/main/java/com/Alchive/backend/controller/SlackController.java b/src/main/java/com/Alchive/backend/controller/SlackController.java index 25e202b..3bf0b02 100644 --- a/src/main/java/com/Alchive/backend/controller/SlackController.java +++ b/src/main/java/com/Alchive/backend/controller/SlackController.java @@ -1,9 +1,7 @@ package com.Alchive.backend.controller; import com.Alchive.backend.config.result.ResultResponse; -import com.Alchive.backend.domain.sns.Sns; import com.Alchive.backend.domain.user.User; -import com.Alchive.backend.dto.request.SnsCreateRequest; import com.Alchive.backend.service.SnsService; import com.Alchive.backend.service.SlackService; import io.swagger.v3.oas.annotations.Operation; @@ -23,34 +21,19 @@ @RequiredArgsConstructor public class SlackController { private final SlackService slackService; - private final SnsService snsService; @Operation(summary = "슬랙 봇 연결", description = "슬랙 액세스 토큰을 요청하고 DM 채널을 연결하는 api입니다. ") @GetMapping("/dm/open") public ResponseEntity openSlackDm(@AuthenticationPrincipal User user, @RequestParam String code) { - // Bot Access Token, User Access Token, Slack User Id 요청 - SnsCreateRequest snsCreateRequest = slackService.getSlackInfo(code); - log.info("사용자 slack 정보를 불러왔습니다. "); - - // Slack SNS 정보 저장 - Sns slackSns = Sns.of(user, snsCreateRequest); - snsService.createSns(slackSns); - log.info("사용자 slack 정보를 저장했습니다. "); - - // DM 전송 요청 - String slackUserId = snsCreateRequest.getSns_id(); - String botAccessToken = snsCreateRequest.getBot_token(); - - slackService.sendDm(slackUserId, botAccessToken, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); - log.info("봇을 사용자 slack 채널에 추가했습니다. "); + slackService.initializeSlackChannelAndSaveSnsInfo(user, code); + slackService.sendSlackDm(user, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); return ResponseEntity.ok(ResultResponse.of(SLACK_DM_SEND_SUCCESS)); } @Operation(summary = "슬랙 DM 전송", description = "슬랙 DM으로 메시지를 전송하는 api입니다. ") @PostMapping("dm/send") public ResponseEntity sendSlackDm(@AuthenticationPrincipal User user, @RequestParam String message) { - Sns sns = slackService.getSlackInfo(user); - slackService.sendDm(sns.getSns_id(), sns.getBot_token(), message); + slackService.sendSlackDm(user, message); return ResponseEntity.ok(ResultResponse.of(SLACK_DM_SEND_SUCCESS)); } } \ No newline at end of file diff --git a/src/main/java/com/Alchive/backend/service/DiscordService.java b/src/main/java/com/Alchive/backend/service/DiscordService.java index af11466..9ff0dc5 100644 --- a/src/main/java/com/Alchive/backend/service/DiscordService.java +++ b/src/main/java/com/Alchive/backend/service/DiscordService.java @@ -1,6 +1,5 @@ package com.Alchive.backend.service; -import com.Alchive.backend.config.error.exception.sns.InvalidGrantException; import com.Alchive.backend.config.error.exception.sns.NoSuchDiscordUserException; import com.Alchive.backend.config.error.exception.sns.NoSuchSnsIdException; import com.Alchive.backend.domain.board.Board; @@ -19,7 +18,6 @@ import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; @@ -30,7 +28,7 @@ @RequiredArgsConstructor @EnableScheduling @Slf4j -public class DiscordService{ +public class DiscordService { private final static String DISCORD_USER_INFO_REQUEST_URL = "https://discord.com/api/v10/users/@me"; private final static String DISCORD_ACCESS_TOKEN_REQUEST_URL = "https://discord.com/api/oauth2/token"; private final static String DISCORD_DM_CHANNEL_REQUEST_URL = "https://discord.com/api/v10/users/@me/channels"; @@ -49,8 +47,6 @@ public class DiscordService{ @Value("${DISCORD_BOT_TOKEN}") private String discordBotToken; - private RestTemplate restTemplate = new RestTemplate(); - public void initializeDiscordChannleAndSaveSnsInfo(com.Alchive.backend.domain.user.User user, String code) { String accessToken = getAccessToken(code); String discordUserId = getDiscordUserIdFromAccessToken(accessToken); @@ -63,7 +59,7 @@ public void initializeDiscordChannleAndSaveSnsInfo(com.Alchive.backend.domain.us .time("0 0 18 ? * MON") .build(); Sns discordInfo = Sns.of(user, snsCreateRequest); - saveDiscordInfo(discordInfo); + snsService.createSns(discordInfo); } private String getAccessToken(String code) { @@ -77,35 +73,17 @@ private String getAccessToken(String code) { httpBody.add("code", code); HttpEntity> request = new HttpEntity<>(httpBody, httpHeader); - ResponseEntity response = sendRestTemplateExchange(DISCORD_ACCESS_TOKEN_REQUEST_URL, HttpMethod.POST, request); - String accessToken = parseResponse(response, "access_token"); + ResponseEntity response = snsService.sendRestTemplateExchange(DISCORD_ACCESS_TOKEN_REQUEST_URL, HttpMethod.POST, request); + String accessToken = (String) snsService.parseResponse(response, "access_token"); return accessToken; } - private ResponseEntity sendRestTemplateExchange(String requestUrl, HttpMethod method, HttpEntity request) { - ResponseEntity response = restTemplate.exchange(requestUrl, method, request, Map.class); - return response; - } - - private String parseResponse(ResponseEntity response, String targetParam) { - Map responseBody = response.getBody(); - checkInvalidGrant(responseBody); - String targetResult = (String) responseBody.get(targetParam); - return targetResult; - } - - private void checkInvalidGrant(Map responseBody) { - if (responseBody.containsKey("error") && responseBody.get("error") == "invalid_grant" ) { - throw new InvalidGrantException(); - } - } - private String getDiscordUserIdFromAccessToken(String accessToken) { HttpHeaders accessTokenHeaders = new HttpHeaders(); accessTokenHeaders.setBearerAuth(accessToken); HttpEntity request = new HttpEntity<>(accessTokenHeaders); - ResponseEntity response = sendRestTemplateExchange(DISCORD_USER_INFO_REQUEST_URL, HttpMethod.GET, request); - String discordUserId = parseResponse(response, "id"); + ResponseEntity response = snsService.sendRestTemplateExchange(DISCORD_USER_INFO_REQUEST_URL, HttpMethod.GET, request); + String discordUserId = (String) snsService.parseResponse(response, "id"); return discordUserId; } @@ -117,15 +95,11 @@ private String getDmChannel(String discordUserId) { httpHeader.set("Authorization", "Bot " + discordBotToken); HttpEntity> request = new HttpEntity<>(httpBody, httpHeader); - ResponseEntity response = sendRestTemplateExchange(DISCORD_DM_CHANNEL_REQUEST_URL, HttpMethod.POST, request); - String channelId = parseResponse(response, "id"); + ResponseEntity response = snsService.sendRestTemplateExchange(DISCORD_DM_CHANNEL_REQUEST_URL, HttpMethod.POST, request); + String channelId = (String) snsService.parseResponse(response, "id"); return channelId; } - private void saveDiscordInfo(Sns discordInfo) { - snsService.createSns(discordInfo); - } - // @Scheduled(cron = "0 */1 * * * *") // todo: Quartz로 동적 스케줄링 작성하기 private void sendMessageReminderBoard(com.Alchive.backend.domain.user.User user) { LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(1); @@ -138,14 +112,13 @@ private void sendMessageReminderBoard(com.Alchive.backend.domain.user.User user) unSolvedBoard.getProblem().getTitle(), unSolvedBoard.getProblem().getUrl()); - Sns discordInfo = getDiscordInfo(user); - sendDmJda(user, message); + sendJdaDm(user, message); } else { log.info("풀지 못한 문제가 존재하지 않습니다. "); } } - public void sendDm(com.Alchive.backend.domain.user.User user, String message) { + public void sendDiscordDm(com.Alchive.backend.domain.user.User user, String message) { String channelId = getUserChannelId(user); String sendMessageUrl = "https://discord.com/api/v10/channels/" + channelId + "/messages"; @@ -156,7 +129,7 @@ public void sendDm(com.Alchive.backend.domain.user.User user, String message) { sendDmParams.put("content", message); HttpEntity> request = new HttpEntity<>(sendDmParams, sendDmHeaders); - sendRestTemplateExchange(sendMessageUrl, HttpMethod.POST, request); + snsService.sendRestTemplateExchange(sendMessageUrl, HttpMethod.POST, request); } private String getUserChannelId(com.Alchive.backend.domain.user.User user) { @@ -171,7 +144,7 @@ private Sns getDiscordInfo(com.Alchive.backend.domain.user.User user) { } // JDA 사용 메서드 - public void sendDmJda(com.Alchive.backend.domain.user.User user, String message) { + public void sendJdaDm(com.Alchive.backend.domain.user.User user, String message) { String discordUserId = getDiscordInfo(user).getSns_id(); User jdaUser = jda.retrieveUserById(discordUserId).complete(); checkUserNull(jdaUser); diff --git a/src/main/java/com/Alchive/backend/service/SlackService.java b/src/main/java/com/Alchive/backend/service/SlackService.java index 88e9239..9511c5e 100644 --- a/src/main/java/com/Alchive/backend/service/SlackService.java +++ b/src/main/java/com/Alchive/backend/service/SlackService.java @@ -1,6 +1,5 @@ package com.Alchive.backend.service; -import com.Alchive.backend.config.error.exception.sns.InvalidGrantException; import com.Alchive.backend.config.error.exception.sns.NoSuchSnsIdException; import com.Alchive.backend.domain.board.Board; import com.Alchive.backend.domain.sns.Sns; @@ -15,16 +14,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.*; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.HashMap; -import java.util.List; import java.util.Map; @@ -34,6 +30,8 @@ @EnableScheduling @Configuration public class SlackService { + private final static String SLACK_TOKEN_REQUEST_URL = "https://slack.com/api/oauth.v2.access"; + private final static String SLACK_SEND_DM_REQUEST_URL = "https://slack.com/api/chat.postMessage"; @Value("${SLACK_CLIENT_ID}") private String clientId; @@ -46,34 +44,16 @@ public class SlackService { @Value("${SLACK_BOT_TOKEN}") private String slackBotToken; - private RestTemplate restTemplate = new RestTemplate(); - private BoardRepository boardRepository; - private SnsReporitory snsReporitory; + private final BoardRepository boardRepository; + private final SnsReporitory snsReporitory; + private final SnsService snsService; - public SnsCreateRequest getSlackInfo(String code) { - String getTokenUrl = "https://slack.com/api/oauth.v2.access"; - - HttpHeaders getTokenHeaders = new HttpHeaders(); - getTokenHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - - MultiValueMap getTokenParams = new LinkedMultiValueMap<>(); - getTokenParams.add("client_id", clientId); - getTokenParams.add("client_secret", clientSecret); - getTokenParams.add("code", code); - getTokenParams.add("redirect_uri", redirectUri); - - HttpEntity> request = new HttpEntity<>(getTokenParams, getTokenHeaders); - ResponseEntity reponse = restTemplate.postForEntity(getTokenUrl, request, Map.class); - Map responseBody = reponse.getBody(); - - if (responseBody.get("ok") == "false") { - throw new InvalidGrantException(); - } - - Map authedUser = (Map) responseBody.get("authed_user"); + public void initializeSlackChannelAndSaveSnsInfo(User user, String code) { + ResponseEntity response = getSlackAuthUserInfo(code); + Map authedUser = (Map) snsService.parseResponse(response, "authed_user"); String slackUserId = (String) authedUser.get("id"); - String botAccessToken = (String) responseBody.get("access_token"); String userAccessToken = (String) authedUser.get("access_token"); + String botAccessToken = (String) snsService.parseResponse(response, "access_token"); SnsCreateRequest snsCreateRequest = SnsCreateRequest.builder() .category(SnsCategory.SLACK) @@ -82,29 +62,23 @@ public SnsCreateRequest getSlackInfo(String code) { .user_token(userAccessToken) .time("0 0 18 ? * MON") .build(); - - return snsCreateRequest; + Sns slackSns = Sns.of(user, snsCreateRequest); + snsService.createSns(slackSns); } - public Sns getSlackInfo(User user) { - Long userId = user.getId(); - Sns slackInfo = snsReporitory.findByUser_IdAndCategory(userId, SnsCategory.SLACK) - .orElseThrow(NoSuchSnsIdException::new); - return slackInfo; - } - - public void sendDm(String slackUserId, String botAccessToken, String message) { - String sendDmUrl = "https://slack.com/api/chat.postMessage"; - HttpHeaders sendDmHeaders = new HttpHeaders(); - sendDmHeaders.setContentType(MediaType.APPLICATION_JSON); - sendDmHeaders.setBearerAuth(botAccessToken); + private ResponseEntity getSlackAuthUserInfo (String code) { + HttpHeaders getTokenHeaders = new HttpHeaders(); + getTokenHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - Map sendDmParams = new HashMap<>(); - sendDmParams.put("channel", slackUserId); - sendDmParams.put("text", message); + MultiValueMap getTokenParams = new LinkedMultiValueMap<>(); + getTokenParams.add("client_id", clientId); + getTokenParams.add("client_secret", clientSecret); + getTokenParams.add("redirect_uri", redirectUri); + getTokenParams.add("code", code); - HttpEntity> request = new HttpEntity<>(sendDmParams, sendDmHeaders); - restTemplate.postForEntity(sendDmUrl, request, Map.class); + HttpEntity> request = new HttpEntity<>(getTokenParams, getTokenHeaders); + ResponseEntity response = snsService.sendRestTemplateExchange(SLACK_TOKEN_REQUEST_URL, HttpMethod.POST, request); + return response; } // @Scheduled(cron = "0 0 * * * *") // todo: Quartz로 동적 스케줄링 작성하기 @@ -119,10 +93,32 @@ public void sendMessageReminderBoard(User user) { unSolvedBoard.getProblem().getTitle(), unSolvedBoard.getProblem().getUrl()); - Sns slackInfo = getSlackInfo(user); - sendDm(slackInfo.getSns_id(), slackInfo.getBot_token(), message); + sendSlackDm(user, message); } else { log.info("풀지 못한 문제가 존재하지 않습니다. "); } } + + public void sendSlackDm(User user, String message) { + Sns slackInfo = getSlackInfo(user); + String slackUserId = slackInfo.getSns_id(); + String botAccessToken = slackInfo.getBot_token(); + HttpHeaders httpHeader = new HttpHeaders(); + httpHeader.setContentType(MediaType.APPLICATION_JSON); + httpHeader.setBearerAuth(botAccessToken); + + Map httpBody = new HashMap<>(); + httpBody.put("channel", slackUserId); + httpBody.put("text", message); + + HttpEntity> request = new HttpEntity<>(httpBody, httpHeader); + snsService.sendRestTemplateExchange(SLACK_SEND_DM_REQUEST_URL, HttpMethod.POST, request); + } + + private Sns getSlackInfo(User user) { + Long userId = user.getId(); + Sns slackInfo = snsReporitory.findByUser_IdAndCategory(userId, SnsCategory.SLACK) + .orElseThrow(NoSuchSnsIdException::new); + return slackInfo; + } } diff --git a/src/main/java/com/Alchive/backend/service/SnsService.java b/src/main/java/com/Alchive/backend/service/SnsService.java index c9d990e..51cb0b9 100644 --- a/src/main/java/com/Alchive/backend/service/SnsService.java +++ b/src/main/java/com/Alchive/backend/service/SnsService.java @@ -1,20 +1,26 @@ package com.Alchive.backend.service; +import com.Alchive.backend.config.error.exception.sns.InvalidGrantException; import com.Alchive.backend.config.error.exception.sns.NoSuchSnsIdException; import com.Alchive.backend.domain.sns.Sns; -import com.Alchive.backend.domain.user.User; -import com.Alchive.backend.dto.request.SnsCreateRequest; import com.Alchive.backend.dto.response.SnsResponseDTO; import com.Alchive.backend.repository.SnsReporitory; import jakarta.transaction.Transactional; +import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.Map; @RequiredArgsConstructor @Service @Slf4j -public class SnsService{ +public class SnsService { private final SnsReporitory snsReporitory; public SnsResponseDTO getSns(Long snsId) { @@ -26,4 +32,23 @@ public SnsResponseDTO getSns(Long snsId) { public void createSns(Sns sns) { snsReporitory.save(sns); } + + private RestTemplate restTemplate = new RestTemplate(); + protected ResponseEntity sendRestTemplateExchange(String requestUrl, HttpMethod method, HttpEntity request) { + ResponseEntity response = restTemplate.exchange(requestUrl, method, request, Map.class); + return response; + } + + protected Object parseResponse(ResponseEntity response, String targetParam) { + Map responseBody = response.getBody(); + checkInvalidGrant(responseBody); + Object targetResult = responseBody.get(targetParam); + return targetResult; + } + + private void checkInvalidGrant(Map responseBody) { + if ((responseBody.containsKey("error") && responseBody.get("error") == "invalid_grant") || (responseBody.containsKey("ok") && responseBody.get("ok") == "false")) { + throw new InvalidGrantException(); + } + } } From 55f5034fc8c5461f4a95206ca58493775349b0a7 Mon Sep 17 00:00:00 2001 From: nahowo Date: Thu, 1 May 2025 16:31:34 +0900 Subject: [PATCH 3/3] =?UTF-8?q?SNS=20controller,=20service=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Alchive/backend/controller/DiscordController.java | 3 +-- .../com/Alchive/backend/controller/SnsController.java | 5 ++--- .../java/com/Alchive/backend/service/DiscordService.java | 5 ++--- .../java/com/Alchive/backend/service/SlackService.java | 3 +-- src/main/java/com/Alchive/backend/service/SnsService.java | 8 ++++++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/Alchive/backend/controller/DiscordController.java b/src/main/java/com/Alchive/backend/controller/DiscordController.java index 111d91c..dd1d8ae 100644 --- a/src/main/java/com/Alchive/backend/controller/DiscordController.java +++ b/src/main/java/com/Alchive/backend/controller/DiscordController.java @@ -2,7 +2,6 @@ import com.Alchive.backend.config.result.ResultResponse; import com.Alchive.backend.domain.user.User; -import com.Alchive.backend.service.SnsService; import com.Alchive.backend.service.DiscordService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -26,7 +25,7 @@ public class DiscordController { @Operation(summary = "디스코드 봇 연결", description = "디스코드 액세스 토큰을 요청하고 DM 채널을 연결하는 api입니다. ") @GetMapping("/dm/open") public ResponseEntity initializeDiscordChannelAndSendWelcomeDM(@AuthenticationPrincipal User user, @RequestParam String code) { - discordService.initializeDiscordChannleAndSaveSnsInfo(user, code); + discordService.initializeDiscordChannelAndSaveSnsInfo(user, code); discordService.sendDiscordDm(user, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); return ResponseEntity.ok(ResultResponse.of(DISCORD_DM_SEND_SUCCESS)); } diff --git a/src/main/java/com/Alchive/backend/controller/SnsController.java b/src/main/java/com/Alchive/backend/controller/SnsController.java index 0a9c41d..b685509 100644 --- a/src/main/java/com/Alchive/backend/controller/SnsController.java +++ b/src/main/java/com/Alchive/backend/controller/SnsController.java @@ -32,9 +32,8 @@ public ResponseEntity getSns(@PathVariable Long snsId) { @Operation(summary = "소셜 정보 생성", description = "소셜 정보를 생성하는 메서드입니다. ") @PostMapping("") - public ResponseEntity createSns(@AuthenticationPrincipal User user, SnsCreateRequest request) { - Sns sns = Sns.of(user, request); - snsService.createSns(sns); + public ResponseEntity createSns(@AuthenticationPrincipal User user, @RequestBody SnsCreateRequest request) { + snsService.createSns(user, request); return ResponseEntity.ok(ResultResponse.of(SNS_CREATE_SUCCESS)); } } diff --git a/src/main/java/com/Alchive/backend/service/DiscordService.java b/src/main/java/com/Alchive/backend/service/DiscordService.java index 9ff0dc5..f77bec1 100644 --- a/src/main/java/com/Alchive/backend/service/DiscordService.java +++ b/src/main/java/com/Alchive/backend/service/DiscordService.java @@ -47,7 +47,7 @@ public class DiscordService { @Value("${DISCORD_BOT_TOKEN}") private String discordBotToken; - public void initializeDiscordChannleAndSaveSnsInfo(com.Alchive.backend.domain.user.User user, String code) { + public void initializeDiscordChannelAndSaveSnsInfo(com.Alchive.backend.domain.user.User user, String code) { String accessToken = getAccessToken(code); String discordUserId = getDiscordUserIdFromAccessToken(accessToken); String channelId = getDmChannel(discordUserId); @@ -58,8 +58,7 @@ public void initializeDiscordChannleAndSaveSnsInfo(com.Alchive.backend.domain.us .channel_id(channelId) // Discord Channel Id .time("0 0 18 ? * MON") .build(); - Sns discordInfo = Sns.of(user, snsCreateRequest); - snsService.createSns(discordInfo); + snsService.createSns(user, snsCreateRequest); } private String getAccessToken(String code) { diff --git a/src/main/java/com/Alchive/backend/service/SlackService.java b/src/main/java/com/Alchive/backend/service/SlackService.java index 9511c5e..e909e20 100644 --- a/src/main/java/com/Alchive/backend/service/SlackService.java +++ b/src/main/java/com/Alchive/backend/service/SlackService.java @@ -62,8 +62,7 @@ public void initializeSlackChannelAndSaveSnsInfo(User user, String code) { .user_token(userAccessToken) .time("0 0 18 ? * MON") .build(); - Sns slackSns = Sns.of(user, snsCreateRequest); - snsService.createSns(slackSns); + snsService.createSns(user, snsCreateRequest); } private ResponseEntity getSlackAuthUserInfo (String code) { diff --git a/src/main/java/com/Alchive/backend/service/SnsService.java b/src/main/java/com/Alchive/backend/service/SnsService.java index 51cb0b9..0e4b032 100644 --- a/src/main/java/com/Alchive/backend/service/SnsService.java +++ b/src/main/java/com/Alchive/backend/service/SnsService.java @@ -3,6 +3,8 @@ import com.Alchive.backend.config.error.exception.sns.InvalidGrantException; import com.Alchive.backend.config.error.exception.sns.NoSuchSnsIdException; import com.Alchive.backend.domain.sns.Sns; +import com.Alchive.backend.domain.user.User; +import com.Alchive.backend.dto.request.SnsCreateRequest; import com.Alchive.backend.dto.response.SnsResponseDTO; import com.Alchive.backend.repository.SnsReporitory; import jakarta.transaction.Transactional; @@ -13,6 +15,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.client.RestTemplate; import java.util.Map; @@ -29,7 +32,8 @@ public SnsResponseDTO getSns(Long snsId) { } @Transactional - public void createSns(Sns sns) { + public void createSns(User user, SnsCreateRequest request) { + Sns sns = Sns.of(user, request); snsReporitory.save(sns); } @@ -47,7 +51,7 @@ protected Object parseResponse(ResponseEntity response, String targetParam) } private void checkInvalidGrant(Map responseBody) { - if ((responseBody.containsKey("error") && responseBody.get("error") == "invalid_grant") || (responseBody.containsKey("ok") && responseBody.get("ok") == "false")) { + if ((responseBody.containsKey("error") && responseBody.get("error").equals("invalid_grant")) || (responseBody.containsKey("ok") && responseBody.get("ok").equals("false"))) { throw new InvalidGrantException(); } }