-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
코드 리뷰~ #4
base: main
Are you sure you want to change the base?
코드 리뷰~ #4
Changes from 3 commits
ba7c924
5e95967
2ff3936
24cbdf4
a3e383e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.DS_Store | ||
build/ | ||
out/ | ||
.gradle | ||
.idea | ||
|
||
src/main/resources/application-db.yml | ||
src/main/resources/application.properties | ||
src/main/resources/application.yml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package kr.where.backend; | ||
|
||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.retry.annotation.EnableRetry; | ||
import org.springframework.web.servlet.config.annotation.CorsRegistry; | ||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||
|
||
@EnableRetry | ||
@SpringBootApplication | ||
public class BackendApplication { | ||
|
||
public static void main(String[] args) { | ||
SpringApplication.run(BackendApplication.class, args); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package kr.where.backend.api; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import kr.where.backend.api.exception.RequestException; | ||
import kr.where.backend.api.http.HttpHeader; | ||
import kr.where.backend.api.http.HttpResponse; | ||
import kr.where.backend.api.http.Uri; | ||
import kr.where.backend.api.http.UriBuilder; | ||
import kr.where.backend.api.json.hane.Hane; | ||
import kr.where.backend.api.json.hane.HaneRequestDto; | ||
import kr.where.backend.api.json.hane.HaneResponseDto; | ||
import kr.where.backend.group.entity.Group; | ||
import kr.where.backend.group.entity.GroupMember; | ||
import kr.where.backend.member.Member; | ||
import kr.where.backend.member.MemberRepository; | ||
import kr.where.backend.member.exception.MemberException.NoMemberException; | ||
import kr.where.backend.oauthtoken.OAuthTokenService; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Slf4j | ||
@Service | ||
@RequiredArgsConstructor | ||
public class HaneApiService { | ||
private final OAuthTokenService oauthTokenService; | ||
private final MemberRepository memberRepository; | ||
private static final String HANE_TOKEN = "hane"; | ||
|
||
/** | ||
* hane api 호출하여 in, out state 반환 | ||
*/ | ||
public Hane getHaneInfo(final String name, final String token) { | ||
try { | ||
return JsonMapper.mapping(HttpResponse.getMethod(HttpHeader.requestHaneInfo(token), UriBuilder.hane(name)), | ||
Hane.class); | ||
} catch (final RequestException exception) { | ||
log.warn("[hane] {} : {}", name, exception.toString()); | ||
return new Hane(); | ||
} | ||
} | ||
|
||
@Transactional | ||
public void updateInClusterForMainPage(final Member member) { | ||
if (member.isAgree()) { | ||
member.setInCluster(getHaneInfo(member.getIntraName(), oauthTokenService.findAccessToken(HANE_TOKEN))); | ||
|
||
log.info("[scheduling] : {}의 imacLocation이 변경되었습니다", member.getIntraName()); | ||
} | ||
} | ||
|
||
public List<HaneResponseDto> getHaneListInfo(final List<HaneRequestDto> haneRequestDto, final String token) { | ||
try { | ||
return JsonMapper.mappings(HttpResponse.postMethod(HttpHeader.requestHaneListInfo(haneRequestDto, token), | ||
UriBuilder.hane(Uri.HANE_INFO_LIST.getValue())), HaneResponseDto[].class); | ||
} catch (final RequestException exception) { | ||
log.warn("[hane] : {}", exception.toString()); | ||
return new ArrayList<>(); | ||
} | ||
} | ||
|
||
@Transactional | ||
public void updateMemberInOrOutState(final Member member, final String state) { | ||
member.setInCluster(Hane.create(state)); | ||
} | ||
|
||
@Transactional | ||
public void updateMyOwnMemberState(final List<GroupMember> friends) { | ||
log.info("[hane] : inCluster 업데이트 스케줄링을 시작합니다!"); | ||
final List<HaneResponseDto> responses = getHaneListInfo( | ||
friends | ||
.stream() | ||
.filter(m -> m.getMember().isPossibleToUpdateInCluster()) | ||
.map(m -> new HaneRequestDto(m.getMember().getIntraName())) | ||
.toList(), | ||
oauthTokenService.findAccessToken(HANE_TOKEN)); | ||
|
||
responses.stream() | ||
.filter(response -> response.getInoutState() != null) | ||
.forEach(response -> { | ||
this.updateMemberInOrOutState( | ||
memberRepository.findByIntraName(response.getLogin()) | ||
.orElseThrow(NoMemberException::new), | ||
response.getInoutState()); | ||
log.info("[hane] : {}의 inCluster가 변경되었습니다", response.getLogin()); | ||
}); | ||
log.info("[hane] : inCluster 업데이트 스케줄링을 끝냅니다!"); | ||
} | ||
|
||
@Transactional | ||
public void updateGroupMemberState(final Group group) { | ||
log.info("[hane] : 메인 페이지 새로고침으로 인한 inCluster 업데이트를 시작합니다!"); | ||
final List<HaneResponseDto> responses = getHaneListInfo( | ||
group | ||
.getGroupMembers() | ||
.stream() | ||
.filter(m -> m.getMember().isPossibleToUpdateInCluster()) | ||
.map(m -> new HaneRequestDto(m.getMember().getIntraName())) | ||
.toList(), | ||
oauthTokenService.findAccessToken(HANE_TOKEN) | ||
); | ||
responses.stream() | ||
.filter(response -> response.getInoutState() != null) | ||
.forEach(response -> { | ||
updateMemberInOrOutState( | ||
memberRepository.findByIntraName(response.getLogin()) | ||
.orElseThrow(NoMemberException::new), | ||
response.getInoutState()); | ||
log.info("[hane] : {}의 inCluster가 변경되었습니다", response.getLogin()); | ||
}); | ||
log.info("[hane] : 메인 페이지 새로고침으로 인한 inCluster 업데이트를 끝냅니다!"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package kr.where.backend.api; | ||
|
||
import java.util.List; | ||
import kr.where.backend.api.exception.RequestException.TooManyRequestException; | ||
import kr.where.backend.api.http.HttpHeader; | ||
import kr.where.backend.api.http.HttpResponse; | ||
import kr.where.backend.api.http.UriBuilder; | ||
import kr.where.backend.api.json.CadetPrivacy; | ||
import kr.where.backend.api.json.Cluster; | ||
import kr.where.backend.exception.CustomException; | ||
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.stereotype.Service; | ||
|
||
@Slf4j | ||
@Service | ||
@RequiredArgsConstructor | ||
public class IntraApiService { | ||
|
||
private static final String END_DELIMITER = "z"; | ||
|
||
/** | ||
* 특정 카텟의 정보 반환 | ||
*/ | ||
@Retryable(retryFor = TooManyRequestException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Retryable 어노테이션을 사용하고 3번의 재시도(maxAttempts)를 하고도 문제가 있으면 어떻게 작동하나요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3번의 재시도에도 문제가 발생한거면 우리 서비스의 문제보다는 |
||
public CadetPrivacy getCadetPrivacy(final String token, final String name) { | ||
return JsonMapper | ||
.mapping( | ||
HttpResponse.getMethod(HttpHeader.request42Info(token), UriBuilder.cadetInfo(name)), | ||
CadetPrivacy.class); | ||
} | ||
|
||
/** | ||
* index page 별로 image 반환 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. index page가 정확히 무엇을 의미하는지 궁금합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 42API는 pagable의 request를 지원하지 않아서 직접 우리가 page를 설정하여, 요청해야합니다. |
||
*/ | ||
@Retryable(retryFor = TooManyRequestException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) | ||
public List<CadetPrivacy> getCadetsImage(final String token, final int page) { | ||
return JsonMapper | ||
.mappings( | ||
HttpResponse.getMethod(HttpHeader.request42Info(token), UriBuilder.image(page)), | ||
CadetPrivacy[].class); | ||
} | ||
|
||
/** | ||
* 클러스터 아이맥에 로그인 한 카뎃 index page 별로 반환 | ||
*/ | ||
@Retryable(retryFor = TooManyRequestException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) | ||
public List<Cluster> getCadetsInCluster(final String token, final int page) { | ||
return JsonMapper | ||
.mappings( | ||
HttpResponse.getMethod(HttpHeader.request42Info(token), UriBuilder.loginCadet(page)), | ||
Cluster[].class); | ||
} | ||
|
||
/** | ||
* 5분 이내 클러스터 아이맥에 로그아웃 한 카뎃 index page 별로 반환 | ||
*/ | ||
@Retryable(retryFor = TooManyRequestException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) | ||
public List<Cluster> getLogoutCadetsLocation(final String token, final int page) { | ||
return JsonMapper | ||
.mappings(HttpResponse.getMethod(HttpHeader.request42Info(token), | ||
UriBuilder.loginBeforeFiveMinute(page, false)), Cluster[].class); | ||
} | ||
|
||
/** | ||
* 5분 이내 클러스터 아이맥에 로그인 한 카뎃 index page 별로 반환 | ||
*/ | ||
@Retryable(retryFor = TooManyRequestException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) | ||
public List<Cluster> getLoginCadetsLocation(final String token, final int page) { | ||
return JsonMapper | ||
.mappings(HttpResponse.getMethod(HttpHeader.request42Info(token), | ||
UriBuilder.loginBeforeFiveMinute(page, true)), Cluster[].class); | ||
} | ||
|
||
/** | ||
* keyWord 부터 end 까지 intra id를 가진 카뎃 10명의 정보 반환 | ||
*/ | ||
@Retryable(retryFor = TooManyRequestException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) | ||
public List<CadetPrivacy> getCadetsInRange(final String token, final String keyWord, final int page) { | ||
return JsonMapper | ||
.mappings(HttpResponse.getMethod( | ||
HttpHeader.request42Info(token), | ||
UriBuilder.searchCadets(keyWord, keyWord + END_DELIMITER, page)), | ||
CadetPrivacy[].class); | ||
} | ||
|
||
/** | ||
* 요청 3번 실패 시 실행되는 메서드 | ||
*/ | ||
@Recover | ||
public CadetPrivacy fallbackCadetPrivacy(final CustomException exception) { | ||
log.warn("[IntraApiService] CadetPrivacy method"); | ||
throw exception; | ||
} | ||
|
||
@Recover | ||
public List<CadetPrivacy> fallbackCadetsPrivacy(final CustomException exception) { | ||
log.warn("[IntraApiService] List<CadetPrivacy> method"); | ||
throw exception; | ||
} | ||
|
||
@Recover | ||
public List<Cluster> fallbackClusterList(final CustomException exception) { | ||
log.warn("[IntraApiService] List<Cluster> method"); | ||
throw exception; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package kr.where.backend.api; | ||
|
||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
import kr.where.backend.api.exception.JsonException; | ||
|
||
public class JsonMapper { | ||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서 ObjectMapper의 개체가 대문자와 스네이크케이스로 작성된 이유가 뭔가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 자바 컨벤션은 상수로 정의할 경우 스네이크 케이스를 사용합니다! |
||
|
||
public static <T> T mapping(final String jsonBody, final Class<T> classType) { | ||
try { | ||
return OBJECT_MAPPER.readValue(jsonBody, classType); | ||
} catch (JsonProcessingException e) { | ||
throw new JsonException.DeserializeException(); | ||
} | ||
} | ||
|
||
public static <T> List<T> mappings(final String jsonBody, final Class<T[]> classType) { | ||
try { | ||
return Arrays.asList(OBJECT_MAPPER.readValue(jsonBody, classType)); | ||
} catch (JsonProcessingException e) { | ||
System.out.println(e.getMessage()); | ||
throw new JsonException.DeserializeException(); | ||
} | ||
} | ||
|
||
public static String convertJsonForm(final List<?> requestBody) { | ||
try { | ||
return OBJECT_MAPPER.writeValueAsString(requestBody); | ||
} catch(JsonProcessingException e) { | ||
throw new JsonException.DeserializeException(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package kr.where.backend.api; | ||
|
||
import kr.where.backend.api.exception.RequestException.TooManyRequestException; | ||
import kr.where.backend.api.http.HttpHeader; | ||
import kr.where.backend.api.http.HttpResponse; | ||
import kr.where.backend.api.http.UriBuilder; | ||
import kr.where.backend.api.json.OAuthTokenDto; | ||
import kr.where.backend.exception.CustomException; | ||
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.stereotype.Service; | ||
|
||
@Slf4j | ||
@Service | ||
@RequiredArgsConstructor | ||
public class TokenApiService { | ||
|
||
/** | ||
* intra 에 oAuth token 발급 요청 후 토큰을 반환 | ||
*/ | ||
@Retryable(retryFor = TooManyRequestException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) | ||
public OAuthTokenDto getOAuthToken(final String code) { | ||
return JsonMapper | ||
.mapping(HttpResponse.postMethod(HttpHeader.requestToken(code), UriBuilder.token()), | ||
OAuthTokenDto.class); | ||
} | ||
|
||
/** | ||
* refreshToken 으로 intra 에 oAuth token 발급 요청 후 토큰 반환 | ||
*/ | ||
@Retryable(retryFor = TooManyRequestException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) | ||
public OAuthTokenDto getOAuthTokenWithRefreshToken(final String refreshToken) { | ||
return JsonMapper | ||
.mapping(HttpResponse.postMethod( | ||
HttpHeader.requestAccessToken(refreshToken), UriBuilder.token()), | ||
OAuthTokenDto.class); | ||
} | ||
|
||
/** | ||
* 요청 3번 실패 시 실행되는 메서드 | ||
*/ | ||
@Recover | ||
public OAuthTokenDto fallback(final CustomException exception) { | ||
log.info("[TokenApiService] back token 발급에 실패하였습니다"); | ||
throw exception; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package kr.where.backend.api.exception; | ||
|
||
import kr.where.backend.exception.ErrorCode; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
@AllArgsConstructor | ||
@Getter | ||
public enum JsonErrorCode implements ErrorCode { | ||
DESERIALIZE_FAIL(1200, "json 맵핑에 실패 했습니다."); | ||
|
||
private final int errorCode; | ||
private final String errorMessage; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package kr.where.backend.api.exception; | ||
|
||
import kr.where.backend.exception.CustomException; | ||
|
||
public class JsonException extends CustomException { | ||
public JsonException(final JsonErrorCode jsonErrorCode) { | ||
super(jsonErrorCode); | ||
} | ||
|
||
public static class DeserializeException extends JsonException { | ||
public DeserializeException() { | ||
super(JsonErrorCode.DESERIALIZE_FAIL); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package kr.where.backend.api.exception; | ||
|
||
import kr.where.backend.exception.ErrorCode; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
@AllArgsConstructor | ||
@Getter | ||
public enum RequestErrorCode implements ErrorCode { | ||
UNAUTHORIZED(3000, "Unauthorized 권한이 없습니다."), | ||
TOO_MANY_REQUEST(3001, "42API 요청 횟수를 초과하였습니다."), | ||
SERVER_ERROR(3002, "외부 API 서버 에러입니다."), | ||
BAD_REQUEST(3003, "잘못된 요청입니다"); | ||
|
||
private final int errorCode; | ||
private final String errorMessage; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
빈 배열을 리턴하는 이유는 단순히 반환값을 맞추려고 일까요??
상위메서드에서 호출하면 빈 배열이니 밖에서는 에러가 안뜨는게 목적이였을까요??
아니면 자바 문법상 반환값이 있는 메서드들은 catch에서도 리턴을 꼭 해야하나요???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이것은 의도된것입니다.
자바 문법상 catch 했을 경우, throw를 하거나, return을 하거나 자유롭게 할 수 있습니다.
하지만 이 method는 외부 API 요청을 하네에게 하는 것이기에 우리 서비스 단의 에러라고 판단하지 않습니다.
그래서 하네측에서 에러가 날 경우, 빈 문자열을 반환하는 것입니다 그래서 log도 warn으로 설정합니다.