From c6998138c11c743abb5aea9f4e3166ab487699d5 Mon Sep 17 00:00:00 2001 From: DaeunSon <130820902+DaeunSon@users.noreply.github.com> Date: Thu, 26 Jun 2025 04:54:13 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Feat=20:=20=EC=9C=A0=EC=A0=80=20=EC=9D=B4?= =?UTF-8?q?=EC=88=98=20=ED=8A=B8=EB=9E=99=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enjoy/controller/TrackController.java | 58 ++++++++---- .../enjoy/controller/UserController.java | 10 ++ .../repository/FavoriteCourseRepository.java | 1 + .../enjoy/repository/TrackRepository.java | 1 + .../example/enjoy/service/TrackService.java | 92 ++++++++++++++++++- .../service/userService/UserService.java | 12 +++ 6 files changed, 155 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/example/enjoy/controller/TrackController.java b/src/main/java/com/example/enjoy/controller/TrackController.java index 425bbd3..3d34bcb 100644 --- a/src/main/java/com/example/enjoy/controller/TrackController.java +++ b/src/main/java/com/example/enjoy/controller/TrackController.java @@ -1,12 +1,14 @@ package com.example.enjoy.controller; import com.example.enjoy.dto.TrackDetailDto; +import com.example.enjoy.entity.Track; import com.example.enjoy.service.TrackService; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequiredArgsConstructor @@ -15,18 +17,42 @@ public class TrackController { private final TrackService trackService; - /** - * 특정 트랙의 상세 정보를 조회하는 API - * @param trackId 조회할 트랙의 ID - * @return TrackDetailDto - 트랙의 상세 정보 - */ - @GetMapping("/{trackId}") - public TrackDetailDto getTrackDetailsById(@PathVariable Long trackId) { +// /** +// * 특정 트랙의 상세 정보를 조회하는 API +// * @param trackId 조회할 트랙의 ID +// * @return TrackDetailDto - 트랙의 상세 정보 +// */ +// @GetMapping("/{trackId}") +// public TrackDetailDto getTrackDetailsById(@PathVariable Long trackId) { +// +// // TODO: 추후 Spring Security 연동 후 실제 로그인한 학생 ID를 가져와야 함 +// String currentStudentId = "1"; +// +// // 5. 서비스의 메서드를 호출하여 결과를 받아온 후, 그대로 반환 +// return trackService.getTrackDetails(currentStudentId, trackId); +// } - // TODO: 추후 Spring Security 연동 후 실제 로그인한 학생 ID를 가져와야 함 - String currentStudentId = "1"; + @Operation(summary = "트랙 상세 정보 조회", description = "트랙의 이름으로 상세 정보를 조회합니다.") + @GetMapping("/{trackId}") + public ResponseEntity getTrackDetailsByName(@RequestParam String trackName) { + TrackDetailDto trackDetailDto = trackService.getTrackDetailsByName(trackName); + if (trackDetailDto == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(trackDetailDto); + } - // 5. 서비스의 메서드를 호출하여 결과를 받아온 후, 그대로 반환 - return trackService.getTrackDetails(currentStudentId, trackId); + @Operation(summary = "트랙 목록 조회", description = "모든 트랙의 목록을 조회합니다.") + @GetMapping("/list") + public ResponseEntity> getAllTracks() { + List tracks = trackService.getAllTracks(); + if (tracks.isEmpty()) { + return ResponseEntity.noContent().build(); + } + List trackDetails = tracks.stream() + .map(track -> new TrackDetailDto().from(track)) + .toList(); + return ResponseEntity.ok(trackDetails); } -} + +} \ No newline at end of file diff --git a/src/main/java/com/example/enjoy/controller/UserController.java b/src/main/java/com/example/enjoy/controller/UserController.java index 652ef00..e0ed2d3 100644 --- a/src/main/java/com/example/enjoy/controller/UserController.java +++ b/src/main/java/com/example/enjoy/controller/UserController.java @@ -124,5 +124,15 @@ public ResponseEntity saveUserInfo(@Valid @RequestBody MemberDto memb userService.saveUserInfo(memberDto); return ResponseEntity.ok().build(); } + + @Operation(summary = "학생이 이수 완료한 트랙 조회", description = "학생이 이수 완료한 트랙 목록을 조회합니다.") + @GetMapping("/{studentId}/tracks/completed") + public ResponseEntity> getCompletedTracks(@PathVariable String studentId) { + List completedTracks = userService.getCompletedTracks(studentId); + if (completedTracks.isEmpty()) { + return ResponseEntity.noContent().build(); + } + return ResponseEntity.ok(completedTracks); + } } diff --git a/src/main/java/com/example/enjoy/repository/FavoriteCourseRepository.java b/src/main/java/com/example/enjoy/repository/FavoriteCourseRepository.java index e73dda4..0785bb9 100644 --- a/src/main/java/com/example/enjoy/repository/FavoriteCourseRepository.java +++ b/src/main/java/com/example/enjoy/repository/FavoriteCourseRepository.java @@ -13,4 +13,5 @@ public interface FavoriteCourseRepository extends JpaRepository { Optional findByUserAndCourseName(User user, String courseName); List findAllByUser(User user); + } diff --git a/src/main/java/com/example/enjoy/repository/TrackRepository.java b/src/main/java/com/example/enjoy/repository/TrackRepository.java index 6cfb305..f7ea875 100644 --- a/src/main/java/com/example/enjoy/repository/TrackRepository.java +++ b/src/main/java/com/example/enjoy/repository/TrackRepository.java @@ -22,4 +22,5 @@ public interface TrackRepository extends JpaRepository { Optional findByIdWithCourses(@Param("trackId") Long trackId); Optional findByName(String name); + } \ No newline at end of file diff --git a/src/main/java/com/example/enjoy/service/TrackService.java b/src/main/java/com/example/enjoy/service/TrackService.java index 149e802..eee0052 100644 --- a/src/main/java/com/example/enjoy/service/TrackService.java +++ b/src/main/java/com/example/enjoy/service/TrackService.java @@ -1,14 +1,19 @@ package com.example.enjoy.service; +import ch.qos.logback.core.joran.sanity.Pair; import com.example.enjoy.dto.CourseDto; import com.example.enjoy.dto.CourseStatusDto; import com.example.enjoy.dto.TrackDetailDto; import com.example.enjoy.dto.TrackProgressDto; +import com.example.enjoy.entity.FavoriteCourse; import com.example.enjoy.entity.StudentCourse; import com.example.enjoy.entity.Track; import com.example.enjoy.entity.TrackCourse; +import com.example.enjoy.entity.user.User; +import com.example.enjoy.repository.FavoriteCourseRepository; import com.example.enjoy.repository.StudentCourseRepository; import com.example.enjoy.repository.TrackRepository; +import com.example.enjoy.repository.UserRepository; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -24,7 +29,10 @@ public class TrackService { private final TrackRepository trackRepository; private final StudentCourseRepository studentCourseRepository; // 기존 기능 + private final FavoriteCourseRepository favoriteCourseRepository; + private final UserRepository userRepository; + //진척률 계산 public List calculateTrackProgress(String studentId) { Set completedCourseNames = getCompletedCourseNames(studentId); // 이수 과목명 목록 @@ -60,13 +68,13 @@ public List calculateTrackProgress(String studentId) { * 학생이 이수한 과목 이름을 Set으로 반환하는 메서드 */ @Transactional(readOnly = true) - public TrackDetailDto getTrackDetails(String studentId, Long trackId) { + public TrackDetailDto getTrackDetails(String studentId, String trackName) { // 1. [리팩토링] 학생 이수 과목 조회 로직을 private 메서드로 호출 Set completedCourseNames = getCompletedCourseNames(studentId); - // 2. ID로 트랙 정보와 소속 과목들을 한번에 조회 - Track track = trackRepository.findByIdWithCourses(trackId) + // 2. 트랙 정보와 소속 과목들을 한번에 조회 + Track track = trackRepository.findByName(trackName) .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 트랙입니다.")); // 3. 트랙의 과목 목록을 CourseStatusDto 리스트로 변환 @@ -101,6 +109,33 @@ public TrackDetailDto getTrackDetails(String studentId, Long trackId) { return trackDetailDto; } + public TrackDetailDto getTrackDetailsByName(String trackName) { + Track track = trackRepository.findByName(trackName) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 트랙입니다.")); + + // 트랙의 과목 목록을 CourseStatusDto 리스트로 변환 + List courseStatusList = track.getCourses().stream() + .map(trackCourse -> { + CourseStatusDto dto = new CourseStatusDto(); + dto.setTitle(trackCourse.getCourseName()); + dto.setCode(trackCourse.getCourseCode()); + dto.setYear(trackCourse.getAcademicYear()); + dto.setSemester(trackCourse.getAcademicSemester()); + dto.setStatus("NONE"); // 기본값 설정, 이수 여부는 별도로 처리 + return dto; + }) + .collect(Collectors.toList()); + + // 최종적으로 TrackDetailDto를 조립하여 반환 + TrackDetailDto trackDetailDto = new TrackDetailDto(); + trackDetailDto.setTrackId(track.getId()); + trackDetailDto.setTrackName(track.getName()); + trackDetailDto.setDepartment(track.getDepartment()); + trackDetailDto.setCourses(courseStatusList); + + return trackDetailDto; + } + /** * 학생 ID로 해당 학생이 이수한 모든 과목명을 조회합니다. */ @@ -118,4 +153,55 @@ private boolean isCourseCompleted(TrackCourse course, Set completedCours return completedCourseNames.contains(course.getCourseName()) || (course.getCourseAlias() != null && completedCourseNames.contains(course.getCourseAlias())); } + + // favoriteScore 기준 1개, progressScore 기준 1개 트랙 추천 + public List getTopTracksByFavoriteAndProgress(String studentId) { + User user = userRepository.findByStudentId(studentId) + .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")); + Set favoriteCourses = favoriteCourseRepository.findAllByUser(user) + .stream() + .map(FavoriteCourse::getCourseName) + .collect(Collectors.toSet()); + + List trackProgress = calculateTrackProgress(studentId); + + // 1. favoriteScore 기준 최고 트랙 + TrackProgressDto favoriteTop = trackProgress.stream() + .max((a, b) -> Double.compare( + calculateFavoriteScore(a, favoriteCourses), + calculateFavoriteScore(b, favoriteCourses))) + .orElse(null); + + // 2. progressScore 기준 최고 트랙 + TrackProgressDto progressTop = trackProgress.stream() + .max((a, b) -> Double.compare( + (double) a.getCompletedCount() / a.getRequiredCount(), + (double) b.getCompletedCount() / b.getRequiredCount())) + .orElse(null); + + // 중복 제거 후 반환 + return List.of(favoriteTop, progressTop).stream() + .filter(java.util.Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + } + + private double calculateFavoriteScore(TrackProgressDto track, Set favoriteCourses) { + List allCourses = new ArrayList<>(); + allCourses.addAll(track.getCompletedCourses()); + allCourses.addAll(track.getRemainingCourses()); + + long matchCount = allCourses.stream() + .filter(course -> + (course.getCourseName() != null && favoriteCourses.contains(course.getCourseName())) || + (course.getCourseAlias() != null && favoriteCourses.contains(course.getCourseAlias())) + ) + .count(); + + return allCourses.isEmpty() ? 0.0 : (double) matchCount / allCourses.size(); + } + + public List getAllTracks() { + return trackRepository.findAll(); + } } diff --git a/src/main/java/com/example/enjoy/service/userService/UserService.java b/src/main/java/com/example/enjoy/service/userService/UserService.java index 8677ed1..299ef6a 100644 --- a/src/main/java/com/example/enjoy/service/userService/UserService.java +++ b/src/main/java/com/example/enjoy/service/userService/UserService.java @@ -126,4 +126,16 @@ public void updateCourseStatus(String studentId, String courseName, StudentCours course.updateStatus(newStatus); } + + public List getCompletedTracks (String studentId) { + List completedCourses = getCompletedCourses(studentId); + List trackCourses = trackCourseRepository.findAll(); + + return trackCourses.stream() + .filter(trackCourse -> completedCourses.stream() + .anyMatch(course -> course.getCourseName().equals(trackCourse.getCourseName()))) + .map(TrackCourse::getTrack) + .distinct() + .collect(Collectors.toList()); + } } \ No newline at end of file From 14db5455d38b6dfe67bb774791a6af4dba7e6d09 Mon Sep 17 00:00:00 2001 From: DaeunSon <130820902+DaeunSon@users.noreply.github.com> Date: Thu, 26 Jun 2025 05:09:20 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Feat=20:=20=EC=B6=94=EC=B2=9C=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=99=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enjoy/controller/TrackController.java | 23 ++++++++++ .../example/enjoy/service/TrackService.java | 42 ++++++++++++------- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/example/enjoy/controller/TrackController.java b/src/main/java/com/example/enjoy/controller/TrackController.java index 3d34bcb..4dc48c9 100644 --- a/src/main/java/com/example/enjoy/controller/TrackController.java +++ b/src/main/java/com/example/enjoy/controller/TrackController.java @@ -1,6 +1,7 @@ package com.example.enjoy.controller; import com.example.enjoy.dto.TrackDetailDto; +import com.example.enjoy.dto.TrackProgressDto; import com.example.enjoy.entity.Track; import com.example.enjoy.service.TrackService; import io.swagger.v3.oas.annotations.Operation; @@ -55,4 +56,26 @@ public ResponseEntity> getAllTracks() { return ResponseEntity.ok(trackDetails); } + @Operation(summary = "진척률에 따른 추천 트랙 조회", description = "학생의 진척률에 따라 추천 트랙을 조회합니다.") + @GetMapping("/recommendations/progress") + public ResponseEntity getRecommendedTrackByProgress(@RequestParam String studentId) { + Track recommendedTrack = trackService.getTopTrackByProgressScore(studentId); + if (recommendedTrack == null) { + return ResponseEntity.noContent().build(); + } + TrackDetailDto trackDetailDto = new TrackDetailDto().from(recommendedTrack); + return ResponseEntity.ok(trackDetailDto); + } + + @Operation(summary = "선호 과목에 따른 추천 트랙 조회", description = "학생의 선호 과목에 따라 추천 트랙을 조회합니다.") + @GetMapping("/recommendations/preferred-courses") + public ResponseEntity getRecommendedTrackByPreferredCourses(@RequestParam String studentId) { + Track recommendedTrack = trackService.getTopTrackByFavoriteScore(studentId); + if (recommendedTrack == null) { + return ResponseEntity.noContent().build(); + } + TrackDetailDto trackDetailDto = new TrackDetailDto().from(recommendedTrack); + return ResponseEntity.ok(trackDetailDto); + } + } \ No newline at end of file diff --git a/src/main/java/com/example/enjoy/service/TrackService.java b/src/main/java/com/example/enjoy/service/TrackService.java index eee0052..cf25282 100644 --- a/src/main/java/com/example/enjoy/service/TrackService.java +++ b/src/main/java/com/example/enjoy/service/TrackService.java @@ -154,8 +154,27 @@ private boolean isCourseCompleted(TrackCourse course, Set completedCours (course.getCourseAlias() != null && completedCourseNames.contains(course.getCourseAlias())); } - // favoriteScore 기준 1개, progressScore 기준 1개 트랙 추천 - public List getTopTracksByFavoriteAndProgress(String studentId) { + public Track getTopTrackByProgressScore(String studentId) { + User user = userRepository.findByStudentId(studentId) + .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")); + List trackProgress = calculateTrackProgress(studentId); + + TrackProgressDto topProgress = trackProgress.stream() + .max((a, b) -> Double.compare( + (double) a.getCompletedCount() / a.getRequiredCount(), + (double) b.getCompletedCount() / b.getRequiredCount())) + .orElse(null); + + if (topProgress == null) { + return null; + } + + return trackRepository.findByName(topProgress.getTrackName()) + .orElse(null); + } + + // 선호과목 기준 추천 트랙 1개 반환 + public Track getTopTrackByFavoriteScore(String studentId) { User user = userRepository.findByStudentId(studentId) .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")); Set favoriteCourses = favoriteCourseRepository.findAllByUser(user) @@ -165,25 +184,18 @@ public List getTopTracksByFavoriteAndProgress(String studentId List trackProgress = calculateTrackProgress(studentId); - // 1. favoriteScore 기준 최고 트랙 - TrackProgressDto favoriteTop = trackProgress.stream() + TrackProgressDto topFavorite = trackProgress.stream() .max((a, b) -> Double.compare( calculateFavoriteScore(a, favoriteCourses), calculateFavoriteScore(b, favoriteCourses))) .orElse(null); - // 2. progressScore 기준 최고 트랙 - TrackProgressDto progressTop = trackProgress.stream() - .max((a, b) -> Double.compare( - (double) a.getCompletedCount() / a.getRequiredCount(), - (double) b.getCompletedCount() / b.getRequiredCount())) - .orElse(null); + if (topFavorite == null) { + return null; + } - // 중복 제거 후 반환 - return List.of(favoriteTop, progressTop).stream() - .filter(java.util.Objects::nonNull) - .distinct() - .collect(Collectors.toList()); + return trackRepository.findByName(topFavorite.getTrackName()) + .orElse(null); } private double calculateFavoriteScore(TrackProgressDto track, Set favoriteCourses) {