Răsfoiți Sursa

feat: dimension-product query + repurchase-reminder service logic

RecommendationService adds 4 methods:
- getDimensionProducts() — query product_dimension_mapping by
  dimensionCode, join products, filter status=上架/stock>0
- getRepurchaseReminders() — query user's unpurchased reminders
- markRepurchaseClicked() — update clicked=1 on reminder record
- generateDailyRepurchaseReminders() — scan paid/received orders,
  apply repurchase_reminder_config rules, insert reminders

Injects ProductDimensionMappingMapper, RepurchaseReminderRecordMapper,
RepurchaseReminderConfigMapper, ProductOrderMapper.

Co-authored-by: Sisyphus <sisyphus@omo.codes>
Sisyphus 2 luni în urmă
părinte
comite
dbdecd7acd

+ 165 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/RecommendationService.java

@@ -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());
+            }
+        }
+    }
 }