抽奖新增连抽功能
This commit is contained in:
parent
8976ce27df
commit
5b42ea8840
@ -157,6 +157,11 @@ public enum LotteryErrorCode implements IResponseErrorCode {
|
|||||||
*/
|
*/
|
||||||
USER_DRAW_COUNT_UPDATE_FAILED(9729, "Failed to update user draw count"),
|
USER_DRAW_COUNT_UPDATE_FAILED(9729, "Failed to update user draw count"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 活动未启用连抽功能.
|
||||||
|
*/
|
||||||
|
MULTI_DRAW_NOT_ENABLED(9730, "Multi-draw feature is not enabled for this activity"),
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
private final Integer code;
|
private final Integer code;
|
||||||
|
|||||||
@ -6,9 +6,11 @@ import com.red.circle.framework.web.controller.BaseController;
|
|||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityDetailCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityDetailCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryDrawResultCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryDrawResultCO;
|
||||||
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryMultiDrawResultCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryRecordCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryRecordCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryTicketCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryTicketCO;
|
||||||
import com.red.circle.other.app.dto.cmd.activity.LotteryDrawCmd;
|
import com.red.circle.other.app.dto.cmd.activity.LotteryDrawCmd;
|
||||||
|
import com.red.circle.other.app.dto.cmd.activity.LotteryMultiDrawCmd;
|
||||||
import com.red.circle.other.app.dto.cmd.activity.LotteryRecordQryCmd;
|
import com.red.circle.other.app.dto.cmd.activity.LotteryRecordQryCmd;
|
||||||
import com.red.circle.other.app.service.activity.LotteryActivityRestService;
|
import com.red.circle.other.app.service.activity.LotteryActivityRestService;
|
||||||
|
|
||||||
@ -82,6 +84,19 @@ public class LotteryActivityRestController extends BaseController {
|
|||||||
return lotteryActivityRestService.draw(cmd);
|
return lotteryActivityRestService.draw(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行连抽.
|
||||||
|
*
|
||||||
|
* @eo.name 执行连抽.
|
||||||
|
* @eo.url /multi-draw
|
||||||
|
* @eo.method post
|
||||||
|
* @eo.request-type json
|
||||||
|
*/
|
||||||
|
@PostMapping("/multi-draw")
|
||||||
|
public LotteryMultiDrawResultCO multiDraw(@RequestBody @Validated LotteryMultiDrawCmd cmd) {
|
||||||
|
return lotteryActivityRestService.multiDraw(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询我的中奖记录.
|
* 查询我的中奖记录.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -0,0 +1,426 @@
|
|||||||
|
package com.red.circle.other.app.command.activity;
|
||||||
|
|
||||||
|
import com.red.circle.framework.core.asserts.ResponseAssert;
|
||||||
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryDrawResultCO;
|
||||||
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryMultiDrawResultCO;
|
||||||
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryPrizeCO;
|
||||||
|
import com.red.circle.other.app.dto.cmd.activity.LotteryMultiDrawCmd;
|
||||||
|
import com.red.circle.other.infra.database.rds.entity.activity.LotteryActivity;
|
||||||
|
import com.red.circle.other.infra.database.rds.entity.activity.LotteryPrize;
|
||||||
|
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.LotteryTicketRecord;
|
||||||
|
import com.red.circle.other.infra.database.rds.entity.activity.LotteryUserCount;
|
||||||
|
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.LotteryRecordService;
|
||||||
|
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.LotteryUserCountService;
|
||||||
|
import com.red.circle.other.inner.asserts.lottery.LotteryErrorCode;
|
||||||
|
import com.red.circle.tool.core.date.TimestampUtils;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行连抽.
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @since 2025-10-20
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LotteryMultiDrawExe {
|
||||||
|
|
||||||
|
private final LotteryActivityService lotteryActivityService;
|
||||||
|
private final LotteryPrizeService lotteryPrizeService;
|
||||||
|
private final LotteryRecordService lotteryRecordService;
|
||||||
|
private final LotteryTicketService lotteryTicketService;
|
||||||
|
private final LotteryTicketRecordService lotteryTicketRecordService;
|
||||||
|
private final LotteryUserCountService lotteryUserCountService;
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public LotteryMultiDrawResultCO execute(LotteryMultiDrawCmd cmd) {
|
||||||
|
Long userId = cmd.requiredReqUserId();
|
||||||
|
Long activityId = cmd.getActivityId();
|
||||||
|
Integer drawCount = cmd.getDrawCount();
|
||||||
|
Timestamp now = TimestampUtils.now();
|
||||||
|
|
||||||
|
// 1. 校验活动
|
||||||
|
LotteryActivity activity = validateActivity(activityId, now);
|
||||||
|
|
||||||
|
// 2. 校验是否启用连抽
|
||||||
|
ResponseAssert.isTrue(LotteryErrorCode.MULTI_DRAW_NOT_ENABLED,
|
||||||
|
activity.getEnableMultiDraw() != null && activity.getEnableMultiDraw() == 1);
|
||||||
|
|
||||||
|
// 3. 校验抽奖次数限制(需要剩余足够次数)
|
||||||
|
validateDrawCount(userId, activityId, activity, drawCount);
|
||||||
|
|
||||||
|
// 4. 校验并扣除抽奖券(连抽消耗更少)
|
||||||
|
Integer ticketCost = activity.getMultiDrawTicketCost() != null
|
||||||
|
? activity.getMultiDrawTicketCost()
|
||||||
|
: drawCount * activity.getTicketCost();
|
||||||
|
|
||||||
|
if (activity.getNeedTicket() == 1) {
|
||||||
|
deductTicket(userId, ticketCost);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 生成批次号
|
||||||
|
String batchNo = generateBatchNo();
|
||||||
|
|
||||||
|
// 6. 执行连抽
|
||||||
|
List<LotteryDrawResultCO> results = new ArrayList<>();
|
||||||
|
boolean guaranteeTriggered = false;
|
||||||
|
|
||||||
|
// 前N-1次正常抽奖(使用连抽概率)
|
||||||
|
for (int i = 0; i < drawCount - 1; i++) {
|
||||||
|
LotteryPrize prize = drawPrizeWithMultiProbability(activityId);
|
||||||
|
|
||||||
|
// 扣减库存
|
||||||
|
if (prize != null) {
|
||||||
|
deductPrizeStock(prize.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存记录
|
||||||
|
LotteryRecord record = saveDrawRecord(userId, activityId, prize, now, batchNo, 2);
|
||||||
|
results.add(convertToResultCO(record, prize));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后一次:检查是否需要保底
|
||||||
|
BigDecimal totalAmount = calculateTotalAmount(results);
|
||||||
|
BigDecimal guaranteeMinAmount = activity.getMultiDrawGuaranteeMinAmount() != null
|
||||||
|
? activity.getMultiDrawGuaranteeMinAmount()
|
||||||
|
: BigDecimal.ZERO;
|
||||||
|
|
||||||
|
LotteryPrize lastPrize;
|
||||||
|
if (totalAmount.compareTo(guaranteeMinAmount) < 0) {
|
||||||
|
// 触发保底
|
||||||
|
guaranteeTriggered = true;
|
||||||
|
lastPrize = getGuaranteePrize(activityId, activity.getMultiDrawGuaranteePrizeId());
|
||||||
|
log.info("Multi-draw guarantee triggered, userId: {}, activityId: {}", userId, activityId);
|
||||||
|
} else {
|
||||||
|
// 正常抽奖
|
||||||
|
lastPrize = drawPrizeWithMultiProbability(activityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扣减库存
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 8. 组装返回结果
|
||||||
|
return buildMultiDrawResult(batchNo, results, guaranteeTriggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验活动状态和时间.
|
||||||
|
*/
|
||||||
|
private LotteryActivity validateActivity(Long activityId, Timestamp now) {
|
||||||
|
LotteryActivity activity = lotteryActivityService.getById(activityId);
|
||||||
|
ResponseAssert.isTrue(LotteryErrorCode.ACTIVITY_NOT_FOUND, activity != null);
|
||||||
|
ResponseAssert.isTrue(LotteryErrorCode.ACTIVITY_CLOSED, activity.getStatus() == 1);
|
||||||
|
ResponseAssert.isTrue(LotteryErrorCode.NOT_IN_ACTIVITY_TIME,
|
||||||
|
now.after(activity.getStartTime()) && now.before(activity.getEndTime()));
|
||||||
|
|
||||||
|
return activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验抽奖次数限制.
|
||||||
|
*/
|
||||||
|
private void validateDrawCount(Long userId, Long activityId, LotteryActivity activity, Integer drawCount) {
|
||||||
|
if (activity.getDrawCountLimit() == null || activity.getDrawCountLimit() <= 0) {
|
||||||
|
return; // 无限制
|
||||||
|
}
|
||||||
|
|
||||||
|
LotteryUserCount userCount = lotteryUserCountService.query()
|
||||||
|
.eq(LotteryUserCount::getUserId, userId)
|
||||||
|
.eq(LotteryUserCount::getActivityId, activityId)
|
||||||
|
.eq(LotteryUserCount::getDrawDate, LocalDate.now())
|
||||||
|
.getOne();
|
||||||
|
|
||||||
|
int currentDrawCount = userCount != null ? userCount.getDrawCount() : 0;
|
||||||
|
ResponseAssert.isTrue(LotteryErrorCode.DRAW_COUNT_EXHAUSTED,
|
||||||
|
currentDrawCount + drawCount <= activity.getDrawCountLimit());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣除抽奖券.
|
||||||
|
*/
|
||||||
|
private void deductTicket(Long userId, Integer ticketCost) {
|
||||||
|
List<LotteryTicket> tickets = lotteryTicketService.query()
|
||||||
|
.eq(LotteryTicket::getUserId, userId)
|
||||||
|
.eq(LotteryTicket::getStatus, 1)
|
||||||
|
.gt(LotteryTicket::getRemainingCount, 0)
|
||||||
|
.orderByAsc(LotteryTicket::getExpireTime)
|
||||||
|
.list();
|
||||||
|
|
||||||
|
int totalRemaining = tickets.stream()
|
||||||
|
.mapToInt(LotteryTicket::getRemainingCount)
|
||||||
|
.sum();
|
||||||
|
ResponseAssert.isTrue(LotteryErrorCode.INSUFFICIENT_TICKETS, totalRemaining >= ticketCost);
|
||||||
|
|
||||||
|
List<LotteryTicketRecord> records = new ArrayList<>();
|
||||||
|
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 LotteryPrize drawPrizeWithMultiProbability(Long activityId) {
|
||||||
|
List<LotteryPrize> prizes = lotteryPrizeService.query()
|
||||||
|
.eq(LotteryPrize::getActivityId, activityId)
|
||||||
|
.gt(LotteryPrize::getRemainingStock, 0)
|
||||||
|
.orderByAsc(LotteryPrize::getSortOrder)
|
||||||
|
.list();
|
||||||
|
|
||||||
|
if (prizes.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用连抽概率(如果配置了)
|
||||||
|
BigDecimal totalProbability = prizes.stream()
|
||||||
|
.map(prize -> prize.getMultiDrawProbability() != null
|
||||||
|
? prize.getMultiDrawProbability()
|
||||||
|
: prize.getProbability())
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
|
||||||
|
double randomValue = ThreadLocalRandom.current().nextDouble() * totalProbability.doubleValue();
|
||||||
|
double cumulative = 0.0;
|
||||||
|
|
||||||
|
for (LotteryPrize prize : prizes) {
|
||||||
|
BigDecimal probability = prize.getMultiDrawProbability() != null
|
||||||
|
? prize.getMultiDrawProbability()
|
||||||
|
: prize.getProbability();
|
||||||
|
cumulative += probability.doubleValue();
|
||||||
|
if (randomValue <= cumulative) {
|
||||||
|
return prize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prizes.get(prizes.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取保底奖品.
|
||||||
|
*/
|
||||||
|
private LotteryPrize getGuaranteePrize(Long activityId, Long guaranteePrizeId) {
|
||||||
|
if (guaranteePrizeId != null) {
|
||||||
|
LotteryPrize prize = lotteryPrizeService.getById(guaranteePrizeId);
|
||||||
|
if (prize != null && prize.getRemainingStock() > 0) {
|
||||||
|
return prize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有配置保底奖品ID,查找可作为保底的奖品
|
||||||
|
return lotteryPrizeService.query()
|
||||||
|
.eq(LotteryPrize::getActivityId, activityId)
|
||||||
|
.eq(LotteryPrize::getIsGuaranteePrize, 1)
|
||||||
|
.gt(LotteryPrize::getRemainingStock, 0)
|
||||||
|
.orderByAsc(LotteryPrize::getPrizeValue)
|
||||||
|
.last("LIMIT 1")
|
||||||
|
.getOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣减奖品库存.
|
||||||
|
*/
|
||||||
|
private boolean deductPrizeStock(Long prizeId) {
|
||||||
|
return lotteryPrizeService.update()
|
||||||
|
.eq(LotteryPrize::getId, prizeId)
|
||||||
|
.gt(LotteryPrize::getRemainingStock, 0)
|
||||||
|
.setSql("remaining_stock = remaining_stock - 1")
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存抽奖记录.
|
||||||
|
*/
|
||||||
|
private LotteryRecord saveDrawRecord(Long userId, Long activityId, LotteryPrize prize,
|
||||||
|
Timestamp now, String batchNo, Integer drawType) {
|
||||||
|
String recordNo = generateRecordNo();
|
||||||
|
LotteryRecord record = new LotteryRecord();
|
||||||
|
record.setRecordNo(recordNo);
|
||||||
|
record.setUserId(userId);
|
||||||
|
record.setActivityId(activityId);
|
||||||
|
record.setDrawTime(now);
|
||||||
|
record.setDrawBatchNo(batchNo);
|
||||||
|
record.setDrawType(drawType);
|
||||||
|
|
||||||
|
if (prize != null) {
|
||||||
|
record.setPrizeId(prize.getId());
|
||||||
|
record.setPrizeName(prize.getPrizeName());
|
||||||
|
record.setPrizeType(prize.getPrizeType());
|
||||||
|
record.setIsWin(1);
|
||||||
|
record.setPrizeStatus(0);
|
||||||
|
} else {
|
||||||
|
record.setIsWin(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
lotteryRecordService.save(record);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户抽奖次数.
|
||||||
|
*/
|
||||||
|
private void updateUserDrawCount(Long userId, Long activityId, Integer drawCount) {
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (!updated) {
|
||||||
|
try {
|
||||||
|
LotteryUserCount userCount = new LotteryUserCount();
|
||||||
|
userCount.setUserId(userId);
|
||||||
|
userCount.setActivityId(activityId);
|
||||||
|
userCount.setDrawDate(today);
|
||||||
|
userCount.setDrawCount(drawCount);
|
||||||
|
lotteryUserCountService.save(userCount);
|
||||||
|
} catch (Exception e) {
|
||||||
|
lotteryUserCountService.update()
|
||||||
|
.eq(LotteryUserCount::getUserId, userId)
|
||||||
|
.eq(LotteryUserCount::getActivityId, activityId)
|
||||||
|
.eq(LotteryUserCount::getDrawDate, today)
|
||||||
|
.setSql("draw_count = draw_count + " + drawCount)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算前N次的总金额.
|
||||||
|
*/
|
||||||
|
private BigDecimal calculateTotalAmount(List<LotteryDrawResultCO> results) {
|
||||||
|
return results.stream()
|
||||||
|
.filter(LotteryDrawResultCO::getIsWin)
|
||||||
|
.map(r -> r.getPrize() != null ? r.getPrize().getPrizeValue() : BigDecimal.ZERO)
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为结果CO.
|
||||||
|
*/
|
||||||
|
private LotteryDrawResultCO convertToResultCO(LotteryRecord record, LotteryPrize prize) {
|
||||||
|
LotteryDrawResultCO result = new LotteryDrawResultCO();
|
||||||
|
result.setRecordNo(record.getRecordNo());
|
||||||
|
result.setIsWin(record.getIsWin() == 1);
|
||||||
|
result.setDrawTime(record.getDrawTime());
|
||||||
|
|
||||||
|
if (prize != null) {
|
||||||
|
LotteryPrizeCO prizeCO = new LotteryPrizeCO();
|
||||||
|
BeanUtils.copyProperties(prize, prizeCO);
|
||||||
|
result.setPrize(prizeCO);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建连抽结果.
|
||||||
|
*/
|
||||||
|
private LotteryMultiDrawResultCO buildMultiDrawResult(String batchNo,
|
||||||
|
List<LotteryDrawResultCO> results,
|
||||||
|
boolean guaranteeTriggered) {
|
||||||
|
LotteryMultiDrawResultCO multiResult = new LotteryMultiDrawResultCO();
|
||||||
|
multiResult.setBatchNo(batchNo);
|
||||||
|
multiResult.setDrawType(2);
|
||||||
|
multiResult.setResults(results);
|
||||||
|
|
||||||
|
// 汇总信息
|
||||||
|
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);
|
||||||
|
summary.setTotalAmount(totalAmount);
|
||||||
|
|
||||||
|
summary.setGuaranteeTriggered(guaranteeTriggered);
|
||||||
|
|
||||||
|
long winCount = results.stream()
|
||||||
|
.filter(LotteryDrawResultCO::getIsWin)
|
||||||
|
.count();
|
||||||
|
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();
|
||||||
|
summary.setBigPrizeCount((int) bigPrizeCount);
|
||||||
|
|
||||||
|
multiResult.setSummary(summary);
|
||||||
|
|
||||||
|
return multiResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成批次号.
|
||||||
|
*/
|
||||||
|
private String generateBatchNo() {
|
||||||
|
return "BATCH" + System.currentTimeMillis() +
|
||||||
|
String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成记录编号.
|
||||||
|
*/
|
||||||
|
private String generateRecordNo() {
|
||||||
|
return "LT" + System.currentTimeMillis() +
|
||||||
|
String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -5,15 +5,18 @@ import com.red.circle.framework.dto.PageResult;
|
|||||||
import com.red.circle.other.app.command.activity.LotteryActivityDetailQryExe;
|
import com.red.circle.other.app.command.activity.LotteryActivityDetailQryExe;
|
||||||
import com.red.circle.other.app.command.activity.LotteryActivityListQryExe;
|
import com.red.circle.other.app.command.activity.LotteryActivityListQryExe;
|
||||||
import com.red.circle.other.app.command.activity.LotteryDrawExe;
|
import com.red.circle.other.app.command.activity.LotteryDrawExe;
|
||||||
|
import com.red.circle.other.app.command.activity.LotteryMultiDrawExe;
|
||||||
import com.red.circle.other.app.command.activity.LotteryRecordQryExe;
|
import com.red.circle.other.app.command.activity.LotteryRecordQryExe;
|
||||||
import com.red.circle.other.app.command.activity.LotteryTicketCountQryExe;
|
import com.red.circle.other.app.command.activity.LotteryTicketCountQryExe;
|
||||||
import com.red.circle.other.app.command.activity.LotteryTicketQryExe;
|
import com.red.circle.other.app.command.activity.LotteryTicketQryExe;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityDetailCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityDetailCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryDrawResultCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryDrawResultCO;
|
||||||
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryMultiDrawResultCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryRecordCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryRecordCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryTicketCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryTicketCO;
|
||||||
import com.red.circle.other.app.dto.cmd.activity.LotteryDrawCmd;
|
import com.red.circle.other.app.dto.cmd.activity.LotteryDrawCmd;
|
||||||
|
import com.red.circle.other.app.dto.cmd.activity.LotteryMultiDrawCmd;
|
||||||
import com.red.circle.other.app.dto.cmd.activity.LotteryRecordQryCmd;
|
import com.red.circle.other.app.dto.cmd.activity.LotteryRecordQryCmd;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -32,6 +35,7 @@ public class LotteryActivityRestServiceImpl implements LotteryActivityRestServic
|
|||||||
private final LotteryActivityListQryExe lotteryActivityListQryExe;
|
private final LotteryActivityListQryExe lotteryActivityListQryExe;
|
||||||
private final LotteryActivityDetailQryExe lotteryActivityDetailQryExe;
|
private final LotteryActivityDetailQryExe lotteryActivityDetailQryExe;
|
||||||
private final LotteryDrawExe lotteryDrawExe;
|
private final LotteryDrawExe lotteryDrawExe;
|
||||||
|
private final LotteryMultiDrawExe lotteryMultiDrawExe;
|
||||||
private final LotteryRecordQryExe lotteryRecordQryExe;
|
private final LotteryRecordQryExe lotteryRecordQryExe;
|
||||||
private final LotteryTicketQryExe lotteryTicketQryExe;
|
private final LotteryTicketQryExe lotteryTicketQryExe;
|
||||||
private final LotteryTicketCountQryExe lotteryTicketCountQryExe;
|
private final LotteryTicketCountQryExe lotteryTicketCountQryExe;
|
||||||
@ -51,6 +55,11 @@ public class LotteryActivityRestServiceImpl implements LotteryActivityRestServic
|
|||||||
return lotteryDrawExe.execute(cmd);
|
return lotteryDrawExe.execute(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LotteryMultiDrawResultCO multiDraw(LotteryMultiDrawCmd cmd) {
|
||||||
|
return lotteryMultiDrawExe.execute(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<LotteryRecordCO> listMyRecords(LotteryRecordQryCmd cmd) {
|
public PageResult<LotteryRecordCO> listMyRecords(LotteryRecordQryCmd cmd) {
|
||||||
return lotteryRecordQryExe.execute(cmd);
|
return lotteryRecordQryExe.execute(cmd);
|
||||||
|
|||||||
@ -0,0 +1,65 @@
|
|||||||
|
package com.red.circle.other.app.dto.clientobject.activity;
|
||||||
|
|
||||||
|
import com.red.circle.framework.dto.ClientObject;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连抽结果CO.
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @since 2025-10-20
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class LotteryMultiDrawResultCO extends ClientObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批次号.
|
||||||
|
*/
|
||||||
|
private String batchNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽奖类型:1-单抽,2-十连抽.
|
||||||
|
*/
|
||||||
|
private Integer drawType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽奖结果列表.
|
||||||
|
*/
|
||||||
|
private List<LotteryDrawResultCO> results;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汇总信息.
|
||||||
|
*/
|
||||||
|
private MultiDrawSummary summary;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public static class MultiDrawSummary {
|
||||||
|
/**
|
||||||
|
* 本次总获得金额.
|
||||||
|
*/
|
||||||
|
private BigDecimal totalAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否触发保底.
|
||||||
|
*/
|
||||||
|
private Boolean guaranteeTriggered;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大奖数量(≥0.5美元).
|
||||||
|
*/
|
||||||
|
private Integer bigPrizeCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中奖次数.
|
||||||
|
*/
|
||||||
|
private Integer winCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.red.circle.other.app.dto.cmd.activity;
|
||||||
|
|
||||||
|
import com.red.circle.common.business.dto.cmd.AppExtCommand;
|
||||||
|
import jakarta.validation.constraints.Max;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行连抽Cmd.
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @since 2025-10-20
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class LotteryMultiDrawCmd extends AppExtCommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 活动ID.
|
||||||
|
*
|
||||||
|
* @eo.required
|
||||||
|
*/
|
||||||
|
@NotNull(message = "activityId required.")
|
||||||
|
private Long activityId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连抽次数.
|
||||||
|
*
|
||||||
|
* @eo.required
|
||||||
|
*/
|
||||||
|
@NotNull(message = "drawCount required.")
|
||||||
|
@Min(value = 1, message = "drawCount must >= 1")
|
||||||
|
@Max(value = 10, message = "drawCount must <= 10")
|
||||||
|
private Integer drawCount;
|
||||||
|
|
||||||
|
}
|
||||||
@ -5,9 +5,11 @@ import com.red.circle.framework.dto.PageResult;
|
|||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityDetailCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryActivityDetailCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryDrawResultCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryDrawResultCO;
|
||||||
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryMultiDrawResultCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryRecordCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryRecordCO;
|
||||||
import com.red.circle.other.app.dto.clientobject.activity.LotteryTicketCO;
|
import com.red.circle.other.app.dto.clientobject.activity.LotteryTicketCO;
|
||||||
import com.red.circle.other.app.dto.cmd.activity.LotteryDrawCmd;
|
import com.red.circle.other.app.dto.cmd.activity.LotteryDrawCmd;
|
||||||
|
import com.red.circle.other.app.dto.cmd.activity.LotteryMultiDrawCmd;
|
||||||
import com.red.circle.other.app.dto.cmd.activity.LotteryRecordQryCmd;
|
import com.red.circle.other.app.dto.cmd.activity.LotteryRecordQryCmd;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -34,6 +36,11 @@ public interface LotteryActivityRestService {
|
|||||||
*/
|
*/
|
||||||
LotteryDrawResultCO draw(LotteryDrawCmd cmd);
|
LotteryDrawResultCO draw(LotteryDrawCmd cmd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行连抽.
|
||||||
|
*/
|
||||||
|
LotteryMultiDrawResultCO multiDraw(LotteryMultiDrawCmd cmd);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询我的中奖记录.
|
* 查询我的中奖记录.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -3,12 +3,11 @@ package com.red.circle.other.infra.database.rds.entity.activity;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.red.circle.framework.mybatis.entity.TimestampBaseEntity;
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,4 +114,34 @@ public class LotteryActivity implements Serializable {
|
|||||||
|
|
||||||
private Timestamp updateTime;
|
private Timestamp updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用连抽:0-否,1-是.
|
||||||
|
*/
|
||||||
|
@TableField("enable_multi_draw")
|
||||||
|
private Integer enableMultiDraw;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连抽次数.
|
||||||
|
*/
|
||||||
|
@TableField("multi_draw_count")
|
||||||
|
private Integer multiDrawCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连抽消耗券数(优惠).
|
||||||
|
*/
|
||||||
|
@TableField("multi_draw_ticket_cost")
|
||||||
|
private Integer multiDrawTicketCost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连抽保底奖品ID.
|
||||||
|
*/
|
||||||
|
@TableField("multi_draw_guarantee_prize_id")
|
||||||
|
private Long multiDrawGuaranteePrizeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连抽保底最低金额.
|
||||||
|
*/
|
||||||
|
@TableField("multi_draw_guarantee_min_amount")
|
||||||
|
private BigDecimal multiDrawGuaranteeMinAmount;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import java.math.BigDecimal;
|
|||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,6 +99,18 @@ public class LotteryPrize implements Serializable {
|
|||||||
@TableField("probability")
|
@TableField("probability")
|
||||||
private BigDecimal probability;
|
private BigDecimal probability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连抽时的概率(可以比单抽高).
|
||||||
|
*/
|
||||||
|
@TableField("multi_draw_probability")
|
||||||
|
private BigDecimal multiDrawProbability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可作为保底奖品:0-否,1-是.
|
||||||
|
*/
|
||||||
|
@TableField("is_guarantee_prize")
|
||||||
|
private Integer isGuaranteePrize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 排序(九宫格位置).
|
* 排序(九宫格位置).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -91,6 +91,18 @@ public class LotteryRecord implements Serializable {
|
|||||||
@TableField("deliver_time")
|
@TableField("deliver_time")
|
||||||
private Timestamp deliverTime;
|
private Timestamp deliverTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽奖批次号(连抽时相同).
|
||||||
|
*/
|
||||||
|
@TableField("draw_batch_no")
|
||||||
|
private String drawBatchNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽奖类型:1-单抽,2-十连抽.
|
||||||
|
*/
|
||||||
|
@TableField("draw_type")
|
||||||
|
private Integer drawType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备注.
|
* 备注.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user