Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.back.domain.cocktail.controller;

import com.back.domain.cocktail.dto.CocktailRecommendResponseDto;
import com.back.domain.cocktail.service.RecommendService;
import com.back.global.rsData.RsData;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/cocktails/recommend")
@Tag(name = "ApiCocktailRecommendController", description = "API 칵테일 추천 컨트롤러")
@RequiredArgsConstructor
public class CocktailRecommendController {

private final RecommendService recommendService;

// 상세페이지 추천 (DTO로 반환)
@Operation(summary = "상세페이지 유사 칵테일 추천", description = "현재 칵테일과 유사한 칵테일 최대 3개를 반환합니다.")
@GetMapping("/related")
public RsData<List<CocktailRecommendResponseDto>> recommendRelated(@RequestParam Long cocktailId) {
return RsData.successOf(recommendService.recommendRelatedCocktails(cocktailId, 3));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.back.domain.cocktail.dto;

public record CocktailRecommendResponseDto(
Long id, // 상세페이지 이동용 ID
String cocktailNameKo, // 한글 이름
String cocktailName, // 영문 이름
String cocktailImgUrl, // 이미지 URL (썸네일)
String alcoholStrength, // 도수 (라이트/미디엄/스트롱 등)
String alcoholBaseType // 베이스 주종 (진, 럼, 보드카 등)
) {
}

Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,12 @@ Page<Cocktail> searchWithFilters(@Param("keyword") String keyword,
@Param("types") List<CocktailType> types,
@Param("bases") List<AlcoholBaseType> bases,
Pageable pageable);
//유사칵테일 추천관련
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유사 칵테일 3개를 유저에게 추천한다고 했을 때, 도수가 같은 칵테일 한 개, 기주가 같은 칵테일 한 개, 타입이 같은 칵테일 한 개 이렇게 세 가지로 추천이 되는 게 맞나요?
생각도 못했어요. 이렇게 구현할 수도 있었네요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헛! 좋은 아이디어 감사합니다~ ㅎㅎ
지금은 아래 순위로 되어있었어욥 ㅎㅎ
다음 PR때 보완해볼게욥 ㅎㅎ
-알코올 강도가 같은 칵테일 먼저
-타입이 같은 칵테일 다음
-베이스가 같은 칵테일 마지막

List<Cocktail> findByAlcoholStrengthAndIdNot(AlcoholStrength strength, Long excludeId);

//유사칵테일 추천관련
List<Cocktail> findByCocktailTypeAndIdNot(CocktailType type, Long excludeId);

//유사칵테일 추천관련
List<Cocktail> findByAlcoholBaseTypeAndIdNot(AlcoholBaseType baseType, Long excludeId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.back.domain.cocktail.service;

import com.back.domain.cocktail.dto.CocktailRecommendResponseDto;
import com.back.domain.cocktail.entity.Cocktail;
import com.back.domain.cocktail.repository.CocktailRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

@Service
@RequiredArgsConstructor
public class RecommendService {

private final CocktailRepository cocktailRepository;

public List<CocktailRecommendResponseDto> recommendRelatedCocktails(Long cocktailId, int maxSize) {
Cocktail current = cocktailRepository.findById(cocktailId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 칵테일입니다."));

// 3가지 조건으로 유사 칵테일 조회
List<Cocktail> byAlcoholStrength = cocktailRepository.findByAlcoholStrengthAndIdNot(current.getAlcoholStrength(), current.getId());
List<Cocktail> byCocktailType = cocktailRepository.findByCocktailTypeAndIdNot(current.getCocktailType(), current.getId());
List<Cocktail> byAlcoholBase = cocktailRepository.findByAlcoholBaseTypeAndIdNot(current.getAlcoholBaseType(), current.getId());

// 합치고 중복 제거
Set<Cocktail> combined = new LinkedHashSet<>();
combined.addAll(byAlcoholStrength);
combined.addAll(byCocktailType);
combined.addAll(byAlcoholBase);

List<Cocktail> combinedList = new ArrayList<>(combined);
if (combinedList.size() > maxSize) {
combinedList = combinedList.subList(0, maxSize);
}

// DTO로 변환
return combinedList.stream()
.map(c -> new CocktailRecommendResponseDto(
c.getId(),
c.getCocktailNameKo(),
c.getCocktailName(),
c.getCocktailImgUrl(),
c.getAlcoholStrength().name(),
c.getAlcoholBaseType().name()
))
.toList();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,9 @@ public ResponseEntity<RsData<Void>> handleIOException(IOException e) {
.body(RsData.of(500, "서버 내부 오류가 발생했습니다.", null));
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<RsData<Void>> handleIllegalArgumentException(IllegalArgumentException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(RsData.of(404, ex.getMessage()));
}
}
6 changes: 3 additions & 3 deletions src/main/resources/data-h2.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- 테이블 생성
CREATE TABLE IF NOT EXISTS cocktail (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
cocktail_name VARCHAR(255),
id BIGINT AUTO_INCREMENT PRIMARY KEY,
cocktail_name VARCHAR(255),
cocktail_name_ko VARCHAR(255),
alcohol_strength VARCHAR(50),
cocktail_story CLOB,
Expand All @@ -14,7 +14,7 @@ CREATE TABLE IF NOT EXISTS cocktail (

-- CSV 파일에서 데이터 읽어오기
INSERT INTO cocktail (
cocktail_name, cocktail_name_ko, alcohol_strength,
cocktail_name,cocktail_name_ko, alcohol_strength,
cocktail_story, cocktail_type, alcohol_base_type,
ingredient, recipe, cocktail_img_url
)
Expand Down