| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- package com.etotem.cfc.service;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.etotem.cfc.entity.Food;
- import com.etotem.cfc.entity.FoodRecommendIndex;
- import com.etotem.cfc.entity.FoodRecommendIdxLog;
- import com.etotem.cfc.entity.FamilyMember;
- import com.etotem.cfc.entity.HealthReport;
- import com.etotem.cfc.mapper.FoodRecommendIndexMapper;
- import com.etotem.cfc.mapper.FoodRecommendIdxLogMapper;
- import com.etotem.cfc.mapper.FamilyMemberMapper;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import javax.annotation.Resource;
- import java.util.*;
- import java.util.stream.Collectors;
- @Slf4j
- @Service
- public class FoodRecommendService {
- @Resource
- private FoodRecommendIndexMapper foodRecommendIndexMapper;
- @Resource
- private FoodRecommendIdxLogMapper foodRecommendIdxLogMapper;
- @Resource
- private FamilyMemberMapper familyMemberMapper;
- @Resource
- private HealthReportService healthReportService;
- @Resource
- private FoodService foodService;
- @Transactional
- public void recalculateForUser(Long userId) {
- HealthReport report = healthReportService.getLatestReport(userId);
- if (report == null) {
- foodRecommendIndexMapper.delete(new LambdaQueryWrapper<FoodRecommendIndex>()
- .eq(FoodRecommendIndex::getUserId, userId));
- log.info("用户{}无最新健康报告,已清除推荐指数", userId);
- return;
- }
- // 从 foods 表读取最新报告关联的食材推荐(正常确认流程写入 foods 表)
- List<Food> reportFoods = foodService.listByReportId(report.getId());
- if (reportFoods.isEmpty()) {
- foodRecommendIndexMapper.delete(new LambdaQueryWrapper<FoodRecommendIndex>()
- .eq(FoodRecommendIndex::getUserId, userId));
- log.info("报告{}无食材推荐数据,已清除用户{}的推荐指数", report.getId(), userId);
- return;
- }
- List<FoodRecommendIndex> existingIndices = foodRecommendIndexMapper.selectList(
- new LambdaQueryWrapper<FoodRecommendIndex>()
- .eq(FoodRecommendIndex::getUserId, userId)
- );
- Map<Long, FoodRecommendIndex> existingMap = existingIndices.stream()
- .collect(Collectors.toMap(FoodRecommendIndex::getFoodId, idx -> idx));
- Date now = new Date();
- Long reportId = report.getId();
- for (Food food : reportFoods) {
- Long foodId = food.getId();
- Integer newIndex = food.getScore();
- String foodName = food.getName();
- FoodRecommendIndex existing = existingMap.get(foodId);
- if (existing != null) {
- if (!Objects.equals(existing.getIndexScore(), newIndex)) {
- Integer oldIndex = existing.getIndexScore();
- existing.setIndexScore(newIndex);
- existing.setFoodName(foodName);
- existing.setHealthReportId(reportId);
- existing.setCalculatedAt(now);
- foodRecommendIndexMapper.updateById(existing);
- FoodRecommendIdxLog logEntry = new FoodRecommendIdxLog();
- logEntry.setUserId(userId);
- logEntry.setFoodId(foodId);
- logEntry.setFoodName(foodName);
- logEntry.setPreviousIndex(oldIndex);
- logEntry.setNewIndex(newIndex);
- logEntry.setHealthReportId(reportId);
- logEntry.setChangeReason("健康报告更新");
- logEntry.setChangedAt(now);
- foodRecommendIdxLogMapper.insert(logEntry);
- log.info("用户{}食材{}推荐指数变更: {} -> {}", userId, foodId, oldIndex, newIndex);
- }
- } else {
- FoodRecommendIndex index = new FoodRecommendIndex();
- index.setUserId(userId);
- index.setFoodId(foodId);
- index.setFoodName(foodName);
- index.setIndexScore(newIndex);
- index.setHealthReportId(reportId);
- index.setCalculatedAt(now);
- index.setCreatedAt(now);
- foodRecommendIndexMapper.insert(index);
- }
- existingMap.remove(foodId);
- }
- for (Map.Entry<Long, FoodRecommendIndex> entry : existingMap.entrySet()) {
- FoodRecommendIndex removed = entry.getValue();
- foodRecommendIndexMapper.deleteById(removed.getId());
- FoodRecommendIdxLog logEntry = new FoodRecommendIdxLog();
- logEntry.setUserId(userId);
- logEntry.setFoodId(removed.getFoodId());
- logEntry.setFoodName(removed.getFoodName());
- logEntry.setPreviousIndex(removed.getIndexScore());
- logEntry.setNewIndex(null);
- logEntry.setHealthReportId(reportId);
- logEntry.setChangeReason("已从最新健康报告中移除");
- logEntry.setChangedAt(now);
- foodRecommendIdxLogMapper.insert(logEntry);
- log.info("用户{}食材{}已从最新报告中移除", userId, removed.getFoodId());
- }
- }
- public List<FoodRecommendIndex> getIndexByUser(Long userId) {
- return foodRecommendIndexMapper.selectList(
- new LambdaQueryWrapper<FoodRecommendIndex>()
- .eq(FoodRecommendIndex::getUserId, userId)
- );
- }
- /**
- * 获取用户"当前食材"推荐列表(含分类与营养信息,供饮食推荐页展示)。
- * 数据来自 food_recommend_idx(有新报告时 recalculateForUser 有则更新/无则添加),
- * join foods 表补充 category/protein 等字段。
- */
- public List<Map<String, Object>> getCurrentFoods(Long userId) {
- List<FoodRecommendIndex> indices = getIndexByUser(userId);
- if (indices.isEmpty()) return new ArrayList<>();
- Set<Long> foodIds = new HashSet<>();
- for (FoodRecommendIndex idx : indices) {
- if (idx.getFoodId() != null) foodIds.add(idx.getFoodId());
- }
- Map<Long, Food> foodMap = new HashMap<>();
- if (!foodIds.isEmpty()) {
- List<Food> foods = foodService.listByIds(foodIds);
- for (Food f : foods) {
- if (f != null) foodMap.put(f.getId(), f);
- }
- }
- List<Map<String, Object>> result = new ArrayList<>();
- for (FoodRecommendIndex idx : indices) {
- Food food = foodMap.get(idx.getFoodId());
- Map<String, Object> m = new LinkedHashMap<>();
- m.put("name", food != null && food.getName() != null ? food.getName() : idx.getFoodName());
- m.put("foodName", m.get("name"));
- m.put("score", idx.getIndexScore());
- m.put("category", food != null ? food.getCategory() : "");
- if (food != null) {
- m.put("protein", food.getProtein());
- m.put("fat", food.getFat());
- m.put("carbs", food.getCarbs());
- m.put("fiber", food.getFiber());
- m.put("energyKj", food.getEnergyKj());
- }
- result.add(m);
- }
- return result;
- }
- public List<FoodRecommendIdxLog> getIndexHistory(Long userId, Long foodId) {
- return foodRecommendIdxLogMapper.selectList(
- new LambdaQueryWrapper<FoodRecommendIdxLog>()
- .eq(FoodRecommendIdxLog::getUserId, userId)
- .eq(FoodRecommendIdxLog::getFoodId, foodId)
- .orderByDesc(FoodRecommendIdxLog::getChangedAt)
- );
- }
- public Map<Long, List<FoodRecommendIndex>> getByFamily(Long familyId) {
- List<FamilyMember> members = familyMemberMapper.selectList(
- new LambdaQueryWrapper<FamilyMember>()
- .eq(FamilyMember::getFamilyId, familyId)
- );
- Map<Long, List<FoodRecommendIndex>> result = new HashMap<>();
- for (FamilyMember member : members) {
- Long memberUserId = member.getUserId();
- if (memberUserId != null) {
- List<FoodRecommendIndex> indices = foodRecommendIndexMapper.selectList(
- new LambdaQueryWrapper<FoodRecommendIndex>()
- .eq(FoodRecommendIndex::getUserId, memberUserId)
- );
- if (!indices.isEmpty()) {
- result.put(memberUserId, indices);
- }
- }
- }
- return result;
- }
- /**
- * 获取戒备食材列表(推荐指数 < 0),按指数升序排列(最需戒备的排最前)
- */
- public List<FoodRecommendIndex> getCautionFoods(Long userId) {
- return foodRecommendIndexMapper.selectList(
- new LambdaQueryWrapper<FoodRecommendIndex>()
- .eq(FoodRecommendIndex::getUserId, userId)
- .lt(FoodRecommendIndex::getIndexScore, 0)
- .orderByAsc(FoodRecommendIndex::getIndexScore)
- );
- }
- }
|