|
@@ -1,7 +1,10 @@
|
|
|
package com.etotem.cfc.service;
|
|
package com.etotem.cfc.service;
|
|
|
|
|
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.etotem.cfc.dto.IngredientRecommendation;
|
|
import com.etotem.cfc.dto.IngredientRecommendation;
|
|
|
|
|
+import com.etotem.cfc.entity.DietFamilySelectedIngredient;
|
|
|
import com.etotem.cfc.entity.Food;
|
|
import com.etotem.cfc.entity.Food;
|
|
|
|
|
+import com.etotem.cfc.mapper.DietFamilySelectedIngredientMapper;
|
|
|
import com.etotem.cfc.mapper.FoodMapper;
|
|
import com.etotem.cfc.mapper.FoodMapper;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
@@ -9,6 +12,7 @@ import org.springframework.stereotype.Service;
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDate;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
@Slf4j
|
|
@Slf4j
|
|
|
@Service
|
|
@Service
|
|
@@ -20,18 +24,39 @@ public class DietIngredientService {
|
|
|
@Resource
|
|
@Resource
|
|
|
private FoodMapper foodMapper;
|
|
private FoodMapper foodMapper;
|
|
|
|
|
|
|
|
- // 会话内食材选择(按 familyId 隔离)
|
|
|
|
|
- private final Map<Long, List<IngredientRecommendation>> selectedIngredients = new HashMap<>();
|
|
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private DietFamilySelectedIngredientMapper selectedIngredientMapper;
|
|
|
|
|
+
|
|
|
|
|
+ // 会话内 refresh 种子(重启后重置,不影响持久化的食材选择)
|
|
|
private final Map<Long, Integer> refreshSeed = new HashMap<>();
|
|
private final Map<Long, Integer> refreshSeed = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 共餐食材推荐(持久化)
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
public Map<String, Object> suggestIngredients(Long familyId) {
|
|
public Map<String, Object> suggestIngredients(Long familyId) {
|
|
|
|
|
+ // 优先从数据库加载已有选择
|
|
|
|
|
+ List<DietFamilySelectedIngredient> dbItems = selectedIngredientMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<DietFamilySelectedIngredient>()
|
|
|
|
|
+ .eq(DietFamilySelectedIngredient::getFamilyId, familyId)
|
|
|
|
|
+ .orderByAsc(DietFamilySelectedIngredient::getSortOrder));
|
|
|
|
|
+
|
|
|
|
|
+ if (!dbItems.isEmpty()) {
|
|
|
|
|
+ List<IngredientRecommendation> existing = dbItemsToRecommendations(dbItems);
|
|
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
+ result.put("ingredients", existing);
|
|
|
|
|
+ result.put("selectedCount", existing.size());
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 无历史选择:重新计算一批并持久化
|
|
|
List<IngredientRecommendation> suggestions = beijingNutritionService.generateIngredientList(familyId, LocalDate.now());
|
|
List<IngredientRecommendation> suggestions = beijingNutritionService.generateIngredientList(familyId, LocalDate.now());
|
|
|
- selectedIngredients.put(familyId, suggestions);
|
|
|
|
|
|
|
+ persistSelectedIngredients(familyId, suggestions);
|
|
|
refreshSeed.put(familyId, 0);
|
|
refreshSeed.put(familyId, 0);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
result.put("ingredients", suggestions);
|
|
result.put("ingredients", suggestions);
|
|
|
- result.put("selectedCount", 0);
|
|
|
|
|
|
|
+ result.put("selectedCount", suggestions.size());
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -39,61 +64,67 @@ public class DietIngredientService {
|
|
|
Integer seed = refreshSeed.getOrDefault(familyId, 0) + 1;
|
|
Integer seed = refreshSeed.getOrDefault(familyId, 0) + 1;
|
|
|
refreshSeed.put(familyId, seed);
|
|
refreshSeed.put(familyId, seed);
|
|
|
|
|
|
|
|
- // 重新生成(通过 seed 偏移截取,真正换一批)
|
|
|
|
|
|
|
+ // 重新计算(含 seed 偏移)
|
|
|
List<IngredientRecommendation> suggestions = beijingNutritionService.generateIngredientList(familyId, LocalDate.now(), seed);
|
|
List<IngredientRecommendation> suggestions = beijingNutritionService.generateIngredientList(familyId, LocalDate.now(), seed);
|
|
|
- selectedIngredients.put(familyId, suggestions);
|
|
|
|
|
|
|
+ // 清空并重新持久化
|
|
|
|
|
+ selectedIngredientMapper.delete(
|
|
|
|
|
+ new LambdaQueryWrapper<DietFamilySelectedIngredient>()
|
|
|
|
|
+ .eq(DietFamilySelectedIngredient::getFamilyId, familyId));
|
|
|
|
|
+ persistSelectedIngredients(familyId, suggestions);
|
|
|
|
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
result.put("ingredients", suggestions);
|
|
result.put("ingredients", suggestions);
|
|
|
- result.put("selectedCount", 0);
|
|
|
|
|
|
|
+ result.put("selectedCount", suggestions.size());
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public Map<String, Object> addIngredient(Long familyId, Long foodId, String name) {
|
|
public Map<String, Object> addIngredient(Long familyId, Long foodId, String name) {
|
|
|
- List<IngredientRecommendation> selected = selectedIngredients.getOrDefault(familyId, new ArrayList<>());
|
|
|
|
|
-
|
|
|
|
|
- // 检查是否已存在
|
|
|
|
|
- boolean exists = selected.stream().anyMatch(f -> f.getFoodId() != null && f.getFoodId().equals(foodId));
|
|
|
|
|
- if (!exists) {
|
|
|
|
|
- IngredientRecommendation item = new IngredientRecommendation();
|
|
|
|
|
- item.setFoodId(foodId);
|
|
|
|
|
- item.setName(name);
|
|
|
|
|
- item.setScore(50);
|
|
|
|
|
- item.setReason("手动添加");
|
|
|
|
|
- selected.add(item);
|
|
|
|
|
- selectedIngredients.put(familyId, selected);
|
|
|
|
|
|
|
+ // 幂等插入
|
|
|
|
|
+ Integer count = selectedIngredientMapper.selectCount(
|
|
|
|
|
+ new LambdaQueryWrapper<DietFamilySelectedIngredient>()
|
|
|
|
|
+ .eq(DietFamilySelectedIngredient::getFamilyId, familyId)
|
|
|
|
|
+ .eq(DietFamilySelectedIngredient::getFoodId, foodId));
|
|
|
|
|
+ if (count != null && count > 0) {
|
|
|
|
|
+ // 已存在,返回当前列表
|
|
|
|
|
+ return listIngredients(familyId);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- Map<String, Object> result = new HashMap<>();
|
|
|
|
|
- result.put("ingredients", selected);
|
|
|
|
|
- result.put("selectedCount", selected.size());
|
|
|
|
|
- return result;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ DietFamilySelectedIngredient item = new DietFamilySelectedIngredient();
|
|
|
|
|
+ item.setFamilyId(familyId);
|
|
|
|
|
+ item.setFoodId(foodId);
|
|
|
|
|
+ item.setFoodName(name);
|
|
|
|
|
+ item.setScore(50);
|
|
|
|
|
+ item.setReason("手动添加");
|
|
|
|
|
+ item.setSortOrder(0);
|
|
|
|
|
+ selectedIngredientMapper.insert(item);
|
|
|
|
|
+
|
|
|
|
|
+ return listIngredients(familyId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public Map<String, Object> removeIngredient(Long familyId, Long foodId) {
|
|
public Map<String, Object> removeIngredient(Long familyId, Long foodId) {
|
|
|
- List<IngredientRecommendation> selected = selectedIngredients.getOrDefault(familyId, new ArrayList<>());
|
|
|
|
|
- selected.removeIf(f -> f.getFoodId() != null && f.getFoodId().equals(foodId));
|
|
|
|
|
- selectedIngredients.put(familyId, selected);
|
|
|
|
|
-
|
|
|
|
|
- Map<String, Object> result = new HashMap<>();
|
|
|
|
|
- result.put("ingredients", selected);
|
|
|
|
|
- result.put("selectedCount", selected.size());
|
|
|
|
|
- return result;
|
|
|
|
|
|
|
+ selectedIngredientMapper.delete(
|
|
|
|
|
+ new LambdaQueryWrapper<DietFamilySelectedIngredient>()
|
|
|
|
|
+ .eq(DietFamilySelectedIngredient::getFamilyId, familyId)
|
|
|
|
|
+ .eq(DietFamilySelectedIngredient::getFoodId, foodId));
|
|
|
|
|
+ return listIngredients(familyId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public Map<String, Object> confirmIngredients(Long familyId, Map<String, Object> request) {
|
|
public Map<String, Object> confirmIngredients(Long familyId, Map<String, Object> request) {
|
|
|
- List<IngredientRecommendation> selected = selectedIngredients.getOrDefault(familyId, new ArrayList<>());
|
|
|
|
|
-
|
|
|
|
|
|
|
+ List<IngredientRecommendation> selected = getFromDb(familyId);
|
|
|
|
|
+
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
result.put("selectedFoods", selected);
|
|
result.put("selectedFoods", selected);
|
|
|
result.put("message", "食材已确认,正在生成食谱...");
|
|
result.put("message", "食材已确认,正在生成食谱...");
|
|
|
-
|
|
|
|
|
- // 清空会话选择
|
|
|
|
|
- selectedIngredients.remove(familyId);
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 确认后将当前选中食材持久化保留,不删除(持久化语义)
|
|
|
|
|
+ // 前端如需清空,可调用 removeIngredient 或 refreshIngredients
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 食材搜索
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 搜索食材(供"添加食材"弹窗使用)
|
|
* 搜索食材(供"添加食材"弹窗使用)
|
|
|
*
|
|
*
|
|
@@ -103,7 +134,7 @@ public class DietIngredientService {
|
|
|
public Map<String, Object> searchFoods(String keyword) {
|
|
public Map<String, Object> searchFoods(String keyword) {
|
|
|
List<Map<String, Object>> foods = new ArrayList<>();
|
|
List<Map<String, Object>> foods = new ArrayList<>();
|
|
|
List<Food> all = foodMapper.selectList(
|
|
List<Food> all = foodMapper.selectList(
|
|
|
- new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Food>()
|
|
|
|
|
|
|
+ new LambdaQueryWrapper<Food>()
|
|
|
.eq(Food::getStatus, "active")
|
|
.eq(Food::getStatus, "active")
|
|
|
.orderByAsc(Food::getSortOrder)
|
|
.orderByAsc(Food::getSortOrder)
|
|
|
);
|
|
);
|
|
@@ -123,4 +154,63 @@ public class DietIngredientService {
|
|
|
result.put("foods", foods);
|
|
result.put("foods", foods);
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 内部辅助
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ private List<IngredientRecommendation> getFromDb(Long familyId) {
|
|
|
|
|
+ return dbItemsToRecommendations(selectedIngredientMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<DietFamilySelectedIngredient>()
|
|
|
|
|
+ .eq(DietFamilySelectedIngredient::getFamilyId, familyId)
|
|
|
|
|
+ .orderByAsc(DietFamilySelectedIngredient::getSortOrder)));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> listIngredients(Long familyId) {
|
|
|
|
|
+ List<IngredientRecommendation> selected = getFromDb(familyId);
|
|
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
+ result.put("ingredients", selected);
|
|
|
|
|
+ result.put("selectedCount", selected.size());
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 将 DB 实体转换为 IngredientRecommendation DTO */
|
|
|
|
|
+ private List<IngredientRecommendation> dbItemsToRecommendations(List<DietFamilySelectedIngredient> items) {
|
|
|
|
|
+ return items.stream().map(item -> {
|
|
|
|
|
+ IngredientRecommendation rec = new IngredientRecommendation();
|
|
|
|
|
+ rec.setFoodId(item.getFoodId());
|
|
|
|
|
+ rec.setName(item.getFoodName());
|
|
|
|
|
+ rec.setScore(item.getScore());
|
|
|
|
|
+ rec.setReason(item.getReason());
|
|
|
|
|
+ rec.setCategory(item.getCategory());
|
|
|
|
|
+ rec.setCaloriesPer100g(item.getCaloriesPer100g());
|
|
|
|
|
+ rec.setCookingLevel(item.getCookingLevel());
|
|
|
|
|
+ rec.setNutritionScore(item.getNutritionScore());
|
|
|
|
|
+ rec.setBalanceReason(item.getBalanceReason());
|
|
|
|
|
+ return rec;
|
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 将推荐列表持久化到 DB */
|
|
|
|
|
+ private void persistSelectedIngredients(Long familyId, List<IngredientRecommendation> suggestions) {
|
|
|
|
|
+ selectedIngredientMapper.delete(
|
|
|
|
|
+ new LambdaQueryWrapper<DietFamilySelectedIngredient>()
|
|
|
|
|
+ .eq(DietFamilySelectedIngredient::getFamilyId, familyId));
|
|
|
|
|
+ for (int i = 0; i < suggestions.size(); i++) {
|
|
|
|
|
+ IngredientRecommendation s = suggestions.get(i);
|
|
|
|
|
+ DietFamilySelectedIngredient item = new DietFamilySelectedIngredient();
|
|
|
|
|
+ item.setFamilyId(familyId);
|
|
|
|
|
+ item.setFoodId(s.getFoodId());
|
|
|
|
|
+ item.setFoodName(s.getName());
|
|
|
|
|
+ item.setScore(s.getScore());
|
|
|
|
|
+ item.setReason(s.getReason());
|
|
|
|
|
+ item.setCategory(s.getCategory());
|
|
|
|
|
+ item.setCaloriesPer100g(s.getCaloriesPer100g());
|
|
|
|
|
+ item.setCookingLevel(s.getCookingLevel());
|
|
|
|
|
+ item.setNutritionScore(s.getNutritionScore());
|
|
|
|
|
+ item.setBalanceReason(s.getBalanceReason());
|
|
|
|
|
+ item.setSortOrder(i);
|
|
|
|
|
+ selectedIngredientMapper.insert(item);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|