Skip to content

Commit 9b4d695

Browse files
authored
[Feature] 사용자 선호 장르 맞춤 댄서, 수업 추천 api 개선 (#115)
* fix: 사용자 장르 맞춤 댄서 추천 api 개선 (#112) * fix: 기존에 찜한 댄서는 추천되지 않도록 개선 (#112) * fix: 사용자 장르 맞춤 댄스수업 추천 api 구현 (#112) * fix: repository custom 추가 구현 (#112) * chore: 추천댄서 조회 api의 쿼리파라미터명 변경 (#112) * fix: 추천댄스수업 조회 api의 버그 픽스 (#112) 사용자의 장르 id를 가져오는게 아닌, userGenre의 id를 가져오던 문제 해결 * feat: 랜덤 댄서, 댄스수업 조회 api uri 수정 (#112) 비로그인 환경에서 사용할 수 있도록 수정
1 parent e1a872a commit 9b4d695

File tree

14 files changed

+117
-35
lines changed

14 files changed

+117
-35
lines changed

src/main/java/com/danthis/backend/api/danceclass/DanceClassController.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public ApiResponse<DanceClassListServiceResponse> getDanceClasses(
182182
}
183183

184184
@Operation(summary = "무작위 댄스 수업 목록 조회 API", description = "무작위로 댄스 수업 목록을 조회합니다.")
185-
@GetMapping("/random")
185+
@GetMapping("/info/random")
186186
@AssignCurrentUserInfo
187187
public ApiResponse<DanceClassListServiceResponse> getRandomDanceClasses(
188188
@RequestParam(defaultValue = "10") int size,
@@ -192,4 +192,16 @@ public ApiResponse<DanceClassListServiceResponse> getRandomDanceClasses(
192192
averRate);
193193
return ApiResponse.OK(response);
194194
}
195+
196+
@Operation(summary = "추천 댄스 수업 목록 조회 API", description = "사용자 선호 장르기반 추천 댄스수업 목록을 조회합니다.")
197+
@GetMapping("/recommendations")
198+
@AssignCurrentUserInfo
199+
public ApiResponse<DanceClassListServiceResponse> getRecommendedDanceClasses(
200+
CurrentUserInfo userInfo,
201+
@RequestParam(defaultValue = "4") int size) {
202+
203+
DanceClassListServiceResponse response =
204+
danceClassService.getRecommendedDanceCalssList(userInfo.getUserId(), size);
205+
return ApiResponse.OK(response);
206+
}
195207
}

src/main/java/com/danthis/backend/api/dancer/DancerController.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ public ApiResponse<Long> updateDancer(
5252
CurrentUserInfo userInfo,
5353
@RequestBody @Valid DancerUpdateRequest request) {
5454

55-
Long dancerId = dancerService.updateDancerInfo(userInfo.getUserId(), request.toServiceRequest());
55+
Long dancerId = dancerService.updateDancerInfo(userInfo.getUserId(),
56+
request.toServiceRequest());
5657
return ApiResponse.OK(dancerId);
5758
}
5859

@@ -109,7 +110,8 @@ public ApiResponse<DanceClassListServiceResponse> getDancerClasses(
109110
@RequestParam(defaultValue = "1") @Min(1) Integer page,
110111
@RequestParam(defaultValue = "9") @Min(1) Integer size) {
111112

112-
DanceClassListServiceResponse response = danceClassService.getDancerClasses(userInfo.getUserId(), dancerId, page, size);
113+
DanceClassListServiceResponse response = danceClassService.getDancerClasses(
114+
userInfo.getUserId(), dancerId, page, size);
113115
return ApiResponse.OK(response);
114116
}
115117

@@ -125,15 +127,16 @@ public ApiResponse<DancerSummaryListResponse> getAllDancers() {
125127
@GetMapping("/recommendations")
126128
@AssignCurrentUserInfo
127129
public ApiResponse<DancerSummaryListResponse> getRecommendationDancers(
130+
@RequestParam(defaultValue = "4") @Min(1) Integer size,
128131
CurrentUserInfo userInfo) {
129132

130-
DancerSummaryListResponse response = dancerService.getRecommendationDancers(userInfo.getUserId());
133+
DancerSummaryListResponse response = dancerService.getRecommendedDancers(
134+
userInfo.getUserId(), size);
131135
return ApiResponse.OK(response);
132136
}
133137

134138
@Operation(summary = "무작위 댄서 추천 API", description = "모든 댄서 중 무작위로 댄서를 추출해서 반환하는 API")
135-
@GetMapping("/random")
136-
139+
@GetMapping("/info/random")
137140
public ApiResponse<DancerSummaryListResponse> getRandomDancers(
138141
@RequestParam(defaultValue = "10") Integer size,
139142
@RequestParam(defaultValue = "1") Long averRate) {

src/main/java/com/danthis/backend/application/danceclass/DanceClassService.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import com.danthis.backend.domain.mapping.danceruserchat.DancerUserChat;
3333
import com.danthis.backend.domain.mapping.wishlist.WishList;
3434
import com.danthis.backend.domain.user.User;
35+
import java.util.ArrayList;
36+
import java.util.Collections;
3537
import java.util.List;
3638
import java.util.Map;
3739
import java.util.Set;
@@ -88,7 +90,8 @@ public void createDanceClass(DanceClassCreateServiceRequest request, Long userId
8890
}
8991

9092
if (request.getDates() != null) {
91-
Set<DanceClassSchedule> schedules = danceClassMapper.mapToDates(danceClass, request.getDates());
93+
Set<DanceClassSchedule> schedules = danceClassMapper.mapToDates(danceClass,
94+
request.getDates());
9295
danceClassManager.saveDanceClassSchedules(schedules);
9396
}
9497
}
@@ -243,7 +246,8 @@ public EligibleUserListServiceResponse getEligibleUsersForDanceClass(Long classI
243246
danceClass);
244247
Map<Long, Boolean> registrationStatus = registeredBookings.stream()
245248
.collect(Collectors.toMap(
246-
booking -> booking.getUser().getId(),
249+
booking -> booking.getUser()
250+
.getId(),
247251
DanceClassBooking::getIsApproved
248252
));
249253

@@ -305,4 +309,25 @@ public Boolean isFavoriteClass(Long userId, Long classId) {
305309
// 찜한 댄스수업면 true, 아니면 false 리턴
306310
return wishListReader.readWishListByUserIdAndClassId(userId, classId) != null;
307311
}
312+
313+
public DanceClassListServiceResponse getRecommendedDanceCalssList(Long userId, int size) {
314+
User user = userReader.readUserById(userId);
315+
Set<Long> userFavoriteGenre = user.getUserGenres().stream()
316+
.map(userGenre -> userGenre.getGenre().getId())
317+
.collect(Collectors.toSet());
318+
List<WishList> wishLists = wishListReader.readAllWishListByUserId(userId);
319+
Set<Long> wishListIds = wishLists.stream().map(WishList::getId).collect(Collectors.toSet());
320+
List<DanceClass> danceClasses =
321+
new ArrayList<>(danceClassReader.readDanceClassesByGenres(userFavoriteGenre));
322+
323+
List<DanceClass> recommendedClasses =
324+
new ArrayList<>(danceClasses.stream()
325+
.filter(danceClass -> !wishListIds.contains(danceClass.getId()))
326+
.toList());
327+
328+
Collections.shuffle(recommendedClasses);
329+
List<DanceClass> results = recommendedClasses.subList(
330+
0, Math.min(size, recommendedClasses.size()));
331+
return DanceClassListServiceResponse.from(results);
332+
}
308333
}

src/main/java/com/danthis/backend/application/danceclass/implement/DanceClassReader.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,22 @@ public Page<DanceClass> readDanceClasses(
6464
return danceClassRepository.findByGenreId(genreId, pageable);
6565
}
6666

67-
if (date != null && day == null) { // 장르 + 날짜로 검색
67+
if (date != null && day == null) { // 장르 + 날짜로 검색
6868
LocalDate localDate;
6969
try {
7070
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
7171
localDate = LocalDate.parse(date, formatter);
72-
}
73-
catch (Exception e) {
72+
} catch (Exception e) {
7473
throw new BusinessException(ErrorCode.INVALID_DATE_FORMAT);
7574
}
7675
return danceClassRepository.findByGenreIdAndDate(genreId, localDate, pageable);
7776
}
7877

7978
if (date == null && day != null) { // 장르 + 요일로 검색
8079
Week week;
81-
try{
80+
try {
8281
week = Week.valueOf(day);
83-
}
84-
catch (IllegalArgumentException e){
82+
} catch (IllegalArgumentException e) {
8583
throw new BusinessException(ErrorCode.INVALID_DAY_FORMAT);
8684
}
8785
return danceClassRepository.findByGenreIdAndDay(genreId, week, pageable);
@@ -113,11 +111,16 @@ public boolean isUserAlreadyRegistered(DanceClass danceClass, User user) {
113111
return bookingRepository.findByDanceClassAndUser(danceClass, user).isPresent();
114112
}
115113

116-
public Page<DanceClassBooking> readRegisteredUsersByClass(DanceClass danceClass, Pageable pageable) {
114+
public Page<DanceClassBooking> readRegisteredUsersByClass(DanceClass danceClass,
115+
Pageable pageable) {
117116
return bookingRepository.findByDanceClass(danceClass, pageable);
118117
}
119118

120119
public List<DanceClass> readRandomDanceClasses(Integer size) {
121120
return danceClassRepository.getRandomDanceClasses(size);
122121
}
122+
123+
public List<DanceClass> readDanceClassesByGenres(Set<Long> genreIds) {
124+
return danceClassRepository.findByGenreIds(genreIds);
125+
}
123126
}

src/main/java/com/danthis/backend/application/dancer/DancerService.java

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import com.danthis.backend.domain.mapping.dancergenre.DancerGenre;
2323
import com.danthis.backend.domain.user.User;
2424
import jakarta.transaction.Transactional;
25+
import java.util.ArrayList;
26+
import java.util.Collections;
27+
import java.util.HashSet;
2528
import java.util.List;
2629
import java.util.Set;
2730
import java.util.stream.Collectors;
@@ -76,7 +79,8 @@ public Long updateDancerInfo(Long userId, DancerUpdateServiceRequest request) {
7679

7780
Set<Genre> genres = dancerMapper.mapToGenre(request.getPreferredGenres());
7881
Set<DancerGenre> dancerGenres = dancerMapper.mapToDancerGenre(dancer, genres);
79-
Set<DancerImage> dancerImages = dancerMapper.mapToDancerImage(dancer, request.getDancerImages());
82+
Set<DancerImage> dancerImages = dancerMapper.mapToDancerImage(
83+
dancer, request.getDancerImages());
8084

8185
dancerGenreManager.deleteByDancer(dancer);
8286
dancerGenreManager.saveAll(dancerGenres);
@@ -135,7 +139,8 @@ public DancerSummaryListResponse getDancersByGenre(Long genreId, Integer page, I
135139

136140
List<Dancer> candidates = dancerReader.readByDancerGenre(pages.getContent());
137141
List<DancerSummaryResponse> dancerInfos = dancerManager.toSummaryInfo(candidates);
138-
return DancerSummaryListResponse.from(dancerInfos, pages.getNumber(), pages.getTotalPages(), pages.getTotalElements());
142+
return DancerSummaryListResponse.from(
143+
dancerInfos, pages.getNumber(), pages.getTotalPages(), pages.getTotalElements());
139144
}
140145

141146
@Transactional
@@ -152,14 +157,25 @@ public DancerSummaryListResponse getAllDancers() {
152157
}
153158

154159
@Transactional
155-
public DancerSummaryListResponse getRecommendationDancers(Long userId) {
160+
public DancerSummaryListResponse getRecommendedDancers(Long userId, Integer dancerNeeded) {
156161
User user = userReader.readUserById(userId);
157-
Set<Long> genreList = user.getUserGenres().stream()
158-
.map(userGenre -> userGenre.getGenre().getId())
159-
.collect(Collectors.toSet());
160-
List<Dancer> dancers = dancerReader.readDancerByGenre(genreList);
161-
List<DancerSummaryResponse> dancerInfos = dancerManager.toSummaryInfo(dancers);
162-
return DancerSummaryListResponse.from(dancerInfos, 0, 0, 4L);
162+
Set<Long> userFavoriteGenres = user.getUserGenres().stream()
163+
.map(userGenre -> userGenre.getGenre().getId())
164+
.collect(Collectors.toSet());
165+
Set<Long> userFavoriteDancers = new HashSet<>(userDancerReader.findDancerIdsByUser(user));
166+
List<Dancer> dancers = dancerReader.readDancersByGenre(userFavoriteGenres);
167+
168+
// 기존에 즐겨찾기한 댄서는 제외
169+
List<Dancer> recommendedDancers = new ArrayList<>(
170+
dancers.stream()
171+
.filter(dancer -> !userFavoriteDancers.contains(dancer.getId()))
172+
.toList()
173+
);
174+
175+
Collections.shuffle(recommendedDancers); // 후보 댄서들 섞기
176+
List<DancerSummaryResponse> dancerInfos = dancerManager.toSummaryInfo(
177+
recommendedDancers.subList(0, Math.min(dancerNeeded, recommendedDancers.size())));
178+
return DancerSummaryListResponse.from(dancerInfos);
163179
}
164180

165181
// TODO: averRate로 특정 평점 이상의 댄서만 추출하도록 수정 가능?

src/main/java/com/danthis/backend/application/dancer/implement/DancerReader.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.danthis.backend.common.exception.ErrorCode;
55
import com.danthis.backend.domain.dancer.Dancer;
66
import com.danthis.backend.domain.dancer.repository.DancerRepository;
7-
import com.danthis.backend.domain.genre.Genre;
87
import com.danthis.backend.domain.mapping.dancergenre.DancerGenre;
98
import com.danthis.backend.domain.mapping.userdancer.UserDancer;
109
import java.util.List;
@@ -43,7 +42,7 @@ public List<Dancer> readAllDancers() {
4342
return dancerRepository.findAll();
4443
}
4544

46-
public List<Dancer> readDancerByGenre(Set<Long> genreList) {
45+
public List<Dancer> readDancersByGenre(Set<Long> genreList) {
4746
return dancerRepository.findByGenres(genreList);
4847
}
4948

src/main/java/com/danthis/backend/application/dancer/response/DancerSummaryListResponse.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ public static DancerSummaryListResponse from(final List<DancerSummaryResponse> d
2525
}
2626

2727
public static DancerSummaryListResponse from(final List<DancerSummaryResponse> dancers) {
28-
return DancerSummaryListResponse.builder()
29-
.dancers(dancers)
30-
.build();
28+
return DancerSummaryListResponse.builder()
29+
.dancers(dancers)
30+
.totalElements((long) dancers.size())
31+
.build();
3132
}
3233

3334
@Getter
3435
@Builder
3536
public static class DancerSummaryResponse {
37+
3638
private Long id;
3739
private String dancerName;
3840
private Set<String> genres;

src/main/java/com/danthis/backend/application/user/implement/mapping/UserDancerReader.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.danthis.backend.application.user.implement.mapping;
22

3-
import com.danthis.backend.domain.mapping.userdancer.UserDancer;
43
import com.danthis.backend.domain.dancer.Dancer;
54
import com.danthis.backend.domain.mapping.userdancer.UserDancer;
65
import com.danthis.backend.domain.mapping.userdancer.repository.UserDancerRepository;

src/main/java/com/danthis/backend/application/user/implement/mapping/WishListReader.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.danthis.backend.domain.mapping.wishlist.WishList;
44
import com.danthis.backend.domain.mapping.wishlist.repository.WishListRepository;
5+
import java.util.List;
56
import lombok.RequiredArgsConstructor;
67
import org.springframework.data.domain.Page;
78
import org.springframework.data.domain.Pageable;
@@ -20,4 +21,8 @@ public Page<WishList> readWishListByUserId(Long userId, Pageable pageable) {
2021
public WishList readWishListByUserIdAndClassId(Long userId, Long classId) {
2122
return wishListRepository.findWishListByUserIdAndClassId(userId, classId);
2223
}
24+
25+
public List<WishList> readAllWishListByUserId(Long userId) {
26+
return wishListRepository.findAllByUserId(userId);
27+
}
2328
}

src/main/java/com/danthis/backend/domain/danceclass/repository/DanceClassRepositoryCustom.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import com.danthis.backend.domain.danceclass.DanceClass;
44
import com.danthis.backend.domain.danceclass.danceclassschedule.Week;
55
import java.time.LocalDate;
6+
import java.util.List;
7+
import java.util.Set;
68
import org.springframework.data.domain.Page;
79
import org.springframework.data.domain.PageRequest;
810

@@ -11,4 +13,6 @@ public interface DanceClassRepositoryCustom {
1113
Page<DanceClass> findByGenreIdAndDate(Long genreId, LocalDate date, PageRequest pageable);
1214

1315
Page<DanceClass> findByGenreIdAndDay(Long genreId, Week day, PageRequest pageable);
16+
17+
List<DanceClass> findByGenreIds(Set<Long> genreIds);
1418
}

0 commit comments

Comments
 (0)