|
@@ -38,6 +38,18 @@ public class NutritionProductService {
|
|
|
@Resource
|
|
@Resource
|
|
|
private NutritionProductReviewMapper reviewMapper;
|
|
private NutritionProductReviewMapper reviewMapper;
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private ProductService productService;
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private NutritionUlLimitMapper ulLimitMapper;
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private NutritionAgeDosageMapper ageDosageMapper;
|
|
|
|
|
+
|
|
|
@Resource
|
|
@Resource
|
|
|
private ProductMapper productMapper;
|
|
private ProductMapper productMapper;
|
|
|
|
|
|
|
@@ -214,6 +226,11 @@ public class NutritionProductService {
|
|
|
}
|
|
}
|
|
|
existing.setStatus(1);
|
|
existing.setStatus(1);
|
|
|
nutritionProductMapper.insert(existing);
|
|
nutritionProductMapper.insert(existing);
|
|
|
|
|
+ // 根据成分功效说明自动生成功效标签
|
|
|
|
|
+ existing.setEfficacyTags(buildEfficacyTags(existing.getId()));
|
|
|
|
|
+ if (existing.getEfficacyTags() != null) {
|
|
|
|
|
+ nutritionProductMapper.updateById(existing);
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
existing.setName(product.getName());
|
|
existing.setName(product.getName());
|
|
|
existing.setImageUrl(product.getCoverImage());
|
|
existing.setImageUrl(product.getCoverImage());
|
|
@@ -242,6 +259,169 @@ public class NutritionProductService {
|
|
|
return fen / 100;
|
|
return fen / 100;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据主平台商品ID创建营养产品条目(标记为本平台商品)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param productId 主平台商品ID(products.id)
|
|
|
|
|
+ * @return true=成功/已存在, false=主商品不存在
|
|
|
|
|
+ */
|
|
|
|
|
+ public boolean addNutritionProductFromProduct(Long productId) {
|
|
|
|
|
+ Product product = productService.getById(productId);
|
|
|
|
|
+ if (product == null) {
|
|
|
|
|
+ log.warn("主平台商品不存在,ID: {}", productId);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 检查是否已存在该主平台商品的营养产品条目
|
|
|
|
|
+ NutritionProduct existing = nutritionProductMapper.selectOne(
|
|
|
|
|
+ new LambdaQueryWrapper<NutritionProduct>()
|
|
|
|
|
+ .eq(NutritionProduct::getLocalProductId, productId)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (existing != null) {
|
|
|
|
|
+ log.info("营养产品条目已存在,主平台商品ID: {}", productId);
|
|
|
|
|
+ return true; // 已存在,视为成功
|
|
|
|
|
+ }
|
|
|
|
|
+ NutritionProduct nutritionProduct = new NutritionProduct();
|
|
|
|
|
+ nutritionProduct.setName(product.getName());
|
|
|
|
|
+ nutritionProduct.setLocalProductId(productId);
|
|
|
|
|
+ nutritionProduct.setStatus(1); // 1 = 上架
|
|
|
|
|
+ nutritionProduct.setSourcePlatform("platform"); // 标记为平台来源
|
|
|
|
|
+ nutritionProduct.setDescription(product.getDescription());
|
|
|
|
|
+ nutritionProduct.setImageUrl(product.getCoverImage());
|
|
|
|
|
+ nutritionProduct.setPrice(BigDecimal.valueOf(product.getPrice() != null ? product.getPrice() : 0));
|
|
|
|
|
+ nutritionProduct.setOriginalPrice(product.getPurchasePrice() != null ? product.getPurchasePrice().intValue() : 0);
|
|
|
|
|
+ nutritionProduct.setSuitableFor("全家人");
|
|
|
|
|
+ nutritionProduct.setCreateTime(new Date());
|
|
|
|
|
+ nutritionProduct.setUpdateTime(new Date());
|
|
|
|
|
+ // 默认分类和品牌设为空,由前端或后续填充
|
|
|
|
|
+ nutritionProductMapper.insert(nutritionProduct);
|
|
|
|
|
+ // 根据成分功效说明自动生成功效标签
|
|
|
|
|
+ nutritionProduct.setEfficacyTags(buildEfficacyTags(nutritionProduct.getId()));
|
|
|
|
|
+ if (nutritionProduct.getEfficacyTags() != null) {
|
|
|
|
|
+ nutritionProductMapper.updateById(nutritionProduct);
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("已创建营养产品条目,主平台商品ID: {}, 营养产品ID: {}", productId, nutritionProduct.getId());
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据产品的成分功效说明生成成分功效标签(JSON数组字符串,如 ["补钙","补锌"])
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param nutritionProductId 营养产品ID
|
|
|
|
|
+ * @return JSON数组字符串;无成分或无法提取时返回 null
|
|
|
|
|
+ */
|
|
|
|
|
+ private String buildEfficacyTags(Long nutritionProductId) {
|
|
|
|
|
+ List<NutritionIngredient> ingredients = ingredientMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<NutritionIngredient>()
|
|
|
|
|
+ .eq(NutritionIngredient::getProductId, nutritionProductId)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (ingredients.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<String> tags = new ArrayList<>();
|
|
|
|
|
+ for (NutritionIngredient ingredient : ingredients) {
|
|
|
|
|
+ String purpose = ingredient.getPurpose();
|
|
|
|
|
+ if (purpose == null || purpose.trim().isEmpty()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 按常见分隔符拆分功效描述,取有效片段作为标签(去重、去空、截断过长)
|
|
|
|
|
+ for (String part : purpose.split("[,,、;;/|\\s]+")) {
|
|
|
|
|
+ String tag = part.trim();
|
|
|
|
|
+ if (tag.isEmpty() || tag.length() > 20 || tags.contains(tag)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ tags.add(tag);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (tags.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ StringBuilder sb = new StringBuilder("[");
|
|
|
|
|
+ for (int i = 0; i < tags.size(); i++) {
|
|
|
|
|
+ if (i > 0) {
|
|
|
|
|
+ sb.append(",");
|
|
|
|
|
+ }
|
|
|
|
|
+ sb.append("\"").append(tags.get(i)).append("\"");
|
|
|
|
|
+ }
|
|
|
|
|
+ sb.append("]");
|
|
|
|
|
+ return sb.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 验证营养产品是否符合年龄和剂量要求
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param productId 营养产品ID
|
|
|
|
|
+ * @param ageInMonths 当前年龄(月)
|
|
|
|
|
+ * @param ageMinMonths 最小年龄(月)
|
|
|
|
|
+ * @param ageMaxMonths 最大年龄(月)
|
|
|
|
|
+ * @param dosageRequirement 剂量要求描述(可为 null)
|
|
|
|
|
+ * @param dosageUnit 剂量单位(可为 null)
|
|
|
|
|
+ * @return 是否通过验证
|
|
|
|
|
+ */
|
|
|
|
|
+ public boolean validateProductForAgeAndDosage(Long productId, Integer ageInMonths,
|
|
|
|
|
+ Integer ageMinMonths, Integer ageMaxMonths,
|
|
|
|
|
+ String dosageRequirement, String dosageUnit) {
|
|
|
|
|
+ // 1. 检查年龄是否在允许范围内
|
|
|
|
|
+ if (ageMinMonths != null && ageInMonths != null
|
|
|
|
|
+ && (ageInMonths < ageMinMonths || ageInMonths > ageMaxMonths)) {
|
|
|
|
|
+ log.info("产品{} 年龄{}个月不在允许范围[{}, {}]", productId, ageInMonths, ageMinMonths, ageMaxMonths);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 2. 检查剂量是否超过 UL 限制
|
|
|
|
|
+ return validateDosageAgainstUlLimit(productId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 检查产品剂量是否超过 UL 限制(多来源累加自动校验)
|
|
|
|
|
+ */
|
|
|
|
|
+ private boolean validateDosageAgainstUlLimit(Long productId) {
|
|
|
|
|
+ // 1. 获取该产品的所有成分
|
|
|
|
|
+ List<NutritionIngredient> ingredients = ingredientMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<NutritionIngredient>()
|
|
|
|
|
+ .eq(NutritionIngredient::getProductId, productId)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (ingredients.isEmpty()) {
|
|
|
|
|
+ return true; // 没有成分则视为通过
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 按营养成分分组,累加同一成分多来源剂量
|
|
|
|
|
+ Map<String, BigDecimal> accumulatedDosage = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ for (NutritionIngredient ingredient : ingredients) {
|
|
|
|
|
+ String nutrientKey = ingredient.getName() != null
|
|
|
|
|
+ ? ingredient.getName().toLowerCase() : ingredient.getIngredientType();
|
|
|
|
|
+ if (nutrientKey == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 累加同一成分剂量(假设所有成分来自同一产品内的多个成分条目)
|
|
|
|
|
+ BigDecimal amount = ingredient.getAmountPerServing() != null
|
|
|
|
|
+ ? ingredient.getAmountPerServing() : BigDecimal.ZERO;
|
|
|
|
|
+ accumulatedDosage.merge(nutrientKey, amount, BigDecimal::add);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 检查每个成分的 UL 限制
|
|
|
|
|
+ for (Map.Entry<String, BigDecimal> entry : accumulatedDosage.entrySet()) {
|
|
|
|
|
+ // 查找该成分的 UL 限制(按营养名称匹配)
|
|
|
|
|
+ List<NutritionUlLimit> ulLimits = ulLimitMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<NutritionUlLimit>()
|
|
|
|
|
+ .eq(NutritionUlLimit::getNutrientName, entry.getKey())
|
|
|
|
|
+ );
|
|
|
|
|
+ if (ulLimits.isEmpty()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ NutritionUlLimit ulLimit = ulLimits.get(0);
|
|
|
|
|
+ if (ulLimit.getLimitValue() != null && entry.getValue() != null) {
|
|
|
|
|
+ double ulValue = ulLimit.getLimitValue().doubleValue();
|
|
|
|
|
+ double actualDose = entry.getValue().doubleValue();
|
|
|
|
|
+ if (actualDose > ulValue) {
|
|
|
|
|
+ log.warn("成分{}剂量{}超过UL限制{}", entry.getKey(), actualDose, ulValue);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// ---- 评分私有方法 ----
|
|
// ---- 评分私有方法 ----
|
|
|
|
|
|
|
|
private double calcIngredientScore(NutritionProduct product, NutritionRecommendDTO.RecommendQuery query) {
|
|
private double calcIngredientScore(NutritionProduct product, NutritionRecommendDTO.RecommendQuery query) {
|