-
Notifications
You must be signed in to change notification settings - Fork 6
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
3주차 과제 (윤석호) #17
base: YoonSeokHo3
Are you sure you want to change the base?
3주차 과제 (윤석호) #17
Changes from 7 commits
6b96ea1
d2d1793
d051aa6
9e548d4
0ca8248
59ade23
70e6cde
eda68fc
cab8423
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,19 @@ | ||
package com.econovation.third_project.config; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RequiredArgsConstructor | ||
@Getter | ||
public enum Field { | ||
WEB("WEB"), | ||
APP("APP"), | ||
AI("AI"), | ||
GAME("GAME"), | ||
IOT("IoT"), | ||
ARVR("AR/VR"); | ||
|
||
private final String fieldName; | ||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.econovation.third_project.config; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RequiredArgsConstructor | ||
@Getter | ||
public enum Job { | ||
DEVELOPER("개발자"), | ||
PLANNER("기획자"), | ||
DESIGNER("디자이너"); | ||
|
||
private final String jobName; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.econovation.third_project.config; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RequiredArgsConstructor | ||
@Getter | ||
public enum SupportPath { | ||
POSTER("홍보 포스터"), | ||
DEPARTMENT_ANNOUNCEMENT("학과 공지사항"), | ||
ACQUAINTANCE("지인 소개"), | ||
INSTAGRAM("인스타그램"), | ||
EVERYTIME("에브리타임"); | ||
private final String entryPathName; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,105 @@ | ||
package com.econovation.third_project.controller; | ||
|
||
import com.econovation.third_project.database.Database; | ||
import com.econovation.third_project.database.Registration; | ||
import com.econovation.third_project.service.DesiredTimeService; | ||
import com.econovation.third_project.service.PathService; | ||
import com.econovation.third_project.service.PersonalInformationService; | ||
import com.econovation.third_project.service.RegistrationService; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.function.Function; | ||
import java.util.function.Supplier; | ||
import static java.util.stream.Collectors.*; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
|
||
@Controller | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/admin") | ||
public class AdminQueryController { | ||
private final Database database; | ||
private final RegistrationService registrationService; | ||
private final PathService pathService; | ||
private final PersonalInformationService personalInformationService; | ||
private final DesiredTimeService desiredTimeService; | ||
|
||
// 예시 코드 | ||
@PostMapping("/registration") | ||
public ResponseEntity<Object> postRegistrate(@RequestBody Registration registration) { | ||
database.register(registration); | ||
return ResponseEntity.ok().build(); | ||
|
||
//모든 데이터 한 번에 반환 | ||
@GetMapping("/applicants") | ||
public ResponseEntity<Map<String, List<?>>> getApplicantsInfo(){ | ||
return ResponseEntity.ok().body(Map.of( | ||
"job", registrationService.getApplicantNumbersEachJob(), | ||
"field", registrationService.getApplicantNumberEachField(), | ||
"major", personalInformationService.getApplicantNumberEachMajor(), | ||
"path", pathService.getApplicantNumberEachPath(), | ||
"desired_time", desiredTimeService.getApplicantNumberEachTime() | ||
)); | ||
} | ||
@GetMapping("/registration") | ||
public ResponseEntity<Registration> getRegistration(String userId) { | ||
return ResponseEntity.ok().body(database.getRegistration(userId)); | ||
|
||
//동적 응답 + 병렬 스트림 | ||
@GetMapping("/applicants/parallel") | ||
public ResponseEntity<Map<String, List<?>>> getApplicantsInfo(@RequestBody List<String> fields){ | ||
Map<String, Supplier<List<?>>> services = Map.of( | ||
"job", registrationService::getApplicantNumbersEachJob, | ||
"field", registrationService::getApplicantNumberEachField, | ||
"major", personalInformationService::getApplicantNumberEachMajor, | ||
"path", pathService::getApplicantNumberEachPath, | ||
"desired_time", desiredTimeService::getApplicantNumberEachTime | ||
); | ||
Map<String, List<?>> result = fields.stream().parallel() | ||
.filter(services::containsKey) | ||
.collect(toMap( | ||
Function.identity(), | ||
requestField-> services.get(requestField).get() | ||
) | ||
); | ||
|
||
return ResponseEntity.ok().body(result); | ||
} | ||
|
||
//동적 응답 + async | ||
@GetMapping("/applicants/async") | ||
public ResponseEntity<Map<String, List<?>>> getApplicantsInfoAsync(@RequestBody List<String> fields){ | ||
Map<String, Supplier<List<?>>> services = Map.of( | ||
"job", registrationService::getApplicantNumbersEachJob, | ||
"field", registrationService::getApplicantNumberEachField, | ||
"major", personalInformationService::getApplicantNumberEachMajor, | ||
"path", pathService::getApplicantNumberEachPath, | ||
"desired_time", desiredTimeService::getApplicantNumberEachTime | ||
); | ||
|
||
Map<String, CompletableFuture<List<?>>> futures = fields.stream() | ||
.collect(toMap( | ||
Function.identity(), | ||
field -> { | ||
Supplier<List<?>> service = services.get(field); | ||
if (service == null) | ||
throw new IllegalArgumentException(); | ||
return CompletableFuture.supplyAsync(service); | ||
} | ||
)); | ||
|
||
CompletableFuture<Map<String, List<?>>> result = CompletableFuture.allOf( | ||
futures.values().toArray(CompletableFuture[]::new) | ||
).thenApply(v -> | ||
fields.stream() | ||
.collect(toMap(Function.identity(), field-> futures.get(field).join())) | ||
); | ||
|
||
return ResponseEntity.ok().body(result.join()); | ||
} | ||
|
||
|
||
//TODO: | ||
// 병렬 스트림 + async의 장점, | ||
// 병렬 스트림, async 각각 적절한 상황, | ||
// 서비스 메서드마다 다른 인자가 필요한 경우, | ||
// 일부 의존 관계가 있을경우 어떻게 처리할지 | ||
// 서비스 메서드 맵 어떻게 뺄지, | ||
// 적절한 스레드 수 알아보기, | ||
// 자바 비동기 기초, | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.econovation.third_project.controller; | ||
|
||
import com.econovation.third_project.dto.CreateApplicationReq; | ||
import com.econovation.third_project.service.ApplicationService; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RequestMapping("/api") | ||
@RequiredArgsConstructor | ||
@RestController | ||
public class ApplicationController { | ||
private final ApplicationService applicationService; | ||
|
||
@PostMapping("/application") | ||
public ResponseEntity<Integer> createApplication(@RequestBody CreateApplicationReq createApplicationReq){ | ||
return ResponseEntity.ok().body(applicationService.createApplication(createApplicationReq)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.econovation.third_project.database; | ||
|
||
import java.util.concurrent.atomic.AtomicInteger; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NonNull; | ||
|
||
|
||
@Getter | ||
public final class Application { | ||
private static final AtomicInteger lastUsedId = new AtomicInteger(0); | ||
@NonNull | ||
private final Integer applicationId = lastUsedId.getAndIncrement(); | ||
@NonNull | ||
private final Registration registration; | ||
@NonNull | ||
private final PersonalInformation personalInformation; | ||
@NonNull | ||
private final Path path; | ||
@NonNull | ||
private final DesiredTime desiredTime; | ||
|
||
@Builder | ||
public Application(Registration registration, PersonalInformation personalInformation, | ||
Path path, | ||
DesiredTime desiredTime) { | ||
this.registration = registration; | ||
this.personalInformation = personalInformation; | ||
this.path = path; | ||
this.desiredTime = desiredTime; | ||
} | ||
} | ||
|
||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
package com.econovation.third_project.database; | ||
|
||
import java.util.HashMap; | ||
import java.util.Collection; | ||
import java.util.Map; | ||
import java.util.UUID; | ||
import java.util.Random; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import org.springframework.stereotype.Component; | ||
|
||
/** | ||
|
@@ -11,18 +12,65 @@ | |
*/ | ||
@Component | ||
public class Database { | ||
private final Map<String, Registration> registration = new HashMap<>(); | ||
private final Map<String, Path> path = new HashMap<>(); | ||
private final Map<String, PersonalInformation> personalInformation = new HashMap<>(); | ||
private final Map<String, DesiredTime> desiredTime = new HashMap<>(); | ||
//key: user id, value: application id | ||
private final Map<Integer, Integer> applications = new ConcurrentHashMap<>(); | ||
//key: application id | ||
private final Map<Integer, Registration> registrations = new ConcurrentHashMap<>(); | ||
//key: application id | ||
private final Map<Integer, Path> paths = new ConcurrentHashMap<>(); | ||
//key: application id | ||
private final Map<Integer, PersonalInformation> personalInformation = new ConcurrentHashMap<>(); | ||
//key: application id | ||
private final Map<Integer, DesiredTime> desiredTimes = new ConcurrentHashMap<>(); | ||
|
||
|
||
public void register(Registration registrationRequest) { | ||
// 기본적으로 이전 제출시 등록된 정보가 있으면 덮어쓰기를 지원합니다. | ||
registration.put(UUID.randomUUID().toString(), registrationRequest); | ||
public Collection<Registration> getAllRegistrations(){ | ||
return registrations.values(); | ||
} | ||
public Collection<PersonalInformation> getAllPersonalInformation(){ | ||
return personalInformation.values(); | ||
} | ||
public Collection<Path> getAllPath() { | ||
return paths.values(); | ||
} | ||
public Collection<DesiredTime> getAllDesiredTime(){ | ||
return desiredTimes.values(); | ||
} | ||
|
||
public Registration getRegistration(String userId) { | ||
return registration.get(userId); | ||
//지원자는 userId가 없으니 같은 지원서인지 판단할 기준을 따로 만들어야 함 | ||
public Integer upsertApplication(Integer userId, Application application){ | ||
Integer applicationId = applications.computeIfAbsent(userId, k -> application.getApplicationId()); | ||
upsertRegistration(applicationId, application.getRegistration()); | ||
upsertPath(applicationId, application.getPath()); | ||
upsertDesiredTime(applicationId, application.getDesiredTime()); | ||
upsertPersonalInformation(applicationId, application.getPersonalInformation()); | ||
return applicationId; | ||
} | ||
public void upsertRegistration(Integer applicationId, Registration registration){ | ||
registrations.put(applicationId, registration); | ||
} | ||
public void upsertPath(Integer applicationId, Path path){ | ||
paths.put(applicationId, path); | ||
} | ||
public void upsertPersonalInformation(Integer applicationId, PersonalInformation personalInformation){ | ||
this.personalInformation.put(applicationId, personalInformation); | ||
} | ||
public void upsertDesiredTime(Integer applicationId, DesiredTime desiredTime){ | ||
desiredTimes.put(applicationId, desiredTime); | ||
Comment on lines
+40
to
+58
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. upsert라는 개념을 put 한줄로 표현한 부분 아주 깔끔합니다. 아주 잘하셨습니다. 차 후, Map이 아니라, 일반 클래스에서도 적용하는 부분도 한번 짜보세요! 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. hashMap에 key가 중복인 데이터를 넣을 때 value가 덮어씌워진다는 것을 이용하여 upsert로 표현한게 멋지네요 |
||
} | ||
|
||
|
||
|
||
// public void register(Registration registrationRequest) { | ||
// // 기본적으로 이전 제출시 등록된 정보가 있으면 덮어쓰기를 지원합니다. | ||
// registration.put(UUID.randomUUID().toString(), registrationRequest); | ||
// } | ||
// | ||
// public Registration getRegistration(String userId) { | ||
// return registration.get(userId); | ||
// } | ||
|
||
|
||
|
||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,32 @@ | ||
package com.econovation.third_project.database; | ||
|
||
import java.util.HashMap; | ||
import java.util.List; | ||
import lombok.AllArgsConstructor; | ||
import java.util.Map; | ||
import lombok.Getter; | ||
|
||
@AllArgsConstructor | ||
@Getter | ||
public class DesiredTime { | ||
String registrationId; | ||
// 희망 시간 (11 * 3)의 테이블 형태를 준수합니다. | ||
private List<int[]> desiredTime; | ||
private static final Map<List<Integer>, List<Integer>> cachedTimes = new HashMap<>(); | ||
private final List<List<Integer>> desiredTimes; | ||
|
||
public DesiredTime(List<List<Integer>> desiredTimes) { | ||
validateDesiredTime(desiredTimes); | ||
this.desiredTimes = desiredTimes.stream() | ||
.map(time-> cachedTimes.computeIfAbsent(time, t->t)) | ||
.toList(); | ||
} | ||
private void validateDesiredTime(List<List<Integer>> desiredTimes) { | ||
//27기 기준 | ||
int interviewDays = 3; | ||
int hoursPerDay = 11; | ||
|
||
desiredTimes.forEach( | ||
time -> { | ||
if (time.get(0) >= interviewDays || time.get(1) >= hoursPerDay) | ||
throw new IllegalArgumentException("면접 시간 잘못 입력"); | ||
} | ||
); | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,27 @@ | ||
package com.econovation.third_project.database; | ||
|
||
import com.econovation.third_project.config.SupportPath; | ||
import java.util.EnumSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
import java.util.stream.Collector; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
@AllArgsConstructor | ||
@Getter | ||
public class Path { | ||
String registrationId; | ||
public class Path{ | ||
// 지원 경로 | ||
private String supportPath; | ||
private final EnumSet<SupportPath> supportPaths; | ||
|
||
public Path(EnumSet<SupportPath> supportPaths) { | ||
this.supportPaths = supportPaths; | ||
} | ||
|
||
public static Path from(Set<String> stringPaths){ | ||
EnumSet<SupportPath> enumPaths = EnumSet.noneOf(SupportPath.class); | ||
stringPaths.stream() | ||
.forEach(stringPath-> enumPaths.add(SupportPath.valueOf(stringPath.toUpperCase()))); | ||
return new Path(enumPaths); | ||
} | ||
} |
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.
컨트롤러에서 집계 호출 때리지 마세요. 컨트롤러는 위 서비스의 기술 스펙이다 생각하셔야지 구현체가 아닙니다.
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.
아 그렇군요 어떻게 할지 고민해보겠습니다