Skip to content

Commit 83a961f

Browse files
committed
2 parents 7f1b105 + b30cd6c commit 83a961f

File tree

10 files changed

+114
-128
lines changed

10 files changed

+114
-128
lines changed

src/main/java/com/back/domain/cocktail/controller/CocktailRecommendController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class CocktailRecommendController {
2121

2222
private final RecommendService recommendService;
2323

24-
// 상세페이지 추천 (DTO로 반환)
24+
// 상세페이지 3개 칵테일 추천 (DTO로 반환)
2525
@Operation(summary = "상세페이지 유사 칵테일 추천", description = "현재 칵테일과 유사한 칵테일 최대 3개를 반환합니다.")
2626
@GetMapping("/related")
2727
public RsData<List<CocktailRecommendResponseDto>> recommendRelated(@RequestParam Long cocktailId) {

src/main/java/com/back/domain/cocktail/entity/Cocktail.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class Cocktail {
4141

4242
private String ingredient;
4343

44+
@Column(length = 1000)
4445
private String recipe;
4546

4647
private String cocktailImgUrl;

src/main/java/com/back/domain/cocktail/repository/CocktailRepository.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,10 @@ Page<Cocktail> searchWithFilters(@Param("keyword") String keyword,
3636
@Param("types") List<CocktailType> types,
3737
@Param("bases") List<AlcoholBaseType> bases,
3838
Pageable pageable);
39-
//유사칵테일 추천관련
40-
List<Cocktail> findByAlcoholStrengthAndIdNot(AlcoholStrength strength, Long excludeId);
4139

42-
//유사칵테일 추천관련
43-
List<Cocktail> findByCocktailTypeAndIdNot(CocktailType type, Long excludeId);
44-
45-
//유사칵테일 추천관련
46-
List<Cocktail> findByAlcoholBaseTypeAndIdNot(AlcoholBaseType baseType, Long excludeId);
40+
List<Cocktail> findByAlcoholStrengthAndAlcoholBaseTypeAndIdNot(
41+
AlcoholStrength alcoholStrength,
42+
AlcoholBaseType alcoholBaseType,
43+
Long id
44+
);
4745
}

src/main/java/com/back/domain/cocktail/service/CocktailService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public List<CocktailSearchResponseDto> searchAndFilter (CocktailSearchRequestDto
7878
int size = cocktailSearchRequestDto.getSize() != null && cocktailSearchRequestDto.getSize() > 0
7979
? cocktailSearchRequestDto.getSize() : DEFAULT_SIZE;
8080

81-
// searchWithFilters에서 조회한 결과값을 pageResult에 저장.
81+
// searchAndFilters에서 조회한 결과값을 pageResult에 저장.
8282
Pageable pageable = PageRequest.of(page, size);
8383

8484
// 빈 리스트(null 또는 [])는 null로 변환

src/main/java/com/back/domain/cocktail/service/RecommendService.java

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66
import lombok.RequiredArgsConstructor;
77
import org.springframework.stereotype.Service;
88

9-
import java.util.ArrayList;
10-
import java.util.LinkedHashSet;
119
import java.util.List;
12-
import java.util.Set;
1310

1411
@Service
1512
@RequiredArgsConstructor
@@ -21,24 +18,20 @@ public List<CocktailRecommendResponseDto> recommendRelatedCocktails(Long cocktai
2118
Cocktail current = cocktailRepository.findById(cocktailId)
2219
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 칵테일입니다."));
2320

24-
// 3가지 조건으로 유사 칵테일 조회
25-
List<Cocktail> byAlcoholStrength = cocktailRepository.findByAlcoholStrengthAndIdNot(current.getAlcoholStrength(), current.getId());
26-
List<Cocktail> byCocktailType = cocktailRepository.findByCocktailTypeAndIdNot(current.getCocktailType(), current.getId());
27-
List<Cocktail> byAlcoholBase = cocktailRepository.findByAlcoholBaseTypeAndIdNot(current.getAlcoholBaseType(), current.getId());
21+
// 알콜 강도와 베이스 타이이 같은 칵테일만 조회
22+
List<Cocktail> related = cocktailRepository
23+
.findByAlcoholStrengthAndAlcoholBaseTypeAndIdNot(
24+
current.getAlcoholStrength(),
25+
current.getAlcoholBaseType(),
26+
current.getId()
27+
);
2828

29-
// 합치고 중복 제거
30-
Set<Cocktail> combined = new LinkedHashSet<>();
31-
combined.addAll(byAlcoholStrength);
32-
combined.addAll(byCocktailType);
33-
combined.addAll(byAlcoholBase);
34-
35-
List<Cocktail> combinedList = new ArrayList<>(combined);
36-
if (combinedList.size() > maxSize) {
37-
combinedList = combinedList.subList(0, maxSize);
29+
if (related.size() > maxSize) {
30+
related = related.subList(0, maxSize);
3831
}
3932

4033
// DTO로 변환
41-
return combinedList.stream()
34+
return related.stream()
4235
.map(c -> new CocktailRecommendResponseDto(
4336
c.getId(),
4437
c.getCocktailNameKo(),

src/main/java/com/back/global/init/DevInitData.java

Lines changed: 62 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
package com.back.global.init;
22

3+
import com.back.domain.cocktail.entity.Cocktail;
4+
import com.back.domain.cocktail.repository.CocktailRepository;
35
import com.back.domain.notification.entity.Notification;
46
import com.back.domain.notification.enums.NotificationType;
57
import com.back.domain.notification.repository.NotificationRepository;
6-
import com.back.domain.cocktail.entity.Cocktail;
7-
import com.back.domain.cocktail.enums.AlcoholBaseType;
8-
import com.back.domain.cocktail.enums.AlcoholStrength;
9-
import com.back.domain.cocktail.enums.CocktailType;
10-
import com.back.domain.cocktail.repository.CocktailRepository;
118
import com.back.domain.post.category.entity.Category;
129
import com.back.domain.post.category.repository.CategoryRepository;
1310
import com.back.domain.post.comment.entity.Comment;
@@ -19,14 +16,14 @@
1916
import com.back.domain.post.post.repository.PostRepository;
2017
import com.back.domain.user.entity.User;
2118
import com.back.domain.user.repository.UserRepository;
22-
import org.springframework.transaction.annotation.Transactional;
2319
import lombok.RequiredArgsConstructor;
2420
import org.springframework.beans.factory.annotation.Autowired;
2521
import org.springframework.boot.ApplicationRunner;
2622
import org.springframework.context.annotation.Bean;
2723
import org.springframework.context.annotation.Configuration;
2824
import org.springframework.context.annotation.Lazy;
2925
import org.springframework.context.annotation.Profile;
26+
import org.springframework.transaction.annotation.Transactional;
3027

3128
@Configuration
3229
@Profile("dev")
@@ -50,67 +47,14 @@ public class DevInitData {
5047
@Bean
5148
ApplicationRunner devInitDataApplicationRunner() {
5249
return args -> {
53-
self.cocktailInit();
5450
self.userInit();
55-
self.boardInit();
51+
// myBar는 사용자 활성 상태 기준으로 초기화가 필요하므로 boardInit(soft delete)보다 먼저 실행
5652
self.myBarInit();
53+
self.boardInit();
5754
self.notificationInit();
5855
};
5956
}
6057

61-
@Transactional
62-
public void cocktailInit() {
63-
if (cocktailRepository.count() > 0) return;
64-
65-
// 1) 하이볼
66-
cocktailRepository.save(Cocktail.builder()
67-
.cocktailName("Highball")
68-
.cocktailNameKo("하이볼")
69-
.alcoholStrength(AlcoholStrength.LIGHT)
70-
.cocktailType(CocktailType.LONG)
71-
.alcoholBaseType(AlcoholBaseType.WHISKY)
72-
.ingredient("위스키, 탄산수, 얼음, 레몬피")
73-
.recipe("잔에 얼음 → 위스키 → 탄산수 → 가볍게 스터")
74-
.cocktailImgUrl("/img/cocktail/1.jpg")
75-
.build());
76-
77-
// 2) 진토닉
78-
cocktailRepository.save(Cocktail.builder()
79-
.cocktailName("Gin and Tonic")
80-
.cocktailNameKo("진토닉")
81-
.alcoholStrength(AlcoholStrength.WEAK)
82-
.cocktailType(CocktailType.LONG)
83-
.alcoholBaseType(AlcoholBaseType.GIN)
84-
.ingredient("진, 토닉워터, 얼음, 라임")
85-
.recipe("잔에 얼음 → 진 → 토닉워터 → 라임")
86-
.cocktailImgUrl("/img/cocktail/2.jpg")
87-
.build());
88-
89-
// 3) 올드패션드
90-
cocktailRepository.save(Cocktail.builder()
91-
.cocktailName("Old Fashioned")
92-
.cocktailNameKo("올드패션드")
93-
.alcoholStrength(AlcoholStrength.STRONG)
94-
.cocktailType(CocktailType.SHORT)
95-
.alcoholBaseType(AlcoholBaseType.WHISKY)
96-
.ingredient("버번 위스키, 설탕/시럽, 앙고스투라 비터스, 오렌지 필")
97-
.recipe("시럽+비터스 → 위스키 → 얼음 → 스터 → 오렌지 필")
98-
.cocktailImgUrl("/img/cocktail/3.jpg")
99-
.build());
100-
101-
// 4) 모히또
102-
cocktailRepository.save(Cocktail.builder()
103-
.cocktailName("Mojito")
104-
.cocktailNameKo("모히또")
105-
.alcoholStrength(AlcoholStrength.LIGHT)
106-
.cocktailType(CocktailType.LONG)
107-
.alcoholBaseType(AlcoholBaseType.RUM)
108-
.ingredient("라임, 민트, 설탕/시럽, 화이트 럼, 탄산수, 얼음")
109-
.recipe("라임+민트+시럽 머들 → 럼 → 얼음 → 탄산수")
110-
.cocktailImgUrl("/img/cocktail/4.jpg")
111-
.build());
112-
}
113-
11458
@Transactional
11559
public void userInit() {
11660
userRepository.findByNickname("사용자A").orElseGet(() ->
@@ -270,39 +214,72 @@ public void notificationInit() {
270214
public void myBarInit() {
271215
if (myBarRepository.count() > 0) return;
272216

273-
User userA = userRepository.findByNickname("사용자A").orElse(null);
274-
User userB = userRepository.findByNickname("사용자B").orElse(null);
275-
User userC = userRepository.findByNickname("사용자C").orElse(null);
276-
277-
if (userA == null || userC == null) return;
217+
User userA = userRepository.findByNickname("사용자A").orElseThrow();
218+
User userB = userRepository.findByNickname("사용자B").orElseThrow();
219+
User userC = userRepository.findByNickname("사용자C").orElseThrow();
278220

279-
// 칵테일 참조 준비
280-
var cocktails = cocktailRepository.findAll();
281-
Cocktail c1 = cocktails.stream().filter(c -> "하이볼".equals(c.getCocktailNameKo())).findFirst().orElse(null);
282-
Cocktail c2 = cocktails.stream().filter(c -> "진토닉".equals(c.getCocktailNameKo())).findFirst().orElse(null);
283-
Cocktail c3 = cocktails.stream().filter(c -> "올드패션드".equals(c.getCocktailNameKo())).findFirst().orElse(null);
284-
Cocktail c4 = cocktails.stream().filter(c -> "모히또".equals(c.getCocktailNameKo())).findFirst().orElse(null);
221+
// 칵테일 참조 준비: 이름 우선 매칭, 부족하면 ID 오름차순으로 보충
222+
var all = cocktailRepository.findAll();
223+
if (all.isEmpty()) return; // 칵테일 데이터 없으면 스킵
224+
225+
java.util.List<String> prefer = java.util.List.of("하이볼", "진토닉", "올드패션드", "모히또");
226+
java.util.List<Cocktail> selected = new java.util.ArrayList<>();
227+
228+
// 선호 이름 매칭
229+
for (String nameKo : prefer) {
230+
all.stream()
231+
.filter(c -> nameKo.equals(c.getCocktailNameKo()))
232+
.findFirst()
233+
.ifPresent(c -> {
234+
if (selected.stream().noneMatch(s -> java.util.Objects.equals(s.getId(), c.getId()))) {
235+
selected.add(c);
236+
}
237+
});
238+
}
285239

286-
// 방어: 칵테일 누락 시 스킵
287-
if (c1 == null || c2 == null || c3 == null || c4 == null) return;
240+
// 부족분 보충: ID 오름차순으로 정렬 후 채우기
241+
all.stream()
242+
.sorted(java.util.Comparator.comparingLong(c -> c.getId() == null ? Long.MAX_VALUE : c.getId()))
243+
.forEach(c -> {
244+
if (selected.size() < 4 && selected.stream().noneMatch(s -> java.util.Objects.equals(s.getId(), c.getId()))) {
245+
selected.add(c);
246+
}
247+
});
248+
249+
// 실제 사용에 필요한 인덱스가 없으면 해당 동작을 스킵
250+
Cocktail c1 = selected.size() > 0 ? selected.get(0) : null;
251+
Cocktail c2 = selected.size() > 1 ? selected.get(1) : null;
252+
Cocktail c3 = selected.size() > 2 ? selected.get(2) : null;
253+
Cocktail c4 = selected.size() > 3 ? selected.get(3) : null;
288254

289255
// A: c1(now-2d), c2(now-1d)
290-
myBarService.keep(userA.getId(), c1.getId());
291-
myBarService.keep(userA.getId(), c2.getId());
292-
myBarRepository.findByUser_IdAndCocktail_Id(userA.getId(), c1.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(2)));
293-
myBarRepository.findByUser_IdAndCocktail_Id(userA.getId(), c2.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(1)));
256+
if (c1 != null) {
257+
myBarService.keep(userA.getId(), c1.getId());
258+
myBarRepository.findByUser_IdAndCocktail_Id(userA.getId(), c1.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(2)));
259+
}
260+
if (c2 != null) {
261+
myBarService.keep(userA.getId(), c2.getId());
262+
myBarRepository.findByUser_IdAndCocktail_Id(userA.getId(), c2.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(1)));
263+
}
294264

295-
if (userB != null && !userB.isDeleted()) {
265+
// B: c3 keep 후 unkeep -> DELETED
266+
if (c3 != null) {
296267
myBarService.keep(userB.getId(), c3.getId());
297268
myBarService.unkeep(userB.getId(), c3.getId());
298269
}
299270

300271
// C: c2(now-3d), c3(now-2d), c4(now-1h)
301-
myBarService.keep(userC.getId(), c2.getId());
302-
myBarService.keep(userC.getId(), c3.getId());
303-
myBarService.keep(userC.getId(), c4.getId());
304-
myBarRepository.findByUser_IdAndCocktail_Id(userC.getId(), c2.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(3)));
305-
myBarRepository.findByUser_IdAndCocktail_Id(userC.getId(), c3.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(2)));
306-
myBarRepository.findByUser_IdAndCocktail_Id(userC.getId(), c4.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusHours(1)));
272+
if (c2 != null) {
273+
myBarService.keep(userC.getId(), c2.getId());
274+
myBarRepository.findByUser_IdAndCocktail_Id(userC.getId(), c2.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(3)));
275+
}
276+
if (c3 != null) {
277+
myBarService.keep(userC.getId(), c3.getId());
278+
myBarRepository.findByUser_IdAndCocktail_Id(userC.getId(), c3.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(2)));
279+
}
280+
if (c4 != null) {
281+
myBarService.keep(userC.getId(), c4.getId());
282+
myBarRepository.findByUser_IdAndCocktail_Id(userC.getId(), c4.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusHours(1)));
283+
}
307284
}
308285
}

src/main/java/com/back/global/security/SecurityConfig.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
1515

1616
import java.util.Arrays;
17-
import static org.springframework.http.HttpMethod.DELETE;
1817

1918
@Configuration
2019
@EnableWebSecurity
@@ -52,7 +51,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
5251
) // OAuth 인증시 필요할때만 세션 사용
5352

5453
.authorizeHttpRequests(auth -> auth
55-
.requestMatchers(DELETE, "/me/account").authenticated()
5654
// 개발 편의성을 위해 모든 요청 허용
5755
.anyRequest().permitAll()
5856

@@ -125,4 +123,4 @@ public CorsConfigurationSource corsConfigurationSource() {
125123
source.registerCorsConfiguration("/**", configuration);
126124
return source;
127125
}
128-
}
126+
}

src/main/resources/application-dev.yml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
spring:
2-
sql:
3-
init:
4-
mode: always
5-
data-locations: classpath:data-h2.sql
62
# H2 Database 설정
73
datasource:
84
driver-class-name: org.h2.Driver
@@ -18,14 +14,21 @@ spring:
1814

1915
# JPA 설정
2016
jpa:
17+
defer-datasource-initialization: true
2118
database-platform: org.hibernate.dialect.H2Dialect
2219
hibernate:
23-
ddl-auto: create-drop # 개발용: 시작할 때 테이블 생성, 종료할 때 삭제 / db_dev 에러 방지용으로 upsdate->create-drop 변경
20+
ddl-auto: create-drop # 개발용: 시작할 때 테이블 생성, 종료할 때 삭제 / db_dev 에러 방지용으로 update->create-drop 변경
2421
properties:
2522
hibernate:
2623
format_sql: true
2724
show_sql: true
2825

26+
sql:
27+
init:
28+
mode: always # 항상 실행
29+
encoding: UTF-8
30+
data-locations: classpath:data-h2.sql
31+
2932
cloud:
3033
aws:
3134
region:

src/main/resources/application-test.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
spring:
2-
sql:
3-
init:
4-
mode: always
5-
data-locations: classpath:data-h2.sql
62
datasource:
73
url: jdbc:h2:mem:db_test;MODE=MySQL
84
username: sa

0 commit comments

Comments
 (0)