-
Notifications
You must be signed in to change notification settings - Fork 8
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
[BE-97] 지원 현황 및 면접 기록 조회 API 정렬 로직 개선, 검색 쿼리 추가 #274
Changes from 13 commits
1eedccd
d168a5f
3f06ec4
e27fc6f
9a05ba9
7500caa
e21cb07
f28b145
9a73ce8
005ddc3
1bbc0c0
6a12907
2600682
9591722
b395075
b4dc8b6
4b0bf42
a613304
dad5f5a
09c1d32
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 |
---|---|---|
|
@@ -45,6 +45,44 @@ public Map<String, Object> execute(String answerId) { | |
return qna; | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public AnswersResponseDto execute(Integer year, Integer page, String sortType, String searchKeyword) { | ||
PageInfo pageInfo = getPageInfo(year, page, searchKeyword); | ||
List<MongoAnswer> sortedResult = answerAdaptor.findByYearAndSearchKeyword(year, page, sortType, searchKeyword); | ||
|
||
List<Map<String, Object>> qnaMapList = getQnaMapListWithIdAndPassState(sortedResult); | ||
|
||
if (qnaMapList.isEmpty()) { | ||
return AnswersResponseDto.of(Collections.emptyList(), pageInfo); | ||
} | ||
return AnswersResponseDto.of(qnaMapList, pageInfo); | ||
} | ||
|
||
private List<Map<String, Object>> getQnaMapListWithIdAndPassState(List<MongoAnswer> sortedResult) { | ||
return sortedResult.stream().map(answer -> { | ||
Map<String, Object> qna = answer.getQna(); | ||
qna.put("id", answer.getId()); | ||
qna.put(PASS_STATE_KEY, answer.getApplicantStateOrDefault()); | ||
return qna; | ||
}).toList(); | ||
} | ||
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. getQnaMapListWithIdAndPassState 를 따로 나눈 이유가 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. 넵 맞습니다. |
||
|
||
@Override | ||
public PageInfo getPageInfo(Integer year, Integer page, String searchKeyword) { | ||
long totalCount = answerAdaptor.getTotalCountByYearAndSearchKeyword(year, searchKeyword); | ||
return new PageInfo(totalCount, page); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public List<MongoAnswer> execute(Integer page, Integer year, String sortType, String searchKeyword, List<String> applicantIds) { | ||
return answerAdaptor.findByYearAndSearchKeywordAndApplicantIds(page, year, sortType, searchKeyword, applicantIds); | ||
} | ||
|
||
@Override | ||
public List<MongoAnswer> execute(Integer year, String sortType, String searchKeyword, List<String> applicantIds) { | ||
return answerAdaptor.findByYearAndSearchKeywordAndApplicantIds(year, sortType, searchKeyword, applicantIds); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public AnswersResponseDto execute(Integer year, Integer page, String sortType) { | ||
PageInfo pageInfo = getPageInfo(year, page); | ||
|
@@ -182,13 +220,9 @@ public List<GetApplicantsStatusResponse> getApplicantsStatus(Integer year, Strin | |
} | ||
|
||
private List<Map<String, Object>> sortAndAddIds(List<MongoAnswer> result, String sortType) { | ||
sortHelper.sort(result, sortType); | ||
return result.stream().map( | ||
answer -> { | ||
Map<String, Object> qna = answer.getQna(); | ||
qna.put("id", answer.getId()); | ||
qna.put(PASS_STATE_KEY, answer.getApplicantStateOrDefault()); | ||
return qna; | ||
}).toList(); | ||
if (!result.isEmpty()) { | ||
sortHelper.sort(result, sortType); | ||
} | ||
return getQnaMapListWithIdAndPassState(result); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,7 +67,9 @@ public List<InterviewerResponseDto> findAll() { | |
@Override | ||
public List<InterviewerResponseDto> findAll(String sortType) { | ||
List<Interviewer> interviewers = interviewerLoadPort.findAll(); | ||
interviewerSortHelper.sort(interviewers, sortType); | ||
if (!interviewers.isEmpty()) { | ||
interviewerSortHelper.sort(interviewers, sortType); | ||
} | ||
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. interviewers가 null 이라면 즉, 빈 배열이라면 아래 return return interviewers.stream().map(InterviewerResponseDto::from).toList(); 에서 NPE가 발생 할 것 같은데 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. 실제로 지금 상황에서 interviewer 가 아무도 없을 때 500에러가 떠서 운영 환경에서 사용자는 아무 액션을 받지못함 (FE가 처리를 못하니까) 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. 확인했습니다! 해당 부분은 9591722 커밋에 반영했습니다. @Override
public List<InterviewerResponseDto> findAll(String sortType) {
List<Interviewer> interviewers = interviewerLoadPort.findAll();
if (interviewers != null && !interviewers.isEmpty()) {
interviewerSortHelper.sort(interviewers, sortType);
return interviewers.stream().map(InterviewerResponseDto::from).toList();
}
return Collections.emptyList();
} |
||
return interviewers.stream().map(InterviewerResponseDto::from).toList(); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
package com.econovation.recruit.api.record.service; | ||
|
||
import static com.econovation.recruit.utils.sort.SortHelper.paginateList; | ||
|
||
import com.econovation.recruit.api.applicant.usecase.ApplicantQueryUseCase; | ||
import com.econovation.recruit.api.record.dto.RecordsViewResponseDto; | ||
import com.econovation.recruit.api.record.usecase.RecordUseCase; | ||
|
@@ -16,8 +18,13 @@ | |
import com.econovation.recruitdomain.out.RecordLoadPort; | ||
import com.econovation.recruitdomain.out.RecordRecordPort; | ||
import com.econovation.recruitdomain.out.ScoreLoadPort; | ||
|
||
import java.util.*; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.Comparator; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
|
@@ -26,6 +33,7 @@ | |
@Service | ||
@RequiredArgsConstructor | ||
public class RecordService implements RecordUseCase { | ||
|
||
private final RecordRecordPort recordRecordPort; | ||
private final RecordLoadPort recordLoadPort; | ||
private final ScoreLoadPort scoreLoadPort; | ||
|
@@ -53,71 +61,153 @@ public List<Record> findAll() { | |
/** | ||
* 1. Newest // 시간 순 오름차순 2. Name // 이름순 오름차순 3. Object // 지원 분야별 오름차순 4. Score // 점수 내림차순 | ||
* | ||
* @return List<RecordResponseDto> // 지원자의 면접기록을 페이지별로 조회합니다 ( 이 화면에서는 Applicants,Scores, | ||
* Records를 모두 조회합니다 ) | ||
* @return List<RecordResponseDto> // 지원자의 면접기록을 페이지별로 조회합니다 ( 이 화면에서는 Applicants,Scores, Records를 모두 조회합니다 ) | ||
*/ | ||
@Override | ||
public RecordsViewResponseDto execute(Integer page, Integer year, String sortType) { | ||
List<Record> result = recordLoadPort.findAll(page); | ||
PageInfo pageInfo = getPageInfo(page); | ||
|
||
List<String> applicantIds = result.stream().map(Record::getApplicantId).toList(); | ||
List<Score> scores = scoreLoadPort.findByApplicantIds(applicantIds); | ||
List<MongoAnswer> applicants = applicantQueryUseCase.execute(applicantIds).stream().filter(applicant ->year == null || applicant.getYear().equals(year)).toList(); | ||
List<MongoAnswer> applicants = applicantQueryUseCase.execute(applicantIds).stream() | ||
.filter(applicant -> year == null || applicant.getYear().equals(year)).toList(); | ||
|
||
if (result.isEmpty() || applicants.isEmpty()) { | ||
return RecordsViewResponseDto.of( | ||
pageInfo, | ||
Collections.emptyList(), | ||
Collections.emptyMap(), | ||
Collections.emptyList()); | ||
return createEmptyResponse(pageInfo); | ||
} | ||
|
||
Map<String, Integer> yearByAnswerIdMap = applicants.stream().collect(Collectors.toMap(MongoAnswer::getId, MongoAnswer::getYear)); | ||
Map<String, Double> scoreMap = | ||
scores.stream() | ||
.filter(score ->year == null || yearByAnswerIdMap.get(score.getApplicantId()).equals(year)) | ||
.collect( | ||
Collectors.groupingBy( | ||
Score::getApplicantId, | ||
Collectors.averagingDouble(Score::getScore))); | ||
Map<String, Double> scoreMap = getScoreMap(year, applicantIds, yearByAnswerIdMap); | ||
|
||
result = result.stream().filter(record -> year == null || | ||
Optional.ofNullable(record.getApplicantId()) | ||
.map(yearByAnswerIdMap::get) | ||
.map(y -> y.equals(year)) | ||
.orElse(false) | ||
) | ||
.toList(); | ||
Optional.ofNullable(record.getApplicantId()) | ||
.map(yearByAnswerIdMap::get) | ||
.map(y -> y.equals(year)) | ||
.orElse(false) | ||
) | ||
.toList(); | ||
|
||
applicants = new ArrayList<>(applicants); // Unmodifiable List일 경우 Sort 불가. stream().toList()의 결과는 Unmodifiable List | ||
|
||
List<Record> records; | ||
if (sortType.equals("score")) { | ||
List<Record> records = sortRecordsByScoresDesc(result, scoreMap); | ||
return RecordsViewResponseDto.of(pageInfo, records, scoreMap, applicants); | ||
records = sortRecordsByScoresDesc(result, scoreMap); | ||
} else { | ||
List<Record> records = sortRecordsByApplicantsAndSortType(result, applicants, sortType); | ||
return RecordsViewResponseDto.of(pageInfo, records, scoreMap, applicants); | ||
records = sortRecordsByApplicantsAndSortType(result, applicants, sortType); | ||
} | ||
|
||
return RecordsViewResponseDto.of(pageInfo, records, scoreMap, applicants); | ||
} | ||
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. 엇 이 부분은 제가 이슈로 작성한 BE-103 내용이긴 한데, 명덕님께서 해결해주셨네요! BE-103 이슈는 제가 당장 해결하지 못할 것 같아서 Assignee를 저로 하지는 않았지만 명덕님께서 해결하셨다면 해결하신 이슈에 Assignee 설정 부탁드리겠습니다! 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. 넵 |
||
|
||
@Override | ||
public RecordsViewResponseDto execute(Integer page, Integer year, String sortType, String searchKeyword) { | ||
List<Record> result = recordLoadPort.findAll(); | ||
List<String> applicantIds = result.stream().map(Record::getApplicantId).toList(); | ||
|
||
List<MongoAnswer> applicants; | ||
if (sortType.equals("score")) { | ||
applicants = applicantQueryUseCase.execute(year, sortType, searchKeyword, applicantIds); | ||
} else { | ||
applicants = applicantQueryUseCase.execute(page, year, sortType, searchKeyword, applicantIds); | ||
} | ||
|
||
if (result.isEmpty() || applicants.isEmpty()) { | ||
return createEmptyResponse(new PageInfo(0, page)); | ||
} | ||
|
||
Map<String, Integer> yearByAnswerIdMap = applicants.stream().collect(Collectors.toMap(MongoAnswer::getId, MongoAnswer::getYear)); | ||
|
||
applicantIds = applicants.stream().map(MongoAnswer::getId).toList(); // 검색 결과에 따라 applicantIds 재할당 | ||
Map<String, Double> scoreMap = getScoreMap(year, applicantIds, yearByAnswerIdMap); | ||
|
||
result = result.stream().filter(record -> year == null || | ||
Optional.ofNullable(record.getApplicantId()) | ||
.map(yearByAnswerIdMap::get) | ||
.map(y -> y.equals(year)) | ||
.orElse(false) | ||
) | ||
.toList(); | ||
|
||
List<Record> records; | ||
if (sortType.equals("score")) { | ||
records = sortRecordsByScoresDesc(result, scoreMap, page); | ||
} else { | ||
records = sortRecordsByApplicantsAndSortType(result, applicants); | ||
} | ||
|
||
PageInfo pageInfo = applicantQueryUseCase.getPageInfo(year, page, searchKeyword); | ||
return RecordsViewResponseDto.of(pageInfo, records, scoreMap, applicants); | ||
} | ||
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. 위에 sortType이 score인걸 확인하고 있는데 왜 아래서 또 확인을하고 적용하는 거죠? 한꺼번에 하면 안되는 이유가 있나요? 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. a613304 해당 커밋에서 공통로직을 메서드로 추출 후 분기를 하나로 처리했습니다! 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. dad5f5a 커밋에서 기수별 면접기록 필터링 로직도 메서드로 추출했습니다. |
||
|
||
private RecordsViewResponseDto createEmptyResponse(PageInfo pageInfo) { | ||
return RecordsViewResponseDto.of( | ||
pageInfo, | ||
Collections.emptyList(), | ||
Collections.emptyMap(), | ||
Collections.emptyList()); | ||
} | ||
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. 저는 Dto 클래스 안에 static으로 Dto를 만드는 정적 팩토리 메서드를 사용하는데 어떤 관점에서 service에 작성을 한건지 궁금합니다. 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. 기존에 메서드 내부에 작성된 로직인데, DTO 클래스에 넣는게 좋다고 생각합니다! 반영하겠습니다. 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. b4dc8b6 커밋에 반영했습니다! |
||
|
||
private Map<String, Double> getScoreMap(Integer year, List<String> applicantIds, Map<String, Integer> yearByAnswerIdMap) { | ||
List<Score> scores = scoreLoadPort.findByApplicantIds(applicantIds); | ||
return scores.stream() | ||
.filter(score -> year == null || yearByAnswerIdMap.get(score.getApplicantId()).equals(year)) | ||
.collect( | ||
Collectors.groupingBy( | ||
Score::getApplicantId, | ||
Collectors.averagingDouble(Score::getScore))); | ||
} | ||
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. 왜 메소드 명이 getScoreMap이죠? 이렇게 메소드 분리해서 책임을 명확히 분리할 거면 이 메소드를 사용하는 곳에서 이 메소드가 어떤 그런데 이 메소드 역할은 Score를 가져와서 평균을 내는 작업이네요 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. 함수 내부 로직으로 있던 코드인데, 변수명이 |
||
|
||
private List<Record> sortRecordsByScoresDesc( | ||
List<Record> records, Map<String, Double> scoreMap) { | ||
// score 내림차순 정렬 | ||
return records.stream() | ||
List<Record> sortedRecords = records.stream() | ||
.sorted( | ||
Comparator.comparing( | ||
record -> { | ||
Double score = scoreMap.get(record.getApplicantId()); | ||
return score == null ? 0 : score; | ||
})) | ||
.toList(); | ||
return sortedRecords; | ||
} | ||
|
||
private List<Record> sortRecordsByScoresDesc( | ||
List<Record> records, Map<String, Double> scoreMap, Integer page) { | ||
// score 내림차순 정렬 | ||
List<Record> sortedRecords = records.stream() | ||
.sorted( | ||
Comparator.comparing( | ||
record -> scoreMap.getOrDefault(record.getApplicantId(), 0.0), | ||
Comparator.reverseOrder() | ||
) | ||
) | ||
.toList(); | ||
// 페이징 함수 호출 | ||
return paginateList(sortedRecords, page); | ||
} | ||
|
||
private List<Record> sortRecordsByApplicantsAndSortType( | ||
List<Record> records, List<MongoAnswer> applicants, String sortType) { | ||
// Newest, Name, Object 정렬 | ||
sortHelper.sort(applicants, sortType); | ||
if (!applicants.isEmpty()) { | ||
sortHelper.sort(applicants, sortType); | ||
} | ||
Map<String, Integer> applicantIndexMap = new HashMap<>(); | ||
for (int i = 0; i < applicants.size(); i++) { | ||
applicantIndexMap.put(applicants.get(i).getId(), i); | ||
} | ||
|
||
return records.stream() | ||
.sorted( | ||
Comparator.comparing( | ||
record -> | ||
applicantIndexMap.getOrDefault( | ||
record.getApplicantId(), Integer.MAX_VALUE))) | ||
.toList(); | ||
} | ||
|
||
private List<Record> sortRecordsByApplicantsAndSortType(List<Record> records, List<MongoAnswer> applicants) { | ||
|
||
Map<String, Integer> applicantIndexMap = new HashMap<>(); | ||
for (int i = 0; i < applicants.size(); i++) { | ||
applicantIndexMap.put(applicants.get(i).getId(), i); | ||
|
@@ -149,9 +239,7 @@ public void updateRecordUrl(String applicantId, String url) { | |
recordLoadPort | ||
.findByApplicantId(applicantId) | ||
.ifPresent( | ||
record -> { | ||
record.updateUrl(url); | ||
}); | ||
record -> record.updateUrl(url)); | ||
} | ||
|
||
@Override | ||
|
@@ -160,9 +248,7 @@ public void updateRecordContents(String applicantId, String contents) { | |
recordLoadPort | ||
.findByApplicantId(applicantId) | ||
.ifPresent( | ||
record -> { | ||
record.updateRecord(contents); | ||
}); | ||
record -> record.updateRecord(contents)); | ||
} | ||
|
||
@Override | ||
|
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.
이 부분은 searchKeyword가 null일 경우에는 전체 조회를 하기 위한 용도인 거죠?
그리고 혹시 @ParameterObject 대신 @RequestParam을 사용하신 이유가 따로 있을까요? ㅎㅎ
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.
@ParameterObject
를 사용하면 swagger에서 required 필드로 보여 필수로 입력해야 한다고 인지됩니다!그래서
@RequestParam
의required
값을false
로 두어 필수 값이 아님을 명시했습니다.