Răsfoiți Sursa

feat: add PromotionTierEvalService and daily scheduled task for tier check

PromotionTierEvalService: evaluateTier/evaluateAndUpdateTier/evaluateAllUsers + WeChat notification on promotion. PromotionTierCheckScheduledTask: cron at 1:00 AM daily.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
User 2 luni în urmă
părinte
comite
e8d4282128

+ 153 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/PromotionTierEvalService.java

@@ -0,0 +1,153 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.PromotionTier;
+import com.etotem.cfc.entity.PromotionTierConfig;
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.PromotionTierConfigMapper;
+import com.etotem.cfc.mapper.PromotionTierMapper;
+import com.etotem.cfc.mapper.UserMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class PromotionTierEvalService {
+
+    private static final Logger log = LoggerFactory.getLogger(PromotionTierEvalService.class);
+
+    @Resource
+    private PromotionTierMapper tierMapper;
+
+    @Resource
+    private PromotionTierConfigMapper configMapper;
+
+    @Resource
+    private PromotionTierService promotionTierService;
+
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
+    private WechatService wechatService;
+
+    @Value("${wechat.promotion-tier-template-id:}")
+    private String promotionTierTemplateId;
+
+    /**
+     * 获取所有启用且按sortOrder desc排序的等级配置
+     */
+    public List<PromotionTierConfig> getEnabledConfigs() {
+        return configMapper.selectList(
+            new LambdaQueryWrapper<PromotionTierConfig>()
+                .eq(PromotionTierConfig::getEnabled, 1)
+                .orderByDesc(PromotionTierConfig::getSortOrder)
+        );
+    }
+
+    /**
+     * 根据用户的 PromotionTier 数据,计算应该属于哪个等级
+     * 遍历配置,从高到低找到第一个满足所有条件的等级
+     * @return 目标等级代码,如 "R2",如果没有任何配置匹配返回 "R0"
+     */
+    public String evaluateTier(Long userId) {
+        PromotionTier tier = promotionTierService.getCurrentTier(userId);
+        List<PromotionTierConfig> configs = getEnabledConfigs();
+
+        int teamSize = tier != null && tier.getTotalTeamSize() != null ? tier.getTotalTeamSize() : 0;
+        int referralEarnings = tier != null && tier.getTotalReferralEarnings() != null ? tier.getTotalReferralEarnings() : 0;
+        int shareEarnings = tier != null && tier.getTotalShareEarnings() != null ? tier.getTotalShareEarnings() : 0;
+
+        // 从高等级到低等级遍历,找到第一个满足所有条件的
+        // configs 已按 sortOrder desc 排序(高等级在前)
+        for (PromotionTierConfig config : configs) {
+            boolean teamSizeOk = teamSize >= config.getMinTeamSize();
+            boolean referralOk = config.getMinReferralEarnings() == null
+                || config.getMinReferralEarnings() == 0
+                || referralEarnings >= config.getMinReferralEarnings();
+            boolean shareOk = config.getMinShareEarnings() == null
+                || config.getMinShareEarnings() == 0
+                || shareEarnings >= config.getMinShareEarnings();
+
+            if (teamSizeOk && referralOk && shareOk) {
+                return config.getTierCode();
+            }
+        }
+        return "R0";
+    }
+
+    /**
+     * 对指定用户执行等级评估,如有晋级/降级则更新
+     * @return true if tier changed, false if unchanged
+     */
+    public boolean evaluateAndUpdateTier(Long userId) {
+        PromotionTier currentTier = promotionTierService.getCurrentTier(userId);
+        String currentTierCode = currentTier != null ? currentTier.getTier() : null;
+        String newTierCode = evaluateTier(userId);
+
+        if (newTierCode.equals(currentTierCode)) {
+            return false;
+        }
+
+        // Tier changed — use existing PromotionTierService.updateTier which logs the change
+        promotionTierService.updateTier(userId, newTierCode, "qualify");
+
+        PromotionTier updatedTier = promotionTierService.getCurrentTier(userId);
+        log.info("推广等级变更: userId={}, {}→{}, 团队={}, 推荐佣金={}, 分润={}",
+                userId, currentTierCode, newTierCode,
+                updatedTier != null ? updatedTier.getTotalTeamSize() : 0,
+                updatedTier != null ? updatedTier.getTotalReferralEarnings() : 0,
+                updatedTier != null ? updatedTier.getTotalShareEarnings() : 0);
+
+        // Send WeChat template notification
+        sendPromotionNotification(userId, currentTierCode, newTierCode);
+        return true;
+    }
+
+    /**
+     * 批量评估所有有 PromotionTier 记录的用户
+     * @return 变更数量
+     */
+    public int evaluateAllUsers() {
+        List<PromotionTier> allTiers = tierMapper.selectList(null);
+        int changed = 0;
+        for (PromotionTier tier : allTiers) {
+            if (evaluateAndUpdateTier(tier.getUserId())) {
+                changed++;
+            }
+        }
+        log.info("批量推广等级评估完成,共{}个用户,检查了{}条记录", changed, allTiers.size());
+        return changed;
+    }
+
+    /**
+     * 发送推广等级变更通知(微信模板消息)
+     */
+    private void sendPromotionNotification(Long userId, String oldTier, String newTier) {
+        if (promotionTierTemplateId == null || promotionTierTemplateId.isEmpty()) {
+            return;
+        }
+        try {
+            User user = userMapper.selectById(userId);
+            if (user == null || user.getOpenid() == null || user.getOpenid().isEmpty()) {
+                return;
+            }
+            Map<String, Object> keywordMap = new HashMap<>();
+            keywordMap.put("keyword1", oldTier != null ? oldTier : "无");
+            keywordMap.put("keyword2", newTier);
+            keywordMap.put("keyword3", new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date()));
+            wechatService.sendTemplateMessage(user.getOpenid(), promotionTierTemplateId, keywordMap);
+            log.info("已发送推广等级变更通知: userId={}, {}→{}", userId, oldTier, newTier);
+        } catch (Exception e) {
+            log.warn("发送推广等级变更通知失败 userId={}", userId, e);
+        }
+    }
+}

+ 31 - 0
cfc-backend/src/main/java/com/etotem/cfc/task/PromotionTierCheckScheduledTask.java

@@ -0,0 +1,31 @@
+package com.etotem.cfc.task;
+
+import com.etotem.cfc.service.PromotionTierEvalService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+@Slf4j
+@Component
+public class PromotionTierCheckScheduledTask {
+
+    @Resource
+    private PromotionTierEvalService evalService;
+
+    /**
+     * 每天 1:00 AM 执行
+     * 遍历所有有推广等级记录的用户,检查是否需要晋级/降级
+     */
+    @Scheduled(cron = "0 0 1 * * ?")
+    public void checkAndUpdateAllTiers() {
+        log.info("开始执行推广等级定时检查任务");
+        try {
+            int changed = evalService.evaluateAllUsers();
+            log.info("推广等级定时检查任务执行完成,变更{}个用户等级", changed);
+        } catch (Exception e) {
+            log.error("推广等级定时检查任务执行失败", e);
+        }
+    }
+}