Skip to content

Commit

Permalink
Merge pull request #53 from CAUSOLDOUTMEN/feat/50-food-analysis-score
Browse files Browse the repository at this point in the history
Feat: FoodService 분석 및 FoodControllerTest 구현 (#50)
  • Loading branch information
win-luck authored Nov 6, 2023
2 parents e61a648 + 7ee2503 commit d7fe019
Show file tree
Hide file tree
Showing 8 changed files with 706 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import com.diareat.diareat.food.dto.*;
import com.diareat.diareat.food.service.FoodService;
import com.diareat.diareat.user.dto.response.ResponseRankUserDto;
import com.diareat.diareat.util.api.ApiResponse;
import com.diareat.diareat.util.api.ResponseCode;
import io.swagger.annotations.Api;
import io.swagger.v3.oas.annotations.Operation;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

Expand Down Expand Up @@ -93,36 +95,42 @@ public ApiResponse<ResponseNutritionSumByDateDto> getNutritionSumByDate(@PathVar
@RequestParam int mm,
@RequestParam int dd){
LocalDate date = LocalDate.of(yy,mm,dd);
return ApiResponse.success(foodService.getNutritionSumByDate(userId,date),ResponseCode.FOOD_FAVORITE_READ_SUCCESS.getMessage());
return ApiResponse.success(foodService.getNutritionSumByDate(userId,date),ResponseCode.FOOD_READ_SUCCESS.getMessage());
}

//"" 7일간 총합 조회
@Operation(summary = "[음식] 최근 7일간 먹은 음식들의 영양성분 총합 조회",description = "최근 7일 간 유저가 먹은 음식들의 영양성분별 총합 및 권장섭취량에 대한 비율을 조회합니다.")
@GetMapping("/{userId}/nutrition/recentWeek")
public ApiResponse<ResponseNutritionSumByDateDto> getNutritionSumByWeek(@PathVariable Long userId){
return ApiResponse.success(foodService.getNutritionSumByWeek(userId),ResponseCode.FOOD_FAVORITE_READ_SUCCESS.getMessage());
return ApiResponse.success(foodService.getNutritionSumByWeek(userId),ResponseCode.FOOD_READ_SUCCESS.getMessage());
}

//"" 30일간 (1달간) 총합 조회
@Operation(summary = "[음식] 최근 한달 간 먹은 음식들의 영양성분 총합 조회",description = "최근 한달 간 유저가 먹은 음식들의 영양성분별 총합 및 권장섭취량에 대한 비율을 조회합니다.")
@GetMapping("/{userId}/nutrition/recentMonth")
public ApiResponse<ResponseNutritionSumByDateDto> getNutritionSumByMonth(@PathVariable Long userId){
return ApiResponse.success(foodService.getNutritionSumByMonth(userId),ResponseCode.FOOD_FAVORITE_READ_SUCCESS.getMessage());
return ApiResponse.success(foodService.getNutritionSumByMonth(userId),ResponseCode.FOOD_READ_SUCCESS.getMessage());

}

//7일간의 Best 3 조회
@Operation(summary = "[음식] 최근 7일 간 먹은 Top3 음식 조회",description = "최근 7일 간 유저가 먹은 음식들 중에서 Top3에 해당한 음식들을 조회합니다.")
@GetMapping("/{userId}/best")
public ApiResponse<ResponseFoodRankDto> getBestFoodByWeek(@PathVariable Long userId){
return ApiResponse.success(foodService.getBestFoodByWeek(userId),ResponseCode.FOOD_FAVORITE_READ_SUCCESS.getMessage());
//유저의 주간 식습관 점수와 best3, worst3 음식 조회
@Operation(summary = "[음식] 유저의 주간 식습관 점수와 best3, worst3 음식 조회",description = "유저의 주간 식습관 점수와 best3, worst3 음식을 조회합니다.")
@GetMapping("/{userId}/score")
public ApiResponse<ResponseScoreBestWorstDto> getScoreOfUserWithBestAndWorstFoods(@PathVariable Long userId){
return ApiResponse.success(foodService.getScoreOfUserWithBestAndWorstFoods(userId),ResponseCode.FOOD_READ_SUCCESS.getMessage());
}

//7일간의 Worst 3 조회
@Operation(summary = "[음식] 최근 7일 간 먹은 Worst3 음식 조회",description = "최근 7일 간 유저가 먹은 음식들 중에서 Worst3에 해당한 음식들을 조회합니다.")
@GetMapping("/{userId}/worst")
public ApiResponse<ResponseFoodRankDto> getWorstFoodByWeek(@PathVariable Long userId){
return ApiResponse.success(foodService.getWorstFoodByWeek(userId),ResponseCode.FOOD_FAVORITE_READ_SUCCESS.getMessage());
//유저의 일기 분석 그래프 데이터 및 식습관 totalScore 조회
@Operation(summary = "[음식] 유저의 일기 분석 그래프 데이터 및 주간 식습관 점수 조회",description = "유저의 일기 분석 그래프 데이터 및 식습관 점수를 조회합니다.")
@GetMapping("/{userId}/analysis")
public ApiResponse<ResponseAnalysisDto> getAnalysisOfUser(@PathVariable Long userId){
return ApiResponse.success(foodService.getAnalysisOfUser(userId),ResponseCode.FOOD_READ_SUCCESS.getMessage());
}

//유저의 식습관 점수를 기반으로 한 주간 랭킹 조회
@Operation(summary = "[음식] 유저의 식습관 점수를 기반으로 한 주간 랭킹 조회",description = "유저의 식습관 점수를 기반으로 한 주간 랭킹을 조회합니다. (팔로잉 상대 기반)")
@GetMapping("/{userId}/rank")
public ApiResponse<List<ResponseRankUserDto>> getUserRankByWeek(@PathVariable Long userId){
return ApiResponse.success(foodService.getUserRankByWeek(userId),ResponseCode.FOOD_RANK_READ_SUCCESS.getMessage());
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/diareat/diareat/food/domain/Food.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ public boolean isFavorite() {
}

public void setId(long id) {this.id = id;}

public void setDate(LocalDate date) {this.date = date;} //food test를 위한 date
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
public class ResponseFoodRankDto {

private Long userId;
private List<ResponseFoodDto> rankFoodList;
private List<ResponseSimpleFoodDto> rankFoodList;
private LocalDate startDate; //해당 날짜로부터 7일전까지
private boolean isBest; //isBest = true 이면 Best 3, false 이면 Worst 3

public static ResponseFoodRankDto of(Long userId, List<ResponseFoodDto> rankFoodList, LocalDate startDate, boolean isBest) {
public static ResponseFoodRankDto of(Long userId, List<ResponseSimpleFoodDto> rankFoodList, LocalDate startDate, boolean isBest) {
return new ResponseFoodRankDto(userId, rankFoodList, startDate, isBest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

public interface FoodRepository extends JpaRepository<Food, Long> {
boolean existsByIdAndUserId(Long id, Long userId); // 유저가 먹은 음식인지 확인
boolean existsByName(String name);
List<Food> findAllByUserIdAndDate(Long userId, LocalDate date); //유저가 특정 날짜에 먹은 음식 반환
List<Food> findAllByUserIdAndDateBetween(Long userId, LocalDate startDate, LocalDate endDate); // 유저가 특정 기간 내에 먹은 음식 반환
}
111 changes: 103 additions & 8 deletions src/main/java/com/diareat/diareat/food/service/FoodService.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public class FoodService {
@CacheEvict(value = "ResponseFoodDto", key = "#createFoodDto.getUserId()+#createFoodDto.getDate()", cacheManager = "diareatCacheManager")
@Transactional
public Long saveFood(CreateFoodDto createFoodDto) {
if (foodRepository.existsByName(createFoodDto.getName())){
throw new FoodException(ResponseCode.FOOD_NAME_ALREADY_EXIST);
}
User user = getUserById(createFoodDto.getUserId());
Food food = Food.createFood(createFoodDto.getName(), user, createFoodDto.getBaseNutrition());
return foodRepository.save(food).getId();
Expand Down Expand Up @@ -88,7 +91,7 @@ public List<ResponseFavoriteFoodDto> getFavoriteFoodList(Long userId){
validateUser(userId);
List<FavoriteFood> foodList = favoriteFoodRepository.findAllByUserId(userId);
return foodList.stream()
.map(favoriteFood -> ResponseFavoriteFoodDto.of(favoriteFood.getId(), favoriteFood.getName(),
.map(favoriteFood -> ResponseFavoriteFoodDto.of(favoriteFood.getUser().getId(),favoriteFood.getName(),
favoriteFood.getBaseNutrition(), favoriteFood.getCount())).collect(Collectors.toList());
}

Expand Down Expand Up @@ -133,7 +136,7 @@ public ResponseNutritionSumByDateDto getNutritionSumByWeek(Long userId) {
public ResponseNutritionSumByDateDto getNutritionSumByMonth(Long userId) {
validateUser(userId);
LocalDate endDate = LocalDate.now();
List<Food> foodList = foodRepository.findAllByUserIdAndDateBetween(userId, endDate.minusWeeks(1), endDate);
List<Food> foodList = foodRepository.findAllByUserIdAndDateBetween(userId, endDate.minusMonths(1), endDate);

return calculateNutritionSumAndRatio(userId, foodList, endDate, 30);
}
Expand All @@ -153,8 +156,9 @@ public ResponseFoodRankDto getBestFoodByWeek(Long userId) {
//사용한 기준은, 고단백과 저지방의 점수 반영 비율을 7:3으로 측정하고, 단백질량이 높을 수록, 지방량이 낮을 수록 점수가 높음. 이후, 내림차순 정렬
// ** Best 3 기준 논의 필요 **

List<ResponseFoodDto> top3FoodsDtoList = top3Foods.stream()
.map(food -> ResponseFoodDto.of(food.getId(), food.getUser().getId(), food.getName(), food.getDate(), food.getTime(), food.getBaseNutrition(), food.isFavorite())).collect(Collectors.toList());
List<ResponseSimpleFoodDto> top3FoodsDtoList = top3Foods.stream()
.map(food -> ResponseSimpleFoodDto.of(food.getName(), food.getBaseNutrition().getKcal(), food.getBaseNutrition().getCarbohydrate(),
food.getBaseNutrition().getProtein(), food.getBaseNutrition().getFat(), food.getDate())).collect(Collectors.toList());

return ResponseFoodRankDto.of(userId, top3FoodsDtoList, endDate, true);
}
Expand All @@ -176,18 +180,109 @@ public ResponseFoodRankDto getWorstFoodByWeek(Long userId) {
// ** 이점은 논의가 필요할 듯? **
// 우선 임시로 지방 비율을 높게 설정

List<ResponseFoodDto> worst3FoodDtoList = worst3Foods.stream()
.map(food -> ResponseFoodDto.of(food.getId(), food.getUser().getId(), food.getName(), food.getDate(), food.getTime(), food.getBaseNutrition(), food.isFavorite())).collect(Collectors.toList());

List<ResponseSimpleFoodDto> worst3FoodDtoList = worst3Foods.stream()
.map(food -> ResponseSimpleFoodDto.of(food.getName(), food.getBaseNutrition().getKcal(), food.getBaseNutrition().getCarbohydrate(),
food.getBaseNutrition().getProtein(), food.getBaseNutrition().getFat(), food.getDate())).collect(Collectors.toList());

return ResponseFoodRankDto.of(userId, worst3FoodDtoList, endDate, false);
}

// 잔여 기능 구현 부분

// 유저의 구체적인 점수 현황과 Best3, Worst3 조회
//유저의 식습관 점수 및 Best 3와 Worst 3 계산
@Transactional(readOnly = true)
public ResponseScoreBestWorstDto getScoreOfUserWithBestAndWorstFoods(Long userId){
validateUser(userId); //유저 객체 검증
double totalScore = 0.0;
double kcalScore = 0.0;
double carbohydrateScore = 0.0;
double proteinScore = 0.0;
double fatScore = 0.0;

User targetUser = userRepository.getReferenceById(userId); //검증된 id로 유저 객체 불러오기

ResponseRankUserDto scoresOfUser = calculateUserScoreThisWeek(targetUser, LocalDate.now().with(DayOfWeek.MONDAY), LocalDate.now());

totalScore = Math.round((scoresOfUser.getTotalScore() * 100.0))/ 100.0;
kcalScore = Math.round((scoresOfUser.getCalorieScore() * 100.0)) / 100.0;
carbohydrateScore = Math.round((scoresOfUser.getCarbohydrateScore() * 100.0)) / 100.0;
proteinScore = Math.round((scoresOfUser.getProteinScore() * 100.0)) / 100.0;
fatScore = Math.round((scoresOfUser.getFatScore() * 100.0 ))/ 100.0;


//Dto의 형식에 맞게 Best3와 Worst3 음식 계산
List<ResponseSimpleFoodDto> simpleBestFoodList = getBestFoodByWeek(userId).getRankFoodList();
List<ResponseSimpleFoodDto> simpleWorstFoodList = getWorstFoodByWeek(userId).getRankFoodList();

return ResponseScoreBestWorstDto.of(kcalScore, carbohydrateScore, proteinScore, fatScore, totalScore, simpleBestFoodList, simpleWorstFoodList);
}



// 유저의 일기 분석 그래프 데이터 및 식습관 totalScore 조회
@Transactional(readOnly = true)
public ResponseAnalysisDto getAnalysisOfUser(Long userId){
validateUser(userId);
User user = userRepository.getReferenceById(userId);

//최근 1주간 유저가 먹은 음식들의 날짜별 HashMap
HashMap<LocalDate, List<BaseNutrition>> nutritionSumOfUserByWeek = getNutritionSumByDateMap(userId, LocalDate.now().minusWeeks(1), LocalDate.now());
HashMap<LocalDate, List<BaseNutrition>> nutritionSumOfUserByMonth = getNutritionSumByDateMap(userId, LocalDate.now().minusWeeks(3).with(DayOfWeek.MONDAY), LocalDate.now());

double totalWeekScore = calculateUserScoreThisWeek(user, LocalDate.now().with(DayOfWeek.MONDAY), LocalDate.now()).getTotalScore();


//날짜 기준으로 정렬 (가장 최근 날짜가 맨 앞으로 오도록)
nutritionSumOfUserByMonth = nutritionSumOfUserByMonth.entrySet().stream()
.sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1,e2) -> e1, LinkedHashMap::new));

nutritionSumOfUserByMonth = nutritionSumOfUserByMonth.entrySet().stream()
.sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1,e2) -> e1, LinkedHashMap::new));


List<Double> calorieLastSevenDays = new ArrayList<>();
List<Double> calorieLastFourWeek = new ArrayList<>();
List<Double> carbohydrateLastSevenDays = new ArrayList<>();
List<Double> carbohydrateLastFourWeek = new ArrayList<>();
List<Double> proteinLastSevenDays = new ArrayList<>();
List<Double> proteinLastFourWeek = new ArrayList<>();
List<Double> fatLastSevenDays = new ArrayList<>();
List<Double> fatLastFourWeek = new ArrayList<>();

//최근 7일의 식습관
for (LocalDate date : nutritionSumOfUserByWeek.keySet()) {
int totalKcal = nutritionSumOfUserByWeek.get(date).stream().mapToInt(BaseNutrition::getKcal).sum();
int totalCarbohydrate = nutritionSumOfUserByWeek.get(date).stream().mapToInt(BaseNutrition::getCarbohydrate).sum();
int totalProtein = nutritionSumOfUserByWeek.get(date).stream().mapToInt(BaseNutrition::getProtein).sum();
int totalFat = nutritionSumOfUserByWeek.get(date).stream().mapToInt(BaseNutrition::getFat).sum();

calorieLastSevenDays.add((double) totalKcal);
carbohydrateLastSevenDays.add((double) totalCarbohydrate);
proteinLastSevenDays.add((double) totalProtein);
fatLastSevenDays.add((double) totalFat);
}


//최근 한달간의 식습관
for (LocalDate date : nutritionSumOfUserByMonth.keySet()) {
int totalKcal = nutritionSumOfUserByMonth.get(date).stream().mapToInt(BaseNutrition::getKcal).sum();
int totalCarbohydrate = nutritionSumOfUserByMonth.get(date).stream().mapToInt(BaseNutrition::getCarbohydrate).sum();
int totalProtein = nutritionSumOfUserByMonth.get(date).stream().mapToInt(BaseNutrition::getProtein).sum();
int totalFat = nutritionSumOfUserByMonth.get(date).stream().mapToInt(BaseNutrition::getFat).sum();

calorieLastFourWeek.add((double) totalKcal);
carbohydrateLastFourWeek.add((double) totalCarbohydrate);
proteinLastFourWeek.add((double) totalProtein);
fatLastFourWeek.add((double) totalFat);
}

totalWeekScore = Math.round(totalWeekScore * 100.0) / 100.0;

return ResponseAnalysisDto.of(totalWeekScore, calorieLastSevenDays, calorieLastFourWeek, carbohydrateLastSevenDays, carbohydrateLastFourWeek, proteinLastSevenDays, proteinLastFourWeek, fatLastSevenDays, fatLastFourWeek);
}



@Cacheable(value = "ResponseRankUserDto", key = "#userId", cacheManager = "diareatCacheManager")
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/diareat/diareat/util/api/ResponseCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public enum ResponseCode {
FOLLOWED_ALREADY(HttpStatus.CONFLICT, false, "이미 팔로우한 사용자입니다."),
UNFOLLOWED_ALREADY(HttpStatus.CONFLICT, false, "이미 언팔로우한 사용자입니다."),
FAVORITE_ALREADY_EXIST(HttpStatus.CONFLICT, false, "이미 즐겨찾기에 존재하는 음식입니다."),
FOOD_NAME_ALREADY_EXIST(HttpStatus.CONFLICT, false, "이미 존재하는 음식 이름입니다."),

// 500 Internal Server Error
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, false, "서버에 오류가 발생하였습니다."),
Expand All @@ -49,6 +50,8 @@ public enum ResponseCode {
FOOD_FAVORITE_UPDATE_SUCCESS(HttpStatus.OK, true, "즐겨찾기 음식 수정 성공"),
FOOD_FAVORITE_DELETE_SUCCESS(HttpStatus.OK, true, "즐겨찾기 음식 삭제 성공"),

FOOD_RANK_READ_SUCCESS(HttpStatus.OK, true, "식습관 점수 기반 랭킹 조회 성공"),

TOKEN_CHECK_SUCCESS(HttpStatus.OK, true, "토큰 검증 완료"),


Expand Down
Loading

0 comments on commit d7fe019

Please sign in to comment.