package cn.lili.modules.lmk.service.impl;
|
|
import cn.lili.base.Result;
|
import cn.lili.common.exception.ServiceException;
|
import cn.lili.common.properties.RocketmqCustomProperties;
|
import cn.lili.common.security.AuthUser;
|
import cn.lili.common.security.context.UserContext;
|
import cn.lili.common.utils.StringUtils;
|
import cn.lili.modules.lmk.domain.entity.*;
|
import cn.lili.modules.lmk.domain.query.PrizeRecordTimeQuery;
|
import cn.lili.modules.lmk.domain.vo.PrizeProbabilityVO;
|
import cn.lili.modules.lmk.domain.vo.PrizeRecordTimeVO;
|
import cn.lili.modules.lmk.enums.general.*;
|
import cn.lili.modules.lmk.service.*;
|
import cn.lili.mybatis.BaseEntity;
|
import cn.lili.rocketmq.RocketmqSendCallbackBuilder;
|
import cn.lili.rocketmq.tags.CommentTagsEnum;
|
import com.alibaba.fastjson.JSON;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import lombok.RequiredArgsConstructor;
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
import org.redisson.api.RLock;
|
import org.redisson.api.RedissonClient;
|
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.support.TransactionSynchronization;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.LocalTime;
|
import java.time.ZoneId;
|
import java.util.*;
|
import java.util.concurrent.TimeUnit;
|
import java.util.function.Function;
|
import java.util.stream.Collectors;
|
|
@Service
|
@RequiredArgsConstructor
|
public class PrizeServiceImpl implements PrizeService {
|
private final PrizeActivityService prizeActivityService;
|
private final PrizeNumberService prizeNumberService;
|
private final PrizeDrawService prizeDrawService;
|
private final ActivityRefPrizeService activityRefPrizeService;
|
private final PrizeRecordService prizeRecordService;
|
private static final String PRIZE_PREFIX = "prize_draw:";
|
private static final String PRIZE_NUMBER = "prize_number:";
|
private final RedisTemplate<String, String> redisTemplate;
|
private final RedissonClient redissonClient;
|
private final RocketmqCustomProperties rocketmqCustomProperties;
|
private final RocketMQTemplate rocketMQTemplate;
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Result prize(String prizeId) {
|
AuthUser currentUser = UserContext.getCurrentUser();
|
if (currentUser == null) {
|
throw new RuntimeException("当前用户没有登录无法抽奖");
|
}
|
String userId = currentUser.getId();
|
if (StringUtils.isBlank(userId)) {
|
throw new RuntimeException("当前用户没有登录无法抽奖");
|
}
|
Result result;
|
String lock = IdWorker.get32UUID();
|
RLock redissonLock = redissonClient.getLock(PRIZE_PREFIX + prizeId);
|
|
try {
|
// Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(PRIZE_PREFIX + prizeId, lock, 60, TimeUnit.SECONDS);
|
// if (Boolean.FALSE.equals(lockResult)) {
|
// throw new ServiceException("当前活动太火爆了请稍后再试");
|
// }
|
redissonLock.lock();
|
result = prizeDraw(prizeId, userId, lock);
|
} finally {
|
if (redissonLock.isHeldByCurrentThread()) {
|
redissonLock.unlock();
|
}
|
// //释放锁
|
// String newLock = redisTemplate.opsForValue().get(PRIZE_PREFIX + prizeId);
|
// if (lock.equals(newLock))
|
// redisTemplate.delete(PRIZE_PREFIX + prizeId);
|
}
|
return result;
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public Result prizeDraw(String prizeId, String userId, String lock) {
|
//活动
|
PrizeActivity prizeActivity;
|
//待使用的抽奖机会
|
PrizeNumber waitUserPrize;
|
//当天开始时间
|
Date beginTime;
|
//当天结束时间
|
Date endTime;
|
//使用了的抽奖机会
|
List<PrizeNumber> usedPrize;
|
//没被使用的抽奖机会
|
List<PrizeNumber> canUsedPrize;
|
//抽奖商品集合
|
List<ActivityRefPrize> refPrizes;
|
//查询当天抽奖记录入参
|
PrizeRecordTimeQuery prizeRecordTimeQuery;
|
//当天抽奖记录汇总
|
List<PrizeRecordTimeVO> prizeRecordListByTime;
|
//还能抽奖商品map集合
|
Map<Long, ActivityRefPrize> canPrizeMap;
|
//待构建抽奖概率商品集合
|
List<ActivityRefPrize> refPrizeList;
|
//概率集合
|
List<PrizeProbabilityVO> prizeProbabilityList;
|
//中奖商品id
|
Long prizeWon = null;
|
|
prizeActivity = prizeActivityService.getById(prizeId);
|
if (prizeActivity == null) {
|
throw new ServiceException("当前活动不存在");
|
}
|
|
Date activityEndtime = prizeActivity.getEndTime();
|
if (new Date().after(activityEndtime)) {
|
throw new ServiceException("当前活动已经结束");
|
}
|
if (!EnableStatusEnums.ENABLE.name().equals(prizeActivity.getEnableStatus())) {
|
throw new ServiceException("活动还没有开始");
|
}
|
//查询当天的抽奖卷
|
LocalDate now = LocalDate.now();
|
LocalDateTime maxTime = LocalDateTime.of(now, LocalTime.MAX);
|
LocalDateTime minTime = LocalDateTime.of(now, LocalTime.MIN);
|
beginTime = Date.from(
|
minTime.atZone(ZoneId.systemDefault()).toInstant()
|
);
|
endTime = Date.from(
|
maxTime.atZone(ZoneId.systemDefault()).toInstant()
|
);
|
LambdaQueryWrapper<PrizeNumber> queryNumber = Wrappers.<PrizeNumber>lambdaQuery();
|
queryNumber.eq(PrizeNumber::getUserId, userId).eq(PrizeNumber::getActivityPrizeId, prizeId);
|
queryNumber.between(BaseEntity::getCreateTime, beginTime, endTime);
|
//判断用户是否有抽奖机会有取出一条未抽奖的数据待使用
|
List<PrizeNumber> list = prizeNumberService.list(queryNumber);
|
if (list == null || list.isEmpty()) {
|
throw new ServiceException("抽奖次数用完了");
|
}
|
usedPrize = list.stream().filter(item -> {
|
return PrizeNumberUseEnum.USED.name().equals(item.getUseStatus());
|
}).collect(Collectors.toList());
|
canUsedPrize = list.stream().filter(item -> {
|
return PrizeNumberUseEnum.WAIT.name().equals(item.getUseStatus());
|
}).collect(Collectors.toList());
|
if (usedPrize.size() > prizeActivity.getMaxPrize()) {
|
throw new ServiceException("抽奖已达到上限");
|
}
|
if (canUsedPrize.isEmpty()) {
|
throw new ServiceException("抽奖次数用完了");
|
}
|
waitUserPrize = canUsedPrize.get(0);
|
//判断奖品是否还有以及当天是否还有奖品
|
LambdaQueryWrapper<ActivityRefPrize> prizeRefQuery = Wrappers.<ActivityRefPrize>lambdaQuery();
|
prizeRefQuery.eq(ActivityRefPrize::getPrizeActivityId, prizeId);
|
refPrizes = activityRefPrizeService.list(prizeRefQuery);
|
canPrizeMap = refPrizes.stream().filter(item -> {
|
return item.getRemainNum() > 0;
|
}).collect(Collectors.toMap(ActivityRefPrize::getPrizeId, Function.identity()));
|
prizeRecordTimeQuery = new PrizeRecordTimeQuery();
|
prizeRecordTimeQuery.setStartTime(beginTime);
|
prizeRecordTimeQuery.setEndTime(endTime);
|
prizeRecordTimeQuery.setRecordActivityId(prizeId);
|
prizeRecordListByTime = prizeRecordService.getPrizeRecordListByTime(prizeRecordTimeQuery);
|
prizeRecordListByTime.stream().filter(item->{
|
return item.getPrizeId() != null;
|
}).forEach(item -> {
|
ActivityRefPrize activityRefPrize = canPrizeMap.get(Long.parseLong(item.getPrizeId()));
|
if (activityRefPrize != null) {
|
//移除当日上限的奖品并且添加到不能抽奖商品中去
|
if (activityRefPrize.getMaxPreDay() <= item.getTotal()) {
|
canPrizeMap.remove(Long.parseLong(item.getPrizeId()));
|
}
|
}
|
});
|
//准备构建范围集合
|
refPrizeList = new ArrayList<>(canPrizeMap.values());
|
//当前中奖比例
|
BigDecimal currentProbability = refPrizeList.stream().map(ActivityRefPrize::getPrizeProbability).reduce(BigDecimal.ZERO, BigDecimal::add);
|
//用不能中奖的商品补齐
|
BigDecimal subtract = new BigDecimal(100).subtract(currentProbability);
|
if (subtract.compareTo(BigDecimal.ZERO) > 0) {
|
ActivityRefPrize activityRefPrize = new ActivityRefPrize();
|
activityRefPrize.setPrizeId(null);
|
activityRefPrize.setPrizeProbability(subtract);
|
refPrizeList.add(activityRefPrize);
|
currentProbability = new BigDecimal(100);
|
}
|
//打乱排序
|
Collections.shuffle(refPrizeList);
|
//构建抽奖范围集合
|
prizeProbabilityList = new ArrayList<>();
|
BigDecimal probabilityBegin = BigDecimal.ZERO;
|
for (ActivityRefPrize item : refPrizeList) {
|
PrizeProbabilityVO prizeProbabilityVO = new PrizeProbabilityVO();
|
prizeProbabilityVO.setPrizeId(item.getPrizeId());
|
BigDecimal multiply = item.getPrizeProbability().multiply(BigDecimal.valueOf(100));
|
BigDecimal[][] position = {{probabilityBegin, multiply.add(probabilityBegin)}};
|
prizeProbabilityVO.setProbability(position);
|
probabilityBegin = multiply.add(probabilityBegin);
|
prizeProbabilityList.add(prizeProbabilityVO);
|
}
|
BigDecimal max = currentProbability.multiply(BigDecimal.valueOf(100));
|
BigDecimal bigDecimal = generateRandom(BigDecimal.ONE, max);
|
for (PrizeProbabilityVO prizeProbabilityVO : prizeProbabilityList) {
|
BigDecimal minP = prizeProbabilityVO.getProbability()[0][0];
|
BigDecimal maxP = prizeProbabilityVO.getProbability()[0][1];
|
if (bigDecimal.compareTo(minP) > 0 && bigDecimal.compareTo(maxP) <= 0) {
|
prizeWon = prizeProbabilityVO.getPrizeId();
|
break;
|
}
|
}
|
//未中奖的情况
|
if (prizeWon == null) {
|
//写入抽奖记录返回没有中奖信息
|
//写入抽奖记录
|
PrizeRecord prizeRecord = new PrizeRecord();
|
prizeRecord.setUserId(Long.parseLong(userId));
|
prizeRecord.setNickName(Objects.requireNonNull(UserContext.getCurrentUser()).getNickName());
|
prizeRecord.setPrizeActivityId(Long.parseLong(prizeId));
|
prizeRecord.setPrizeActivityName(prizeActivity.getActivityName());
|
prizeRecord.setPrizeActivityCover(prizeActivity.getActivityCover());
|
prizeRecord.setPrizeStatus(PrizeStatusEnum.NOT_WIN.name());
|
prizeRecord.setPrizeContent(null);
|
prizeRecord.setPrizeId(null);
|
prizeRecord.setPrizeNumId(Long.parseLong(waitUserPrize.getId()));
|
prizeRecord.setActivityPrizeRefId(null);
|
prizeRecord.setDistributeStatus(PrizeDistributeStatusEnum.NOT_WAIT.name());
|
prizeRecordService.save(prizeRecord);
|
waitUserPrize.setUseStatus(PrizeNumberUseEnum.USED.name());
|
prizeNumberService.updateById(waitUserPrize);
|
return Result.error("未中奖");
|
}
|
ActivityRefPrize activityRefPrize;
|
String nowLock;
|
activityRefPrize = canPrizeMap.get(prizeWon);
|
|
//将活动当前活动奖品库存使用排他锁锁住
|
activityRefPrizeService.lockPrizeRef(activityRefPrize.getId());
|
//修改抽奖机会状态
|
waitUserPrize.setUseStatus(PrizeNumberUseEnum.USED.name());
|
prizeNumberService.updateById(waitUserPrize);
|
//扣库存
|
LambdaUpdateWrapper<ActivityRefPrize> set = Wrappers.lambdaUpdate(ActivityRefPrize.class)
|
.eq(ActivityRefPrize::getId, activityRefPrize.getId())
|
.eq(ActivityRefPrize::getRemainNum, activityRefPrize.getRemainNum())
|
.eq(ActivityRefPrize::getVersion, activityRefPrize.getVersion())
|
.gt(ActivityRefPrize::getRemainNum, 0)
|
.set(ActivityRefPrize::getRemainNum, activityRefPrize.getRemainNum() - 1)
|
.set(ActivityRefPrize::getVersion, activityRefPrize.getVersion() + 1);
|
boolean update = activityRefPrizeService.update(set);
|
if (!update) {
|
throw new ServiceException("当前活动太火爆了请稍后再试");
|
}
|
PrizeDraw prizeDraw = prizeDrawService.getById(prizeWon);
|
//写入抽奖记录
|
PrizeRecord prizeRecord = new PrizeRecord();
|
prizeRecord.setUserId(Long.parseLong(userId));
|
prizeRecord.setNickName(Objects.requireNonNull(UserContext.getCurrentUser()).getNickName());
|
prizeRecord.setPrizeActivityId(Long.parseLong(prizeId));
|
prizeRecord.setPrizeActivityName(prizeActivity.getActivityName());
|
prizeRecord.setPrizeActivityCover(prizeActivity.getActivityCover());
|
prizeRecord.setPrizeStatus(PrizeStatusEnum.WIN.name());
|
prizeRecord.setPrizeContent(prizeDraw.getPrizeContent());
|
prizeRecord.setPrizeImg(prizeActivity.getActivityImg());
|
prizeRecord.setPrizeNumId(Long.parseLong(waitUserPrize.getId()));
|
prizeRecord.setActivityPrizeRefId(Long.parseLong(activityRefPrize.getId()));
|
prizeRecord.setDistributeStatus(PrizeDistributeStatusEnum.WAIT.name());
|
prizeRecord.setPrizeId(prizeWon);
|
prizeRecord.setPrizeName(prizeDraw.getPrizeName());
|
prizeRecord.setPrizeActivityCover(prizeActivity.getActivityCover());
|
prizeRecord.setPrizeImg(prizeDraw.getPrizeImg());
|
prizeRecordService.save(prizeRecord);
|
//判断当前锁还是不是当前的锁如果不是直接抛出异常回滚
|
// nowLock = redisTemplate.opsForValue().get(PRIZE_PREFIX + prizeId);
|
// if (!lock.equals(nowLock)) {
|
// throw new RuntimeException("当前活动太火爆了请稍后再试");
|
// }
|
// 走mq异步处理
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
@Override
|
public void afterCommit() {
|
String destination = rocketmqCustomProperties.getPrizeTopic() + ":" + PrizeStatusEnum.WIN.name();
|
rocketMQTemplate.asyncSend(destination, JSON.toJSONString(prizeRecord), RocketmqSendCallbackBuilder.commonCallback());
|
}
|
});
|
return Result.ok().data(activityRefPrize);
|
}
|
|
public static BigDecimal generateRandom(BigDecimal min, BigDecimal max) {
|
if (min.compareTo(max) >= 0) {
|
throw new IllegalArgumentException("最小值必须小于最大值");
|
}
|
// 计算范围差值
|
BigDecimal range = max.subtract(min);
|
|
// 生成0-1之间的随机double数
|
double randomDouble = new Random().nextDouble();
|
|
// 计算随机数并设置精度
|
return min.add(range.multiply(new BigDecimal(randomDouble)))
|
.setScale(0, RoundingMode.HALF_UP);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Result prizeNum(String prizeId) {
|
|
String userId;
|
AuthUser currentUser = UserContext.getCurrentUser();
|
if (currentUser == null) {
|
throw new RuntimeException("当前用户没有登录");
|
}
|
userId = currentUser.getId();
|
//获取redis锁
|
RLock lock = redissonClient.getLock(PRIZE_NUMBER + userId);
|
PrizeActivity activity = prizeActivityService.getById(prizeId);
|
if (activity == null) {
|
throw new RuntimeException("当前活动不存在");
|
}
|
//获取用户抽奖次数
|
Integer prizeNum = activity.getPrizeNum();
|
//加锁判断添加系统赠送次数
|
List<PrizeNumber> prizeNumberList;
|
try {
|
lock.lock();
|
prizeNumberList = getPrizeNumberList(prizeId, userId);
|
// 当前用户没有初始化抽奖数据需要初始化当天抽奖数据
|
if (prizeNumberList == null || prizeNumberList.isEmpty()) {
|
//没有默认抽奖次数直接返回0次
|
if (prizeNum == null || prizeNum <= 0) {
|
return Result.ok().data(0);
|
}
|
prizeNumberList = new ArrayList<>();
|
//设置默认抽奖次数并返回默认抽奖次数
|
for (int i = 0; i < prizeNum; i++) {
|
PrizeNumber prizeNumber = new PrizeNumber();
|
prizeNumber.setActivityPrizeId(Long.parseLong(prizeId));
|
prizeNumber.setUserId(Long.parseLong(userId));
|
prizeNumber.setUserAction(PrizeUserActionEnum.SYSTEM.name());
|
prizeNumber.setUseStatus(PrizeNumberUseEnum.WAIT.name());
|
prizeNumberList.add(prizeNumber);
|
}
|
//添加抽奖次数并返回
|
prizeNumberService.saveBatch(prizeNumberList);
|
return Result.ok().data(prizeNumberList.size());
|
}
|
} finally {
|
if (lock.isHeldByCurrentThread()) {
|
lock.unlock();
|
}
|
}
|
Integer maxPrize = activity.getMaxPrize();
|
//其他情况
|
int useNum = 0;
|
int notUseNum = 0;
|
for (PrizeNumber prizeNumber : prizeNumberList) {
|
if (PrizeNumberUseEnum.WAIT.name().equals(prizeNumber.getUseStatus())) {
|
notUseNum++;
|
} else if (PrizeNumberUseEnum.USED.name().equals(prizeNumber.getUseStatus())) {
|
useNum++;
|
}
|
}
|
int userPrizeNum = prizeNumberList.size();
|
if (useNum >= maxPrize) {
|
return Result.ok().data(0);
|
} else {
|
return Result.ok().data(userPrizeNum>maxPrize?maxPrize-useNum:notUseNum);
|
}
|
}
|
|
/**
|
* 获取当天某个用户的所有的优惠卷
|
*
|
* @param prizeId 活动id
|
* @param userId 用户id
|
* @return 抽奖次数集合
|
*/
|
public List<PrizeNumber> getPrizeNumberList(String prizeId, String userId) {
|
Date beginTime;
|
Date endTime;
|
//查询当天的优惠卷
|
LocalDate now = LocalDate.now();
|
LocalDateTime maxTime = LocalDateTime.of(now, LocalTime.MAX);
|
LocalDateTime minTime = LocalDateTime.of(now, LocalTime.MIN);
|
beginTime = Date.from(
|
minTime.atZone(ZoneId.systemDefault()).toInstant()
|
);
|
endTime = Date.from(
|
maxTime.atZone(ZoneId.systemDefault()).toInstant()
|
);
|
LambdaQueryWrapper<PrizeNumber> queryNumber = Wrappers.<PrizeNumber>lambdaQuery();
|
queryNumber.eq(PrizeNumber::getUserId, userId).eq(PrizeNumber::getActivityPrizeId, prizeId);
|
queryNumber.between(BaseEntity::getCreateTime, beginTime, endTime);
|
return prizeNumberService.list(queryNumber);
|
}
|
|
@Override
|
public Result prizeInfo(String prizeActivityId) {
|
return null;
|
}
|
}
|