Sfoglia il codice sorgente

课堂随机红包

xw 6 giorni fa
parent
commit
2d96494505

+ 44 - 0
fs-admin/src/main/java/com/fs/course/controller/CourseRandomRedPacketController.java

@@ -0,0 +1,44 @@
+package com.fs.course.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.enums.BusinessType;
+import com.fs.course.config.CourseRandomRedPacketConfig;
+import com.fs.course.service.ICourseRandomRedPacketService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 课程随机红包配置控制器,对应 fs_user_course.project
+ */
+@RestController
+@RequestMapping("/course/courseRandomRedPacket")
+public class CourseRandomRedPacketController extends BaseController {
+
+    @Autowired
+    private ICourseRandomRedPacketService courseRandomRedPacketService;
+
+    /**
+     * 获取课程随机红包配置
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseRandomRedPacket:query')")
+    @GetMapping("/{project}")
+    public R getConfig(@PathVariable Long project) {
+        CourseRandomRedPacketConfig config = courseRandomRedPacketService.getConfigByProject(project);
+        return R.ok().put("data", config);
+    }
+
+    /**
+     * 修改/保存课程随机红包配置
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseRandomRedPacket:edit')")
+    @Log(title = "课程随机红包配置", businessType = BusinessType.UPDATE)
+    @PostMapping
+    public AjaxResult saveConfig(@RequestBody CourseRandomRedPacketConfig config) {
+        courseRandomRedPacketService.saveConfig(config);
+        return AjaxResult.success();
+    }
+}

+ 24 - 0
fs-service/src/main/java/com/fs/course/config/CourseRandomRedPacketConfig.java

@@ -0,0 +1,24 @@
+package com.fs.course.config;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 课程随机红包配置,对应 fs_user_course.project 字段
+ */
+@Data
+public class CourseRandomRedPacketConfig {
+
+    /** 课程项目ID,对应 fs_user_course.project */
+    private Long project;
+
+    /** 是否开启随机红包 */
+    private Boolean enableRandom;
+
+    /** 企业白名单,多个企业逗号分隔,ALL代表全部企业可见 */
+    private String companyWhitelist;
+
+    /** 红包档位配置 */
+    private List<CourseRandomRedPacketTier> tiers;
+}

+ 18 - 0
fs-service/src/main/java/com/fs/course/config/CourseRandomRedPacketTier.java

@@ -0,0 +1,18 @@
+package com.fs.course.config;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 课程随机红包档位
+ */
+@Data
+public class CourseRandomRedPacketTier {
+
+    /** 红包金额 */
+    private BigDecimal amount;
+
+    /** 权重,随机抽取时按权重分配概率,权重越大中奖概率越高 */
+    private Integer weight;
+}

+ 34 - 0
fs-service/src/main/java/com/fs/course/service/ICourseRandomRedPacketService.java

@@ -0,0 +1,34 @@
+package com.fs.course.service;
+
+import com.fs.course.config.CourseRandomRedPacketConfig;
+import com.fs.course.domain.FsUserCourseVideo;
+
+import java.math.BigDecimal;
+
+/**
+ * 课程随机红包配置服务接口
+ */
+public interface ICourseRandomRedPacketService {
+
+    /**
+     * 根据课程项目project获取红包配置,无配置返回null
+     */
+    CourseRandomRedPacketConfig getConfigByProject(Long project);
+
+    /**
+     * 保存/更新课程随机红包配置
+     */
+    void saveConfig(CourseRandomRedPacketConfig config);
+
+    /**
+     * 计算最终红包金额:优先使用课程红包配置,异常时返回基础金额兜底
+     *
+     * @param baseAmount         原始基础红包金额
+     * @param courseId           课程ID,用于查询项目project
+     * @param companyId          企业ID
+     * @param video              视频信息,用于兼容旧版红包规则
+     * @param allowLegacyRandom  无课程红包配置时是否启用旧版全局红包逻辑
+     */
+    BigDecimal resolveAmount(BigDecimal baseAmount, Long courseId, Long companyId,
+                             FsUserCourseVideo video, boolean allowLegacyRandom);
+}

+ 246 - 0
fs-service/src/main/java/com/fs/course/service/impl/CourseRandomRedPacketServiceImpl.java

@@ -0,0 +1,246 @@
+package com.fs.course.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.utils.StringUtils;
+import com.fs.course.config.CourseRandomRedPacketConfig;
+import com.fs.course.config.CourseRandomRedPacketTier;
+import com.fs.course.config.RandomRedPacketConfig;
+import com.fs.course.config.RandomRedPacketRule;
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.mapper.FsUserCourseMapper;
+import com.fs.course.service.ICourseRandomRedPacketService;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.service.ISysConfigService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * 课程随机红包配置服务实现,对应 fs_user_course.project 字段
+ */
+@Service
+public class CourseRandomRedPacketServiceImpl implements ICourseRandomRedPacketService {
+
+    private static final Logger logger = LoggerFactory.getLogger(CourseRandomRedPacketServiceImpl.class);
+
+    private static final String CONFIG_KEY_PREFIX = "course.randomRedPacket:project:";
+
+    @Autowired
+    private FsUserCourseMapper fsUserCourseMapper;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Override
+    public CourseRandomRedPacketConfig getConfigByProject(Long project) {
+        if (project == null) {
+            return null;
+        }
+        String json = configService.selectConfigByKey(buildConfigKey(project));
+        if (StringUtils.isBlank(json)) {
+            return null;
+        }
+        try {
+            CourseRandomRedPacketConfig config = JSONUtil.toBean(json, CourseRandomRedPacketConfig.class);
+            if (config != null) {
+                config.setProject(project);
+            }
+            return config;
+        } catch (Exception e) {
+            logger.error("解析课程随机红包配置失败 project={}", project, e);
+            return null;
+        }
+    }
+
+    @Override
+    public void saveConfig(CourseRandomRedPacketConfig config) {
+        if (config == null || config.getProject() == null) {
+            throw new IllegalArgumentException("课程项目不能为空");
+        }
+        String configKey = buildConfigKey(config.getProject());
+        String json = JSONUtil.toJsonStr(config);
+        SysConfig existing = configService.selectConfigByConfigKey(configKey);
+        if (existing != null) {
+            existing.setConfigValue(json);
+            configService.updateConfig(existing);
+            return;
+        }
+        SysConfig sysConfig = new SysConfig();
+        sysConfig.setConfigName("课程红包配置-" + config.getProject());
+        sysConfig.setConfigKey(configKey);
+        sysConfig.setConfigValue(json);
+        sysConfig.setConfigType("N");
+        configService.insertConfig(sysConfig);
+    }
+
+    @Override
+    public BigDecimal resolveAmount(BigDecimal baseAmount, Long courseId, Long companyId,
+                                    FsUserCourseVideo video, boolean allowLegacyRandom) {
+        BigDecimal safeBase = baseAmount == null ? BigDecimal.ZERO : baseAmount;
+        try {
+            Long project = resolveProject(courseId, video);
+            if (project == null) {
+                return fallbackAmount(safeBase, video, allowLegacyRandom);
+            }
+            CourseRandomRedPacketConfig config = getConfigByProject(project);
+            if (config != null && Boolean.TRUE.equals(config.getEnableRandom())
+                    && CollectionUtils.isNotEmpty(config.getTiers())) {
+                if (isCompanyInWhitelist(config.getCompanyWhitelist(), companyId)) {
+                    BigDecimal randomAmount = pickAmountByTiers(config.getTiers());
+                    if (randomAmount.compareTo(BigDecimal.ZERO) > 0) {
+                        logger.info("课程随机红包生效 project={}, courseId={}, companyId={}, amount={}",
+                                project, courseId, companyId, randomAmount);
+                        return randomAmount;
+                    }
+                }
+                return safeBase;
+            }
+            return fallbackAmount(safeBase, video, allowLegacyRandom);
+        } catch (Exception e) {
+            logger.error("红包金额计算异常,使用基础金额兜底 courseId={}, companyId={}, baseAmount={}",
+                    courseId, companyId, safeBase, e);
+            return safeBase;
+        }
+    }
+
+    private BigDecimal fallbackAmount(BigDecimal safeBase, FsUserCourseVideo video, boolean allowLegacyRandom) {
+        if (allowLegacyRandom && video != null) {
+            return applyLegacyRandomRedPacket(safeBase, video);
+        }
+        return safeBase;
+    }
+
+    /**
+     * 通过 courseId 或 video.courseId 查询 fs_user_course.project
+     */
+    private Long resolveProject(Long courseId, FsUserCourseVideo video) {
+        Long targetCourseId = courseId;
+        if (targetCourseId == null && video != null) {
+            targetCourseId = video.getCourseId();
+        }
+        if (targetCourseId == null) {
+            return null;
+        }
+        FsUserCourse course = fsUserCourseMapper.selectFsUserCourseByCourseId(targetCourseId);
+        return course == null ? null : course.getProject();
+    }
+
+    private String buildConfigKey(Long project) {
+        return CONFIG_KEY_PREFIX + project;
+    }
+
+    private boolean isCompanyInWhitelist(String whitelist, Long companyId) {
+        if (StringUtils.isBlank(whitelist) || "ALL".equalsIgnoreCase(whitelist.trim())) {
+            return true;
+        }
+        if (companyId == null) {
+            return false;
+        }
+        String companyIdStr = String.valueOf(companyId);
+        for (String item : whitelist.split(",")) {
+            if (companyIdStr.equals(item.trim())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private BigDecimal pickAmountByTiers(List<CourseRandomRedPacketTier> tiers) {
+        int totalWeight = tiers.stream()
+                .filter(t -> t.getWeight() != null && t.getWeight() > 0 && t.getAmount() != null)
+                .mapToInt(CourseRandomRedPacketTier::getWeight)
+                .sum();
+        if (totalWeight <= 0) {
+            return BigDecimal.ZERO;
+        }
+        int randomPoint = new Random().nextInt(totalWeight);
+        int currentWeight = 0;
+        for (CourseRandomRedPacketTier tier : tiers) {
+            if (tier.getWeight() == null || tier.getWeight() <= 0 || tier.getAmount() == null) {
+                continue;
+            }
+            currentWeight += tier.getWeight();
+            if (randomPoint < currentWeight) {
+                return tier.getAmount().setScale(2, RoundingMode.HALF_UP);
+            }
+        }
+        return tiers.get(0).getAmount().setScale(2, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal applyLegacyRandomRedPacket(BigDecimal baseAmount, FsUserCourseVideo video) {
+        String json = configService.selectConfigByKey("randomRedpacket:config");
+        if (StringUtils.isBlank(json)) {
+            return baseAmount;
+        }
+        RandomRedPacketConfig randomRedPacket = JSONUtil.toBean(json, RandomRedPacketConfig.class);
+        if (randomRedPacket == null || !Boolean.TRUE.equals(randomRedPacket.getEnableRandomRedpacket())) {
+            return baseAmount;
+        }
+        BigDecimal randomMoney = BigDecimal.ZERO;
+        String randomRedPacketRules = video.getRandomRedPacketRules();
+        if (StringUtils.isNotBlank(randomRedPacketRules)) {
+            JSONArray array = JSONObject.parseArray(randomRedPacketRules);
+            List<RandomRedPacketRule> rules = new ArrayList<>();
+            for (Object o : array) {
+                rules.add(JSONObject.toJavaObject((JSONObject) o, RandomRedPacketRule.class));
+            }
+            randomMoney = getRandomMoneyByLegacyRules(rules);
+        } else if (CollectionUtils.isNotEmpty(randomRedPacket.getRules())) {
+            randomMoney = getRandomMoneyByLegacyRules(randomRedPacket.getRules());
+        }
+        if (randomMoney.compareTo(BigDecimal.ZERO) > 0) {
+            return randomMoney;
+        }
+        return baseAmount;
+    }
+
+    private BigDecimal getRandomMoneyByLegacyRules(List<RandomRedPacketRule> rules) {
+        try {
+            if (CollectionUtils.isEmpty(rules)) {
+                return BigDecimal.ZERO;
+            }
+            int totalWeight = rules.stream()
+                    .mapToInt(RandomRedPacketRule::getWeight)
+                    .sum();
+            if (totalWeight <= 0) {
+                return BigDecimal.ZERO;
+            }
+            Random random = new Random();
+            int randomPoint = random.nextInt(totalWeight);
+            RandomRedPacketRule selectedRule = null;
+            int currentWeight = 0;
+            for (RandomRedPacketRule rule : rules) {
+                currentWeight += rule.getWeight();
+                if (randomPoint < currentWeight) {
+                    selectedRule = rule;
+                    break;
+                }
+            }
+            if (selectedRule == null) {
+                selectedRule = rules.get(0);
+            }
+            BigDecimal maxAmount = selectedRule.getMaxAmount();
+            BigDecimal minAmount = selectedRule.getMinAmount();
+            if (maxAmount.compareTo(minAmount) == 0) {
+                return maxAmount;
+            }
+            BigDecimal range = maxAmount.subtract(minAmount);
+            BigDecimal randomValue = range.multiply(BigDecimal.valueOf(random.nextDouble()));
+            return minAmount.add(randomValue).setScale(2, RoundingMode.HALF_UP);
+        } catch (Exception e) {
+            logger.error("旧版红包规则计算异常", e);
+            return BigDecimal.ZERO;
+        }
+    }
+}

+ 5 - 87
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -2,7 +2,6 @@ package com.fs.course.service.impl;
 
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -32,8 +31,6 @@ import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.service.ICompanyService;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.config.CourseConfig;
-import com.fs.course.config.RandomRedPacketConfig;
-import com.fs.course.config.RandomRedPacketRule;
 import com.fs.course.domain.*;
 import com.fs.course.dto.CoursePeriodSyncResultDTO;
 import com.fs.course.dto.CoursePackageDTO;
@@ -193,6 +190,8 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     @Autowired
     private ISysConfigService configService;
     @Autowired
+    private ICourseRandomRedPacketService courseRandomRedPacketService;
+    @Autowired
     private QwApiService qwApiService;
     @Autowired
     private QwUserMapper qwUserMapper;
@@ -1823,33 +1822,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             amount = redPackage.getRedPacketMoney();
         } else if (video != null) {
             amount = video.getRedPacketMoney();
-            //是否开启了随机红包
-            String json = configService.selectConfigByKey("randomRedpacket:config");
-            if (StringUtils.isNotBlank(json)) {
-                RandomRedPacketConfig randomRedPacket = JSONUtil.toBean(json, RandomRedPacketConfig.class);
-                //是否开启拼手气红包
-                if (null != randomRedPacket && randomRedPacket.getEnableRandomRedpacket()) {
-                    BigDecimal randomMoney = BigDecimal.ZERO;
-                    //优先读取课程配置随机红包规则
-                    String randomRedPacketRules = video.getRandomRedPacketRules();
-                    if (StringUtils.isNotBlank(randomRedPacketRules)) {
-                        JSONArray array = JSONObject.parseArray(randomRedPacketRules);
-                        List<RandomRedPacketRule> rules = new ArrayList<>();
-                        for (Object o : array) {
-                            rules.add(JSONObject.toJavaObject((JSONObject) o, RandomRedPacketRule.class));
-                        }
-                        randomMoney = getRandomMoneyByRules(rules);
-                    }
-                    //如果课程没有配置 读取后台默认随机规则
-                    else {
-                        randomMoney = getRandomMoneyByRules(randomRedPacket.getRules());
-                    }
-                    //兼容拼手气红包报错情况的发放红包情况
-                    if (BigDecimal.ZERO.compareTo(randomMoney) < 0) {
-                        amount = randomMoney;
-                    }
-                }
-            }
+            amount = courseRandomRedPacketService.resolveAmount(amount, param.getCourseId(), param.getCompanyId(), video, true);
         }
 
         // 准备发送红包参数
@@ -2195,6 +2168,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         } else if (video != null && video.getRedPacketMoney() != null) {
             amount = video.getRedPacketMoney();
         }
+        amount = courseRandomRedPacketService.resolveAmount(amount, param.getCourseId(), param.getCompanyId(), video, false);
 
         // 准备发送红包参数
         WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
@@ -4467,63 +4441,6 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         return R.ok();
     }
 
-    /**
-     * 根据匹配规则返回随机红包值(需求是概率独立计算)
-     *
-     * @param rules
-     * @return
-     */
-    private BigDecimal getRandomMoneyByRules(List<RandomRedPacketRule> rules) {
-        try {
-
-            if (CollectionUtils.isEmpty(rules)) {
-                logger.error("随机红包规则列表为空,不予处理");
-//            throw new ServiceException("随机红包规则列表为空");
-            }
-            // 计算总权重
-            int totalWeight = rules.stream()
-                    .mapToInt(RandomRedPacketRule::getWeight)
-                    .sum();
-            // 根据权重随机选择规则
-            Random random = new Random();
-            int randomPoint = random.nextInt(totalWeight);
-
-            RandomRedPacketRule selectedRule = null;
-            int currentWeight = 0;
-
-            for (RandomRedPacketRule rule : rules) {
-                currentWeight += rule.getWeight();
-                if (randomPoint < currentWeight) {
-                    selectedRule = rule;
-                    break;
-                }
-            }
-
-            // 如果因为计算精度问题没有选中规则,默认选第一个
-            if (selectedRule == null) {
-                selectedRule = rules.get(0);
-            }
-            // 在选中规则的范围内生成随机金额
-            BigDecimal maxAmount = selectedRule.getMaxAmount();
-            BigDecimal minAmount = selectedRule.getMinAmount();
-            // 如果最大最小金额相等,直接返回该金额
-            if (maxAmount.compareTo(minAmount) == 0) {
-                return maxAmount;
-            }
-            // 生成随机金额
-            BigDecimal range = maxAmount.subtract(minAmount);
-            BigDecimal randomValue = range.multiply(BigDecimal.valueOf(random.nextDouble()));
-            BigDecimal result = minAmount.add(randomValue);
-            // 保留两位小数,四舍五入
-            return result.setScale(2, RoundingMode.HALF_UP);
-        } catch (Exception e) {
-            logger.error("获取拼手气红包金额异常:{}", rules, e);
-        }
-
-        return BigDecimal.ZERO;
-    }
-
-
     @Override
     public R uploadVideoToHuoShanByUrl() {
         List <FsVideoResource> videos = fsUserCourseVideoMapper.selectVideoByHuaWei();
@@ -5140,6 +5057,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         } else if (video != null && video.getRedPacketMoney() != null) {
             amount = video.getRedPacketMoney();
         }
+        amount = courseRandomRedPacketService.resolveAmount(amount, log.getCourseId(), log.getCompanyId(), video, false);
         packetParam.setAmount(amount);
 
         if (amount.compareTo(BigDecimal.ZERO) > 0) {

+ 1 - 1
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

@@ -300,7 +300,7 @@ public class CourseQwController extends AppBaseController {
     }
 
     /**
-     * 是否添加客服
+     * 自动看课是否添加客服
      */
     @Login
     @ApiOperation("是否添加客服")