Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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"
Expand All @@ -57,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'
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/kcs/funding/fundingboost/api/common/Const.java
Original file line number Diff line number Diff line change
@@ -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 = "";
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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.http.ResponseEntity;
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;

private final CustomMessageService customMessageService;

@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();
}

@GetMapping("/send/me")
public ResponseEntity<String> sendMyMessage() {
customMessageService.sendReminderMessage();
return ResponseEntity.accepted().body("나에게 메시지 전송을 요청했습니다. 처리 중입니다.");
}

@GetMapping("/send/friends")
public ResponseEntity<String> sendMessageToFriends() {
customMessageService.sendMessageToFriends();
return ResponseEntity.accepted().body("친구들에게 메시지 전송을 요청했습니다. 처리 중입니다.");
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package kcs.funding.fundingboost.api.service;

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;

@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 void sendReminderMessage() {
DefaultMessageDto myMsg = DefaultMessageDto.createDefaultMessageDto("text", "바로 확인하기",
"https://www.naver.com", "펀딩 남은 기간이 2일 남았습니다!!");
String accessToken = HttpCallService.accessToken;
CompletableFuture<Boolean> result = messageService.sendMessageToMe(accessToken, myMsg);

result.thenAccept(success -> {
if (success) {
log.info("메시지 전송 성공");
} else {
log.warn("메시지 전송 실패");
}
});
}

@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 void sendMessageToFriends() {
DefaultMessageDto myMsg = DefaultMessageDto.createDefaultMessageDto("text", "버튼 버튼",
"https://www.naver.com", "내가 지금 생각하고 있는 것은??");
String accessToken = HttpCallService.accessToken;
log.info("----------------------친구한테 메시지 보내기 성공!!!----------------------");
List<String> friendUuids = Arrays.asList(
"aFtpXm1ZaVtuQnRMeUp9Tn5PY1JiV2JRaF8z"); // TODO: 실제로는 적절한 UUID 목록을 제공해야 합니다.
CompletableFuture<Boolean> result = messageService.sendMessageToFriends(accessToken, myMsg,
friendUuids);

result.thenAccept(success -> {
if (success) {
log.info("메시지 전송 성공");
} else {
log.warn("메시지 전송 실패");
}
});
}

@Recover
public boolean recover(Exception e) {
log.error("sendMessageToFriends 메서드가 최대 재시도 횟수를 초과하여 실패했습니다.", e);
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
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.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

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);
}

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;
}

/**
* Http 요청 클라이언트 객체 생성 method
*
* @ param Map<String,String> 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<String> httpRequest(String url, HttpMethod method, HttpEntity<?> entity) {
RestTemplate restTemplate = new RestTemplate();
return restTemplate.exchange(url, method, entity, String.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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());
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());
}
}
Loading