From dc2c26ae74907ef7298d286fce654557f2ec3f78 Mon Sep 17 00:00:00 2001 From: liyj <1617420630@qq.com> Date: Fri, 12 Dec 2025 11:07:46 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A0=8D=E4=BB=B7=E6=96=B0=E5=A2=9E=E6=91=87?= =?UTF-8?q?=E8=89=B2=E5=AD=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../activity/ShopActivityCutprice.java | 3 + .../activity/ShopActivityCutpriceHistory.java | 13 +- .../mall/common/modules/page/ShopPageApp.java | 6 +- .../store/ShopActivityCutLotteryHistory.java | 53 ++ .../modules/store/ShopStoreActivityBase.java | 4 + .../mall/common/utils/RollDiceUtils.java | 290 ++++++++ .../mobile/UserActivityController.java | 15 +- .../ShopActivityCutLotteryHistoryMapper.java | 10 + .../ShopActivityCutLotteryHistoryService.java | 11 + .../service/ShopActivityCutpriceService.java | 9 + .../ShopActivityCutLotteryHistoryImpl.java | 15 + .../impl/ShopActivityCutpriceServiceImpl.java | 663 +++++++++++++----- .../ShopStoreActivityBaseServiceImpl.java | 107 +++ .../shop/sync/Utils/BigDecimalFormatter.java | 23 + sql/shop/dev/20251209_ddl.sql | 19 + sql/shop/dev/20251212_dml.sql | 1 + 16 files changed, 1074 insertions(+), 168 deletions(-) create mode 100644 mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopActivityCutLotteryHistory.java create mode 100644 mall-common/src/main/java/com/suisung/mall/common/utils/RollDiceUtils.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/activity/mapper/ShopActivityCutLotteryHistoryMapper.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/activity/service/ShopActivityCutLotteryHistoryService.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/activity/service/impl/ShopActivityCutLotteryHistoryImpl.java create mode 100644 sql/shop/dev/20251209_ddl.sql create mode 100644 sql/shop/dev/20251212_dml.sql diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/activity/ShopActivityCutprice.java b/mall-common/src/main/java/com/suisung/mall/common/modules/activity/ShopActivityCutprice.java index 4c349501..67b216d6 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/activity/ShopActivityCutprice.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/activity/ShopActivityCutprice.java @@ -69,4 +69,7 @@ public class ShopActivityCutprice implements Serializable { @ApiModelProperty(value = "更新时间") private Date updated_at; + @ApiModelProperty(value = "大转盘次数") + private Integer lottery_num; + } diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/activity/ShopActivityCutpriceHistory.java b/mall-common/src/main/java/com/suisung/mall/common/modules/activity/ShopActivityCutpriceHistory.java index 0da05526..eb43ad9d 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/activity/ShopActivityCutpriceHistory.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/activity/ShopActivityCutpriceHistory.java @@ -1,8 +1,6 @@ package com.suisung.mall.common.modules.activity; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.*; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -52,5 +50,14 @@ public class ShopActivityCutpriceHistory implements Serializable { @ApiModelProperty(value = "砍价编号") private Integer ac_id; + @ApiModelProperty(value = "摇骰子次数") + private Integer lottery_num; + @ApiModelProperty(value = "骰子点数") + @TableField(updateStrategy = FieldStrategy.NOT_EMPTY) + private Integer alh_point; + + @ApiModelProperty(value = "转盘前砍掉的价格") + @TableField(updateStrategy = FieldStrategy.NOT_EMPTY) + private BigDecimal ach_price_pre; } diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/page/ShopPageApp.java b/mall-common/src/main/java/com/suisung/mall/common/modules/page/ShopPageApp.java index ae5aace8..ae742fe5 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/page/ShopPageApp.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/page/ShopPageApp.java @@ -1,9 +1,6 @@ package com.suisung.mall.common.modules.page; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.*; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -84,5 +81,6 @@ public class ShopPageApp implements Serializable { private String app_market_images; @ApiModelProperty(value = "行业类别:1超市,2数码家电, 3水果生鲜, 4烘培饮品, 5社区团购, 6时尚美妆, 7婴儿服饰, 8家居, 9汽车, 10酒店旅游, 11鲜花绿植, 12医药健康, 13工业五金, 14节日模板,0其他行业") + @TableField(updateStrategy = FieldStrategy.NOT_EMPTY) private String app_industry; } diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopActivityCutLotteryHistory.java b/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopActivityCutLotteryHistory.java new file mode 100644 index 00000000..24e04437 --- /dev/null +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopActivityCutLotteryHistory.java @@ -0,0 +1,53 @@ +package com.suisung.mall.common.modules.store; +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 砍价抽奖历史记录DTO + */ +@Data +@TableName("shop_activity_cut_lottery_history") +@ApiModel(value = "ShopActivityCutLotteryHistory", description = "砍价大转盘抽奖历史记录") +public class ShopActivityCutLotteryHistory implements Serializable{ + private static final long serialVersionUID = 1L; + + @TableId(value = "alh_id", type = IdType.AUTO) + @ApiModelProperty(value = "用户中奖编号", example = "1") + private Long alhId; + + @TableField(value ="user_id",updateStrategy = FieldStrategy.NOT_EMPTY) + @ApiModelProperty(value = "用户编号", example = "10001") + private Integer userId; + + @TableField(value ="activity_id",updateStrategy = FieldStrategy.NOT_EMPTY) + @ApiModelProperty(value = "活动编号", example = "20001") + private Integer activityId; + + @TableField(value ="alh_item_id",updateStrategy = FieldStrategy.NOT_EMPTY) + @ApiModelProperty(value = "抽奖物品编号", example = "30001") + private Integer alhItemId; + + @TableField(value ="alh_item_name",updateStrategy = FieldStrategy.NOT_EMPTY) + @ApiModelProperty(value = "抽奖物品名称", example = "iPhone 15 Pro") + private String alhItemName; + + @TableField(value = "alh_award_flag",updateStrategy = FieldStrategy.NOT_EMPTY) + @ApiModelProperty(value = "是否中奖:0-未中奖;1-中奖", example = "1") + private Boolean alhAwardFlag; + + @TableField(value ="create_time") + @ApiModelProperty(value = "创建时间", example = "2023-12-01 10:00:00") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + @TableField("update_time") + @ApiModelProperty(value = "更新时间", example = "2023-12-01 10:00:00") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; +} diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopStoreActivityBase.java b/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopStoreActivityBase.java index 47838d78..f375cd9e 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopStoreActivityBase.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopStoreActivityBase.java @@ -135,4 +135,8 @@ public class ShopStoreActivityBase implements Serializable { @ApiModelProperty(value = "参与活动商品的总数量(个)") private Integer product_count; + @ApiModelProperty(value = "规则配置") + @TableField(updateStrategy = NOT_EMPTY) + private String lucky_turn; + } diff --git a/mall-common/src/main/java/com/suisung/mall/common/utils/RollDiceUtils.java b/mall-common/src/main/java/com/suisung/mall/common/utils/RollDiceUtils.java new file mode 100644 index 00000000..06691aff --- /dev/null +++ b/mall-common/src/main/java/com/suisung/mall/common/utils/RollDiceUtils.java @@ -0,0 +1,290 @@ +package com.suisung.mall.common.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@lombok.extern.slf4j.Slf4j +public class RollDiceUtils { + // 骰子游戏类 + public static class DiceGame { + private List> diceConfigs; + private Random random; + private Double totalProbability; + private double[] probabilityRanges; + + public DiceGame(List> configs) { + this.diceConfigs = configs; + this.random = new Random(); + validateConfigs(); + initializeProbabilityRanges(); + } + + // 验证配置数据 + private void validateConfigs() { + if (diceConfigs == null || diceConfigs.isEmpty()) { + throw new IllegalArgumentException("骰子配置不能为空"); + } + + for (Map config : diceConfigs) { + if (!config.containsKey("doubleValue") || !config.containsKey("probability")) { + throw new IllegalArgumentException("骰子配置缺少必要字段"); + } + + Object value = config.get("doubleValue"); + Object prob = config.get("probability"); + + if (!(value instanceof Integer) || !(prob instanceof Double)) { + throw new IllegalArgumentException("骰子配置字段类型错误"); + } + } + } + + // 初始化概率范围 + private void initializeProbabilityRanges() { + totalProbability = 0.00; + probabilityRanges = new double[diceConfigs.size()]; + + for (int i = 0; i < diceConfigs.size(); i++) { + Map config = diceConfigs.get(i); + double probability = (Double) config.get("probability"); + totalProbability += probability; + probabilityRanges[i] = totalProbability; + } + + // 验证概率总和是否为100 + if (totalProbability != 100) { + log.warn("警告:概率总和为 {}%,不是100%", totalProbability); + } + } + + /** + * 摇骰子(根据概率获取点数) + * @param cutPrice 砍掉的价格 + * @param targetPrice 剩余价格 + * @param targetPrice 目标价 + * @return + */ + public int rollDice(BigDecimal cutPrice,BigDecimal lessPrice,BigDecimal targetPrice,Integer maxNum) { + if (diceConfigs.isEmpty()) { + throw new IllegalStateException("骰子配置为空"); + } + //不能超过最大的点数,如果超过就不能中奖 + BigDecimal suPrice=lessPrice.subtract(targetPrice).setScale(2, RoundingMode.HALF_UP); + BigDecimal maxCutPrice= cutPrice.multiply(new BigDecimal(maxNum)).setScale(2, RoundingMode.HALF_UP); + if(maxCutPrice.compareTo(suPrice)>-1){ + return 1; + } + int randomValue = random.nextInt(100); // 生成0-99的随机数 + // 根据概率范围确定点数 + for (int i = 0; i < probabilityRanges.length; i++) { + double prevRange = (i == 0) ? 0 : probabilityRanges[i - 1]; + if (randomValue >= prevRange && randomValue < probabilityRanges[i]) { + Map config = diceConfigs.get(i); + return (Integer) config.get("doubleValue"); + } + } + + // 如果概率总和不是100,返回第一个非零概率的点数 + Map config = getFirstNonZeroProbabilityConfig(); + return (Integer) config.get("doubleValue"); + } + + // 获取第一个非零概率的配置 + private Map getFirstNonZeroProbabilityConfig() { + for (Map config : diceConfigs) { + double probability = (double) config.get("probability"); + if (probability > 0) { + return config; + } + } + return diceConfigs.get(0); // 如果所有概率都是0,返回第一个配置 + } + public static List randMoney(BigDecimal money, Integer num) { + // 只有一个人直接返回 + if (num == 1) { + return Collections.singletonList(NumberUtil.mul(money, 100).intValue()); + } + + money = NumberUtil.mul(money, 100); // 将元转成分(小数计算有误差,随机数也都是整数) + BigDecimal rest_money = money; // 初始化,剩余钱的变量 + Integer average = NumberUtil.div(rest_money, num, 0).intValue(); // 求出均分情况下,每人的红包值 + List arr = new ArrayList<>(); + + if (ObjectUtil.compare(average, 1) < 0) { + // 钱不够所有人分 + return Stream.generate(() -> 0).limit(num).collect(Collectors.toList()); + } else if (ObjectUtil.compare(average, 1) == 0) { + // 所有人*均分这笔钱(钱数只够这么分的) + for (Integer i = 0; i < num; i++) { + arr.add(average); + } + } else { + // 每个人随机分配 + for (int i = 0; i < num; i++) { + BigDecimal range_money = NumberUtil.div(rest_money, (num - i), 0); + Integer rand_money = RandomUtil.randomInt(1, range_money.intValue()); + arr.add(rand_money); + rest_money = NumberUtil.sub(rest_money, rand_money); // 获取剩下的钱 + } + } + + int arr_sum = 0; // 保存数组和 + if (CollUtil.isNotEmpty(arr)) { + // 随机分配,会调用此方法将剩余的钱分掉,此数组为随机分配后的结果 + arr_sum = arr.stream().mapToInt(Integer::intValue).sum(); // 统计随机分配已经分配了总钱数 + } else { + // 初始化每个人的数组,兼容下边循环处理部分 + arr = Stream.generate(() -> 0).limit(num).collect(Collectors.toList()); + } + + BigDecimal add_money = NumberUtil.sub(money, arr_sum); + // 如果总钱数和之前随机分配的数组的总和差值为0,就说明随机分配已经将钱全部分出去了,就不需要再*均分配处理了 + if (ObjectUtil.compare(add_money, BigDecimal.ZERO) == 0) { + return arr; + } + + // 先把剩余的能均分的部分均分一下,然后若再有剩余,则从前到后,注意分配 + // 如果之前有随机分配,则是将剩余的钱*均追加入随机分配的值里 + Integer avg_add_money = NumberUtil.div(add_money, num, 0).intValue(); + arr = arr.stream().map(s -> s + avg_add_money).collect(Collectors.toList()); + // 分配后,求和,用于修正最后剩余的零钱 + arr_sum = arr.stream().mapToInt(Integer::intValue).sum(); + // 如果还有剩余,这部分说明每人一分都不够,就从头开始没人一分的分下去,直到分完为止 + BigDecimal odd_money = NumberUtil.sub(money, arr_sum, 0); + int i = 0; + while (ObjectUtil.compare(odd_money, BigDecimal.ONE) >= 0) { + arr.set(i, arr.get(i) + 1); // 每人加1分钱 + odd_money = NumberUtil.sub(odd_money, 1); // 剩余的金额,每分掉一个人,就减1分钱 + if (i == num) { + i = 0; + } else { + i++; + } + } + + return arr; + } + + // 批量测试摇骰子结果分布 + public Map testDistribution(int rollCount) { + Map distribution = new HashMap<>(); + + // 初始化分布图 + for (Map config : diceConfigs) { + int point = (Integer) config.get("doubleValue"); + distribution.put(point, 0); + } + BigDecimal salePrice = new BigDecimal("47"); + int accNum=100; + // 摇骰子多次并统计 + for (int i = 0; i < rollCount; i++) { + List arr= randMoney(salePrice, accNum); + Integer randPrice = arr.size() == 1 ? arr.get(0) : arr.get(RandomUtil.randomInt(0, arr.size())); + BigDecimal cutPrice = NumberUtil.div(BigDecimal.valueOf(randPrice), 100, 2); + int result = rollDice(cutPrice,salePrice,new BigDecimal("0.01"),6); + BigDecimal sumPrice = cutPrice.multiply(new BigDecimal(result)).setScale(2, RoundingMode.HALF_UP); + System.out.println("第"+(i+1)+"次;砍价:"+cutPrice+";点数:"+result+",最终砍价数据:"+sumPrice); + salePrice=salePrice.subtract(sumPrice).setScale(2, RoundingMode.HALF_UP); + distribution.put(result, distribution.get(result) + 1); + accNum--; + } + + return distribution; + } + + // 显示概率配置 + public void displayConfig() { + System.out.println("当前骰子概率配置:"); + System.out.println("点数\t概率"); + System.out.println("----------------"); + + for (Map config : diceConfigs) { + int point = (Integer) config.get("doubleValue"); + double probability = (double) config.get("probability"); + System.out.printf("%d\t%f%%\n", point, probability); + } + + System.out.println("----------------"); + System.out.printf("概率总和:%f%%\n\n", totalProbability); + } + + // 获取预期概率 + public Map getExpectedProbabilities() { + Map expected = new HashMap<>(); + + for (Map config : diceConfigs) { + int point = (Integer) config.get("doubleValue"); + double probability = (double) config.get("probability"); + expected.put(point, probability / 100.0); + } + + return expected; + } + } + + // 主程序 + public static void main(String[] args) { + BigDecimal achPrice=new BigDecimal(5.99); + achPrice= achPrice.multiply(new BigDecimal(1)).divide(new BigDecimal(10),3, RoundingMode.HALF_UP).setScale(3, RoundingMode.HALF_UP); + System.out.println(achPrice); + // 创建骰子配置(使用 Map) + List> configs = new ArrayList<>(); + + // 添加配置项 + configs.add(createDiceConfig(1, 45)); + configs.add(createDiceConfig(2, 45)); + configs.add(createDiceConfig(3, 3)); + configs.add(createDiceConfig(4, 2)); + configs.add(createDiceConfig(5, 3)); + configs.add(createDiceConfig(6, 2)); + + // 创建骰子游戏实例 + DiceGame diceGame = new DiceGame(configs); + + // 显示配置 + diceGame.displayConfig(); + + // 模拟摇骰子 + System.out.println("=== 模拟摇骰子10次 ==="); + for (int i = 1; i <= 100; i++) { + int result = diceGame.rollDice(new BigDecimal(0.5),new BigDecimal(50),new BigDecimal(0.01),6); + System.out.println("第" + i + "次摇骰子: " + result); + } + + // 测试概率分布 + System.out.println("\n=== 概率分布测试(100次) ==="); + int testCount = 100; + Map distribution = diceGame.testDistribution(testCount); + + System.out.println("点数\t出现次数\t实际概率\t预期概率"); + System.out.println("----------------------------------------"); + + Map expectedProbabilities = diceGame.getExpectedProbabilities(); + + for (Map.Entry entry : distribution.entrySet()) { + int point = entry.getKey(); + int count = entry.getValue(); + double actualProbability = (double) count / testCount * 100; + double expectedProbability = expectedProbabilities.getOrDefault(point, 0.0) * 100; + + System.out.printf("%d\t%d\t\t%.2f%%\t\t%.2f%%\n", + point, count, actualProbability, expectedProbability); + } + } + + // 辅助方法:创建骰子配置 Map + public static Map createDiceConfig(int value, double probability) { + Map config = new HashMap<>(); + config.put("doubleValue", value); + config.put("probability", probability); + return config; + } +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/activity/controller/mobile/UserActivityController.java b/mall-shop/src/main/java/com/suisung/mall/shop/activity/controller/mobile/UserActivityController.java index b58226a1..10b0cc4b 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/activity/controller/mobile/UserActivityController.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/activity/controller/mobile/UserActivityController.java @@ -11,6 +11,7 @@ import com.suisung.mall.common.modules.activity.ShopActivityGroupbookingHistory; import com.suisung.mall.common.modules.activity.ShopActivityGroupbuyStoreHistory; import com.suisung.mall.common.service.impl.BaseControllerImpl; import com.suisung.mall.common.utils.CheckUtil; +import com.suisung.mall.common.utils.ContextUtil; import com.suisung.mall.common.utils.I18nUtil; import com.suisung.mall.shop.activity.service.ShopActivityCutpriceHistoryService; import com.suisung.mall.shop.activity.service.ShopActivityCutpriceService; @@ -121,7 +122,7 @@ public class UserActivityController extends BaseControllerImpl { @ApiOperation(value = "立即砍价", notes = "自己砍价、邀请朋友过来也能砍价") @RequestMapping(value = "/doCutPrice", method = {RequestMethod.GET, RequestMethod.POST}) public CommonResult doCutPrice(@RequestParam(name = "ac_id", defaultValue = "0") Integer ac_id) { - UserDto user = getCurrentUser(); + UserDto user = ContextUtil.getCurrentUser(); if (user == null || CheckUtil.isEmpty(user.getId())) { throw new ApiException(ResultCode.NEED_LOGIN); } @@ -251,5 +252,17 @@ public class UserActivityController extends BaseControllerImpl { public CommonResult getGiftbag(@RequestParam(name = "activity_id") Integer activity_id) { return CommonResult.success(shopStoreActivityBaseService.getGiftbag(activity_id)); } + + @ApiOperation(value = "砍价大转盘", notes = "自己砍价、邀请朋友过来也能转") + @RequestMapping(value = "/dolookTurnCutPrice", method = {RequestMethod.GET, RequestMethod.POST}) + public CommonResult dolookTurnCutPrice(@RequestParam(name = "ac_id", defaultValue = "0") Integer ac_id) { + UserDto user = ContextUtil.getCurrentUser(); + if (user == null || CheckUtil.isEmpty(user.getId())) { + throw new ApiException(ResultCode.NEED_LOGIN); + } + Integer user_id = user.getId(); + return shopActivityCutpriceService.dolookTurnCutPrice(ac_id, user_id); + } + } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/activity/mapper/ShopActivityCutLotteryHistoryMapper.java b/mall-shop/src/main/java/com/suisung/mall/shop/activity/mapper/ShopActivityCutLotteryHistoryMapper.java new file mode 100644 index 00000000..6855216a --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/activity/mapper/ShopActivityCutLotteryHistoryMapper.java @@ -0,0 +1,10 @@ + +package com.suisung.mall.shop.activity.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.suisung.mall.common.modules.store.ShopActivityCutLotteryHistory; +import org.springframework.stereotype.Repository; + +@Repository +public interface ShopActivityCutLotteryHistoryMapper extends BaseMapper { +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/ShopActivityCutLotteryHistoryService.java b/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/ShopActivityCutLotteryHistoryService.java new file mode 100644 index 00000000..d74a1f0b --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/ShopActivityCutLotteryHistoryService.java @@ -0,0 +1,11 @@ +package com.suisung.mall.shop.activity.service; + + +import com.suisung.mall.common.modules.store.ShopActivityCutLotteryHistory; +import com.suisung.mall.core.web.service.IBaseService; + +public interface ShopActivityCutLotteryHistoryService extends IBaseService { + + + +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/ShopActivityCutpriceService.java b/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/ShopActivityCutpriceService.java index 8c958650..6c8d1986 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/ShopActivityCutpriceService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/ShopActivityCutpriceService.java @@ -65,4 +65,13 @@ public interface ShopActivityCutpriceService extends IBaseService implements ShopActivityCutLotteryHistoryService { + + +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/impl/ShopActivityCutpriceServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/impl/ShopActivityCutpriceServiceImpl.java index bca71d1e..e9b834e9 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/impl/ShopActivityCutpriceServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/activity/service/impl/ShopActivityCutpriceServiceImpl.java @@ -7,6 +7,7 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; @@ -19,25 +20,25 @@ import com.suisung.mall.common.domain.UserDto; import com.suisung.mall.common.exception.ApiException; import com.suisung.mall.common.exception.ApiUserException; import com.suisung.mall.common.feignService.AccountService; +import com.suisung.mall.common.modules.account.AccountUserBase; import com.suisung.mall.common.modules.activity.ShopActivityCutprice; import com.suisung.mall.common.modules.activity.ShopActivityCutpriceHistory; import com.suisung.mall.common.modules.activity.ShopActivityGroupbooking; import com.suisung.mall.common.modules.activity.ShopActivityGroupbookingHistory; +import com.suisung.mall.common.modules.store.ShopActivityCutLotteryHistory; import com.suisung.mall.common.modules.store.ShopStoreActivityBase; -import com.suisung.mall.common.utils.CheckUtil; -import com.suisung.mall.common.utils.DateTimeUtils; -import com.suisung.mall.common.utils.I18nUtil; +import com.suisung.mall.common.utils.*; import com.suisung.mall.core.web.service.RedisService; import com.suisung.mall.core.web.service.impl.BaseServiceImpl; import com.suisung.mall.shop.activity.mapper.ShopActivityCutpriceMapper; -import com.suisung.mall.shop.activity.service.ShopActivityCutpriceHistoryService; -import com.suisung.mall.shop.activity.service.ShopActivityCutpriceService; -import com.suisung.mall.shop.activity.service.ShopActivityGroupbookingHistoryService; -import com.suisung.mall.shop.activity.service.ShopActivityGroupbookingService; +import com.suisung.mall.shop.activity.service.*; import com.suisung.mall.shop.base.service.AccountBaseConfigService; import com.suisung.mall.shop.order.service.ShopOrderInfoService; import com.suisung.mall.shop.store.service.ShopStoreActivityBaseService; +import com.suisung.mall.shop.sync.Utils.BigDecimalFormatter; import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.data.util.Pair; @@ -45,8 +46,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.Serializable; +import java.lang.reflect.Type; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static com.suisung.mall.common.utils.ContextUtil.getCurrentUser; @@ -95,6 +99,11 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl check_result = checkCutPriceExpiredAndStock(activityBase); - if (!check_result.getFirst()) { - // 库存不够,立即更改活动状态为已结束 - shopStoreActivityBaseService.updateActivityState(cutprice.getActivity_id(), StateCode.ACTIVITY_STATE_FINISHED); - throw new ApiException(check_result.getSecond()); + } catch (InterruptedException e) { + throw new RuntimeException(e); } - - } catch (Exception e) { - return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!")); - } - - // 检查帮砍次数限制 - if (!cutprice.getUser_id().equals(user_id)) { - try { - if (accountBaseConfigService != null && redisService != null) { - DateTime today = DateUtil.beginOfDay(new Date()); - Integer maxCuts = accountBaseConfigService.getConfig("user_cutprice_num", 0); - Integer usedCuts = Convert.toInt(redisService.hGet("cutprice-" + today, user_id.toString()), 0); - - if (maxCuts > 0 && usedCuts >= maxCuts) { - return CommonResult.failed(I18nUtil._("今日帮砍次数已达上限!")); - } - } - } catch (Exception e) { - // Redis检查失败时继续执行砍价操作 - } - } - - BigDecimal salePrice = cutprice.getAc_sale_price(); - BigDecimal minPrice = cutprice.getAc_mix_limit_price(); - - // 检查价格数据完整性 - if (salePrice == null || minPrice == null) { - return CommonResult.failed(I18nUtil._("商品价格数据异常!")); - } - - // 检查是否已达到最低价 - if (NumberUtil.isLessOrEqual(salePrice, minPrice)) { - updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_CutFinished); - return CommonResult.failed(I18nUtil._("已砍到最低价啦!")); - } - - // 检查是否已经帮过好友砍价 try { - if (shopActivityCutpriceHistoryService == null) { + // 参数校验 + if (ac_id == null || ac_id <= 0 || user_id == null || user_id <= 0) { + return CommonResult.failed(I18nUtil._("活动或用户编号无效!")); + } + + // 获取砍价记录 + ShopActivityCutprice cutprice = get(ac_id); + if (cutprice == null) { + return CommonResult.failed(I18nUtil._("未找到砍价记录!")); + } + + // 检查活动状态 + if (shopStoreActivityBaseService == null) { return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!")); } - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("user_id", user_id).eq("ac_id", ac_id); - if (shopActivityCutpriceHistoryService.count(queryWrapper) > 0) { - return CommonResult.failed(I18nUtil._("您已帮好友砍过价了!")); + ShopStoreActivityBase activityBase = shopStoreActivityBaseService.get(cutprice.getActivity_id()); + if (activityBase == null) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Canceled); + return CommonResult.failed(I18nUtil._("抱歉,砍价活动已失效啦!")); } - } catch (Exception e) { - return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!")); - } - // 计算砍价金额 - BigDecimal cutPrice; - try { - cutPrice = shopStoreActivityBaseService.getCutDownPrice(activityBase, cutprice); - if (cutPrice == null || cutPrice.compareTo(BigDecimal.ZERO) <= 0) { - return CommonResult.failed(I18nUtil._("砍价失败,请重试!")); + // 检查活动状态是否正常 + if (!ObjectUtil.equal(activityBase.getActivity_state(), StateCode.ACTIVITY_STATE_NORMAL)) { + if (ObjectUtil.equal(activityBase.getActivity_state(), StateCode.ACTIVITY_STATE_FINISHED) + || ObjectUtil.equal(activityBase.getActivity_state(), StateCode.ACTIVITY_STATE_CLOSED)) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Canceled); + } + return CommonResult.failed(I18nUtil._("抱歉,砍价活动已结束啦!")); } - } catch (Exception e) { - return CommonResult.failed(I18nUtil._("砍价失败,请重试!")); - } - // 获取商品ID - String ruleStr = activityBase.getActivity_rule(); - if (StrUtil.isBlank(ruleStr)) { - return CommonResult.failed(I18nUtil._("活动规则数据异常!")); - } + Date now = new Date(); + Long expiredAt = cutprice.getExpired_at(); - try { + // 检查砍价记录是否过期 + if (CheckUtil.isNotEmpty(expiredAt) && expiredAt < now.getTime()) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Expired); + return CommonResult.failed(I18nUtil._("抱歉,砍价已过期啦!")); + } + + // 检查活动时间是否有效 + if (!shopStoreActivityBaseService.isActivityTimeValid(activityBase, now)) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Expired); + return CommonResult.failed(I18nUtil._("活动已结束!")); + } + + // 需要检查活动有效期和活动商品库存是否足够 + Pair check_result = checkCutPriceExpiredAndStock(activityBase); + if (!check_result.getFirst()) { + // 库存不够,立即更改活动状态为已结束 + shopStoreActivityBaseService.updateActivityState(cutprice.getActivity_id(), StateCode.ACTIVITY_STATE_FINISHED); + throw new ApiException(check_result.getSecond()); + } + // 检查帮砍次数限制 + if (!cutprice.getUser_id().equals(user_id)) { + if (accountBaseConfigService != null && redisService != null) { + DateTime today = DateUtil.beginOfDay(new Date()); + //Integer maxCuts = accountBaseConfigService.getConfig("user_cutprice_num", 0); + String activityRuleStr= activityBase.getActivity_rule(); + JSONObject jsonObject= JSONUtil.parseObj(activityRuleStr); + Integer maxCuts = jsonObject.getInt("user_cutprice_num",5); + Integer usedCuts = Convert.toInt(redisService.hGet("cutprice-" + today, user_id.toString()), 0); + if (maxCuts > 0 && usedCuts >= maxCuts) { + return CommonResult.failed(I18nUtil._("今日帮砍次数已达上限!")); + } + } + } + + BigDecimal salePrice = cutprice.getAc_sale_price(); + BigDecimal minPrice = cutprice.getAc_mix_limit_price(); + + // 检查价格数据完整性 + if (salePrice == null || minPrice == null) { + return CommonResult.failed(I18nUtil._("商品价格数据异常!")); + } + + // 检查是否已达到最低价 + if (NumberUtil.isLessOrEqual(salePrice, minPrice)) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_CutFinished); + return CommonResult.failed(I18nUtil._("已砍到最低价啦!")); + } + + // 检查是否已经帮过好友砍价 + if (shopActivityCutpriceHistoryService == null) { + return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!")); + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("user_id", user_id).eq("ac_id", ac_id); + if (shopActivityCutpriceHistoryService.count(queryWrapper) > 0) { + return CommonResult.failed(I18nUtil._("您已帮好友砍过价了!")); + } + // 获取商品ID + String ruleStr = activityBase.getActivity_rule(); + if (StrUtil.isBlank(ruleStr)) { + return CommonResult.failed(I18nUtil._("活动规则数据异常!")); + } JSONObject activityRule = JSONUtil.parseObj(ruleStr); - Long itemId = activityRule.get("item_id", Long.class); - if (itemId == null) { - return CommonResult.failed(I18nUtil._("未找到对应商品!")); - } - // 创建砍价历史记录 - ShopActivityCutpriceHistory history = new ShopActivityCutpriceHistory(); - history.setActivity_id(cutprice.getActivity_id()); - history.setUser_id(user_id); - history.setAch_price(cutPrice); - history.setItem_id(itemId); - history.setAch_datetime(new Date()); - history.setAc_id(ac_id); - // 保存历史记录 - if (!shopActivityCutpriceHistoryService.saveOrUpdate(history)) { - throw new ApiException(I18nUtil._("保存砍价记录失败!")); - } - - // 更新砍价信息 - cutprice.setAc_sale_price(NumberUtil.sub(salePrice, cutPrice)); - cutprice.setAc_num(cutprice.getAc_num() + 1); - if (!edit(cutprice)) { - throw new ApiException(I18nUtil._("更新砍价信息失败!")); - } - - // 更新帮砍次数 - if (!cutprice.getUser_id().equals(user_id) && redisService != null) { - try { - DateTime today = DateUtil.beginOfDay(new Date()); - Integer cutNum = Convert.toInt(redisService.hGet("cutprice-" + today, user_id.toString()), 0); - redisService.hSet("cutprice-" + today, user_id.toString(), cutNum + 1, 24 * 60 * 60 * 1000); - } catch (Exception e) { - // Redis更新失败不影响主流程 + // 计算砍价金额 + BigDecimal cutPrice = null; + Integer lottery_num=0; + if(cutprice.getAc_num()>0){//如不是首刀,不是首刀 + cutPrice = shopStoreActivityBaseService.getCutDownPrice(activityBase, cutprice); + }else {//首刀砍价计算规则 + String cut_first_price=activityRule.getStr("cut_first_price");//首刀砍价 + String cut_first_percent=activityRule.getStr("cut_first_percent");//首刀砍价百分比 + if(StringUtils.isNotEmpty(cut_first_price)){ + cutPrice=new BigDecimal(cut_first_price); + } else if(StringUtils.isNotEmpty(cut_first_percent)) {//百分比计算 + cutPrice=new BigDecimal(cut_first_percent).multiply(cutprice.getAc_sale_price()).divide(new BigDecimal(100),2,RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP); + }else {//兼容旧数据,不存在 + cutPrice = shopStoreActivityBaseService.getCutDownPrice(activityBase, cutprice); } } - - // 如果已达到最低价,更新状态 - if (NumberUtil.isLessOrEqual(cutprice.getAc_sale_price(), minPrice)) { - updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_CutFinished); + String luckyTurn=activityBase.getLucky_turn(); + if (StrUtil.isNotBlank(luckyTurn)) {//设置赠送的转盘次数 + JSONObject luckyTurnJson = JSONUtil.parseObj(luckyTurn); + lottery_num=Convert.toInt(luckyTurnJson.get("lottery_num"),1); } - return CommonResult.success(history); - } catch (ApiException e) { - throw e; + if (cutPrice == null || cutPrice.compareTo(BigDecimal.ZERO) <= 0) { + return CommonResult.failed(I18nUtil._("砍价失败,请重试!")); + } + + Long itemId = activityRule.get("item_id", Long.class); + if (itemId == null) { + return CommonResult.failed(I18nUtil._("未找到对应商品!")); + } + // 创建砍价历史记录 + ShopActivityCutpriceHistory history = new ShopActivityCutpriceHistory(); + history.setActivity_id(cutprice.getActivity_id()); + history.setUser_id(user_id); + history.setAch_price(cutPrice); + history.setAch_price_pre(cutPrice); + history.setItem_id(itemId); + history.setAch_datetime(new Date()); + history.setAc_id(ac_id); + history.setLottery_num(lottery_num); + // 保存历史记录 + if (!shopActivityCutpriceHistoryService.saveOrUpdate(history)) { + throw new ApiException(I18nUtil._("保存砍价记录失败!")); + } + + // 更新砍价信息 + cutprice.setAc_sale_price(NumberUtil.sub(salePrice, cutPrice)); + cutprice.setLottery_num(lottery_num); + cutprice.setAc_num(cutprice.getAc_num() + 1); + if (!edit(cutprice)) { + throw new ApiException(I18nUtil._("更新砍价信息失败!")); + } + + // 更新帮砍次数 + if (!cutprice.getUser_id().equals(user_id) && redisService != null) { + DateTime today = DateUtil.beginOfDay(new Date()); + Integer cutNum = Convert.toInt(redisService.hGet("cutprice-" + today, user_id.toString()), 0); + redisService.hSet("cutprice-" + today, user_id.toString(), cutNum + 1, 24 * 60 * 60 * 1000); + } + + // 如果已达到最低价,更新状态 + if (NumberUtil.isLessOrEqual(cutprice.getAc_sale_price(), minPrice)) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_CutFinished); + } + return CommonResult.success(history); } catch (Exception e) { - throw new ApiException(I18nUtil._("砍价操作失败!")); - } + throw new RuntimeException(e); + } finally { + // 5. 最终检查并释放锁,确保锁一定被释放 + if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + log.info("成功释放锁:{}", lockKey); + } } @@ -848,4 +886,309 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl 0 && user_id != null && user_id > 0; + } + + /** + * 核心业务逻辑 + */ + public CommonResult doCutPriceBusiness(Integer ac_id, Integer user_id) { + try { + // 获取砍价记录 + ShopActivityCutprice cutprice = get(ac_id); + if (cutprice == null) { + return CommonResult.failed(I18nUtil._("未找到砍价记录!")); + } + + // 检查活动状态 + if (shopStoreActivityBaseService == null) { + return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!")); + } + ShopStoreActivityBase activityBase = shopStoreActivityBaseService.get(cutprice.getActivity_id()); + if (activityBase == null) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Canceled); + return CommonResult.failed(I18nUtil._("抱歉,砍价活动已失效啦!")); + } + + // 检查活动状态是否正常 + if (!ObjectUtil.equal(activityBase.getActivity_state(), StateCode.ACTIVITY_STATE_NORMAL)) { + if (ObjectUtil.equal(activityBase.getActivity_state(), StateCode.ACTIVITY_STATE_FINISHED) + || ObjectUtil.equal(activityBase.getActivity_state(), StateCode.ACTIVITY_STATE_CLOSED)) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Canceled); + } + return CommonResult.failed(I18nUtil._("抱歉,砍价活动已结束啦!")); + } + + Date now = new Date(); + Long expiredAt = cutprice.getExpired_at(); + + // 检查砍价记录是否过期 + if (CheckUtil.isNotEmpty(expiredAt) && expiredAt < now.getTime()) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Expired); + return CommonResult.failed(I18nUtil._("抱歉,砍价已过期啦!")); + } + + // 检查活动时间是否有效 + try { + if (!shopStoreActivityBaseService.isActivityTimeValid(activityBase, now)) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Expired); + return CommonResult.failed(I18nUtil._("活动已结束!")); + } + + // 需要检查活动有效期和活动商品库存是否足够 + Pair check_result = checkCutPriceExpiredAndStock(activityBase); + if (!check_result.getFirst()) { + // 库存不够,立即更改活动状态为已结束 + shopStoreActivityBaseService.updateActivityState(cutprice.getActivity_id(), StateCode.ACTIVITY_STATE_FINISHED); + throw new ApiException(check_result.getSecond()); + } + + } catch (Exception e) { + return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!")); + } + + BigDecimal salePrice = cutprice.getAc_sale_price(); + BigDecimal minPrice = cutprice.getAc_mix_limit_price(); + // 检查价格数据完整性 + if (salePrice == null || minPrice == null) { + return CommonResult.failed(I18nUtil._("商品价格数据异常!")); + } + + // 检查是否已达到最低价 + if (NumberUtil.isLessOrEqual(salePrice, minPrice)) { + updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_CutFinished); + return CommonResult.failed(I18nUtil._("已砍到最低价啦!")); + } + + // 检查帮砍次数限制 + ShopActivityCutpriceHistory shopActivityCutpriceHistory =null; + Integer lotteryNumHistory=0; + boolean isSelf=false; + //看看有没有转盘次数 + try { + if (shopActivityCutpriceHistoryService == null) { + return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!")); + } + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("user_id", user_id).eq("ac_id", ac_id); + List shopActivityCutpriceHistoryList= shopActivityCutpriceHistoryService.list(queryWrapper); + if (shopActivityCutpriceHistoryList.isEmpty()) { + return CommonResult.failed(I18nUtil._("未找到砍价记录!")); + } + shopActivityCutpriceHistory = shopActivityCutpriceHistoryList.get(0); + lotteryNumHistory= shopActivityCutpriceHistory.getLottery_num(); + if(lotteryNumHistory==0){ + return CommonResult.failed(I18nUtil._("没有摇色子记录了!")); + } + } catch (Exception e) { + throw new Exception("系统繁忙,请稍后再试!"); + } + if (cutprice.getUser_id().equals(user_id)) {//自己转 + isSelf=true; + } + + // 开始摇色子逻辑 + Map data = calculateDiceResult(ac_id, user_id, cutprice,isSelf,shopActivityCutpriceHistory); + Integer point= (Integer) data.get("point"); + BigDecimal achPrice= (BigDecimal) data.get("achPrice");//砍掉的总价格 + BigDecimal pointPrice= (BigDecimal) data.get("pointPrice");//自己砍价的价格 + //计算摇色子次数 + Integer lottery_numHistory= shopActivityCutpriceHistory.getLottery_num()-1; + shopActivityCutpriceHistory.setLottery_num(lottery_numHistory); + if(isSelf){ + Integer lottery_numMain=cutprice.getLottery_num()-1; + cutprice.setLottery_num(lottery_numMain); + } + BigDecimal fianalSalePrice= salePrice.subtract(pointPrice).setScale(2, RoundingMode.HALF_UP); + + //计算价格 + BigDecimal oldAchPrice=shopActivityCutpriceHistory.getAch_price(); + shopActivityCutpriceHistory.setAch_price_pre(oldAchPrice); + cutprice.setAc_sale_price(fianalSalePrice);//要减去转盘转出的价格 + shopActivityCutpriceHistory.setAch_price(achPrice);//砍掉的价格 + shopActivityCutpriceHistory.setAlh_point(point);//骰子点数 + + // 更新数据库 + updateCutPriceData(cutprice, shopActivityCutpriceHistory, (Integer) data.get("point")); + return CommonResult.success(data); + + } catch (ApiException e) { + // 业务异常,直接返回错误信息 + return CommonResult.failed(e.getMessage()); + } catch (Exception e) { + // 其他异常,记录日志 + log.error("业务处理异常, ac_id: {}, user_id: {}", ac_id, user_id, e); + throw new RuntimeException("业务处理失败", e); + } + } + + /** + //更新砍后价格 todo + //更新历史砍价 新增转盘点数 todo + //更新转盘次数 todo + //转盘历史新建 todo + */ + public void updateCutPriceData(ShopActivityCutprice cutprice,ShopActivityCutpriceHistory shopActivityCutpriceHistory,Integer point){ + if(cutprice!=null){ + Integer lotteryNum= cutprice.getLottery_num(); + if(lotteryNum<0){ + lotteryNum=0; + cutprice.setLottery_num(lotteryNum); + } + this.updateById(cutprice); + } + if(shopActivityCutpriceHistory!=null){ + Integer lotteryNum= shopActivityCutpriceHistory.getLottery_num(); + if(lotteryNum<0){ + lotteryNum=0; + shopActivityCutpriceHistory.setLottery_num(lotteryNum); + } + shopActivityCutpriceHistoryService.updateById(shopActivityCutpriceHistory); + } + if(cutprice!=null&&shopActivityCutpriceHistory!=null){ + //创建摇色子记录 + ShopActivityCutLotteryHistory shopActivityCutLotteryHistory=new ShopActivityCutLotteryHistory(); + shopActivityCutLotteryHistory.setActivityId(shopActivityCutpriceHistory.getActivity_id()); + shopActivityCutLotteryHistory.setUserId(shopActivityCutpriceHistory.getUser_id()); + shopActivityCutLotteryHistory.setAlhAwardFlag(true); + shopActivityCutLotteryHistory.setAlhItemName("摇出点数:"+point); + shopActivityCutLotteryHistory.setAlhItemId(point); + shopActivityCutLotteryHistoryService.add(shopActivityCutLotteryHistory); + } + + } + + /** + * 安全的锁释放方法 + */ + private void releaseLockSafely(RLock lock, boolean lockAcquired, String lockKey) { + if (lock != null && lockAcquired) { + try { + // 检查锁是否仍然被当前线程持有 + if (lock.isHeldByCurrentThread()) { + // 检查锁是否仍然存在 + if (lock.isLocked()) { + lock.unlock(); + log.info("成功释放锁: {}", lockKey); + } else { + log.warn("锁已自动释放或已过期: {}", lockKey); + } + } else { + log.warn("当前线程未持有锁,无需释放: {}", lockKey); + } + } catch (IllegalMonitorStateException e) { + // 锁已被释放或当前线程未持有锁 + log.warn("锁状态异常,可能已自动释放: {}, error: {}", lockKey, e.getMessage()); + } catch (Exception e) { + // 其他异常,记录但不抛出,避免影响主流程 + log.error("释放锁异常, lockKey: {}", lockKey, e); + } + } else if (lock != null) { + log.debug("锁未获取成功,无需释放: {}", lockKey); + } + } + /** + * 摇色子计算逻辑 + */ + public Map calculateDiceResult(Integer ac_id, Integer user_id, ShopActivityCutprice cutprice,boolean isSelf,ShopActivityCutpriceHistory history) { + try { + Integer lotteryNumHistory = history.getLottery_num(); + + if (lotteryNumHistory == 0) { + throw new RuntimeException("没有摇色子记录了!"); + } + + // 创建骰子游戏实例 + ShopStoreActivityBase activityBase = shopStoreActivityBaseService.get(cutprice.getActivity_id()); + String luckyTurn = activityBase.getLucky_turn(); + JSONObject jsonObject = JSONUtil.parseObj(luckyTurn); + JSONArray luckyList = jsonObject.getJSONArray("luckyList"); + + List> configs = new ArrayList<>(); + luckyList.forEach(object -> { + JSONObject luckyObject = (JSONObject) object; + configs.add(RollDiceUtils.createDiceConfig( + Convert.toInt(luckyObject.get("doubleValue")), + Convert.toDouble(luckyObject.get("probability")) + )); + }); + + RollDiceUtils.DiceGame diceGame = new RollDiceUtils.DiceGame(configs); + int point = diceGame.rollDice(history.getAch_price_pre(),cutprice.getAc_sale_price(),cutprice.getAc_mix_limit_price(),6); + + BigDecimal oldAchPrice = history.getAch_price(); + BigDecimal pointPrice; + BigDecimal achPrice; + if (!isSelf) { + // 帮砍:按倍数计算 + achPrice = oldAchPrice.multiply(new BigDecimal(point)) + .setScale(4, RoundingMode.HALF_UP); + pointPrice=achPrice.subtract(oldAchPrice).setScale(4, RoundingMode.HALF_UP); + } else { + // 自己砍:按百分比计算 + pointPrice = oldAchPrice.multiply(new BigDecimal(point)) + .divide(new BigDecimal(10), 4, RoundingMode.HALF_UP) + .setScale(4, RoundingMode.HALF_UP); + achPrice = oldAchPrice.add(pointPrice); + } + achPrice= BigDecimalFormatter.formatWithoutTrailingZerosBigDecimal(achPrice); + pointPrice= BigDecimalFormatter.formatWithoutTrailingZerosBigDecimal(pointPrice); + Map result=new HashMap(); + result.put("achPrice",achPrice); + result.put("point",point); + result.put("pointPrice",pointPrice); + return result; + } catch (Exception e) { + log.error("计算骰子结果异常, ac_id: {}, user_id: {}", ac_id, user_id, e); + throw new RuntimeException("计算砍价金额失败", e); + } + } } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreActivityBaseServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreActivityBaseServiceImpl.java index e072ea71..01dd0239 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreActivityBaseServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopStoreActivityBaseServiceImpl.java @@ -3714,6 +3714,58 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl 0) { activity_rule.put("product_count", product_count); } + String user_cutprice_num=getParameter("user_cutprice_num"); + if (StringUtils.isNotEmpty(user_cutprice_num)){ + activity_rule.put("user_cutprice_num", Convert.toInt(user_cutprice_num));//砍价次数 + } + + String cut_first_price=getParameter("cut_first_price");//首刀金额 + String cut_first_percent=getParameter("cut_first_percent");//首刀金额比例 + if(StringUtils.isNotEmpty(cut_first_price)&&StringUtils.isNotEmpty(cut_first_percent)) { + throw new ApiException("首刀金额,首刀金额比例只能存在一个"); + } + if(StringUtils.isNotEmpty(cut_first_price)){ + activity_rule.put("cut_first_price", cut_first_price); // 首刀砍掉价格 cut_first_price和cut_first_percent 只能存在一个 + } + if(StringUtils.isNotEmpty(cut_first_percent)){ + activity_rule.put("cut_first_percent", cut_first_percent); // 首刀砍掉百分比 + } + String diceObj=getParameter("diceObj"); + if(StringUtils.isNotEmpty(diceObj)){ + /** + * { + * "luckyList": [ + * { + * "doubleValue": 1,//点数 + * "probability":0//概率 + * }, + * { + * "doubleValue": 2,//点数 + * "probability":10//概率 + * }, + * { + * "doubleValue": 3,//点数 + * "probability":20//概率 + * }, + * { + * "doubleValue": 4,//点数 + * "probability":20//概率 + * }, + * { + * "doubleValue": 5,//点数 + * "probability":20//概率 + * }, + * { + * "doubleValue": 6,//点数 + * "probability":20//概率 + * } + * ], + * "sub_num":"1"//减刀人数,如1,每邀请1个人,减少一刀 todo + * "lottery_num":1 每邀请一人,给一次转盘抽奖 + * } + */ + shopStoreActivityBase.setLucky_turn(diceObj); + } shopStoreActivityBase.setActivity_rule(Convert.toStr(activity_rule)); } else if (base.getActivity_type_id().equals(StateCode.ACTIVITY_TYPE_GIFTBAG)) { @@ -5662,9 +5714,64 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl