JAVA 拼手气红包 领取算法 记录

废话不多说,直接上代码

	//平分红包
	public static final Integer normalPacket = 1;
	//拼手气红包
    public static final Integer luckyPacket = 2;
    //默认最小红包金额
    public static final BigDecimal minAmount = new BigDecimal(0.1);
    
	@Override
    @Transactional
    public BigDecimal receiveRedPacket(ReceiveRedPacketEntity rrp) {
    
    
        //是否已经领过
        if (redPacketUserMapper.checkUserHadReceived(rrp.getRedPacketId(), rrp.getUserid())) {
    
    
        	//您已领取过,不可重复领取
            throw new RuntimeException(ExceptionEnum.Already_Have_Received);
        }
        RedPacket rp = null;
        Boolean flag = false; //执行成功flag
        int k = 0; //一次请求最多执行3次
        BigDecimal money = null;
        while (!flag && k < 3) {
    
    
            //红包信息select …… for update 行锁
            rp = redPacketMapper.checkRedPacketInfo(rrp.getRedPacketId());
            if (rp == null) {
    
    
            	//数据跑丢了,啥也没找到~
                throw new RuntimeException(ExceptionEnum.Nothing_Found);
            }
            //红包剩余判断
            if (rp.getRestCount() <= 0) {
    
    
            	//手慢啦,红包被抢完啦
                throw new RuntimeException(ExceptionEnum.Red_Packet_Was_Robbed);
            }
            //计算金额
            if (rp.getType().equals(normalPacket)) {
    
    
                //普通红包
                //初始总额除以初始总数,为红包均分金额
                money = rp.getOriginalAmount().divide(BigDecimal.valueOf(rp.getStartCount()));
            } else if (rp.getType().equals(luckyPacket)) {
    
    
                //拼手气红包
                //算出金额,当前总余额,总剩余数,小数点后一位,最小金额值
                money = randomHandOutAlgorithm(rp.getSurplusAmount(), rp.getRestCount(), 1, minAmount);
            } else {
    
    
                log.error("领取红包,红包的类型超出定义范围:" + rp);
                //未知异常
                throw new RuntimeException(ExceptionEnum.Missing_Error);
            }
            //计算 覆写的 红包余额
            BigDecimal over = rp.getSurplusAmount().subtract(money);
            //插入领取记录
            int i = redPacketUserMapper.insertRecord(rrp.getUserid(), rp.getId(), money);
            //更新红包,将查出来的值作为匹配条件,影响行返回0 表明红包被其他人先领一步,循环再执行,最多三次
            //(发放数量,覆写余额,旧值匹配)
            int j = redPacketMapper.updateRedPacket(1, over, rp);
            if (i > 0 && j > 0) {
    
    
                flag = true;
                log.info("用户:" + rrp.getUserid() + "领取红包:" + rrp.getRedPacketId() + ",金额:" + money);
            } else {
    
    
                k++;
                try {
    
    
                    Thread.sleep(300);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
        if (!flag) {
    
    
        	//队伍拥挤,网络繁忙,请重试
            throw new RuntimeException(ExceptionEnum.Busy_NetWork);
        }
        return money;
    }

ReceiveRedPacketEntity 是接参的VO对象,ExceptionEnum是自己定义的枚举异常
拼手气算法:

/**
     * 随机红包金额算法
     *
     * @param surplusAmount 总金额
     * @param restCount     总剩余个数
     * @param scale         小数点位
     * @param minAmount     最小值
     * @return
     */
    public static BigDecimal randomHandOutAlgorithm(BigDecimal surplusAmount, Integer restCount, Integer scale, BigDecimal minAmount) {
    
    
        //剩余红包金额
        BigDecimal remainAmount = surplusAmount.setScale(scale, BigDecimal.ROUND_DOWN);
        BigDecimal amount;
        //剩余红包个数
        if (restCount > 1) {
    
    
            //前n-1个红包的金额,用随机算法
            BigDecimal random = BigDecimal.valueOf(Math.random());
            BigDecimal halfRemainSize = BigDecimal.valueOf(restCount).divide(new BigDecimal(2), BigDecimal.ROUND_UP);
            //计算单次红包的最大值,该算法也是微信的红包算法,可以保证抢红包的期望收益应与先后顺序无关,但后抢红包的方差更大,因此手气最佳更可能在后抢的人中诞生
            BigDecimal max1 = remainAmount.divide(halfRemainSize, BigDecimal.ROUND_DOWN);
            //同时,最大值需要保证,减去该红包后,剩下的红包足以满足剩余人数的最小金额
            BigDecimal minRemainAmount = minAmount.multiply(BigDecimal.valueOf(restCount - 1)).setScale(scale, BigDecimal.ROUND_DOWN);
            BigDecimal max2 = remainAmount.subtract(minRemainAmount);
            //最终,单次红包的最大值等于两个最大值中较小的一个
            BigDecimal max = (max1.compareTo(max2) < 0) ? max1 : max2;
            amount = random.multiply(max).setScale(scale, BigDecimal.ROUND_DOWN);
            //每个红包的数额不能小于预设的最小金额
            if (amount.compareTo(minAmount) < 0) {
    
    
                amount = minAmount;
            }
        } else {
    
    
            //最后一个红包,金额等于剩余金额
            amount = remainAmount;
        }
        return amount;
    }

红包条目表

CREATE TABLE `red_packet` (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `create_date` datetime NOT NULL COMMENT '创建时间',
  `modify_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `create_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '创建人',
  `name` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '红包名',
  `type` int(2) NOT NULL COMMENT '红包类型 1 普通发放 2拼手气红包,影响金额的分配方式,1为平均分,2为随机分',
  `original_amount` decimal(10,2) NOT NULL COMMENT '初始总额',
  `surplus_amount` decimal(10,2) DEFAULT NULL COMMENT '剩余余额',
  `start_count` int(10) NOT NULL COMMENT '初始总数',
  `rest_count` int(10) NOT NULL DEFAULT '0' COMMENT '剩余数量',
  `start_date` datetime NOT NULL COMMENT '开始使用时间',
  `end_date` datetime NOT NULL COMMENT '结束使用时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='红包条目';

用户红包记录表

CREATE TABLE `red_packet_user` (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `red_packet_id` int(10) NOT NULL COMMENT '红包id,关联red_packet表',
  `userid` varchar(50) NOT NULL COMMENT '用户',
  `collect_money` decimal(10,2) DEFAULT NULL COMMENT '领取到的金额',
  `is_used` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已使用',
  `collect_date` datetime NOT NULL COMMENT '领取时间',
  `use_date` datetime DEFAULT NULL COMMENT '使用时间',
  `order_number` varchar(25) DEFAULT NULL COMMENT '使用该红包的订单号',
  PRIMARY KEY (`id`),
  UNIQUE KEY `red_packet_id` (`red_packet_id`,`userid`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='用户红包记录表';

猜你喜欢

转载自blog.csdn.net/weixin_44548582/article/details/111220303
今日推荐