-
Notifications
You must be signed in to change notification settings - Fork 2
142 refactor solvedac client/service 역할 분리 #143
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
Merged
The head ref may contain hidden characters: "142-refactor-solvedac-clientservice-\uC5ED\uD560-\uBD84\uB9AC"
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
869161e
solvedac: Client/Service 역할 분리 및 dead code 정리
ryuwldnjs 9eacddc
solvedac: Service 코드 리팩토링 및 패키지 구조 개선
ryuwldnjs 8a57fa9
refactor: BojVerificationFacade import 경로 변경 (api → client)
ryuwldnjs 27d9a09
solvedac: Service 네이밍 개선 및 패키지 구조 정리
ryuwldnjs 6bc0dd6
solvedac: fix ResponseType 파라미터명 lowerCamelCase 위반
ryuwldnjs f7d262b
solvedac: 쿼리 구분자 +로 통일
ryuwldnjs 5649dcc
Merge branch 'main' into 142-refactor-solvedac-clientservice-역할-분리
ryuwldnjs c9325f1
solvedac: fix ProblemService 머지 충돌 후 잔여 import 정리
ryuwldnjs bb71a50
member: fix MemberServiceTest 생성자 인자 누락
ryuwldnjs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
128 changes: 128 additions & 0 deletions
128
src/main/java/com/ryu/studyhelper/infrastructure/solvedac/SolvedAcClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| package com.ryu.studyhelper.infrastructure.solvedac; | ||
|
|
||
| import com.ryu.studyhelper.common.enums.CustomResponseStatus; | ||
| import com.ryu.studyhelper.common.exception.CustomException; | ||
| import com.ryu.studyhelper.infrastructure.solvedac.client.SolvedAcRestClient; | ||
| import com.ryu.studyhelper.infrastructure.solvedac.dto.SolvedAcUserBioResponse; | ||
| import com.ryu.studyhelper.infrastructure.solvedac.dto.ProblemInfo; | ||
| import com.ryu.studyhelper.infrastructure.solvedac.dto.ProblemSearchResponse; | ||
| import com.ryu.studyhelper.infrastructure.solvedac.dto.SolvedAcUserResponse; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.client.HttpClientErrorException; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.StringJoiner; | ||
|
|
||
| @Component | ||
| @Slf4j | ||
| @RequiredArgsConstructor | ||
| public class SolvedAcClient { | ||
| private static final int MIN_LEVEL = 1; | ||
| private static final int MAX_LEVEL = 30; | ||
| private static final int MIN_SOLVED_COUNT = 1000; | ||
|
|
||
| private final SolvedAcRestClient solvedAcRestClient; | ||
|
|
||
| public SolvedAcUserResponse getUserInfo(String handle) { | ||
| try { | ||
| return solvedAcRestClient.getUserInfo(handle); | ||
| } catch (HttpClientErrorException.NotFound e) { | ||
| log.info("solved.ac user not found: {}", handle); | ||
| throw new CustomException(CustomResponseStatus.SOLVED_AC_USER_NOT_FOUND); | ||
| } catch (Exception e) { | ||
| log.error("Failed to fetch user info from solved.ac: {}", handle, e); | ||
| throw new CustomException(CustomResponseStatus.SOLVED_AC_API_ERROR); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 풀지 않은 문제를 추천합니다. | ||
| * @param handles 추천할 사용자 핸들 목록 | ||
| * @param count 추천할 문제 개수 | ||
| * @param minLevel 최소 난이도 (1~30, null이면 1) | ||
| * @param maxLevel 최대 난이도 (1~30, null이면 30) | ||
| * @param tagKeys 포함할 태그 키 목록 (null 또는 빈 목록이면 필터 없음) | ||
| */ | ||
| public List<ProblemInfo> recommendUnsolvedProblems(List<String> handles, int count, | ||
| Integer minLevel, Integer maxLevel, | ||
| List<String> tagKeys) { | ||
| try { | ||
| String query = buildRecommendQuery(handles, minLevel, maxLevel, tagKeys); | ||
| log.debug("solved.ac 추천 쿼리: {}", query); | ||
|
|
||
| ProblemSearchResponse response = solvedAcRestClient.searchProblems(query, "random", "asc"); | ||
| if (response.items() == null) { | ||
| return List.of(); | ||
| } | ||
|
|
||
| return response.items().stream() | ||
| .map(ProblemInfo::withUrl) | ||
| .limit(count) | ||
| .toList(); | ||
| } catch (Exception e) { | ||
| log.error("Failed to recommend problems for handles: {}", handles, e); | ||
| throw new CustomException(CustomResponseStatus.SOLVED_AC_API_ERROR); | ||
| } | ||
| } | ||
|
|
||
| private String buildRecommendQuery(List<String> handles, Integer minLevel, Integer maxLevel, List<String> tagKeys) { | ||
| List<String> conditions = new ArrayList<>(); | ||
|
|
||
| // 난이도 범위 | ||
| int min = (minLevel != null && minLevel >= MIN_LEVEL && minLevel <= MAX_LEVEL) ? minLevel : MIN_LEVEL; | ||
| int max = (maxLevel != null && maxLevel >= MIN_LEVEL && maxLevel <= MAX_LEVEL) ? maxLevel : MAX_LEVEL; | ||
| conditions.add(String.format("*%d..%d", Math.min(min, max), Math.max(min, max))); | ||
|
|
||
| // 기본 조건: 1000명 이상 풀이, 한국어 | ||
| conditions.add("s#" + MIN_SOLVED_COUNT + ".."); | ||
| conditions.add("lang:ko"); | ||
|
|
||
| // 태그 필터 (선택) | ||
| if (tagKeys != null && !tagKeys.isEmpty()) { | ||
| conditions.add(buildTagFilter(tagKeys)); | ||
| } | ||
|
|
||
| // 사용자 제외 조건 | ||
| handles.forEach(h -> conditions.add("!s@" + h)); | ||
|
|
||
| return String.join("+", conditions); | ||
| } | ||
|
|
||
| private String buildTagFilter(List<String> tagKeys) { | ||
| StringJoiner joiner = new StringJoiner("|", "(", ")"); | ||
| tagKeys.forEach(key -> joiner.add("tag:" + key)); | ||
| return joiner.toString(); | ||
| } | ||
|
|
||
| /** | ||
| * 백준 핸들 인증용 사용자 bio 조회 | ||
| */ | ||
| public SolvedAcUserBioResponse getUserBio(String handle) { | ||
| try { | ||
| return solvedAcRestClient.getUserBio(handle); | ||
| } catch (HttpClientErrorException.NotFound e) { | ||
| log.info("solved.ac user not found: {}", handle); | ||
| throw new CustomException(CustomResponseStatus.SOLVED_AC_USER_NOT_FOUND); | ||
| } catch (Exception e) { | ||
| log.error("Failed to fetch user bio from solved.ac: {}", handle, e); | ||
| throw new CustomException(CustomResponseStatus.SOLVED_AC_API_ERROR); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 특정 사용자가 특정 문제를 풀었는지 확인 | ||
| */ | ||
| public boolean hasUserSolvedProblem(String handle, Long problemId) { | ||
| try { | ||
| String query = "id:" + problemId + "+s@" + handle; | ||
| ProblemSearchResponse response = solvedAcRestClient.searchProblems(query, "id", "asc"); | ||
| return response.items() != null && !response.items().isEmpty(); | ||
| } catch (Exception e) { | ||
| log.error("Failed to check if user {} solved problem {}", handle, problemId, e); | ||
| throw new CustomException(CustomResponseStatus.SOLVED_AC_API_ERROR); | ||
| } | ||
| } | ||
| } | ||
67 changes: 67 additions & 0 deletions
67
src/main/java/com/ryu/studyhelper/infrastructure/solvedac/client/SolvedAcRestClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package com.ryu.studyhelper.infrastructure.solvedac.client; | ||
|
|
||
| import com.ryu.studyhelper.infrastructure.solvedac.dto.SolvedAcUserBioResponse; | ||
| import com.ryu.studyhelper.infrastructure.solvedac.dto.ProblemSearchResponse; | ||
| import com.ryu.studyhelper.infrastructure.solvedac.dto.SolvedAcUserResponse; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.client.RestClient; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| @Component | ||
| public class SolvedAcRestClient { | ||
| private final RestClient rest; | ||
|
|
||
| public SolvedAcRestClient() { | ||
| this.rest = RestClient.builder() | ||
| .baseUrl("https://solved.ac/api/v3") | ||
| .defaultHeader("User-Agent", "studyhelper/1.0") | ||
| .build(); | ||
| } | ||
|
|
||
|
|
||
|
|
||
| private <T> T get(String path, Map<String, String> params, Class<T> responseType) { | ||
| return rest.get() | ||
| .uri(b -> { | ||
| var ub = b.path(path); | ||
| if (params != null) params.forEach(ub::queryParam); | ||
| return ub.build(); // 인코딩/결합은 RestClient에 맡김 | ||
| }) | ||
| .accept(MediaType.APPLICATION_JSON) | ||
| .retrieve() | ||
| .body(responseType); | ||
| } | ||
|
|
||
|
|
||
| public SolvedAcUserResponse getUserInfo(String handle) { | ||
| return get("/user/show", Map.of("handle", handle), SolvedAcUserResponse.class); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * 문제 검색 API 호출 (순수 HTTP 호출만 담당) | ||
| * @param query 검색 쿼리 | ||
| * @param sort 정렬 기준 (id, level, title, solved, random 등) | ||
| * @param direction 정렬 방향 (asc, desc) | ||
| * @return API 응답 원본 | ||
| */ | ||
| public ProblemSearchResponse searchProblems(String query, String sort, String direction) { | ||
| return get("/search/problem", Map.of( | ||
| "query", query, | ||
| "sort", sort, | ||
| "direction", direction | ||
| ), ProblemSearchResponse.class); | ||
| } | ||
|
|
||
| /** | ||
| * 백준 핸들 인증용 사용자 정보 조회 (bio 포함) | ||
| * @param handle 백준 핸들 | ||
| * @return 핸들과 bio 정보 | ||
| */ | ||
| public SolvedAcUserBioResponse getUserBio(String handle) { | ||
| return get("/user/show", Map.of("handle", handle), SolvedAcUserBioResponse.class); | ||
| } | ||
|
|
||
| } |
2 changes: 1 addition & 1 deletion
2
...studyhelper/solvedac/dto/ProblemInfo.java → ...rastructure/solvedac/dto/ProblemInfo.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...r/solvedac/dto/ProblemSearchResponse.java → ...e/solvedac/dto/ProblemSearchResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...yhelper/solvedac/dto/SolvedAcTagInfo.java → ...ructure/solvedac/dto/SolvedAcTagInfo.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/main/java/com/ryu/studyhelper/infrastructure/solvedac/dto/SolvedAcUserBioResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.ryu.studyhelper.infrastructure.solvedac.dto; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
|
||
| /** | ||
| * solved.ac 사용자 bio 응답 DTO | ||
| */ | ||
| public record SolvedAcUserBioResponse( | ||
| @JsonProperty("handle") String handle, | ||
| @JsonProperty("bio") String bio | ||
| ) {} |
2 changes: 1 addition & 1 deletion
2
...er/solvedac/dto/SolvedAcUserResponse.java → ...re/solvedac/dto/SolvedAcUserResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...dac/dto/SolvedAcVerificationResponse.java → ...dac/dto/SolvedAcVerificationResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
src/main/java/com/ryu/studyhelper/problem/dto/TeamProblemRecommendResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.