现在市面上很多聊天交友app,其中的佼佼者就是我们都在用的微信,微信的红包功能更是增加了我们
生活的便利,随份子可以不用到场,发红包就行,在群里发个广告,不发个红包都不好意思,母亲节、
父亲节、情人节,不再只是一句简单的问候和祝福,发个红包更能增进之间的感情。今天就来聊一下该
如何实现发红包、抢红包功能。
群红包和个人红包。
个人红包就比较简单了,就只有两个角色,发送者和接收者,类似我们经常说的生产者和消费者。
群红包相对复杂一点,分为普通红包和拼手气红包,一种是平均分配,另一种是完全随机。
CREATE TABLE `redpacket` (
`id` bigint(18) NOT NULL COMMENT 'id',
`recordid` bigint(18) DEFAULT NULL COMMENT '乐观锁 防并发id',
`userid` bigint(18) DEFAULT NULL COMMENT '用户id',
`groupid` bigint(18) DEFAULT NULL COMMENT '群id',
`redpacketmode` int(1) DEFAULT NULL COMMENT '红包模式:普通/0、拼手气/1',
`redpackettype` int(1) DEFAULT NULL COMMENT '红包类型:个人/0、群红包1',
`redpacketcopulation` decimal(18,2) DEFAULT NULL COMMENT '红包交子金额数',
`surpluscopulation` decimal(18,2) DEFAULT NULL COMMENT '剩余交子金额数',
`robpeoplenum` int(1) DEFAULT NULL COMMENT '设定的可抢人数',
`robgrabitnum` int(1) DEFAULT NULL COMMENT '已经抢到的人数',
`redpacketdesc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '红包描述',
`redpacketstatus` int(1) DEFAULT NULL COMMENT '红包状态:0/未抢完、1/抢完、2红包过期',
`redpackettimeslot` varchar(255) DEFAULT NULL COMMENT '抢红包时长:例如:5分钟抢完',
`endtime` datetime DEFAULT NULL COMMENT '被抢完时间/过期时间',
`createtime` datetime DEFAULT NULL COMMENT '红包创建时间',
PRIMARY KEY (`redpacketid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在这张表里就可以把红包类型和红包模式区分开来了,表设计好了,那么开始梳理逻辑。
首先是发红包。
群红包的普通红包可以通过(发送的金额 / 红包个数)来实现平均分配,拼手气红包则需要做到随机。
拼手气红包算法奉上:
public class RedPacketUtil {
/**
* 生成红包最小值 1分
*/
private static final int MIN_MONEY = 1;
/**
* 生成红包最大值 200人民币
*/
private static final int MAX_MONEY = 200 * 100;
/**
* 小于最小值
*/
private static final int LESS = -1;
/**
* 大于最大值
*/
private static final int MORE = -2;
/**
* 正常值
*/
private static final int OK = 1;
/**
* 最大的红包是平均值的 TIMES 倍,防止某一次分配红包较大
*/
private static final double TIMES = 2.1F;
private int recursiveCount = 0;
public List<Integer> splitRedPacket(int money, int count) {
List<Integer> moneys = new LinkedList<>();
//金额检查,如果最大红包 * 个数 < 总金额;则需要调大最小红包 MAX_MONEY
if (MAX_MONEY * count <= money) {
return moneys ;
}
//计算出最大红包
int max = (int) ((money / count) * TIMES);
max = max > MAX_MONEY ? MAX_MONEY : max;
for (int i = 0; i < count; i++) {
//随机获取红包
int redPacket = randomRedPacket(money, MIN_MONEY, max, count - i);
moneys.add(redPacket);
//总金额每次减少
money -= redPacket;
}
return moneys;
}
private int randomRedPacket(int totalMoney, int minMoney, int maxMoney, int count) {
//只有一个红包直接返回
if (count == 1) {
return totalMoney;
}
if (minMoney == maxMoney) {
return minMoney;
}
//如果最大金额大于了剩余金额 则用剩余金额 因为这个 money 每分配一次都会减小
maxMoney = maxMoney > totalMoney ? totalMoney : maxMoney;
//在 minMoney到maxMoney 生成一个随机红包
int redPacket = (int) (Math.random() * (maxMoney - minMoney) + minMoney);
int lastMoney = totalMoney - redPacket;
int status = checkMoney(lastMoney, count - 1);
//正常金额
if (OK == status) {
return redPacket;
}
//如果生成的金额不合法 则递归重新生成
if (LESS == status) {
recursiveCount++;
return randomRedPacket(totalMoney, minMoney, redPacket, count);
}
if (MORE == status) {
recursiveCount++;
return randomRedPacket(totalMoney, redPacket, maxMoney, count);
}
return redPacket;
}
/**
* 校验剩余的金额的平均值是否在 最小值和最大值这个范围内
*
* @param lastMoney
* @param count
* @return
*/
private int checkMoney(int lastMoney, int count) {
double avg = lastMoney / count;
if (avg < MIN_MONEY) {
return LESS;
}
if (avg > MAX_MONEY) {
return MORE;
}
return OK;
}
}
群红包逻辑实现:
判断红包是否抢完,判断红包是否已经抢过了, 判断红包是否过期。根据第几位用户从redis中获取存
储的红包数据的索引位置拿到抢到的红包数额,就是该用户抢到的红包金额。如果该用户最后一个抢该
红包,判断是否手气最佳,余额增加,生成抢红包记录、资金流水记录,修改红包状态,清除redis缓
存。
抢红包很容易造成并发的情况,所以可以采用 Synchronized 加锁实现并发处理。
表设计:
CREATE TABLE `rob_redpacket` (
`id` bigint(18) NOT NULL,
`redpacketid` bigint(18) DEFAULT NULL COMMENT '红包id',
`sendredpacketuserid` bigint(18) DEFAULT NULL COMMENT '发红包用户id',
`robredpacketuserid` bigint(18) DEFAULT NULL COMMENT '收到红包用户id',
`robcopulation` decimal(18,0) DEFAULT NULL COMMENT '抢到的交子金额',
`isbesthand` int(1) DEFAULT NULL COMMENT '是否手气最佳:0/否、1/是',
`createtime` datetime DEFAULT NULL COMMENT '抢红包时间',
PRIMARY KEY (`robredpacketid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在发红包的时候,我们设定了过期时间,也就是redis中红包数据的过期时间,那么触发红包过期退还,
就需要定时任务来实现了,关于红包过期处理,我们下期再聊。