From 8a8514672de12b52caac2db2d01cba715f71c4f1 Mon Sep 17 00:00:00 2001 From: JHyun0302 Date: Sat, 13 Apr 2024 12:49:01 +0900 Subject: [PATCH 1/8] =?UTF-8?q?:sparkles:=20[Feat]=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=A1=B0=ED=9A=8C,=20=EC=B9=9C=EA=B5=AC=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C,=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fundingboost/api/common/Const.java | 9 +++ .../api/controller/KakaoContoller.java | 41 ++++++++++ .../api/service/HttpCallService.java | 65 ++++++++++++++++ .../api/service/KakaoService.java | 74 +++++++++++++++++++ .../fundingboost/api/transformer/Trans.java | 12 +++ src/main/resources/application.yml | 3 + src/main/resources/static/index.html | 55 ++++++++++++++ 7 files changed, 259 insertions(+) create mode 100644 src/main/java/kcs/funding/fundingboost/api/common/Const.java create mode 100644 src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java create mode 100644 src/main/java/kcs/funding/fundingboost/api/service/HttpCallService.java create mode 100644 src/main/java/kcs/funding/fundingboost/api/service/KakaoService.java create mode 100644 src/main/java/kcs/funding/fundingboost/api/transformer/Trans.java create mode 100644 src/main/resources/static/index.html diff --git a/src/main/java/kcs/funding/fundingboost/api/common/Const.java b/src/main/java/kcs/funding/fundingboost/api/common/Const.java new file mode 100644 index 00000000..26611393 --- /dev/null +++ b/src/main/java/kcs/funding/fundingboost/api/common/Const.java @@ -0,0 +1,9 @@ +package kcs.funding.fundingboost.api.common; + +public class Const { + public static final String POST = "POST"; + + public static final String GET = "GET"; + + public static final String EMPTY = ""; +} diff --git a/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java b/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java new file mode 100644 index 00000000..4c54095e --- /dev/null +++ b/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java @@ -0,0 +1,41 @@ +package kcs.funding.fundingboost.api.controller; + +import kcs.funding.fundingboost.api.service.KakaoService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.view.RedirectView; + +@RestController +@RequiredArgsConstructor +public class KakaoContoller { + + private final KakaoService kakaoService; + + @RequestMapping("/login") + public RedirectView goKakaoOAuth() { + return kakaoService.goKakaoOAuth(); + } + + @RequestMapping("/login-callback") + public RedirectView loginCallback(@RequestParam("code") String code) { + return kakaoService.loginCallback(code); + } + + @GetMapping("/profile") + public String getProfile() { + return kakaoService.getProfile(); + } + + @GetMapping("/friends") + public String getFriends() { + return kakaoService.getFriends(); + } + + @RequestMapping("/logout") + public String logout() { + return kakaoService.logout(); + } +} diff --git a/src/main/java/kcs/funding/fundingboost/api/service/HttpCallService.java b/src/main/java/kcs/funding/fundingboost/api/service/HttpCallService.java new file mode 100644 index 00000000..b95abd79 --- /dev/null +++ b/src/main/java/kcs/funding/fundingboost/api/service/HttpCallService.java @@ -0,0 +1,65 @@ +package kcs.funding.fundingboost.api.service; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Scanner; +import org.springframework.stereotype.Service; + +@Service +public class HttpCallService { + + public String CallwithToken(String method, String reqURL, String access_Token) { + String header = "Bearer " + access_Token; + return Call(method, reqURL, header, null); + } + + public String Call(String method, String reqURL, String header, String param) { + String result = ""; + try { + String response = ""; + URL url = new URL(reqURL); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod(method); + conn.setRequestProperty("Authorization", header); + if (param != null) { + System.out.println("param : " + param); + conn.setDoOutput(true); + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream())); + bw.write(param); + bw.flush(); + + } + int responseCode = conn.getResponseCode(); + System.out.println("responseCode : " + responseCode); + + System.out.println("reqURL : " + reqURL); + System.out.println("method : " + method); + System.out.println("Authorization : " + header); + InputStream stream = conn.getErrorStream(); + if (stream != null) { + try (Scanner scanner = new Scanner(stream)) { + scanner.useDelimiter("\\Z"); + response = scanner.next(); + } + System.out.println("error response : " + response); + } + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line = ""; + while ((line = br.readLine()) != null) { + result += line; + } + System.out.println("response body : " + result); + + br.close(); + } catch (IOException e) { + return e.getMessage(); + } + return result; + } +} diff --git a/src/main/java/kcs/funding/fundingboost/api/service/KakaoService.java b/src/main/java/kcs/funding/fundingboost/api/service/KakaoService.java new file mode 100644 index 00000000..ad86d37b --- /dev/null +++ b/src/main/java/kcs/funding/fundingboost/api/service/KakaoService.java @@ -0,0 +1,74 @@ +package kcs.funding.fundingboost.api.service; + +import com.google.gson.JsonParser; +import jakarta.servlet.http.HttpSession; +import kcs.funding.fundingboost.api.common.Const; +import kcs.funding.fundingboost.api.transformer.Trans; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.view.RedirectView; + +@RequiredArgsConstructor +@Service +public class KakaoService { + + private final HttpSession httpSession; + + + private final HttpCallService httpCallService; + + public static String token; + + @Value("${rest-api-key}") + private String REST_API_KEY; + + @Value("${redirect-uri}") + private String REDIRECT_URI; + + @Value("${authorize-uri}") + private String AUTHORIZE_URI; + + @Value("${token-uri}") + public String TOKEN_URI; + + @Value("${client-secret}") + private String CLIENT_SECRET; + + @Value("${kakao-api-host}") + private String KAKAO_API_HOST; + + + public RedirectView goKakaoOAuth() { + String uri = AUTHORIZE_URI + "?redirect_uri=" + REDIRECT_URI + "&response_type=code&client_id=" + REST_API_KEY; + if (!"".isEmpty()) { + uri += "&scope=" + ""; + } + return new RedirectView(uri); + } + + public RedirectView loginCallback(String code) { + String param = "grant_type=authorization_code&client_id=" + REST_API_KEY + "&redirect_uri=" + REDIRECT_URI + + "&client_secret=" + CLIENT_SECRET + "&code=" + code; + String rtn = httpCallService.Call(Const.POST, TOKEN_URI, Const.EMPTY, param); + token = Trans.token(rtn, new JsonParser()); //access token??? + httpSession.setAttribute("token", token); + + return new RedirectView("/index.html"); + } + + public String getProfile() { + String uri = KAKAO_API_HOST + "/v2/user/me"; + return httpCallService.CallwithToken(Const.GET, uri, httpSession.getAttribute("token").toString()); + } + + public String getFriends() { + String uri = KAKAO_API_HOST + "/v1/api/talk/friends"; + return httpCallService.CallwithToken(Const.GET, uri, httpSession.getAttribute("token").toString()); + } + + public String logout() { + String uri = KAKAO_API_HOST + "/v1/user/logout"; + return httpCallService.CallwithToken(Const.POST, uri, httpSession.getAttribute("token").toString()); + } +} diff --git a/src/main/java/kcs/funding/fundingboost/api/transformer/Trans.java b/src/main/java/kcs/funding/fundingboost/api/transformer/Trans.java new file mode 100644 index 00000000..9c1da33c --- /dev/null +++ b/src/main/java/kcs/funding/fundingboost/api/transformer/Trans.java @@ -0,0 +1,12 @@ +package kcs.funding.fundingboost.api.transformer; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +public class Trans { + + public static String token(String rtn, JsonParser parser) { + JsonElement element = parser.parse(rtn); + return element.getAsJsonObject().get("access_token").getAsString(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ecbfabcd..286c3ad3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -9,6 +9,9 @@ spring: default_batch_fetch_size: 3 database-platform: org.hibernate.dialect.H2Dialect + profiles: + include: api + h2: console: enabled: true diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 00000000..f181b9f3 --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,55 @@ + + + + + Kakao REST-API python Flask example + + + + + +

1. 카카오 로그인 및 프로필 조회 예제

+
+- [KOE101, KOE004] 내 애플리케이션>제품 설정>카카오 로그인 > 활성화 설정 : ON
+- [KOE006] 내 애플리케이션>제품 설정>카카오 로그인 > Redirect URI : http://localhost/redirect
+
+
+ + + +
+ +
+ +
+ + + + + +
+
+ + From 4ae390365a9c9616e31e898042e74c19d28cc2c1 Mon Sep 17 00:00:00 2001 From: JHyun0302 Date: Sat, 13 Apr 2024 12:49:23 +0900 Subject: [PATCH 2/8] =?UTF-8?q?:wrench:=20[Add]=20json=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +++++ .../fundingboost/domain/entity/GiftHubItem.java | 12 +++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 8cec578a..7797696b 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,11 @@ dependencies { // Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' + //json + implementation group: 'org.json', name: 'json', version: '20210307' + //gson + implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.0' + //Querydsl 추가 implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" diff --git a/src/main/java/kcs/funding/fundingboost/domain/entity/GiftHubItem.java b/src/main/java/kcs/funding/fundingboost/domain/entity/GiftHubItem.java index ae80448c..84e5260c 100644 --- a/src/main/java/kcs/funding/fundingboost/domain/entity/GiftHubItem.java +++ b/src/main/java/kcs/funding/fundingboost/domain/entity/GiftHubItem.java @@ -43,11 +43,13 @@ public class GiftHubItem extends BaseTimeEntity { @OnDelete(action = OnDeleteAction.CASCADE) private Member member; + private GiftHubItem(int quantity, Item item, Member member) { + this.quantity = quantity; + this.item = item; + this.member = member; + } + public static GiftHubItem createGiftHubItem(int quantity, Item item, Member member) { - GiftHubItem giftHubItem = new GiftHubItem(); - giftHubItem.quantity = quantity; - giftHubItem.item = item; - giftHubItem.member = member; - return giftHubItem; + return new GiftHubItem(quantity, item, member); } } From 0e19da4cefb386cc7c3f26eeb6cf5f5130698189 Mon Sep 17 00:00:00 2001 From: JHyun0302 Date: Sat, 13 Apr 2024 12:50:14 +0900 Subject: [PATCH 3/8] =?UTF-8?q?:see=5Fno=5Fevil:=20[Chore]=20=EC=B9=B4?= =?UTF-8?q?=EC=B9=B4=EC=98=A4=20api=20=EA=B4=80=EB=A0=A8=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=EC=9D=80=20git=20=EA=B4=80=EB=A6=AC=20=EB=8C=80?= =?UTF-8?q?=EC=83=81=EC=97=90=EC=84=9C=20=EC=A0=9C=EC=99=B8=ED=95=9C?= =?UTF-8?q?=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c2065bc2..ee845a4c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ out/ ### VS Code ### .vscode/ + +### API ### +applicaiton-api.properties \ No newline at end of file From b3392aa23054bb710423596e805623e068a0a2c1 Mon Sep 17 00:00:00 2001 From: JHyun0302 Date: Sat, 13 Apr 2024 19:29:20 +0900 Subject: [PATCH 4/8] =?UTF-8?q?:sparkles:=20[Feat]=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20api=20(=EB=82=98=EC=97=90=EA=B2=8C=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EB=B3=B4=EB=82=B4=EA=B8=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5,=20=EC=B9=9C=EA=B5=AC=EC=97=90=EA=B2=8C=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EB=B3=B4=EB=82=B4=EA=B8=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/KakaoContoller.java | 13 +++ .../api/dto/DefaultMessageDto.java | 8 ++ .../api/service/CustomMessageService.java | 28 ++++++ .../api/service/HttpCallService.java | 42 ++++++++- .../api/service/KakaoService.java | 5 +- .../api/service/MessageService.java | 85 +++++++++++++++++++ 6 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 src/main/java/kcs/funding/fundingboost/api/dto/DefaultMessageDto.java create mode 100644 src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java create mode 100644 src/main/java/kcs/funding/fundingboost/api/service/MessageService.java diff --git a/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java b/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java index 4c54095e..698df191 100644 --- a/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java +++ b/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java @@ -1,5 +1,6 @@ package kcs.funding.fundingboost.api.controller; +import kcs.funding.fundingboost.api.service.CustomMessageService; import kcs.funding.fundingboost.api.service.KakaoService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; @@ -14,6 +15,8 @@ public class KakaoContoller { private final KakaoService kakaoService; + private final CustomMessageService customMessageService; + @RequestMapping("/login") public RedirectView goKakaoOAuth() { return kakaoService.goKakaoOAuth(); @@ -38,4 +41,14 @@ public String getFriends() { public String logout() { return kakaoService.logout(); } + + @GetMapping("/send/me") + public void sendMyMessage() { + customMessageService.sendReminderMessage(); + } + + @GetMapping("/send/friends") + public void sendMessageToFriends() { + customMessageService.sendMessageToFriends(); + } } diff --git a/src/main/java/kcs/funding/fundingboost/api/dto/DefaultMessageDto.java b/src/main/java/kcs/funding/fundingboost/api/dto/DefaultMessageDto.java new file mode 100644 index 00000000..96f491cd --- /dev/null +++ b/src/main/java/kcs/funding/fundingboost/api/dto/DefaultMessageDto.java @@ -0,0 +1,8 @@ +package kcs.funding.fundingboost.api.dto; + + +public record DefaultMessageDto(String objType, String text, String webUrl, String btnTitle) { + public static DefaultMessageDto createDefaultMessageDto(String objType, String text, String webUrl, String btn) { + return new DefaultMessageDto(objType, text, webUrl, btn); + } +} diff --git a/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java b/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java new file mode 100644 index 00000000..55da4627 --- /dev/null +++ b/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java @@ -0,0 +1,28 @@ +package kcs.funding.fundingboost.api.service; + +import java.util.Arrays; +import java.util.List; +import kcs.funding.fundingboost.api.dto.DefaultMessageDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomMessageService { + private final MessageService messageService; + + public boolean sendReminderMessage() { + DefaultMessageDto myMsg = DefaultMessageDto.createDefaultMessageDto("text", "바로 확인하기", + "https://www.naver.com", "펀딩 남은 기간이 2일 남았습니다!!"); + String accessToken = HttpCallService.accessToken; + return messageService.sendMessageToMe(accessToken, myMsg); + } + + public boolean sendMessageToFriends() { + DefaultMessageDto myMsg = DefaultMessageDto.createDefaultMessageDto("text", "버튼 버튼", + "https://www.naver.com", "내가 지금 생각하고 있는 것은??"); + String accessToken = HttpCallService.accessToken; + List friendUuids = Arrays.asList("친구의 UUID 여기에 추가"); // TODO: 실제로는 적절한 UUID 목록을 제공해야 합니다. + return messageService.sendMessageToFriends(accessToken, myMsg, friendUuids); + } +} diff --git a/src/main/java/kcs/funding/fundingboost/api/service/HttpCallService.java b/src/main/java/kcs/funding/fundingboost/api/service/HttpCallService.java index b95abd79..6639a511 100644 --- a/src/main/java/kcs/funding/fundingboost/api/service/HttpCallService.java +++ b/src/main/java/kcs/funding/fundingboost/api/service/HttpCallService.java @@ -9,13 +9,18 @@ import java.net.HttpURLConnection; import java.net.URL; import java.util.Scanner; -import org.springframework.stereotype.Service; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; -@Service public class HttpCallService { - + public static String accessToken; + public String CallwithToken(String method, String reqURL, String access_Token) { String header = "Bearer " + access_Token; + accessToken = access_Token; return Call(method, reqURL, header, null); } @@ -62,4 +67,35 @@ public String Call(String method, String reqURL, String header, String param) { } return result; } + + /** + * Http 요청 클라이언트 객체 생성 method + * + * @ param Map header HttpHeader 정보 + * @ param Object params HttpBody 정보 + * @ return HttpEntity 생성된 HttpClient객체 정보 반환 + * @ exception 예외사항 + */ + public HttpEntity httpClientEntity(HttpHeaders header, Object params) { + HttpHeaders requestHeaders = header; + + if (params == null || "".equals(params)) { + return new HttpEntity<>(requestHeaders); + } else { + return new HttpEntity<>(params, requestHeaders); + } + } + + /** + * Http 요청 method + * + * @ param String url 요청 URL 정보 + * @ param HttpMethod method 요청 Method 정보 + * @ param HttpEntity entity 요청 EntityClient 객체 정보 + * @ return HttpEntity 생성된 HttpClient객체 정보 반환 + */ + public ResponseEntity httpRequest(String url, HttpMethod method, HttpEntity entity) { + RestTemplate restTemplate = new RestTemplate(); + return restTemplate.exchange(url, method, entity, String.class); + } } diff --git a/src/main/java/kcs/funding/fundingboost/api/service/KakaoService.java b/src/main/java/kcs/funding/fundingboost/api/service/KakaoService.java index ad86d37b..8daba00a 100644 --- a/src/main/java/kcs/funding/fundingboost/api/service/KakaoService.java +++ b/src/main/java/kcs/funding/fundingboost/api/service/KakaoService.java @@ -14,8 +14,7 @@ public class KakaoService { private final HttpSession httpSession; - - + private final HttpCallService httpCallService; public static String token; @@ -51,7 +50,7 @@ public RedirectView loginCallback(String code) { String param = "grant_type=authorization_code&client_id=" + REST_API_KEY + "&redirect_uri=" + REDIRECT_URI + "&client_secret=" + CLIENT_SECRET + "&code=" + code; String rtn = httpCallService.Call(Const.POST, TOKEN_URI, Const.EMPTY, param); - token = Trans.token(rtn, new JsonParser()); //access token??? + token = Trans.token(rtn, new JsonParser()); httpSession.setAttribute("token", token); return new RedirectView("/index.html"); diff --git a/src/main/java/kcs/funding/fundingboost/api/service/MessageService.java b/src/main/java/kcs/funding/fundingboost/api/service/MessageService.java new file mode 100644 index 00000000..ad71f771 --- /dev/null +++ b/src/main/java/kcs/funding/fundingboost/api/service/MessageService.java @@ -0,0 +1,85 @@ +package kcs.funding.fundingboost.api.service; + +import java.util.List; +import kcs.funding.fundingboost.api.dto.DefaultMessageDto; +import lombok.extern.slf4j.Slf4j; +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +@Service +@Slf4j +public class MessageService extends HttpCallService { + private static final String MSG_SEND_TO_ME_URL = "https://kapi.kakao.com/v2/api/talk/memo/default/send"; + private static final String MSG_SEND_TO_FRIENDS_URL = "https://kapi.kakao.com/v1/api/talk/friends/message/default/send"; + private static final String SEND_SUCCESS_MSG = "메시지 전송에 성공했습니다."; + private static final String SEND_FAIL_MSG = "메시지 전송에 실패했습니다."; + + private static final String SUCCESS_CODE = "0"; //kakao api에서 return해주는 success code 값 + + public boolean sendMessageToMe(String accessToken, DefaultMessageDto msgDto) { + MultiValueMap parameters = createMessageParameters(msgDto, null); + return sendMessage(MSG_SEND_TO_ME_URL, accessToken, parameters); + } + + public boolean sendMessageToFriends(String accessToken, DefaultMessageDto msgDto, List uuids) { + JSONArray receiverUuids = new JSONArray(uuids); + MultiValueMap parameters = createMessageParameters(msgDto, receiverUuids.toString()); + return sendMessage(MSG_SEND_TO_FRIENDS_URL, accessToken, parameters); + } + + private MultiValueMap createMessageParameters(DefaultMessageDto msgDto, String receiverUuids) { + JSONObject templateObj = createTemplateObject(msgDto); + MultiValueMap parameters = new LinkedMultiValueMap<>(); + parameters.add("template_object", templateObj.toString()); + if (receiverUuids != null) { + parameters.add("receiver_uuids", receiverUuids); + } + return parameters; + } + + private boolean sendMessage(String url, String accessToken, MultiValueMap parameters) { + HttpHeaders headers = createHeaders(accessToken); + HttpEntity> requestEntity = new HttpEntity<>(parameters, headers); + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.postForEntity(url, requestEntity, String.class); + return processResponse(response.getBody()); + } + + private HttpHeaders createHeaders(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.set("Authorization", "Bearer " + accessToken); + return headers; + } + + private JSONObject createTemplateObject(DefaultMessageDto msgDto) { + JSONObject linkObj = new JSONObject(); + linkObj.put("web_url", msgDto.webUrl()); + JSONObject templateObj = new JSONObject(); + templateObj.put("object_type", msgDto.objType()); + templateObj.put("text", msgDto.text()); + templateObj.put("link", linkObj); + templateObj.put("button_title", msgDto.btnTitle()); + return templateObj; + } + + private boolean processResponse(String responseBody) { + JSONObject jsonData = new JSONObject(responseBody); + String resultCode = jsonData.optString("result_code", ""); + if (SUCCESS_CODE.equals(resultCode) || !jsonData.optString("successful_receiver_uuids", "").isEmpty()) { + log.info(SEND_SUCCESS_MSG); + return true; + } else { + log.debug(SEND_FAIL_MSG); + return false; + } + } +} \ No newline at end of file From 35f4ee289bc7906357e0e8c861531cd9dfadabed Mon Sep 17 00:00:00 2001 From: JHyun0302 Date: Sat, 13 Apr 2024 19:33:07 +0900 Subject: [PATCH 5/8] =?UTF-8?q?:lipstick:=20[styled]=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EA=B8=B0=EB=B3=B8=20html?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index f181b9f3..f62a76a2 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -48,6 +48,8 @@

1. 카카오 로그인 및 프로필 조회 예제

+ +

From 0d991dc62f0bb1fa4cfe3da44f81d33c7a85ed64 Mon Sep 17 00:00:00 2001 From: JHyun0302 Date: Tue, 28 May 2024 23:33:16 +0900 Subject: [PATCH 6/8] =?UTF-8?q?:wrench:=20[Config]=20Async=20retry=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 91669e94..abd9d71d 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,10 @@ dependencies { //oauth2 추가 implementation group: 'org.springframework.boot', name: 'spring-boot-starter-oauth2-client', version: '3.2.5' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mustache', version: '3.2.5' + + //Async retry + implementation 'org.springframework.retry:spring-retry' + implementation 'org.springframework:spring-aspects' } def generated = 'src/main/generated' From ebf0619e982794189c0d4b21871a678a447093b9 Mon Sep 17 00:00:00 2001 From: JHyun0302 Date: Tue, 28 May 2024 23:33:53 +0900 Subject: [PATCH 7/8] =?UTF-8?q?:sparkles:=20[Feat]=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=EB=A7=81=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20@Retryable=EC=9D=84=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=A4=84=EB=A7=81=20=EC=9E=AC=EC=8B=9C?= =?UTF-8?q?=EB=8F=84=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/config/SchedulerConfig.java | 26 ++++++++++++++ .../api/controller/KakaoContoller.java | 25 +++++++++++--- .../api/service/CustomMessageService.java | 34 +++++++++++++++++-- .../api/service/MessageService.java | 27 ++++++++++----- 4 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 src/main/java/kcs/funding/fundingboost/api/config/SchedulerConfig.java diff --git a/src/main/java/kcs/funding/fundingboost/api/config/SchedulerConfig.java b/src/main/java/kcs/funding/fundingboost/api/config/SchedulerConfig.java new file mode 100644 index 00000000..69f87baf --- /dev/null +++ b/src/main/java/kcs/funding/fundingboost/api/config/SchedulerConfig.java @@ -0,0 +1,26 @@ +package kcs.funding.fundingboost.api.config; + +import java.util.concurrent.Executor; +import org.springframework.context.annotation.Configuration; +import org.springframework.retry.annotation.EnableRetry; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +@EnableAsync +@EnableRetry +@EnableScheduling +public class SchedulerConfig implements AsyncConfigurer { + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); //스레드 풀의 기본 크기 + executor.setMaxPoolSize(20); // 스레드 풀 최대 크기 (동시에 실행 가능한 최대 스레드 수) + executor.setQueueCapacity(500); // 작업 큐의 용량 (최대 스레드 수 초과 시 대기 시킬 큐의 크기) + executor.setThreadNamePrefix("Async-"); // 스레드 풀에서 생성된 스레드의 이름 접두사 + executor.initialize(); // 스레드 풀을 초기화 & 사용 준비 완료 + return executor; + } +} diff --git a/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java b/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java index 698df191..f919540c 100644 --- a/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java +++ b/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java @@ -1,8 +1,12 @@ package kcs.funding.fundingboost.api.controller; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import kcs.funding.fundingboost.api.service.CustomMessageService; import kcs.funding.fundingboost.api.service.KakaoService; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -43,12 +47,25 @@ public String logout() { } @GetMapping("/send/me") - public void sendMyMessage() { - customMessageService.sendReminderMessage(); + public CompletableFuture sendMyMessage() { + return customMessageService.sendReminderMessage(); } @GetMapping("/send/friends") - public void sendMessageToFriends() { - customMessageService.sendMessageToFriends(); + public ResponseEntity sendMessageToFriends() { + CompletableFuture resultFuture = customMessageService.sendMessageToFriends(); + boolean result; + try { + result = resultFuture.get(); + } catch (InterruptedException | ExecutionException exception) { + Thread.currentThread().interrupt(); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("메시지 전송 실패"); + } + + if (result) { + return ResponseEntity.ok().body("친구들에게 메세지 전송 성공"); + } else { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("메시지 전송 실패"); + } } } diff --git a/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java b/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java index 55da4627..639155c2 100644 --- a/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java +++ b/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java @@ -2,27 +2,55 @@ import java.util.Arrays; import java.util.List; +import java.util.concurrent.CompletableFuture; import kcs.funding.fundingboost.api.dto.DefaultMessageDto; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Recover; +import org.springframework.retry.annotation.Retryable; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor +@Slf4j public class CustomMessageService { private final MessageService messageService; - public boolean sendReminderMessage() { + @Async + @Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 5000)) + //메서드가 실패할 경우 최대 3번까지 재시도, 각 재시도 사이에 5초의 지연을 둠 + //예외(Exception.class)가 발생할 경우 최대 3회까지 자동으로 재시도 +// @Scheduled(cron = "0 0 0 * * ?") // 매일 00:00 작업 실행 + @Scheduled(cron = "*/5 * * * * *") // 5초마다 작업 실행 + public CompletableFuture sendReminderMessage() { DefaultMessageDto myMsg = DefaultMessageDto.createDefaultMessageDto("text", "바로 확인하기", "https://www.naver.com", "펀딩 남은 기간이 2일 남았습니다!!"); String accessToken = HttpCallService.accessToken; return messageService.sendMessageToMe(accessToken, myMsg); } - public boolean sendMessageToFriends() { + @Async + @Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 5000)) + //메서드가 실패할 경우 최대 3번까지 재시도, 각 재시도 사이에 5초의 지연을 둠 + //예외(Exception.class)가 발생할 경우 최대 3회까지 자동으로 재시도 +// @Scheduled(cron = "0 0 0 * * ?") // 매일 00:00 작업 실행 + @Scheduled(cron = "*/5 * * * * *") // 5초마다 작업 실행 + public CompletableFuture sendMessageToFriends() { DefaultMessageDto myMsg = DefaultMessageDto.createDefaultMessageDto("text", "버튼 버튼", "https://www.naver.com", "내가 지금 생각하고 있는 것은??"); String accessToken = HttpCallService.accessToken; - List friendUuids = Arrays.asList("친구의 UUID 여기에 추가"); // TODO: 실제로는 적절한 UUID 목록을 제공해야 합니다. + log.info("----------------------친구한테 메시지 보내기 성공!!!----------------------"); + List friendUuids = Arrays.asList( + "aFtpXm1ZaVtuQnRMeUp9Tn5PY1JiV2JRaF8z"); // TODO: 실제로는 적절한 UUID 목록을 제공해야 합니다. return messageService.sendMessageToFriends(accessToken, myMsg, friendUuids); } + + @Recover + public boolean recover(Exception e) { + log.error("sendMessageToFriends 메서드가 최대 재시도 횟수를 초과하여 실패했습니다.", e); + return false; + } } diff --git a/src/main/java/kcs/funding/fundingboost/api/service/MessageService.java b/src/main/java/kcs/funding/fundingboost/api/service/MessageService.java index ad71f771..6dac16b0 100644 --- a/src/main/java/kcs/funding/fundingboost/api/service/MessageService.java +++ b/src/main/java/kcs/funding/fundingboost/api/service/MessageService.java @@ -1,6 +1,7 @@ package kcs.funding.fundingboost.api.service; import java.util.List; +import java.util.concurrent.CompletableFuture; import kcs.funding.fundingboost.api.dto.DefaultMessageDto; import lombok.extern.slf4j.Slf4j; import org.json.JSONArray; @@ -24,12 +25,13 @@ public class MessageService extends HttpCallService { private static final String SUCCESS_CODE = "0"; //kakao api에서 return해주는 success code 값 - public boolean sendMessageToMe(String accessToken, DefaultMessageDto msgDto) { + public CompletableFuture sendMessageToMe(String accessToken, DefaultMessageDto msgDto) { MultiValueMap parameters = createMessageParameters(msgDto, null); return sendMessage(MSG_SEND_TO_ME_URL, accessToken, parameters); } - public boolean sendMessageToFriends(String accessToken, DefaultMessageDto msgDto, List uuids) { + public CompletableFuture sendMessageToFriends(String accessToken, DefaultMessageDto msgDto, + List uuids) { JSONArray receiverUuids = new JSONArray(uuids); MultiValueMap parameters = createMessageParameters(msgDto, receiverUuids.toString()); return sendMessage(MSG_SEND_TO_FRIENDS_URL, accessToken, parameters); @@ -45,12 +47,21 @@ private MultiValueMap createMessageParameters(DefaultMessageDto return parameters; } - private boolean sendMessage(String url, String accessToken, MultiValueMap parameters) { - HttpHeaders headers = createHeaders(accessToken); - HttpEntity> requestEntity = new HttpEntity<>(parameters, headers); - RestTemplate restTemplate = new RestTemplate(); - ResponseEntity response = restTemplate.postForEntity(url, requestEntity, String.class); - return processResponse(response.getBody()); + private CompletableFuture sendMessage(String url, String accessToken, + MultiValueMap parameters) { + return CompletableFuture.supplyAsync(() -> { + HttpHeaders headers = createHeaders(accessToken); + HttpEntity> requestEntity = new HttpEntity<>(parameters, headers); + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response; + try { + response = restTemplate.postForEntity(url, requestEntity, String.class); + return processResponse(response.getBody()); + } catch (Exception e) { + // 로그 기록, 에러 처리 + return false; + } + }); } private HttpHeaders createHeaders(String accessToken) { From 968faf0e48fef13a0daecf7837050cea32fdd1e8 Mon Sep 17 00:00:00 2001 From: JHyun0302 Date: Tue, 28 May 2024 23:50:54 +0900 Subject: [PATCH 8/8] =?UTF-8?q?:bug:=20[Fix]=20Async=20=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20return=20Type=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/KakaoContoller.java | 24 ++++-------------- .../api/service/CustomMessageService.java | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java b/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java index f919540c..39809192 100644 --- a/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java +++ b/src/main/java/kcs/funding/fundingboost/api/controller/KakaoContoller.java @@ -1,11 +1,8 @@ package kcs.funding.fundingboost.api.controller; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import kcs.funding.fundingboost.api.service.CustomMessageService; import kcs.funding.fundingboost.api.service.KakaoService; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -47,25 +44,14 @@ public String logout() { } @GetMapping("/send/me") - public CompletableFuture sendMyMessage() { - return customMessageService.sendReminderMessage(); + public ResponseEntity sendMyMessage() { + customMessageService.sendReminderMessage(); + return ResponseEntity.accepted().body("나에게 메시지 전송을 요청했습니다. 처리 중입니다."); } @GetMapping("/send/friends") public ResponseEntity sendMessageToFriends() { - CompletableFuture resultFuture = customMessageService.sendMessageToFriends(); - boolean result; - try { - result = resultFuture.get(); - } catch (InterruptedException | ExecutionException exception) { - Thread.currentThread().interrupt(); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("메시지 전송 실패"); - } - - if (result) { - return ResponseEntity.ok().body("친구들에게 메세지 전송 성공"); - } else { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("메시지 전송 실패"); - } + customMessageService.sendMessageToFriends(); + return ResponseEntity.accepted().body("친구들에게 메시지 전송을 요청했습니다. 처리 중입니다."); } } diff --git a/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java b/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java index 639155c2..25de710b 100644 --- a/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java +++ b/src/main/java/kcs/funding/fundingboost/api/service/CustomMessageService.java @@ -25,11 +25,19 @@ public class CustomMessageService { //예외(Exception.class)가 발생할 경우 최대 3회까지 자동으로 재시도 // @Scheduled(cron = "0 0 0 * * ?") // 매일 00:00 작업 실행 @Scheduled(cron = "*/5 * * * * *") // 5초마다 작업 실행 - public CompletableFuture sendReminderMessage() { + public void sendReminderMessage() { DefaultMessageDto myMsg = DefaultMessageDto.createDefaultMessageDto("text", "바로 확인하기", "https://www.naver.com", "펀딩 남은 기간이 2일 남았습니다!!"); String accessToken = HttpCallService.accessToken; - return messageService.sendMessageToMe(accessToken, myMsg); + CompletableFuture result = messageService.sendMessageToMe(accessToken, myMsg); + + result.thenAccept(success -> { + if (success) { + log.info("메시지 전송 성공"); + } else { + log.warn("메시지 전송 실패"); + } + }); } @Async @@ -38,14 +46,23 @@ public CompletableFuture sendReminderMessage() { //예외(Exception.class)가 발생할 경우 최대 3회까지 자동으로 재시도 // @Scheduled(cron = "0 0 0 * * ?") // 매일 00:00 작업 실행 @Scheduled(cron = "*/5 * * * * *") // 5초마다 작업 실행 - public CompletableFuture sendMessageToFriends() { + public void sendMessageToFriends() { DefaultMessageDto myMsg = DefaultMessageDto.createDefaultMessageDto("text", "버튼 버튼", "https://www.naver.com", "내가 지금 생각하고 있는 것은??"); String accessToken = HttpCallService.accessToken; log.info("----------------------친구한테 메시지 보내기 성공!!!----------------------"); List friendUuids = Arrays.asList( "aFtpXm1ZaVtuQnRMeUp9Tn5PY1JiV2JRaF8z"); // TODO: 실제로는 적절한 UUID 목록을 제공해야 합니다. - return messageService.sendMessageToFriends(accessToken, myMsg, friendUuids); + CompletableFuture result = messageService.sendMessageToFriends(accessToken, myMsg, + friendUuids); + + result.thenAccept(success -> { + if (success) { + log.info("메시지 전송 성공"); + } else { + log.warn("메시지 전송 실패"); + } + }); } @Recover