Просмотр исходного кода

chore: auto bump version and changelog [skip ci]

iwt 1 месяц назад
Родитель
Сommit
479a6ffb85

+ 80 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/recommendation/RecommendationRuleAdminController.java

@@ -0,0 +1,80 @@
+package com.etotem.cfc.controller.recommendation;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.RecommendationRule;
+import com.etotem.cfc.service.RecommendationRuleService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.log4j.Log4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Log4j
+@Tag(name = "商品推荐规则管理", description = "商品推荐规则配置管理接口")
+@RestController
+@RequestMapping("/api/admin/recommendation-rules")
+public class RecommendationRuleAdminController {
+
+    @Resource
+    private RecommendationRuleService recommendationRuleService;
+
+    @Operation(summary = "获取推荐规则列表")
+    @PostMapping("/list")
+    public Result<List<RecommendationRule>> list(@RequestBody Map<String, Object> params) {
+        String sceneCode = (String) params.get("sceneCode");
+        Integer ruleType = (Integer) params.get("ruleType");
+        List<RecommendationRule> list = recommendationRuleService.loadRulesByScene(sceneCode);
+        if (ruleType != null) {
+            list = list.stream()
+                    .filter(r -> r.getRuleType().equals(ruleType))
+                    .collect(Collectors.toList());
+        }
+        return Result.success(list);
+    }
+
+    @Operation(summary = "创建推荐规则")
+    @PostMapping("/create")
+    public Result<Boolean> create(@RequestBody RecommendationRule rule) {
+        boolean saved = recommendationRuleService.save(rule);
+        return Result.success(saved);
+    }
+
+    @Operation(summary = "更新推荐规则")
+    @PostMapping("/update")
+    public Result<Boolean> update(@RequestBody RecommendationRule rule) {
+        boolean updated = recommendationRuleService.updateById(rule);
+        return Result.success(updated);
+    }
+
+    @Operation(summary = "删除/停用推荐规则")
+    @PostMapping("/delete/{id}")
+    public Result<Boolean> delete(@PathVariable Long id) {
+        RecommendationRule rule = recommendationRuleService.getById(id);
+        if (rule != null) {
+            rule.setEnabled(0);
+            recommendationRuleService.updateById(rule);
+        }
+        return Result.success(true);
+    }
+
+    @Operation(summary = "批量启用/停用")
+    @PostMapping("/batch-enable")
+    public Result<Boolean> batchEnable(@RequestBody Map<String, Object> params) {
+        List<Long> ids = (List<Long>) params.get("ids");
+        Integer enabled = (Integer) params.get("enabled");
+        if (ids != null && !ids.isEmpty()) {
+            for (Long id : ids) {
+                RecommendationRule rule = recommendationRuleService.getById(id);
+                if (rule != null) {
+                    rule.setEnabled(enabled);
+                    recommendationRuleService.updateById(rule);
+                }
+            }
+        }
+        return Result.success(true);
+    }
+}

+ 55 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/RecommendationRule.java

@@ -0,0 +1,55 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("recommendation_rules")
+public class RecommendationRule implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 规则类型:1-已购不推,2-定时复购,3-购买A推B,4-购买A不推B,5-系列重点推 */
+    private Integer ruleType;
+
+    /** 规则名称(运营可读) */
+    private String ruleName;
+
+    /** 触发商品ID(规则1/2/3/4 单商品) */
+    private Long triggerProductId;
+
+    /** 触发商品ID数组 JSON(规则5 系列,如 "[1,2,3]") */
+    private String triggerProductIds;
+
+    /** 目标商品ID(被推荐/排除/重点推) */
+    private Long targetProductId;
+
+    /** 优先级,越大越优 */
+    private Integer priority;
+
+    /** 启用 1/0 */
+    private Integer enabled;
+
+    /** 购买回溯窗口(天) */
+    private Integer timeWindowDays;
+
+    /** 复购延迟天数(规则2) */
+    private Integer delayDays;
+
+    /** 范围:family/individual */
+    private String scope;
+
+    /** 生效场景 JSON 数组 */
+    private String sceneCodes;
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}

+ 22 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/RecommendationRuleMapper.java

@@ -0,0 +1,22 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.RecommendationRule;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+@Mapper
+public interface RecommendationRuleMapper extends BaseMapper<RecommendationRule> {
+
+    @Select("SELECT * FROM recommendation_rules WHERE enabled = 1 AND FIND_IN_SET(?, scene_codes) LIMIT 1")
+    RecommendationRule selectByScene(String sceneCode);
+
+    @Select("SELECT * FROM recommendation_rules WHERE enabled = 1 AND rule_type = ? ORDER BY priority DESC LIMIT 1")
+    RecommendationRule selectByRuleType(Integer ruleType);
+
+    @Select("SELECT * FROM recommendation_rules WHERE enabled = 1 AND rule_type = ? AND trigger_product_id = ?")
+    RecommendationRule selectByTriggerProductId(Integer ruleType, Long triggerProductId);
+
+    @Select("SELECT * FROM recommendation_rules WHERE enabled = 1 AND rule_type = ? AND FIND_IN_SET(?, trigger_product_ids)")
+    RecommendationRule selectByTriggerProductIds(Integer ruleType, String triggerProductIdsJson);
+}

+ 104 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/RecommendationRuleService.java

@@ -0,0 +1,104 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.etotem.cfc.entity.RecommendationRule;
+import com.etotem.cfc.mapper.RecommendationRuleMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.etotem.cfc.entity.ProductOrder;
+import com.etotem.cfc.mapper.ProductOrderMapper;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class RecommendationRuleService extends ServiceImpl<RecommendationRuleMapper, RecommendationRule> {
+
+    @Resource
+    private RecommendationRuleMapper recommendationRuleMapper;
+
+    @Resource
+    private ProductOrderMapper productOrderMapper;
+
+    /**
+     * 根据场景码加载启用的规则,按 priority 降序
+     */
+    public List<RecommendationRule> loadRulesByScene(String sceneCode) {
+        List<RecommendationRule> allEnabled = this.list(new LambdaQueryWrapper<RecommendationRule>()
+                .eq(RecommendationRule::getEnabled, 1));
+        return allEnabled.stream()
+                .filter(rule -> rule.getSceneCodes() != null && rule.getSceneCodes().contains(sceneCode))
+                .sorted(Comparator.comparing(RecommendationRule::getPriority).reversed())
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 查询已购商品ID(回溯窗口内)
+     */
+    public Set<Long> getPurchasedProductIds(Long familyId, Long memberId, int daysAgo) {
+        Set<Long> ids = new HashSet<>();
+        if (familyId == null) return ids;
+
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.DAY_OF_YEAR, -daysAgo);
+        Date cutoff = cal.getTime();
+
+        // 查 family 成员订单
+        List<ProductOrder> orders = productOrderMapper.selectList(
+                new LambdaQueryWrapper<ProductOrder>()
+                        .eq(ProductOrder::getFamilyId, familyId)
+                        .eq(ProductOrder::getStatus, "已支付")
+                        .gt(ProductOrder::getPaidAt, cutoff)
+        );
+        for (ProductOrder order : orders) {
+            if (order.getProductId() != null) {
+                ids.add(order.getProductId());
+            }
+        }
+        // 若未查到家庭订单,且 memberId 不为空,查个人订单
+        if (ids.isEmpty() && memberId != null) {
+            List<ProductOrder> orders2 = productOrderMapper.selectList(
+                    new LambdaQueryWrapper<ProductOrder>()
+                            .eq(ProductOrder::getBuyerId, memberId)
+                            .eq(ProductOrder::getStatus, "已支付")
+                            .gt(ProductOrder::getPaidAt, cutoff)
+            );
+            for (ProductOrder order : orders2) {
+                if (order.getProductId() != null) {
+                    ids.add(order.getProductId());
+                }
+            }
+        }
+        return ids;
+    }
+
+    /**
+     * 查询当前用户已购商品(用于 individual scope)
+     */
+    public Set<Long> getMyPurchasedProductIds(Long userId, int daysAgo) {
+        Set<Long> ids = new HashSet<>();
+        if (userId == null) return ids;
+
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.DAY_OF_YEAR, -daysAgo);
+        Date cutoff = cal.getTime();
+
+        List<ProductOrder> orders = productOrderMapper.selectList(
+                new LambdaQueryWrapper<ProductOrder>()
+                        .eq(ProductOrder::getBuyerId, userId)
+                        .eq(ProductOrder::getStatus, "已支付")
+                        .gt(ProductOrder::getPaidAt, cutoff)
+        );
+        for (ProductOrder order : orders) {
+            if (order.getProductId() != null) {
+                ids.add(order.getProductId());
+            }
+        }
+        return ids;
+    }
+}

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-68a9c8026db8ec2166f394d4b368317fd18f2590
+a32778d07a8c9bdbcc20d3b0522b745e35dba100

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1431",
+  "version": "1.0.1432",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1431",
+      "version": "1.0.1432",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1432",
+  "version": "1.0.1433",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 6 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,12 @@
 
 ---
 
+## v1.0.1433 (2026-09-17)
+
+### Bug 修复
+- 创建任务页输入框高度不足、截止时间选择器列数不匹配
+
+
 ## v1.0.1432 (2026-09-16)
 
 ### 新功能

+ 7 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1432
+> 当前版本: v1.0.1433
 
 ## 历史版本
 
@@ -8,6 +8,12 @@
 
 ---
 
+## v1.0.1433 (2026-09-17)
+
+### Bug 修复
+- 创建任务页输入框高度不足、截止时间选择器列数不匹配
+
+
 ## v1.0.1432 (2026-09-16)
 
 ### 新功能