|
|
@@ -9,6 +9,14 @@ import com.etotem.cfc.entity.Product;
|
|
|
import com.etotem.cfc.mapper.ActivityMapper;
|
|
|
import com.etotem.cfc.mapper.ArticleMapper;
|
|
|
import com.etotem.cfc.mapper.ProductMapper;
|
|
|
+import com.etotem.cfc.mapper.ProductDimensionMappingMapper;
|
|
|
+import com.etotem.cfc.mapper.RepurchaseReminderRecordMapper;
|
|
|
+import com.etotem.cfc.entity.ProductDimensionMapping;
|
|
|
+import com.etotem.cfc.entity.RepurchaseReminderRecord;
|
|
|
+import com.etotem.cfc.entity.ProductOrder;
|
|
|
+import com.etotem.cfc.entity.RepurchaseReminderConfig;
|
|
|
+import com.etotem.cfc.mapper.ProductOrderMapper;
|
|
|
+import com.etotem.cfc.mapper.RepurchaseReminderConfigMapper;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.http.*;
|
|
|
@@ -43,6 +51,18 @@ public class RecommendationService {
|
|
|
@Resource
|
|
|
private ArticleMapper articleMapper;
|
|
|
|
|
|
+ @Resource
|
|
|
+ private ProductDimensionMappingMapper productDimensionMappingMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private RepurchaseReminderRecordMapper repurchaseReminderRecordMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private RepurchaseReminderConfigMapper repurchaseReminderConfigMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ProductOrderMapper productOrderMapper;
|
|
|
+
|
|
|
/**
|
|
|
* 按营养标签搜索推荐内容
|
|
|
*/
|
|
|
@@ -208,4 +228,149 @@ public class RecommendationService {
|
|
|
}
|
|
|
return results;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按维度获取匹配商品
|
|
|
+ */
|
|
|
+ public List<RecommendationResult> getDimensionProducts(String dimensionCode, Long familyId, Integer limit) {
|
|
|
+ List<RecommendationResult> results = new ArrayList<>();
|
|
|
+ if (dimensionCode == null || dimensionCode.isEmpty()) return results;
|
|
|
+
|
|
|
+ int maxLimit = limit != null ? limit : 10;
|
|
|
+
|
|
|
+ LambdaQueryWrapper<ProductDimensionMapping> wrapper = new LambdaQueryWrapper<ProductDimensionMapping>()
|
|
|
+ .eq(ProductDimensionMapping::getDimensionCode, dimensionCode)
|
|
|
+ .eq(ProductDimensionMapping::getEnabled, 1)
|
|
|
+ .orderByDesc(ProductDimensionMapping::getMatchScore)
|
|
|
+ .last("LIMIT " + maxLimit);
|
|
|
+
|
|
|
+ List<ProductDimensionMapping> mappings = productDimensionMappingMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ for (ProductDimensionMapping m : mappings) {
|
|
|
+ Product product = productMapper.selectById(m.getProductId());
|
|
|
+ if (product == null || !"上架".equals(product.getStatus()) || product.getStock() <= 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ RecommendationResult r = new RecommendationResult();
|
|
|
+ r.setType("product");
|
|
|
+ r.setId(product.getId());
|
|
|
+ r.setProductId(product.getId());
|
|
|
+ r.setName(product.getName());
|
|
|
+ r.setDescription(product.getIntro() != null ? product.getIntro() : product.getDescription());
|
|
|
+ r.setCoverImage(product.getCoverImage());
|
|
|
+ r.setPrice(product.getPrice());
|
|
|
+ r.setUrl("/pages/shop/detail?id=" + product.getId());
|
|
|
+ r.setSource("dimension");
|
|
|
+ r.setMatchScore(m.getMatchScore());
|
|
|
+ r.setMatchReason(m.getMatchReason());
|
|
|
+ results.add(r);
|
|
|
+ }
|
|
|
+
|
|
|
+ return results;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取用户复购提醒列表
|
|
|
+ */
|
|
|
+ public List<RecommendationResult> getRepurchaseReminders(Long userId) {
|
|
|
+ List<RecommendationResult> results = new ArrayList<>();
|
|
|
+ if (userId == null) return results;
|
|
|
+
|
|
|
+ LambdaQueryWrapper<RepurchaseReminderRecord> wrapper = new LambdaQueryWrapper<RepurchaseReminderRecord>()
|
|
|
+ .eq(RepurchaseReminderRecord::getUserId, userId)
|
|
|
+ .eq(RepurchaseReminderRecord::getPurchased, 0)
|
|
|
+ .orderByDesc(RepurchaseReminderRecord::getSentAt);
|
|
|
+
|
|
|
+ List<RepurchaseReminderRecord> records = repurchaseReminderRecordMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ for (RepurchaseReminderRecord record : records) {
|
|
|
+ Product product = productMapper.selectById(record.getProductId());
|
|
|
+ if (product == null) continue;
|
|
|
+
|
|
|
+ RecommendationResult r = new RecommendationResult();
|
|
|
+ r.setType("repurchase");
|
|
|
+ r.setId(record.getId());
|
|
|
+ r.setProductId(product.getId());
|
|
|
+ r.setName(product.getName());
|
|
|
+ r.setDescription(product.getIntro() != null ? product.getIntro() : product.getDescription());
|
|
|
+ r.setCoverImage(product.getCoverImage());
|
|
|
+ r.setPrice(product.getPrice());
|
|
|
+ r.setUrl("/pages/shop/detail?id=" + product.getId());
|
|
|
+ r.setSource("repurchase");
|
|
|
+ r.setReason("复购提醒");
|
|
|
+ results.add(r);
|
|
|
+ }
|
|
|
+
|
|
|
+ return results;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 标记复购提醒已点击
|
|
|
+ */
|
|
|
+ public void markRepurchaseClicked(Long reminderId) {
|
|
|
+ if (reminderId == null) return;
|
|
|
+
|
|
|
+ RepurchaseReminderRecord record = repurchaseReminderRecordMapper.selectById(reminderId);
|
|
|
+ if (record != null && (record.getClicked() == null || record.getClicked() == 0)) {
|
|
|
+ RepurchaseReminderRecord update = new RepurchaseReminderRecord();
|
|
|
+ update.setId(reminderId);
|
|
|
+ update.setClicked(1);
|
|
|
+ repurchaseReminderRecordMapper.updateById(update);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void generateDailyRepurchaseReminders() {
|
|
|
+ List<RepurchaseReminderConfig> configs = repurchaseReminderConfigMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<RepurchaseReminderConfig>()
|
|
|
+ .eq(RepurchaseReminderConfig::getEnabled, 1)
|
|
|
+ );
|
|
|
+ if (configs.isEmpty()) return;
|
|
|
+
|
|
|
+ for (RepurchaseReminderConfig config : configs) {
|
|
|
+ int reminderDays = config.getReminderDays() != null ? config.getReminderDays() : 30;
|
|
|
+ Date since = new Date(System.currentTimeMillis() - reminderDays * 24L * 60 * 60 * 1000);
|
|
|
+
|
|
|
+ List<ProductOrder> orders = productOrderMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ProductOrder>()
|
|
|
+ .in(ProductOrder::getStatus, "paid", "received")
|
|
|
+ .le(ProductOrder::getCreatedAt, since)
|
|
|
+ .last("LIMIT 500")
|
|
|
+ );
|
|
|
+
|
|
|
+ for (ProductOrder order : orders) {
|
|
|
+ Product product = productMapper.selectById(order.getProductId());
|
|
|
+ if (product == null) continue;
|
|
|
+
|
|
|
+ if (config.getProductId() != null && !config.getProductId().equals(order.getProductId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (config.getProductCategory() != null && !config.getProductCategory().isEmpty()
|
|
|
+ && !config.getProductCategory().equals(
|
|
|
+ product.getCategoryId() != null ? String.valueOf(product.getCategoryId()) : "")) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ Long existingCount = repurchaseReminderRecordMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<RepurchaseReminderRecord>()
|
|
|
+ .eq(RepurchaseReminderRecord::getUserId, order.getBuyerId())
|
|
|
+ .eq(RepurchaseReminderRecord::getProductId, order.getProductId())
|
|
|
+ .eq(RepurchaseReminderRecord::getPurchased, 0)
|
|
|
+ );
|
|
|
+ if (existingCount > 0) continue;
|
|
|
+
|
|
|
+ RepurchaseReminderRecord record = new RepurchaseReminderRecord();
|
|
|
+ record.setUserId(order.getBuyerId());
|
|
|
+ record.setProductId(order.getProductId());
|
|
|
+ record.setOrderId(order.getId());
|
|
|
+ record.setReminderDays(reminderDays);
|
|
|
+ record.setSentAt(new Date());
|
|
|
+ record.setClicked(0);
|
|
|
+ record.setPurchased(0);
|
|
|
+ repurchaseReminderRecordMapper.insert(record);
|
|
|
+ log.info("生成复购提醒: userId={}, productId={}, orderId={}",
|
|
|
+ order.getBuyerId(), order.getProductId(), order.getId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|