砍价新增摇色子功能

This commit is contained in:
liyj 2025-12-12 11:07:46 +08:00
parent f4ab420e95
commit 565fb70b1b
16 changed files with 1074 additions and 168 deletions

View File

@ -69,4 +69,7 @@ public class ShopActivityCutprice implements Serializable {
@ApiModelProperty(value = "更新时间")
private Date updated_at;
@ApiModelProperty(value = "大转盘次数")
private Integer lottery_num;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -65,4 +65,13 @@ public interface ShopActivityCutpriceService extends IBaseService<ShopActivityCu
* @return
*/
Integer autoUpdateCutPriceStateJob();
/**
* 砍价大转盘
* @param ac_id
* @param user_id
* @return
*/
CommonResult dolookTurnCutPrice(Integer ac_id, Integer user_id);
}

View File

@ -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 {
}

View File

@ -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<ShopActivit
@Autowired
private RedisService redisService;
@Autowired
private RedissonClient redissonClient;
@Autowired
private ShopActivityCutLotteryHistoryService shopActivityCutLotteryHistoryService;
@Override
public Map listsUserGroupbooking() {
@ -279,6 +288,10 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
Integer participant_id = getParameter("participant_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) {
throw new ApiException(I18nUtil._("活动编号无效!"));
@ -314,6 +327,13 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
// 如果用户未参与该砍价活动则创建新的砍价记录
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()) {
// 库存不够立即更改活动状态为已结束
@ -337,6 +357,7 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
cutprice_row.setUser_id(user_id);
cutprice_row.setAc_sale_price(item_unit_price);
cutprice_row.setAc_mix_limit_price(cut_down_min_limit_price);
cutprice_row.setLottery_num(lottery_num);//赠送转盘
Date now = new Date();
// 该砍价记录过期时间戳
@ -398,7 +419,7 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
canBuyNow ? "恭喜您,商品可以立即出手了。" :
String.format("还剩%.2f元到达商品底价,继续加油!", subtractPrice));
}
activity_row.remove("lucky_turn");
return activity_row;
}
@ -413,178 +434,195 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
@Override
@Transactional
public CommonResult doCutPrice(Integer ac_id, Integer user_id) {
// 参数校验
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._("系统繁忙,请稍后再试!"));
}
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._("抱歉,砍价已过期啦!"));
}
// 检查活动时间是否有效
RLock lock =null;
String lockKey= "CUT_LOCK:"+ac_id+user_id;
lock = redissonClient.getLock(lockKey); // 锁名称通常与业务关联"ORDER_LOCK_123"
//尝试三次刚好2分分钟
boolean isLocked = false;
try {
if (!shopStoreActivityBaseService.isActivityTimeValid(activityBase, now)) {
updateCutPriceState(cutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Expired);
return CommonResult.failed(I18nUtil._("活动已结束!"));
isLocked = lock.tryLock(2, 3, TimeUnit.SECONDS);
if (!isLocked) {
// 获取锁失败可以根据业务逻辑进行重试或抛出异常
throw new RuntimeException("系统繁忙,请稍后再试");
}
// 需要检查活动有效期和活动商品库存是否足够
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 (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<ShopActivityCutpriceHistory> 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<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());
}
// 检查帮砍次数限制
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<ShopActivityCutpriceHistory> 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<ShopActivit
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);
}
}
}

View File

@ -3714,6 +3714,58 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
if (product_count > 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<ShopStoreA
Integer cut_hour = getParameter("cut_hour", 72);// cut_hour - 砍价有效期小时
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("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("cut_hour", cut_hour);
} else if (ObjectUtil.equal(activity_type_id, StateCode.ACTIVITY_TYPE_GIFTBAG)) {

View File

@ -26,4 +26,27 @@ public class BigDecimalFormatter {
}
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);
}
}

View 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 '摇骰子前砍掉的价格';

View 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','客户端摇色子');