砍价新增摇色子功能
This commit is contained in:
parent
1637c1bf92
commit
dc2c26ae74
@ -69,4 +69,7 @@ public class ShopActivityCutprice implements Serializable {
|
|||||||
@ApiModelProperty(value = "更新时间")
|
@ApiModelProperty(value = "更新时间")
|
||||||
private Date updated_at;
|
private Date updated_at;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "大转盘次数")
|
||||||
|
private Integer lottery_num;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
package com.suisung.mall.common.modules.activity;
|
package com.suisung.mall.common.modules.activity;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -52,5 +50,14 @@ public class ShopActivityCutpriceHistory implements Serializable {
|
|||||||
@ApiModelProperty(value = "砍价编号")
|
@ApiModelProperty(value = "砍价编号")
|
||||||
private Integer ac_id;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
package com.suisung.mall.common.modules.page;
|
package com.suisung.mall.common.modules.page;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -84,5 +81,6 @@ public class ShopPageApp implements Serializable {
|
|||||||
private String app_market_images;
|
private String app_market_images;
|
||||||
|
|
||||||
@ApiModelProperty(value = "行业类别:1超市,2数码家电, 3水果生鲜, 4烘培饮品, 5社区团购, 6时尚美妆, 7婴儿服饰, 8家居, 9汽车, 10酒店旅游, 11鲜花绿植, 12医药健康, 13工业五金, 14节日模板,0其他行业")
|
@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;
|
private String app_industry;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
@ -135,4 +135,8 @@ public class ShopStoreActivityBase implements Serializable {
|
|||||||
@ApiModelProperty(value = "参与活动商品的总数量(个)")
|
@ApiModelProperty(value = "参与活动商品的总数量(个)")
|
||||||
private Integer product_count;
|
private Integer product_count;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "规则配置")
|
||||||
|
@TableField(updateStrategy = NOT_EMPTY)
|
||||||
|
private String lucky_turn;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<Map<String, Object>> diceConfigs;
|
||||||
|
private Random random;
|
||||||
|
private Double totalProbability;
|
||||||
|
private double[] probabilityRanges;
|
||||||
|
|
||||||
|
public DiceGame(List<Map<String, Object>> configs) {
|
||||||
|
this.diceConfigs = configs;
|
||||||
|
this.random = new Random();
|
||||||
|
validateConfigs();
|
||||||
|
initializeProbabilityRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证配置数据
|
||||||
|
private void validateConfigs() {
|
||||||
|
if (diceConfigs == null || diceConfigs.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("骰子配置不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map<String, Object> 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<String, Object> 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<String, Object> config = diceConfigs.get(i);
|
||||||
|
return (Integer) config.get("doubleValue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果概率总和不是100,返回第一个非零概率的点数
|
||||||
|
Map<String, Object> config = getFirstNonZeroProbabilityConfig();
|
||||||
|
return (Integer) config.get("doubleValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取第一个非零概率的配置
|
||||||
|
private Map<String, Object> getFirstNonZeroProbabilityConfig() {
|
||||||
|
for (Map<String, Object> config : diceConfigs) {
|
||||||
|
double probability = (double) config.get("probability");
|
||||||
|
if (probability > 0) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diceConfigs.get(0); // 如果所有概率都是0,返回第一个配置
|
||||||
|
}
|
||||||
|
public static List<Integer> 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<Integer> 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<Integer, Integer> testDistribution(int rollCount) {
|
||||||
|
Map<Integer, Integer> distribution = new HashMap<>();
|
||||||
|
|
||||||
|
// 初始化分布图
|
||||||
|
for (Map<String, Object> 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<Integer> 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<String, Object> 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<Integer, Double> getExpectedProbabilities() {
|
||||||
|
Map<Integer, Double> expected = new HashMap<>();
|
||||||
|
|
||||||
|
for (Map<String, Object> 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<Map<String, Object>> 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<Integer, Integer> distribution = diceGame.testDistribution(testCount);
|
||||||
|
|
||||||
|
System.out.println("点数\t出现次数\t实际概率\t预期概率");
|
||||||
|
System.out.println("----------------------------------------");
|
||||||
|
|
||||||
|
Map<Integer, Double> expectedProbabilities = diceGame.getExpectedProbabilities();
|
||||||
|
|
||||||
|
for (Map.Entry<Integer, Integer> 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<String, Object> createDiceConfig(int value, double probability) {
|
||||||
|
Map<String, Object> config = new HashMap<>();
|
||||||
|
config.put("doubleValue", value);
|
||||||
|
config.put("probability", probability);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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.modules.activity.ShopActivityGroupbuyStoreHistory;
|
||||||
import com.suisung.mall.common.service.impl.BaseControllerImpl;
|
import com.suisung.mall.common.service.impl.BaseControllerImpl;
|
||||||
import com.suisung.mall.common.utils.CheckUtil;
|
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.common.utils.I18nUtil;
|
||||||
import com.suisung.mall.shop.activity.service.ShopActivityCutpriceHistoryService;
|
import com.suisung.mall.shop.activity.service.ShopActivityCutpriceHistoryService;
|
||||||
import com.suisung.mall.shop.activity.service.ShopActivityCutpriceService;
|
import com.suisung.mall.shop.activity.service.ShopActivityCutpriceService;
|
||||||
@ -121,7 +122,7 @@ public class UserActivityController extends BaseControllerImpl {
|
|||||||
@ApiOperation(value = "立即砍价", notes = "自己砍价、邀请朋友过来也能砍价")
|
@ApiOperation(value = "立即砍价", notes = "自己砍价、邀请朋友过来也能砍价")
|
||||||
@RequestMapping(value = "/doCutPrice", method = {RequestMethod.GET, RequestMethod.POST})
|
@RequestMapping(value = "/doCutPrice", method = {RequestMethod.GET, RequestMethod.POST})
|
||||||
public CommonResult doCutPrice(@RequestParam(name = "ac_id", defaultValue = "0") Integer ac_id) {
|
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())) {
|
if (user == null || CheckUtil.isEmpty(user.getId())) {
|
||||||
throw new ApiException(ResultCode.NEED_LOGIN);
|
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) {
|
public CommonResult getGiftbag(@RequestParam(name = "activity_id") Integer activity_id) {
|
||||||
return CommonResult.success(shopStoreActivityBaseService.getGiftbag(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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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<ShopActivityCutLotteryHistory> {
|
||||||
|
}
|
||||||
@ -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<ShopActivityCutLotteryHistory> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -65,4 +65,13 @@ public interface ShopActivityCutpriceService extends IBaseService<ShopActivityCu
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
Integer autoUpdateCutPriceStateJob();
|
Integer autoUpdateCutPriceStateJob();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 砍价大转盘
|
||||||
|
* @param ac_id
|
||||||
|
* @param user_id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
CommonResult dolookTurnCutPrice(Integer ac_id, Integer user_id);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
package com.suisung.mall.shop.activity.service.impl;
|
||||||
|
|
||||||
|
import com.suisung.mall.common.modules.store.ShopActivityCutLotteryHistory;
|
||||||
|
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
|
||||||
|
import com.suisung.mall.shop.activity.mapper.ShopActivityCutLotteryHistoryMapper;
|
||||||
|
import com.suisung.mall.shop.activity.service.ShopActivityCutLotteryHistoryService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ShopActivityCutLotteryHistoryImpl extends BaseServiceImpl<ShopActivityCutLotteryHistoryMapper, ShopActivityCutLotteryHistory> implements ShopActivityCutLotteryHistoryService {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import cn.hutool.core.date.DateUtil;
|
|||||||
import cn.hutool.core.util.NumberUtil;
|
import cn.hutool.core.util.NumberUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONArray;
|
||||||
import cn.hutool.json.JSONObject;
|
import cn.hutool.json.JSONObject;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
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.ApiException;
|
||||||
import com.suisung.mall.common.exception.ApiUserException;
|
import com.suisung.mall.common.exception.ApiUserException;
|
||||||
import com.suisung.mall.common.feignService.AccountService;
|
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.ShopActivityCutprice;
|
||||||
import com.suisung.mall.common.modules.activity.ShopActivityCutpriceHistory;
|
import com.suisung.mall.common.modules.activity.ShopActivityCutpriceHistory;
|
||||||
import com.suisung.mall.common.modules.activity.ShopActivityGroupbooking;
|
import com.suisung.mall.common.modules.activity.ShopActivityGroupbooking;
|
||||||
import com.suisung.mall.common.modules.activity.ShopActivityGroupbookingHistory;
|
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.modules.store.ShopStoreActivityBase;
|
||||||
import com.suisung.mall.common.utils.CheckUtil;
|
import com.suisung.mall.common.utils.*;
|
||||||
import com.suisung.mall.common.utils.DateTimeUtils;
|
|
||||||
import com.suisung.mall.common.utils.I18nUtil;
|
|
||||||
import com.suisung.mall.core.web.service.RedisService;
|
import com.suisung.mall.core.web.service.RedisService;
|
||||||
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
|
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
|
||||||
import com.suisung.mall.shop.activity.mapper.ShopActivityCutpriceMapper;
|
import com.suisung.mall.shop.activity.mapper.ShopActivityCutpriceMapper;
|
||||||
import com.suisung.mall.shop.activity.service.ShopActivityCutpriceHistoryService;
|
import com.suisung.mall.shop.activity.service.*;
|
||||||
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.base.service.AccountBaseConfigService;
|
import com.suisung.mall.shop.base.service.AccountBaseConfigService;
|
||||||
import com.suisung.mall.shop.order.service.ShopOrderInfoService;
|
import com.suisung.mall.shop.order.service.ShopOrderInfoService;
|
||||||
import com.suisung.mall.shop.store.service.ShopStoreActivityBaseService;
|
import com.suisung.mall.shop.store.service.ShopStoreActivityBaseService;
|
||||||
|
import com.suisung.mall.shop.sync.Utils.BigDecimalFormatter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.data.util.Pair;
|
import org.springframework.data.util.Pair;
|
||||||
@ -45,8 +46,11 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.suisung.mall.common.utils.ContextUtil.getCurrentUser;
|
import static com.suisung.mall.common.utils.ContextUtil.getCurrentUser;
|
||||||
@ -95,6 +99,11 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisService redisService;
|
private RedisService redisService;
|
||||||
|
@Autowired
|
||||||
|
private RedissonClient redissonClient;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ShopActivityCutLotteryHistoryService shopActivityCutLotteryHistoryService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map listsUserGroupbooking() {
|
public Map listsUserGroupbooking() {
|
||||||
@ -279,6 +288,10 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
Integer participant_id = getParameter("participant_id", 0);
|
Integer participant_id = getParameter("participant_id", 0);
|
||||||
Integer user_id = getParameter("user_id", 0);
|
Integer user_id = getParameter("user_id", 0);
|
||||||
|
|
||||||
|
AccountUserBase accountUserBase= accountService.getUserBase(user_id);
|
||||||
|
if(null==accountUserBase){
|
||||||
|
throw new ApiException(I18nUtil._("用户编号无效!"));
|
||||||
|
}
|
||||||
// 参数校验
|
// 参数校验
|
||||||
if (activity_id == null || activity_id <= 0) {
|
if (activity_id == null || activity_id <= 0) {
|
||||||
throw new ApiException(I18nUtil._("活动编号无效!"));
|
throw new ApiException(I18nUtil._("活动编号无效!"));
|
||||||
@ -314,6 +327,13 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
|
|
||||||
// 如果用户未参与该砍价活动,则创建新的砍价记录
|
// 如果用户未参与该砍价活动,则创建新的砍价记录
|
||||||
if (cutprice_row == null) {
|
if (cutprice_row == null) {
|
||||||
|
String luckyTurn=activityBase.getLucky_turn();
|
||||||
|
Integer lottery_num=0;
|
||||||
|
if (StrUtil.isNotBlank(luckyTurn)) {//设置赠送的转盘次数
|
||||||
|
JSONObject luckyTurnJson = JSONUtil.parseObj(luckyTurn);
|
||||||
|
String lotteryNumStr= luckyTurnJson.getStr("lottery_num");
|
||||||
|
lottery_num=Convert.toInt(lotteryNumStr,0);
|
||||||
|
}
|
||||||
|
|
||||||
if (checkStockResult != null && !checkStockResult.getFirst()) {
|
if (checkStockResult != null && !checkStockResult.getFirst()) {
|
||||||
// 库存不够,立即更改活动状态为已结束
|
// 库存不够,立即更改活动状态为已结束
|
||||||
@ -337,6 +357,7 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
cutprice_row.setUser_id(user_id);
|
cutprice_row.setUser_id(user_id);
|
||||||
cutprice_row.setAc_sale_price(item_unit_price);
|
cutprice_row.setAc_sale_price(item_unit_price);
|
||||||
cutprice_row.setAc_mix_limit_price(cut_down_min_limit_price);
|
cutprice_row.setAc_mix_limit_price(cut_down_min_limit_price);
|
||||||
|
cutprice_row.setLottery_num(lottery_num);//赠送转盘
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
|
|
||||||
// 该砍价记录过期时间戳
|
// 该砍价记录过期时间戳
|
||||||
@ -398,7 +419,7 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
canBuyNow ? "恭喜您,商品可以立即出手了。" :
|
canBuyNow ? "恭喜您,商品可以立即出手了。" :
|
||||||
String.format("还剩%.2f元到达商品底价,继续加油!", subtractPrice));
|
String.format("还剩%.2f元到达商品底价,继续加油!", subtractPrice));
|
||||||
}
|
}
|
||||||
|
activity_row.remove("lucky_turn");
|
||||||
return activity_row;
|
return activity_row;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,6 +434,21 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public CommonResult doCutPrice(Integer ac_id, Integer user_id) {
|
public CommonResult doCutPrice(Integer ac_id, Integer user_id) {
|
||||||
|
RLock lock =null;
|
||||||
|
String lockKey= "CUT_LOCK:"+ac_id+user_id;
|
||||||
|
lock = redissonClient.getLock(lockKey); // 锁名称通常与业务关联,如"ORDER_LOCK_123"
|
||||||
|
//尝试三次刚好2分分钟
|
||||||
|
boolean isLocked = false;
|
||||||
|
try {
|
||||||
|
isLocked = lock.tryLock(2, 3, TimeUnit.SECONDS);
|
||||||
|
if (!isLocked) {
|
||||||
|
// 获取锁失败,可以根据业务逻辑进行重试或抛出异常
|
||||||
|
throw new RuntimeException("系统繁忙,请稍后再试");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
// 参数校验
|
// 参数校验
|
||||||
if (ac_id == null || ac_id <= 0 || user_id == null || user_id <= 0) {
|
if (ac_id == null || ac_id <= 0 || user_id == null || user_id <= 0) {
|
||||||
return CommonResult.failed(I18nUtil._("活动或用户编号无效!"));
|
return CommonResult.failed(I18nUtil._("活动或用户编号无效!"));
|
||||||
@ -454,7 +490,6 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查活动时间是否有效
|
// 检查活动时间是否有效
|
||||||
try {
|
|
||||||
if (!shopStoreActivityBaseService.isActivityTimeValid(activityBase, now)) {
|
if (!shopStoreActivityBaseService.isActivityTimeValid(activityBase, now)) {
|
||||||
updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Expired);
|
updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Expired);
|
||||||
return CommonResult.failed(I18nUtil._("活动已结束!"));
|
return CommonResult.failed(I18nUtil._("活动已结束!"));
|
||||||
@ -467,26 +502,19 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
shopStoreActivityBaseService.updateActivityState(cutprice.getActivity_id(), StateCode.ACTIVITY_STATE_FINISHED);
|
shopStoreActivityBaseService.updateActivityState(cutprice.getActivity_id(), StateCode.ACTIVITY_STATE_FINISHED);
|
||||||
throw new ApiException(check_result.getSecond());
|
throw new ApiException(check_result.getSecond());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查帮砍次数限制
|
// 检查帮砍次数限制
|
||||||
if (!cutprice.getUser_id().equals(user_id)) {
|
if (!cutprice.getUser_id().equals(user_id)) {
|
||||||
try {
|
|
||||||
if (accountBaseConfigService != null && redisService != null) {
|
if (accountBaseConfigService != null && redisService != null) {
|
||||||
DateTime today = DateUtil.beginOfDay(new Date());
|
DateTime today = DateUtil.beginOfDay(new Date());
|
||||||
Integer maxCuts = accountBaseConfigService.getConfig("user_cutprice_num", 0);
|
//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);
|
Integer usedCuts = Convert.toInt(redisService.hGet("cutprice-" + today, user_id.toString()), 0);
|
||||||
|
|
||||||
if (maxCuts > 0 && usedCuts >= maxCuts) {
|
if (maxCuts > 0 && usedCuts >= maxCuts) {
|
||||||
return CommonResult.failed(I18nUtil._("今日帮砍次数已达上限!"));
|
return CommonResult.failed(I18nUtil._("今日帮砍次数已达上限!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
// Redis检查失败时继续执行砍价操作
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BigDecimal salePrice = cutprice.getAc_sale_price();
|
BigDecimal salePrice = cutprice.getAc_sale_price();
|
||||||
@ -504,7 +532,6 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已经帮过好友砍价
|
// 检查是否已经帮过好友砍价
|
||||||
try {
|
|
||||||
if (shopActivityCutpriceHistoryService == null) {
|
if (shopActivityCutpriceHistoryService == null) {
|
||||||
return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!"));
|
return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!"));
|
||||||
}
|
}
|
||||||
@ -514,43 +541,54 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
if (shopActivityCutpriceHistoryService.count(queryWrapper) > 0) {
|
if (shopActivityCutpriceHistoryService.count(queryWrapper) > 0) {
|
||||||
return CommonResult.failed(I18nUtil._("您已帮好友砍过价了!"));
|
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._("砍价失败,请重试!"));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
return CommonResult.failed(I18nUtil._("砍价失败,请重试!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取商品ID
|
// 获取商品ID
|
||||||
String ruleStr = activityBase.getActivity_rule();
|
String ruleStr = activityBase.getActivity_rule();
|
||||||
if (StrUtil.isBlank(ruleStr)) {
|
if (StrUtil.isBlank(ruleStr)) {
|
||||||
return CommonResult.failed(I18nUtil._("活动规则数据异常!"));
|
return CommonResult.failed(I18nUtil._("活动规则数据异常!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
JSONObject activityRule = JSONUtil.parseObj(ruleStr);
|
JSONObject activityRule = JSONUtil.parseObj(ruleStr);
|
||||||
|
|
||||||
|
|
||||||
|
// 计算砍价金额
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String luckyTurn=activityBase.getLucky_turn();
|
||||||
|
if (StrUtil.isNotBlank(luckyTurn)) {//设置赠送的转盘次数
|
||||||
|
JSONObject luckyTurnJson = JSONUtil.parseObj(luckyTurn);
|
||||||
|
lottery_num=Convert.toInt(luckyTurnJson.get("lottery_num"),1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cutPrice == null || cutPrice.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
return CommonResult.failed(I18nUtil._("砍价失败,请重试!"));
|
||||||
|
}
|
||||||
|
|
||||||
Long itemId = activityRule.get("item_id", Long.class);
|
Long itemId = activityRule.get("item_id", Long.class);
|
||||||
if (itemId == null) {
|
if (itemId == null) {
|
||||||
return CommonResult.failed(I18nUtil._("未找到对应商品!"));
|
return CommonResult.failed(I18nUtil._("未找到对应商品!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建砍价历史记录
|
// 创建砍价历史记录
|
||||||
ShopActivityCutpriceHistory history = new ShopActivityCutpriceHistory();
|
ShopActivityCutpriceHistory history = new ShopActivityCutpriceHistory();
|
||||||
history.setActivity_id(cutprice.getActivity_id());
|
history.setActivity_id(cutprice.getActivity_id());
|
||||||
history.setUser_id(user_id);
|
history.setUser_id(user_id);
|
||||||
history.setAch_price(cutPrice);
|
history.setAch_price(cutPrice);
|
||||||
|
history.setAch_price_pre(cutPrice);
|
||||||
history.setItem_id(itemId);
|
history.setItem_id(itemId);
|
||||||
history.setAch_datetime(new Date());
|
history.setAch_datetime(new Date());
|
||||||
history.setAc_id(ac_id);
|
history.setAc_id(ac_id);
|
||||||
|
history.setLottery_num(lottery_num);
|
||||||
// 保存历史记录
|
// 保存历史记录
|
||||||
if (!shopActivityCutpriceHistoryService.saveOrUpdate(history)) {
|
if (!shopActivityCutpriceHistoryService.saveOrUpdate(history)) {
|
||||||
throw new ApiException(I18nUtil._("保存砍价记录失败!"));
|
throw new ApiException(I18nUtil._("保存砍价记录失败!"));
|
||||||
@ -558,6 +596,7 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
|
|
||||||
// 更新砍价信息
|
// 更新砍价信息
|
||||||
cutprice.setAc_sale_price(NumberUtil.sub(salePrice, cutPrice));
|
cutprice.setAc_sale_price(NumberUtil.sub(salePrice, cutPrice));
|
||||||
|
cutprice.setLottery_num(lottery_num);
|
||||||
cutprice.setAc_num(cutprice.getAc_num() + 1);
|
cutprice.setAc_num(cutprice.getAc_num() + 1);
|
||||||
if (!edit(cutprice)) {
|
if (!edit(cutprice)) {
|
||||||
throw new ApiException(I18nUtil._("更新砍价信息失败!"));
|
throw new ApiException(I18nUtil._("更新砍价信息失败!"));
|
||||||
@ -565,25 +604,24 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
|
|||||||
|
|
||||||
// 更新帮砍次数
|
// 更新帮砍次数
|
||||||
if (!cutprice.getUser_id().equals(user_id) && redisService != null) {
|
if (!cutprice.getUser_id().equals(user_id) && redisService != null) {
|
||||||
try {
|
|
||||||
DateTime today = DateUtil.beginOfDay(new Date());
|
DateTime today = DateUtil.beginOfDay(new Date());
|
||||||
Integer cutNum = Convert.toInt(redisService.hGet("cutprice-" + today, user_id.toString()), 0);
|
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);
|
redisService.hSet("cutprice-" + today, user_id.toString(), cutNum + 1, 24 * 60 * 60 * 1000);
|
||||||
} catch (Exception e) {
|
|
||||||
// Redis更新失败不影响主流程
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果已达到最低价,更新状态
|
// 如果已达到最低价,更新状态
|
||||||
if (NumberUtil.isLessOrEqual(cutprice.getAc_sale_price(), minPrice)) {
|
if (NumberUtil.isLessOrEqual(cutprice.getAc_sale_price(), minPrice)) {
|
||||||
updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_CutFinished);
|
updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_CutFinished);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommonResult.success(history);
|
return CommonResult.success(history);
|
||||||
} catch (ApiException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
} 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<ShopActivit
|
|||||||
|
|
||||||
return successCount;
|
return successCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public CommonResult dolookTurnCutPrice(Integer ac_id, Integer user_id) {
|
||||||
|
String lockKey = "LOOK_CUT_LOCK:" + ac_id + ":" + user_id;
|
||||||
|
RLock lock = null;
|
||||||
|
boolean lockAcquired = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 获取锁,设置合理的leaseTime和等待时间
|
||||||
|
lock = redissonClient.getLock(lockKey);
|
||||||
|
|
||||||
|
// 尝试获取锁,最多等待3秒,锁的持有时间30秒(Redisson的看门狗会自动续期)
|
||||||
|
// 参数说明:waitTime-等待时间,leaseTime-锁持有时间,unit-时间单位
|
||||||
|
lockAcquired = lock.tryLock(3, 3, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
if (!lockAcquired) {
|
||||||
|
log.warn("获取锁失败,lockKey: {}, thread: {}", lockKey, Thread.currentThread().getName());
|
||||||
|
return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试"));
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("成功获取锁,lockKey: {}, thread: {}", lockKey, Thread.currentThread().getName());
|
||||||
|
|
||||||
|
// 2. 参数校验(移动到锁外,减少锁内代码量)
|
||||||
|
if (!validateParameters(ac_id, user_id)) {
|
||||||
|
return CommonResult.failed(I18nUtil._("活动或用户编号无效!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 核心业务逻辑
|
||||||
|
return doCutPriceBusiness(ac_id, user_id);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||||
|
log.error("获取锁时线程被中断, lockKey: {}", lockKey, e);
|
||||||
|
return CommonResult.failed(I18nUtil._("操作被中断,请重试"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("砍价处理异常, lockKey: {}, ac_id: {}, user_id: {}",
|
||||||
|
lockKey, ac_id, user_id, e);
|
||||||
|
return CommonResult.failed(I18nUtil._("系统繁忙,请稍后再试!"));
|
||||||
|
} finally {
|
||||||
|
// 4. 确保锁被正确释放
|
||||||
|
releaseLockSafely(lock, lockAcquired, lockKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 参数校验
|
||||||
|
*/
|
||||||
|
private boolean validateParameters(Integer ac_id, Integer user_id) {
|
||||||
|
return ac_id != null && ac_id > 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<Boolean, String> 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<ShopActivityCutpriceHistory> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("user_id", user_id).eq("ac_id", ac_id);
|
||||||
|
List<ShopActivityCutpriceHistory> 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<Map<String, Object>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3714,6 +3714,58 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
|
|||||||
if (product_count > 0) {
|
if (product_count > 0) {
|
||||||
activity_rule.put("product_count", product_count);
|
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));
|
shopStoreActivityBase.setActivity_rule(Convert.toStr(activity_rule));
|
||||||
} else if (base.getActivity_type_id().equals(StateCode.ACTIVITY_TYPE_GIFTBAG)) {
|
} else if (base.getActivity_type_id().equals(StateCode.ACTIVITY_TYPE_GIFTBAG)) {
|
||||||
@ -5662,9 +5714,64 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
|
|||||||
|
|
||||||
Integer cut_hour = getParameter("cut_hour", 72);// cut_hour - 砍价有效期(小时)
|
Integer cut_hour = getParameter("cut_hour", 72);// cut_hour - 砍价有效期(小时)
|
||||||
Integer product_count = getParameter("product_count", 3);// product_count - 参与活动商品总数(个)
|
Integer product_count = getParameter("product_count", 3);// product_count - 参与活动商品总数(个)
|
||||||
|
Integer user_cutprice_num=getParameter("user_cutprice_num",5);//砍价次数
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
activity_rule.put("product_count", product_count); // 参与活动的商品总数(个)
|
activity_rule.put("product_count", product_count); // 参与活动的商品总数(个)
|
||||||
activity_rule.put("cut_hour", cut_hour); // 砍价的有效期(小时)
|
activity_rule.put("cut_hour", cut_hour); // 砍价的有效期(小时)
|
||||||
|
|
||||||
|
//附加活动内容 todo
|
||||||
|
activity_rule.put("user_cutprice_num", user_cutprice_num);//砍价次数
|
||||||
|
|
||||||
|
String cut_first_price=getParameter("cut_first_price");//首刀金额
|
||||||
|
String cut_first_percent=getParameter("cut_first_percent");//首刀金额比例
|
||||||
|
String lottery_num=getParameter("lottery_num");//赠送转盘次数
|
||||||
|
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 每邀请一人,给一次转盘抽奖
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
data.put("lucky_turn",diceObj);//幸运大转盘
|
||||||
|
}
|
||||||
data.put("product_count", product_count);
|
data.put("product_count", product_count);
|
||||||
data.put("cut_hour", cut_hour);
|
data.put("cut_hour", cut_hour);
|
||||||
} else if (ObjectUtil.equal(activity_type_id, StateCode.ACTIVITY_TYPE_GIFTBAG)) {
|
} else if (ObjectUtil.equal(activity_type_id, StateCode.ACTIVITY_TYPE_GIFTBAG)) {
|
||||||
|
|||||||
@ -26,4 +26,27 @@ public class BigDecimalFormatter {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
public static BigDecimal formatWithoutTrailingZerosBigDecimal(BigDecimal number) {
|
||||||
|
if (number == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去除末尾0并转换为普通字符串表示
|
||||||
|
BigDecimal stripped = number.stripTrailingZeros();
|
||||||
|
String result = stripped.toPlainString();
|
||||||
|
|
||||||
|
// 处理结果为负0的情况(返回"0"而不是"-0")
|
||||||
|
if (result.startsWith("-0")) {
|
||||||
|
if (result.indexOf('.') == -1) {
|
||||||
|
if (result.length() == 2) { // "-0"
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
} else if (result.matches("-0+")) { // "-000"
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
} else if (result.matches("-0\\.0*")) { // "-0.0" 或 "-0.00"
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new BigDecimal(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
sql/shop/dev/20251209_ddl.sql
Normal file
19
sql/shop/dev/20251209_ddl.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
CREATE TABLE `shop_activity_cut_lottery_history` (
|
||||||
|
`alh_id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '用户中奖编号',
|
||||||
|
`user_id` int unsigned NOT NULL DEFAULT '0' COMMENT '用户编号',
|
||||||
|
`activity_id` int unsigned NOT NULL DEFAULT '0' COMMENT '活动编号',
|
||||||
|
`alh_item_id` int unsigned NOT NULL DEFAULT '0' COMMENT '抽奖物品编号',
|
||||||
|
`alh_item_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '抽奖物品名称',
|
||||||
|
`alh_award_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否中奖(BOOL):0-未中奖;1-中奖',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`alh_id`) USING BTREE,
|
||||||
|
KEY `activity_id` (`activity_id`) USING BTREE
|
||||||
|
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='砍价抽奖';
|
||||||
|
|
||||||
|
ALTER table shop_activity_cutprice add column `lottery_num` int unsigned NOT NULL DEFAULT '0' COMMENT '次数';
|
||||||
|
ALTER table shop_activity_cutprice_history add column `lottery_num` int unsigned NOT NULL DEFAULT '0' COMMENT '次数';
|
||||||
|
ALTER table shop_store_activity_base add column lucky_turn varchar(1024) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '{}' COMMENT '规则配置';
|
||||||
|
|
||||||
|
ALTER TABLE shop_activity_cutprice_history add column alh_point int unsigned NOT NULL default 0 COMMENT '骰子点数';
|
||||||
|
ALTER TABLE shop_activity_cutprice_history add column `ach_price_pre` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '摇骰子前砍掉的价格';
|
||||||
1
sql/shop/dev/20251212_dml.sql
Normal file
1
sql/shop/dev/20251212_dml.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
INSERT INTO `admin_base_protocol` (`ctl`, `met`, `db`, `rights_id`, `log`, `path`,`comment`) VALUES ('/mobile/shop/userActivity/dolookTurnCutPrice', 'index', 'master', '', '0', '/mobile/shop/userActivity/dolookTurnCutPrice','客户端摇色子');
|
||||||
Loading…
Reference in New Issue
Block a user