废话不多说,直接上代码
//平分红包
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='用户红包记录表';