123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- package com.webchat.ugc.service.redpacket;
- import com.webchat.common.constants.RedPacketConstants;
- import com.webchat.common.enums.RedisKeyEnum;
- import com.webchat.domain.vo.response.RedPacketBaseVO;
- import org.redisson.api.RLock;
- import org.redisson.api.RedissonClient;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.util.Assert;
- import java.math.BigDecimal;
- import java.util.concurrent.ThreadLocalRandom;
- /**
- * 方案一:基于redisson分布式锁实现
- *
- * 注意:
- * 这里红包剩余金额以及剩余可拆分份额,完全依赖redis(最好缓存失效可以查询保证一致性)
- */
- @Service
- public class OpenRedPacketWithRedissonLockService extends AbstractOpenRedPacketService {
- @Autowired
- private RedissonClient redissonClient;
- /**
- * 采用实时计算红包金额方式
- *
- * @param redPacket
- * @param userId
- * @return
- */
- @Override
- public int openRedPacket(RedPacketBaseVO redPacket, String userId) {
- String lockKey = RedisKeyEnum.LOCK_OPEN_RED_PACKET.getKey(String.valueOf(redPacket.getId()));
- RLock lock = redissonClient.getLock(lockKey);
- try {
- // 阻塞等待获取锁
- lock.lock();
- return doOpenRedPacket(redPacket, userId);
- } catch (Exception e) {
- throw e;
- } finally {
- if (lock.isLocked() && lock.isHeldByCurrentThread()) {
- lock.unlock();
- }
- }
- }
- /**
- * 红包拆分
- *
- * @param redPacket
- * @param userId
- * @return
- */
- private int doOpenRedPacket(RedPacketBaseVO redPacket, String userId) {
- Long redPacketId = redPacket.getId();
- /**
- * 1. 读取当前红包剩余金额
- *
- * 10、10、10
- */
- int balanceAmount = this.getBalance(redPacketId);
- Assert.isTrue(balanceAmount > 0, "红包被瓜分完了");
- /**
- * 2. 获取红包当前剩余可拆分份额
- * 2、2、2
- */
- int gradCount = this.getGradCount(redPacketId);
- Assert.isTrue(gradCount > 0, "来晚了");
- /**
- * 3. 判断红包类型(算法)
- * 3.1 普通红包:非最后一个拆分,平均瓜分(总金额 / 总人数);最后一个拆分取剩余金额
- * 3.2 拼手气:”二倍均值算法“ 为当前用户随机红包金额
- *
- * 二倍均值红包算法:
- * 示例:100元红包、份10份
- * 1️⃣ 100元 ===> 10000分,保证所有金额为整数(因为我们小数点精确到后两位)
- * 2️⃣ 第1份红包:
- * 均值AVG = 余额 / 剩余份额
- * 2倍均值= 2 * 均值AVG
- * 用于随机生成红包金额范围:[0.01元/1分钱 ~ 2 * 均值AVG] --> randomAmount(1050分)
- * 剩余:9份,余额 10000 - 1050 = 8950分
- * 3️⃣ 第2份红包:
- * 均值AVG = 8950分 / 9
- * randomAmount(1566分)
- * 剩余:8份,余额 8950分 - 1566分
- *
- * ……
- * 最后一次:
- * randomAmount = 10000分 - 历史总拆分金额
- * 剩余:0份,余额 0
- *
- */
- // 单位为分
- int openAmount = 0;
- if (gradCount == 1) {
- // 最后一个可拆分名额
- openAmount = balanceAmount;
- } else {
- // 二倍均值
- BigDecimal avgAmount = new BigDecimal(balanceAmount).divide(new BigDecimal(gradCount), 0, BigDecimal.ROUND_HALF_UP);
- BigDecimal avgAmount2 = avgAmount.multiply(new BigDecimal(2));
- int maxVal = avgAmount2.intValue();
- // 在[minVal, maxVal]之间随机一个红包金额:openAmount
- openAmount = ThreadLocalRandom.current().nextInt(RedPacketConstants.MIN_AMOUNT, maxVal + 1);
- }
- /**
- * 4. 写刷新红包剩余金额
- */
- balanceAmount = balanceAmount - openAmount;
- // 刷新当前红包剩余可拆分金额
- this.setBalance(redPacketId, balanceAmount);
- // 红包剩余可拆分次数原子-1
- this.increxGradCountCache(redPacketId, -1);
- // 将当前用户加入红包拆分用户名单缓存
- super.addUser2OpenRedPacketUsersCache(redPacketId, userId);
- if (balanceAmount == 0) {
- // 红包被拆分完
- super.updateRedPacketStatus(redPacketId, RedPacketConstants.RedPacketStatus.END);
- }
- return openAmount;
- }
- /**
- * 这里余额为啥是整数?
- * 因为我们会讲所用 单位元 转化为 分,同时保证元的小数点精确到后两位。
- *
- * @return 单位:分,1块钱,这里返回100分
- */
- private Integer getBalance(Long redPacketId) {
- String balanceKey = RedisKeyEnum.RED_PACKET_BALANCE_COUNT.getKey();
- String balanceCache = redisService.hget(balanceKey, String.valueOf(redPacketId));
- return Integer.valueOf(balanceCache);
- }
- /**
- * 刷新红包剩余可拆分金额
- *
- * @param redPacketId
- * @param balance 单位需要转为分
- */
- private void setBalance(Long redPacketId, Integer balance) {
- String balanceKey = RedisKeyEnum.RED_PACKET_BALANCE_COUNT.getKey();
- redisService.hset(balanceKey, String.valueOf(redPacketId), String.valueOf(balance),
- RedisKeyEnum.RED_PACKET_BALANCE_COUNT.getExpireTime());
- }
- /**
- * 当前已瓜分数量
- *
- * @param redPacketId
- * @return
- */
- private Integer getGradCount(Long redPacketId) {
- String balanceKey = RedisKeyEnum.RED_PACKET_GRAD_COUNT.getKey();
- String countCache = redisService.hget(balanceKey, String.valueOf(redPacketId));
- return Integer.valueOf(countCache);
- }
- /**
- *
- *
- *
- * @param redPacketId
- * @return
- */
- private void increxGradCountCache(Long redPacketId, long count) {
- String balanceKey = RedisKeyEnum.RED_PACKET_GRAD_COUNT.getKey();
- redisService.hIncrementVal(balanceKey, String.valueOf(redPacketId), count);
- }
- /**
- * 初始化红包剩余可拆分金额
- *
- * @param redPacketId
- * @param balance 单位需要转为分
- */
- public void initBalanceCache(Long redPacketId, Integer balance) {
- String balanceKey = RedisKeyEnum.RED_PACKET_BALANCE_COUNT.getKey();
- redisService.hset(balanceKey, String.valueOf(redPacketId), String.valueOf(balance),
- RedisKeyEnum.RED_PACKET_BALANCE_COUNT.getExpireTime());
- }
- /**
- * 初始化红包剩余可以瓜分数量
- *
- * @param redPacketId
- * @return
- */
- public void initGradCountCache(Long redPacketId, int gradCount) {
- String balanceKey = RedisKeyEnum.RED_PACKET_GRAD_COUNT.getKey();
- redisService.hset(balanceKey, String.valueOf(redPacketId), gradCount + "",
- RedisKeyEnum.RED_PACKET_GRAD_COUNT.getExpireTime());
- }
- }
|