抽奖 十连抽完善
This commit is contained in:
parent
6420fb8d1d
commit
dddf6b1d4c
@ -11,20 +11,25 @@ import com.red.circle.other.infra.database.rds.entity.activity.LotteryRecord;
|
|||||||
import com.red.circle.other.infra.database.rds.entity.activity.LotteryTicket;
|
import com.red.circle.other.infra.database.rds.entity.activity.LotteryTicket;
|
||||||
import com.red.circle.other.infra.database.rds.entity.activity.LotteryTicketRecord;
|
import com.red.circle.other.infra.database.rds.entity.activity.LotteryTicketRecord;
|
||||||
import com.red.circle.other.infra.database.rds.entity.activity.LotteryUserCount;
|
import com.red.circle.other.infra.database.rds.entity.activity.LotteryUserCount;
|
||||||
|
import com.red.circle.other.infra.database.rds.entity.activity.LotteryUserProgress;
|
||||||
import com.red.circle.other.infra.database.rds.service.activity.LotteryActivityService;
|
import com.red.circle.other.infra.database.rds.service.activity.LotteryActivityService;
|
||||||
import com.red.circle.other.infra.database.rds.service.activity.LotteryPrizeService;
|
import com.red.circle.other.infra.database.rds.service.activity.LotteryPrizeService;
|
||||||
import com.red.circle.other.infra.database.rds.service.activity.LotteryRecordService;
|
import com.red.circle.other.infra.database.rds.service.activity.LotteryRecordService;
|
||||||
import com.red.circle.other.infra.database.rds.service.activity.LotteryTicketRecordService;
|
import com.red.circle.other.infra.database.rds.service.activity.LotteryTicketRecordService;
|
||||||
import com.red.circle.other.infra.database.rds.service.activity.LotteryTicketService;
|
import com.red.circle.other.infra.database.rds.service.activity.LotteryTicketService;
|
||||||
import com.red.circle.other.infra.database.rds.service.activity.LotteryUserCountService;
|
import com.red.circle.other.infra.database.rds.service.activity.LotteryUserCountService;
|
||||||
|
import com.red.circle.other.infra.database.rds.service.activity.LotteryUserProgressService;
|
||||||
import com.red.circle.other.inner.asserts.lottery.LotteryErrorCode;
|
import com.red.circle.other.inner.asserts.lottery.LotteryErrorCode;
|
||||||
import com.red.circle.tool.core.date.TimestampUtils;
|
import com.red.circle.tool.core.date.TimestampUtils;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.temporal.WeekFields;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
@ -32,7 +37,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行连抽.
|
* 执行连抽(支持动态概率池).
|
||||||
*
|
*
|
||||||
* @author system
|
* @author system
|
||||||
* @since 2025-10-20
|
* @since 2025-10-20
|
||||||
@ -48,6 +53,7 @@ public class LotteryMultiDrawExe {
|
|||||||
private final LotteryTicketService lotteryTicketService;
|
private final LotteryTicketService lotteryTicketService;
|
||||||
private final LotteryTicketRecordService lotteryTicketRecordService;
|
private final LotteryTicketRecordService lotteryTicketRecordService;
|
||||||
private final LotteryUserCountService lotteryUserCountService;
|
private final LotteryUserCountService lotteryUserCountService;
|
||||||
|
private final LotteryUserProgressService lotteryUserProgressService;
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public LotteryMultiDrawResultCO execute(LotteryMultiDrawCmd cmd) {
|
public LotteryMultiDrawResultCO execute(LotteryMultiDrawCmd cmd) {
|
||||||
@ -75,183 +81,271 @@ public class LotteryMultiDrawExe {
|
|||||||
deductTicket(userId, ticketCost);
|
deductTicket(userId, ticketCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 生成批次号
|
// 5. 获取或创建用户进度(用于动态概率)
|
||||||
|
LotteryUserProgress progress = getOrCreateUserProgress(userId, activityId);
|
||||||
|
|
||||||
|
// 6. 生成批次号
|
||||||
String batchNo = generateBatchNo();
|
String batchNo = generateBatchNo();
|
||||||
|
|
||||||
// 6. 执行连抽
|
// 7. 执行连抽
|
||||||
List<LotteryDrawResultCO> results = new ArrayList<>();
|
List<LotteryDrawResultCO> results = new ArrayList<>();
|
||||||
boolean guaranteeTriggered = false;
|
boolean guaranteeTriggered = false;
|
||||||
|
BigDecimal batchTotalAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
// 前N-1次正常抽奖(使用连抽概率)
|
// 每次抽奖都使用动态概率
|
||||||
for (int i = 0; i < drawCount - 1; i++) {
|
for (int i = 0; i < drawCount; i++) {
|
||||||
LotteryPrize prize = drawPrizeWithMultiProbability(activityId);
|
// 实时获取用户进度(因为每次抽奖后都会更新)
|
||||||
|
LotteryUserProgress currentProgress = lotteryUserProgressService.getById(progress.getId());
|
||||||
|
|
||||||
|
// 使用动态概率抽奖
|
||||||
|
LotteryPrize prize = drawPrizeWithDynamicProbability(activityId, activity, currentProgress);
|
||||||
|
|
||||||
// 扣减库存
|
// 扣减库存
|
||||||
if (prize != null) {
|
if (prize != null) {
|
||||||
deductPrizeStock(prize.getId());
|
boolean deductSuccess = deductPrizeStock(prize.getId());
|
||||||
|
if (!deductSuccess) {
|
||||||
|
log.warn("Prize stock deduction failed in multi-draw, prizeId: {}", prize.getId());
|
||||||
|
prize = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存记录
|
// 保存记录
|
||||||
LotteryRecord record = saveDrawRecord(userId, activityId, prize, now, batchNo, 2);
|
LotteryRecord record = saveDrawRecord(userId, activityId, prize, now, batchNo, 2);
|
||||||
results.add(convertToResultCO(record, prize));
|
results.add(convertToResultCO(record, prize));
|
||||||
|
|
||||||
|
// 临时更新用户进度(用于下次抽奖判断阶段)
|
||||||
|
if (prize != null && prize.getPrizeValue() != null) {
|
||||||
|
BigDecimal prizeValue = prize.getPrizeValue();
|
||||||
|
batchTotalAmount = batchTotalAmount.add(prizeValue);
|
||||||
|
|
||||||
|
// 立即更新数据库
|
||||||
|
updateUserProgress(progress, prize);
|
||||||
|
} else {
|
||||||
|
// 没中奖也要更新次数
|
||||||
|
lotteryUserProgressService.update()
|
||||||
|
.eq(LotteryUserProgress::getId, progress.getId())
|
||||||
|
.setSql("total_draw_count = total_draw_count + 1")
|
||||||
|
.set(LotteryUserProgress::getLastDrawDate, LocalDate.now())
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最后一次:检查是否需要保底
|
// 8. 检查连抽保底
|
||||||
BigDecimal totalAmount = calculateTotalAmount(results);
|
|
||||||
BigDecimal guaranteeMinAmount = activity.getMultiDrawGuaranteeMinAmount() != null
|
BigDecimal guaranteeMinAmount = activity.getMultiDrawGuaranteeMinAmount() != null
|
||||||
? activity.getMultiDrawGuaranteeMinAmount()
|
? activity.getMultiDrawGuaranteeMinAmount()
|
||||||
: BigDecimal.ZERO;
|
: BigDecimal.ZERO;
|
||||||
|
|
||||||
LotteryPrize lastPrize;
|
if (batchTotalAmount.compareTo(guaranteeMinAmount) < 0 && guaranteeMinAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
if (totalAmount.compareTo(guaranteeMinAmount) < 0) {
|
// 触发保底:替换最后一个未中奖的记录
|
||||||
// 触发保底
|
|
||||||
guaranteeTriggered = true;
|
guaranteeTriggered = true;
|
||||||
lastPrize = getGuaranteePrize(activityId, activity.getMultiDrawGuaranteePrizeId());
|
replaceWithGuaranteePrize(results, activity, userId, activityId, now, batchNo, progress);
|
||||||
log.info("Multi-draw guarantee triggered, userId: {}, activityId: {}", userId, activityId);
|
log.info("Multi-draw guarantee triggered, userId: {}, activityId: {}, batchAmount: {}",
|
||||||
} else {
|
userId, activityId, batchTotalAmount);
|
||||||
// 正常抽奖
|
|
||||||
lastPrize = drawPrizeWithMultiProbability(activityId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 扣减库存
|
// 9. 更新用户抽奖次数(+drawCount次)
|
||||||
if (lastPrize != null) {
|
|
||||||
deductPrizeStock(lastPrize.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存最后一次记录
|
|
||||||
LotteryRecord lastRecord = saveDrawRecord(userId, activityId, lastPrize, now, batchNo, 2);
|
|
||||||
results.add(convertToResultCO(lastRecord, lastPrize));
|
|
||||||
|
|
||||||
// 7. 更新用户抽奖次数(+drawCount次)
|
|
||||||
updateUserDrawCount(userId, activityId, drawCount);
|
updateUserDrawCount(userId, activityId, drawCount);
|
||||||
|
|
||||||
// 8. 组装返回结果
|
// 10. 组装返回结果
|
||||||
return buildMultiDrawResult(batchNo, results, guaranteeTriggered);
|
return buildMultiDrawResult(batchNo, results, guaranteeTriggered);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验活动状态和时间.
|
|
||||||
*/
|
|
||||||
private LotteryActivity validateActivity(Long activityId, Timestamp now) {
|
private LotteryActivity validateActivity(Long activityId, Timestamp now) {
|
||||||
LotteryActivity activity = lotteryActivityService.getById(activityId);
|
LotteryActivity activity = lotteryActivityService.getById(activityId);
|
||||||
ResponseAssert.isTrue(LotteryErrorCode.ACTIVITY_NOT_FOUND, activity != null);
|
ResponseAssert.isTrue(LotteryErrorCode.ACTIVITY_NOT_FOUND, activity != null);
|
||||||
ResponseAssert.isTrue(LotteryErrorCode.ACTIVITY_CLOSED, activity.getStatus() == 1);
|
ResponseAssert.isTrue(LotteryErrorCode.ACTIVITY_CLOSED, activity.getStatus() == 1);
|
||||||
ResponseAssert.isTrue(LotteryErrorCode.NOT_IN_ACTIVITY_TIME,
|
ResponseAssert.isTrue(LotteryErrorCode.NOT_IN_ACTIVITY_TIME,
|
||||||
now.after(activity.getStartTime()) && now.before(activity.getEndTime()));
|
now.after(activity.getStartTime()) && now.before(activity.getEndTime()));
|
||||||
|
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验抽奖次数限制.
|
|
||||||
*/
|
|
||||||
private void validateDrawCount(Long userId, Long activityId, LotteryActivity activity, Integer drawCount) {
|
private void validateDrawCount(Long userId, Long activityId, LotteryActivity activity, Integer drawCount) {
|
||||||
if (activity.getDrawCountLimit() == null || activity.getDrawCountLimit() <= 0) {
|
if (activity.getDrawCountLimit() == null || activity.getDrawCountLimit() <= 0) {
|
||||||
return; // 无限制
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LotteryUserCount userCount = lotteryUserCountService.query()
|
LotteryUserCount userCount = lotteryUserCountService.query()
|
||||||
.eq(LotteryUserCount::getUserId, userId)
|
.eq(LotteryUserCount::getUserId, userId)
|
||||||
.eq(LotteryUserCount::getActivityId, activityId)
|
.eq(LotteryUserCount::getActivityId, activityId)
|
||||||
.eq(LotteryUserCount::getDrawDate, LocalDate.now())
|
.eq(LotteryUserCount::getDrawDate, LocalDate.now())
|
||||||
.getOne();
|
.getOne();
|
||||||
|
|
||||||
int currentDrawCount = userCount != null ? userCount.getDrawCount() : 0;
|
int currentDrawCount = userCount != null ? userCount.getDrawCount() : 0;
|
||||||
ResponseAssert.isTrue(LotteryErrorCode.DRAW_COUNT_EXHAUSTED,
|
ResponseAssert.isTrue(LotteryErrorCode.DRAW_COUNT_EXHAUSTED,
|
||||||
currentDrawCount + drawCount <= activity.getDrawCountLimit());
|
currentDrawCount + drawCount <= activity.getDrawCountLimit());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private LotteryUserProgress getOrCreateUserProgress(Long userId, Long activityId) {
|
||||||
* 扣除抽奖券.
|
String weekKey = getWeekKey();
|
||||||
*/
|
LotteryUserProgress progress = lotteryUserProgressService.query()
|
||||||
private void deductTicket(Long userId, Integer ticketCost) {
|
.eq(LotteryUserProgress::getUserId, userId)
|
||||||
List<LotteryTicket> tickets = lotteryTicketService.query()
|
.eq(LotteryUserProgress::getActivityId, activityId)
|
||||||
.eq(LotteryTicket::getUserId, userId)
|
.eq(LotteryUserProgress::getWeekKey, weekKey)
|
||||||
.eq(LotteryTicket::getStatus, 1)
|
.getOne();
|
||||||
.gt(LotteryTicket::getRemainingCount, 0)
|
if (progress == null) {
|
||||||
.orderByAsc(LotteryTicket::getExpireTime)
|
progress = new LotteryUserProgress();
|
||||||
.list();
|
progress.setUserId(userId);
|
||||||
|
progress.setActivityId(activityId);
|
||||||
int totalRemaining = tickets.stream()
|
progress.setWeekKey(weekKey);
|
||||||
.mapToInt(LotteryTicket::getRemainingCount)
|
progress.setTotalAmount(BigDecimal.ZERO);
|
||||||
.sum();
|
progress.setTotalDrawCount(0);
|
||||||
ResponseAssert.isTrue(LotteryErrorCode.INSUFFICIENT_TICKETS, totalRemaining >= ticketCost);
|
progress.setBigPrizeCount(0);
|
||||||
|
progress.setCurrentStage("EARLY");
|
||||||
List<LotteryTicketRecord> records = new ArrayList<>();
|
progress.setLastDrawDate(LocalDate.now());
|
||||||
int remaining = ticketCost;
|
lotteryUserProgressService.save(progress);
|
||||||
|
|
||||||
for (LotteryTicket ticket : tickets) {
|
|
||||||
if (remaining <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int deduct = Math.min(remaining, ticket.getRemainingCount());
|
|
||||||
|
|
||||||
boolean updateSuccess = lotteryTicketService.update()
|
|
||||||
.eq(LotteryTicket::getId, ticket.getId())
|
|
||||||
.eq(LotteryTicket::getRemainingCount, ticket.getRemainingCount())
|
|
||||||
.setSql("used_count = used_count + " + deduct)
|
|
||||||
.setSql("remaining_count = remaining_count - " + deduct)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
ResponseAssert.isTrue(LotteryErrorCode.INSUFFICIENT_TICKETS, updateSuccess);
|
|
||||||
|
|
||||||
LotteryTicketRecord ticketRecord = new LotteryTicketRecord();
|
|
||||||
ticketRecord.setUserId(userId);
|
|
||||||
ticketRecord.setTicketId(ticket.getId());
|
|
||||||
ticketRecord.setChangeCount(-deduct);
|
|
||||||
ticketRecord.setChangeType(2);
|
|
||||||
ticketRecord.setRemark("连抽消耗");
|
|
||||||
records.add(ticketRecord);
|
|
||||||
|
|
||||||
remaining -= deduct;
|
|
||||||
}
|
}
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
if (!records.isEmpty()) {
|
private LotteryPrize drawPrizeWithDynamicProbability(Long activityId, LotteryActivity activity, LotteryUserProgress progress) {
|
||||||
lotteryTicketRecordService.saveBatch(records);
|
if (activity.getEnableDynamicProbability() == null || activity.getEnableDynamicProbability() != 1) {
|
||||||
|
return drawPrizeWithMultiProbability(activityId);
|
||||||
|
}
|
||||||
|
String stage = determinePrizePool(progress, activity);
|
||||||
|
log.debug("Multi-draw stage: {}, userId: {}, drawCount: {}, totalAmount: {}",
|
||||||
|
stage, progress.getUserId(), progress.getTotalDrawCount(), progress.getTotalAmount());
|
||||||
|
List<LotteryPrize> prizes = getPrizesByPool(activityId, stage);
|
||||||
|
prizes = filterOverThresholdPrizes(prizes, progress, activity.getWithdrawThreshold());
|
||||||
|
if (prizes.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (LotteryPrize prize : prizes) {
|
||||||
|
if (prize.getMultiDrawProbability() != null) {
|
||||||
|
prize.setProbability(prize.getMultiDrawProbability());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adjustProbability(prizes, progress, activity.getWithdrawThreshold());
|
||||||
|
return randomSelectByProbability(prizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String determinePrizePool(LotteryUserProgress progress, LotteryActivity activity) {
|
||||||
|
int drawCount = progress.getTotalDrawCount();
|
||||||
|
BigDecimal totalAmount = progress.getTotalAmount();
|
||||||
|
BigDecimal threshold = activity.getWithdrawThreshold() != null ? activity.getWithdrawThreshold() : new BigDecimal("10");
|
||||||
|
int earlyMax = activity.getEarlyStageMaxCount() != null ? activity.getEarlyStageMaxCount() : 15;
|
||||||
|
int middleMax = activity.getMiddleStageMaxCount() != null ? activity.getMiddleStageMaxCount() : 50;
|
||||||
|
BigDecimal lateTrigger = activity.getLateStageTriggerAmount() != null ? activity.getLateStageTriggerAmount() : new BigDecimal("6");
|
||||||
|
if (drawCount < earlyMax) {
|
||||||
|
return "EARLY";
|
||||||
|
}
|
||||||
|
BigDecimal gap = threshold.subtract(totalAmount);
|
||||||
|
if (drawCount >= middleMax || gap.compareTo(lateTrigger) <= 0) {
|
||||||
|
return "LATE";
|
||||||
|
}
|
||||||
|
return "MIDDLE";
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LotteryPrize> getPrizesByPool(Long activityId, String pool) {
|
||||||
|
return lotteryPrizeService.query()
|
||||||
|
.eq(LotteryPrize::getActivityId, activityId)
|
||||||
|
.gt(LotteryPrize::getRemainingStock, 0)
|
||||||
|
.and(wrapper -> wrapper.eq(LotteryPrize::getPrizePool, pool).or().eq(LotteryPrize::getPrizePool, "ALL"))
|
||||||
|
.orderByAsc(LotteryPrize::getSortOrder)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LotteryPrize> filterOverThresholdPrizes(List<LotteryPrize> prizes, LotteryUserProgress progress, BigDecimal threshold) {
|
||||||
|
if (threshold == null) {
|
||||||
|
return prizes;
|
||||||
|
}
|
||||||
|
BigDecimal currentAmount = progress.getTotalAmount();
|
||||||
|
int drawCount = progress.getTotalDrawCount();
|
||||||
|
if (currentAmount.compareTo(threshold) >= 0) {
|
||||||
|
return prizes.stream().filter(prize -> prize.getPrizeValue().compareTo(BigDecimal.ZERO) == 0).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
if (currentAmount.compareTo(new BigDecimal("5.0")) >= 0 && drawCount < 50) {
|
||||||
|
return prizes.stream().filter(prize -> prize.getPrizeValue().compareTo(new BigDecimal("0.15")) <= 0).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return prizes.stream().filter(prize -> {
|
||||||
|
BigDecimal afterAmount = currentAmount.add(prize.getPrizeValue());
|
||||||
|
return afterAmount.compareTo(threshold) <= 0;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void adjustProbability(List<LotteryPrize> prizes, LotteryUserProgress progress, BigDecimal threshold) {
|
||||||
|
if (threshold == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BigDecimal totalAmount = progress.getTotalAmount();
|
||||||
|
BigDecimal gap = threshold.subtract(totalAmount);
|
||||||
|
for (LotteryPrize prize : prizes) {
|
||||||
|
BigDecimal prizeValue = prize.getPrizeValue();
|
||||||
|
BigDecimal originalProbability = prize.getProbability();
|
||||||
|
BigDecimal diff = gap.subtract(prizeValue).abs();
|
||||||
|
if (diff.compareTo(new BigDecimal("0.5")) <= 0) {
|
||||||
|
prize.setProbability(originalProbability.multiply(new BigDecimal("2")));
|
||||||
|
}
|
||||||
|
if (totalAmount.compareTo(new BigDecimal("9")) > 0) {
|
||||||
|
if (prizeValue.compareTo(gap) <= 0 && prizeValue.compareTo(new BigDecimal("0.5")) >= 0) {
|
||||||
|
prize.setProbability(originalProbability.multiply(new BigDecimal("3")));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private LotteryPrize randomSelectByProbability(List<LotteryPrize> prizes) {
|
||||||
* 使用连抽概率抽奖.
|
BigDecimal totalProbability = prizes.stream().map(LotteryPrize::getProbability).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
*/
|
if (totalProbability.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
double randomValue = ThreadLocalRandom.current().nextDouble() * totalProbability.doubleValue();
|
||||||
|
double cumulative = 0.0;
|
||||||
|
for (LotteryPrize prize : prizes) {
|
||||||
|
cumulative += prize.getProbability().doubleValue();
|
||||||
|
if (randomValue <= cumulative) {
|
||||||
|
return prize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prizes.get(prizes.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
private LotteryPrize drawPrizeWithMultiProbability(Long activityId) {
|
private LotteryPrize drawPrizeWithMultiProbability(Long activityId) {
|
||||||
List<LotteryPrize> prizes = lotteryPrizeService.query()
|
List<LotteryPrize> prizes = lotteryPrizeService.query()
|
||||||
.eq(LotteryPrize::getActivityId, activityId)
|
.eq(LotteryPrize::getActivityId, activityId)
|
||||||
.gt(LotteryPrize::getRemainingStock, 0)
|
.gt(LotteryPrize::getRemainingStock, 0)
|
||||||
.orderByAsc(LotteryPrize::getSortOrder)
|
.orderByAsc(LotteryPrize::getSortOrder)
|
||||||
.list();
|
.list();
|
||||||
|
|
||||||
if (prizes.isEmpty()) {
|
if (prizes.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用连抽概率(如果配置了)
|
|
||||||
BigDecimal totalProbability = prizes.stream()
|
BigDecimal totalProbability = prizes.stream()
|
||||||
.map(prize -> prize.getMultiDrawProbability() != null
|
.map(prize -> prize.getMultiDrawProbability() != null ? prize.getMultiDrawProbability() : prize.getProbability())
|
||||||
? prize.getMultiDrawProbability()
|
|
||||||
: prize.getProbability())
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
|
||||||
double randomValue = ThreadLocalRandom.current().nextDouble() * totalProbability.doubleValue();
|
double randomValue = ThreadLocalRandom.current().nextDouble() * totalProbability.doubleValue();
|
||||||
double cumulative = 0.0;
|
double cumulative = 0.0;
|
||||||
|
|
||||||
for (LotteryPrize prize : prizes) {
|
for (LotteryPrize prize : prizes) {
|
||||||
BigDecimal probability = prize.getMultiDrawProbability() != null
|
BigDecimal probability = prize.getMultiDrawProbability() != null ? prize.getMultiDrawProbability() : prize.getProbability();
|
||||||
? prize.getMultiDrawProbability()
|
|
||||||
: prize.getProbability();
|
|
||||||
cumulative += probability.doubleValue();
|
cumulative += probability.doubleValue();
|
||||||
if (randomValue <= cumulative) {
|
if (randomValue <= cumulative) {
|
||||||
return prize;
|
return prize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return prizes.get(prizes.size() - 1);
|
return prizes.get(prizes.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void replaceWithGuaranteePrize(List<LotteryDrawResultCO> results, LotteryActivity activity, Long userId, Long activityId, Timestamp now, String batchNo, LotteryUserProgress progress) {
|
||||||
* 获取保底奖品.
|
int replaceIndex = results.size() - 1;
|
||||||
*/
|
for (int i = results.size() - 1; i >= 0; i--) {
|
||||||
|
if (!results.get(i).getIsWin()) {
|
||||||
|
replaceIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LotteryPrize guaranteePrize = getGuaranteePrize(activityId, activity.getMultiDrawGuaranteePrizeId());
|
||||||
|
if (guaranteePrize != null) {
|
||||||
|
deductPrizeStock(guaranteePrize.getId());
|
||||||
|
LotteryDrawResultCO oldResult = results.get(replaceIndex);
|
||||||
|
LotteryRecord record = lotteryRecordService.query().eq(LotteryRecord::getRecordNo, oldResult.getRecordNo()).getOne();
|
||||||
|
if (record != null) {
|
||||||
|
record.setPrizeId(guaranteePrize.getId());
|
||||||
|
record.setPrizeName(guaranteePrize.getPrizeName());
|
||||||
|
record.setPrizeType(guaranteePrize.getPrizeType());
|
||||||
|
record.setIsWin(1);
|
||||||
|
lotteryRecordService.updateSelectiveById(record);
|
||||||
|
}
|
||||||
|
results.set(replaceIndex, convertToResultCO(record, guaranteePrize));
|
||||||
|
updateUserProgress(progress, guaranteePrize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private LotteryPrize getGuaranteePrize(Long activityId, Long guaranteePrizeId) {
|
private LotteryPrize getGuaranteePrize(Long activityId, Long guaranteePrizeId) {
|
||||||
if (guaranteePrizeId != null) {
|
if (guaranteePrizeId != null) {
|
||||||
LotteryPrize prize = lotteryPrizeService.getById(guaranteePrizeId);
|
LotteryPrize prize = lotteryPrizeService.getById(guaranteePrizeId);
|
||||||
@ -259,20 +353,34 @@ public class LotteryMultiDrawExe {
|
|||||||
return prize;
|
return prize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return lotteryPrizeService.query().eq(LotteryPrize::getActivityId, activityId).eq(LotteryPrize::getIsGuaranteePrize, 1).gt(LotteryPrize::getRemainingStock, 0).orderByDesc(LotteryPrize::getPrizeValue).last("LIMIT 1").getOne();
|
||||||
// 如果没有配置保底奖品ID,查找可作为保底的奖品
|
}
|
||||||
return lotteryPrizeService.query()
|
|
||||||
.eq(LotteryPrize::getActivityId, activityId)
|
private void deductTicket(Long userId, Integer ticketCost) {
|
||||||
.eq(LotteryPrize::getIsGuaranteePrize, 1)
|
List<LotteryTicket> tickets = lotteryTicketService.query().eq(LotteryTicket::getUserId, userId).eq(LotteryTicket::getStatus, 1).gt(LotteryTicket::getRemainingCount, 0).orderByAsc(LotteryTicket::getExpireTime).list();
|
||||||
.gt(LotteryPrize::getRemainingStock, 0)
|
int totalRemaining = tickets.stream().mapToInt(LotteryTicket::getRemainingCount).sum();
|
||||||
.orderByAsc(LotteryPrize::getPrizeValue)
|
ResponseAssert.isTrue(LotteryErrorCode.INSUFFICIENT_TICKETS, totalRemaining >= ticketCost);
|
||||||
.last("LIMIT 1")
|
List<LotteryTicketRecord> records = new ArrayList<>();
|
||||||
.getOne();
|
int remaining = ticketCost;
|
||||||
|
for (LotteryTicket ticket : tickets) {
|
||||||
|
if (remaining <= 0) break;
|
||||||
|
int deduct = Math.min(remaining, ticket.getRemainingCount());
|
||||||
|
boolean updateSuccess = lotteryTicketService.update().eq(LotteryTicket::getId, ticket.getId()).eq(LotteryTicket::getRemainingCount, ticket.getRemainingCount()).setSql("used_count = used_count + " + deduct).setSql("remaining_count = remaining_count - " + deduct).execute();
|
||||||
|
ResponseAssert.isTrue(LotteryErrorCode.INSUFFICIENT_TICKETS, updateSuccess);
|
||||||
|
LotteryTicketRecord ticketRecord = new LotteryTicketRecord();
|
||||||
|
ticketRecord.setUserId(userId);
|
||||||
|
ticketRecord.setTicketId(ticket.getId());
|
||||||
|
ticketRecord.setChangeCount(-deduct);
|
||||||
|
ticketRecord.setChangeType(2);
|
||||||
|
ticketRecord.setRemark("连抽消耗");
|
||||||
|
records.add(ticketRecord);
|
||||||
|
remaining -= deduct;
|
||||||
|
}
|
||||||
|
if (!records.isEmpty()) {
|
||||||
|
lotteryTicketRecordService.saveBatch(records);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 扣减奖品库存.
|
|
||||||
*/
|
|
||||||
private boolean deductPrizeStock(Long prizeId) {
|
private boolean deductPrizeStock(Long prizeId) {
|
||||||
return lotteryPrizeService.update()
|
return lotteryPrizeService.update()
|
||||||
.eq(LotteryPrize::getId, prizeId)
|
.eq(LotteryPrize::getId, prizeId)
|
||||||
@ -281,11 +389,7 @@ public class LotteryMultiDrawExe {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private LotteryRecord saveDrawRecord(Long userId, Long activityId, LotteryPrize prize, Timestamp now, String batchNo, Integer drawType) {
|
||||||
* 保存抽奖记录.
|
|
||||||
*/
|
|
||||||
private LotteryRecord saveDrawRecord(Long userId, Long activityId, LotteryPrize prize,
|
|
||||||
Timestamp now, String batchNo, Integer drawType) {
|
|
||||||
String recordNo = generateRecordNo();
|
String recordNo = generateRecordNo();
|
||||||
LotteryRecord record = new LotteryRecord();
|
LotteryRecord record = new LotteryRecord();
|
||||||
record.setRecordNo(recordNo);
|
record.setRecordNo(recordNo);
|
||||||
@ -294,7 +398,6 @@ public class LotteryMultiDrawExe {
|
|||||||
record.setDrawTime(now);
|
record.setDrawTime(now);
|
||||||
record.setDrawBatchNo(batchNo);
|
record.setDrawBatchNo(batchNo);
|
||||||
record.setDrawType(drawType);
|
record.setDrawType(drawType);
|
||||||
|
|
||||||
if (prize != null) {
|
if (prize != null) {
|
||||||
record.setPrizeId(prize.getId());
|
record.setPrizeId(prize.getId());
|
||||||
record.setPrizeName(prize.getPrizeName());
|
record.setPrizeName(prize.getPrizeName());
|
||||||
@ -304,24 +407,13 @@ public class LotteryMultiDrawExe {
|
|||||||
} else {
|
} else {
|
||||||
record.setIsWin(0);
|
record.setIsWin(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
lotteryRecordService.save(record);
|
lotteryRecordService.save(record);
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新用户抽奖次数.
|
|
||||||
*/
|
|
||||||
private void updateUserDrawCount(Long userId, Long activityId, Integer drawCount) {
|
private void updateUserDrawCount(Long userId, Long activityId, Integer drawCount) {
|
||||||
LocalDate today = LocalDate.now();
|
LocalDate today = LocalDate.now();
|
||||||
|
boolean updated = lotteryUserCountService.update().eq(LotteryUserCount::getUserId, userId).eq(LotteryUserCount::getActivityId, activityId).eq(LotteryUserCount::getDrawDate, today).setSql("draw_count = draw_count + " + drawCount).execute();
|
||||||
boolean updated = lotteryUserCountService.update()
|
|
||||||
.eq(LotteryUserCount::getUserId, userId)
|
|
||||||
.eq(LotteryUserCount::getActivityId, activityId)
|
|
||||||
.eq(LotteryUserCount::getDrawDate, today)
|
|
||||||
.setSql("draw_count = draw_count + " + drawCount)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
try {
|
try {
|
||||||
LotteryUserCount userCount = new LotteryUserCount();
|
LotteryUserCount userCount = new LotteryUserCount();
|
||||||
@ -331,96 +423,66 @@ public class LotteryMultiDrawExe {
|
|||||||
userCount.setDrawCount(drawCount);
|
userCount.setDrawCount(drawCount);
|
||||||
lotteryUserCountService.save(userCount);
|
lotteryUserCountService.save(userCount);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
lotteryUserCountService.update()
|
lotteryUserCountService.update().eq(LotteryUserCount::getUserId, userId).eq(LotteryUserCount::getActivityId, activityId).eq(LotteryUserCount::getDrawDate, today).setSql("draw_count = draw_count + " + drawCount).execute();
|
||||||
.eq(LotteryUserCount::getUserId, userId)
|
|
||||||
.eq(LotteryUserCount::getActivityId, activityId)
|
|
||||||
.eq(LotteryUserCount::getDrawDate, today)
|
|
||||||
.setSql("draw_count = draw_count + " + drawCount)
|
|
||||||
.execute();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void updateUserProgress(LotteryUserProgress progress, LotteryPrize prize) {
|
||||||
* 计算前N次的总金额.
|
BigDecimal addAmount = prize != null ? prize.getPrizeValue() : BigDecimal.ZERO;
|
||||||
*/
|
int addBigPrizeCount = (prize != null && prize.getPrizeValue().compareTo(new BigDecimal("0.5")) >= 0) ? 1 : 0;
|
||||||
private BigDecimal calculateTotalAmount(List<LotteryDrawResultCO> results) {
|
lotteryUserProgressService.update()
|
||||||
return results.stream()
|
.eq(LotteryUserProgress::getId, progress.getId())
|
||||||
.filter(LotteryDrawResultCO::getIsWin)
|
.setSql("total_amount = total_amount + " + addAmount)
|
||||||
.map(r -> r.getPrize() != null ? r.getPrize().getPrizeValue() : BigDecimal.ZERO)
|
.setSql("total_draw_count = total_draw_count + 1")
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
.setSql("big_prize_count = big_prize_count + " + addBigPrizeCount)
|
||||||
|
.set(LotteryUserProgress::getLastDrawDate, LocalDate.now())
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getWeekKey() {
|
||||||
|
LocalDate now = LocalDate.now();
|
||||||
|
int year = now.getYear();
|
||||||
|
int weekOfYear = now.get(WeekFields.of(Locale.getDefault()).weekOfYear());
|
||||||
|
return String.format("%dW%02d", year, weekOfYear);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换为结果CO.
|
|
||||||
*/
|
|
||||||
private LotteryDrawResultCO convertToResultCO(LotteryRecord record, LotteryPrize prize) {
|
private LotteryDrawResultCO convertToResultCO(LotteryRecord record, LotteryPrize prize) {
|
||||||
LotteryDrawResultCO result = new LotteryDrawResultCO();
|
LotteryDrawResultCO result = new LotteryDrawResultCO();
|
||||||
result.setRecordNo(record.getRecordNo());
|
result.setRecordNo(record.getRecordNo());
|
||||||
result.setIsWin(record.getIsWin() == 1);
|
result.setIsWin(record.getIsWin() == 1);
|
||||||
result.setDrawTime(record.getDrawTime());
|
result.setDrawTime(record.getDrawTime());
|
||||||
|
|
||||||
if (prize != null) {
|
if (prize != null) {
|
||||||
LotteryPrizeCO prizeCO = new LotteryPrizeCO();
|
LotteryPrizeCO prizeCO = new LotteryPrizeCO();
|
||||||
BeanUtils.copyProperties(prize, prizeCO);
|
BeanUtils.copyProperties(prize, prizeCO);
|
||||||
result.setPrize(prizeCO);
|
result.setPrize(prizeCO);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private LotteryMultiDrawResultCO buildMultiDrawResult(String batchNo, List<LotteryDrawResultCO> results, boolean guaranteeTriggered) {
|
||||||
* 构建连抽结果.
|
|
||||||
*/
|
|
||||||
private LotteryMultiDrawResultCO buildMultiDrawResult(String batchNo,
|
|
||||||
List<LotteryDrawResultCO> results,
|
|
||||||
boolean guaranteeTriggered) {
|
|
||||||
LotteryMultiDrawResultCO multiResult = new LotteryMultiDrawResultCO();
|
LotteryMultiDrawResultCO multiResult = new LotteryMultiDrawResultCO();
|
||||||
multiResult.setBatchNo(batchNo);
|
multiResult.setBatchNo(batchNo);
|
||||||
multiResult.setDrawType(2);
|
multiResult.setDrawType(2);
|
||||||
multiResult.setResults(results);
|
multiResult.setResults(results);
|
||||||
|
|
||||||
// 汇总信息
|
|
||||||
LotteryMultiDrawResultCO.MultiDrawSummary summary = new LotteryMultiDrawResultCO.MultiDrawSummary();
|
LotteryMultiDrawResultCO.MultiDrawSummary summary = new LotteryMultiDrawResultCO.MultiDrawSummary();
|
||||||
|
BigDecimal totalAmount = results.stream().filter(LotteryDrawResultCO::getIsWin).map(r -> r.getPrize() != null ? r.getPrize().getPrizeValue() : BigDecimal.ZERO).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
BigDecimal totalAmount = results.stream()
|
|
||||||
.filter(LotteryDrawResultCO::getIsWin)
|
|
||||||
.map(r -> r.getPrize() != null ? r.getPrize().getPrizeValue() : BigDecimal.ZERO)
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
summary.setTotalAmount(totalAmount);
|
summary.setTotalAmount(totalAmount);
|
||||||
|
|
||||||
summary.setGuaranteeTriggered(guaranteeTriggered);
|
summary.setGuaranteeTriggered(guaranteeTriggered);
|
||||||
|
long winCount = results.stream().filter(LotteryDrawResultCO::getIsWin).count();
|
||||||
long winCount = results.stream()
|
|
||||||
.filter(LotteryDrawResultCO::getIsWin)
|
|
||||||
.count();
|
|
||||||
summary.setWinCount((int) winCount);
|
summary.setWinCount((int) winCount);
|
||||||
|
long bigPrizeCount = results.stream().filter(LotteryDrawResultCO::getIsWin).filter(r -> r.getPrize() != null && r.getPrize().getPrizeValue().compareTo(new BigDecimal("0.5")) >= 0).count();
|
||||||
long bigPrizeCount = results.stream()
|
|
||||||
.filter(LotteryDrawResultCO::getIsWin)
|
|
||||||
.filter(r -> r.getPrize() != null && r.getPrize().getPrizeValue().compareTo(new BigDecimal("0.5")) >= 0)
|
|
||||||
.count();
|
|
||||||
summary.setBigPrizeCount((int) bigPrizeCount);
|
summary.setBigPrizeCount((int) bigPrizeCount);
|
||||||
|
|
||||||
multiResult.setSummary(summary);
|
multiResult.setSummary(summary);
|
||||||
|
|
||||||
return multiResult;
|
return multiResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成批次号.
|
|
||||||
*/
|
|
||||||
private String generateBatchNo() {
|
private String generateBatchNo() {
|
||||||
return "BATCH" + System.currentTimeMillis() +
|
return "BATCH" + System.currentTimeMillis() + String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
|
||||||
String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成记录编号.
|
|
||||||
*/
|
|
||||||
private String generateRecordNo() {
|
private String generateRecordNo() {
|
||||||
return "LT" + System.currentTimeMillis() +
|
return "LT" + System.currentTimeMillis() + String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
|
||||||
String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user