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