说明
下面的红包分配逻辑借鉴微信红包,采用二倍均值法随机拆分;
代码
普通使用
private static Triple<Integer, Integer, Integer> pocket(int minMoneyInt, int remainInt, int remainSize) {
int red;
if (remainSize == 1) {
red = remainInt;
} else {
//获取红包的最大值
red = RandomUtils.nextInt(0, remainInt / remainSize * 2 + 1);
}
if (remainInt > red) {
remainInt -= red;
} else {
remainInt = 0;
}
return Triple.of(minMoneyInt + red, remainInt, remainSize - 1);
}
public static void main(String[] args) {
// 总人数
int totalPeople = 10;
// 总金额或剩余可分配金额
BigDecimal remainPocket = BigDecimal.valueOf(10);
// 红包最小值
BigDecimal minPocket = BigDecimal.valueOf(0.02);
// 发放时可分配数量,保证用户一定有最小值的红包
remainPocket = remainPocket.subtract(minPocket.multiply(BigDecimal.valueOf(totalPeople)));
// 剩余人数
int remainSize = totalPeople;
// 发红包总数量
BigDecimal total = BigDecimal.ZERO;
BigDecimal hundred = new BigDecimal("100");
for (int i = 1; i <= totalPeople; i++) {
// 分配红包
Triple<Integer, Integer, Integer> pocket = pocket(minPocket.multiply(hundred).intValue(), remainPocket.multiply(hundred).intValue(), remainSize);
BigDecimal packet = BigDecimal.valueOf((double) pocket.getLeft() / 100);
// 更新剩余红包金额
remainPocket = BigDecimal.valueOf((double) pocket.getMiddle() / 100);
// 更新剩余人数
remainSize = pocket.getRight();
// 更新已发放红包总金额
total = total.add(packet);
System.out.printf("第%d人:抢到%s元,当前红包发放%s元,剩余%s元,剩余%d个红包%n", i, packet, total, remainPocket, remainSize);
}
System.out.println("发放红包总金额" + total);
}
基于Redis的Lua实现
lua脚本
local minMoneyInt = tonumber(redis.call("HGET", KEYS[1], "minMoneyInt"))
local remainInt = tonumber(redis.call("HGET", KEYS[1], "remainInt"))
local remainSize = tonumber(redis.call("HGET", KEYS[1], "remainSize"))
local red
--计算红包大小
if (remainSize == 1) then
red = remainInt
else
math.randomseed(tonumber(tostring(ARGV[1]):reverse()))
red = math.random(0, remainInt / remainSize * 2)
end
--计算剩余金额
if (remainInt > red) then
remainInt = remainInt - red
else
remainInt = 0
end
--设置redis的红包属性
redis.call("HMSET", KEYS[1], "remainInt", remainInt, "remainSize", remainSize - 1)
--返回红包金额
return minMoneyInt + red
Java使用
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestLua {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedisUtils redisUtils;
private DefaultRedisScript<Long> pocketLua;
@PostConstruct
public void init() {
// 执行 lua 脚本
pocketLua = new DefaultRedisScript<>();
// 指定 lua 脚本
pocketLua.setScriptSource(new ResourceScriptSource(new ClassPathResource("pocket.lua")));
// 指定返回类型
pocketLua.setResultType(Long.class);
}
@Test
public void testLua() {
String lockKey = "test";
// 总人数
int totalPeople = 10;
// 总金额或剩余可分配金额
BigDecimal remainPocket = BigDecimal.valueOf(100);
// 红包最小值
BigDecimal minPocket = BigDecimal.valueOf(9);
// 发放时可分配数量,保证用户一定有最小值的红包
remainPocket = remainPocket.subtract(minPocket.multiply(BigDecimal.valueOf(totalPeople)));
// 测试代码忽略
redisUtils.hset(lockKey, "minMoneyInt", minPocket.multiply(BigDecimal.valueOf(100)).intValue());
redisUtils.hset(lockKey, "remainInt", remainPocket.multiply(BigDecimal.valueOf(100)).intValue());
redisUtils.hset(lockKey, "remainSize", totalPeople);
// 发红包总数量
BigDecimal total = BigDecimal.ZERO;
for (int i = 1; i <= 10; i++) {
Long result = stringRedisTemplate.execute(pocketLua, Collections.singletonList(lockKey), System.nanoTime() + "");
BigDecimal packet = BigDecimal.valueOf((double) result / 100);
System.out.printf("第%d人:抢到%s元%n", i, packet);
total = total.add(packet);
}
System.out.println("发放红包总金额" + total);
}
}
示例
-
当
minPocket*totalPeople==remainPocket// 总人数 int totalPeople = 10; // 总金额或剩余可分配金额 BigDecimal remainPocket = BigDecimal.valueOf(3.3); // 红包最小值 BigDecimal minPocket = BigDecimal.valueOf(0.33); // 结果 第1人:抢到0.33元,当前红包发放0.33元,剩余0.0元,剩余9个红包 第2人:抢到0.33元,当前红包发放0.66元,剩余0.0元,剩余8个红包 第3人:抢到0.33元,当前红包发放0.99元,剩余0.0元,剩余7个红包 第4人:抢到0.33元,当前红包发放1.32元,剩余0.0元,剩余6个红包 第5人:抢到0.33元,当前红包发放1.65元,剩余0.0元,剩余5个红包 第6人:抢到0.33元,当前红包发放1.98元,剩余0.0元,剩余4个红包 第7人:抢到0.33元,当前红包发放2.31元,剩余0.0元,剩余3个红包 第8人:抢到0.33元,当前红包发放2.64元,剩余0.0元,剩余2个红包 第9人:抢到0.33元,当前红包发放2.97元,剩余0.0元,剩余1个红包 第10人:抢到0.33元,当前红包发放3.30元,剩余0.0元,剩余0个红包 发放红包总金额3.30 -
当
minPocket*totalPeople!=remainPocket// 总人数 int totalPeople = 10; // 总金额或剩余可分配金额 BigDecimal remainPocket = BigDecimal.valueOf(5); // 红包最小值 BigDecimal minPocket = BigDecimal.valueOf(0.33); // 结果 第1人:抢到0.55元,当前红包发放0.55元,剩余1.48元,剩余9个红包 第2人:抢到0.33元,当前红包发放0.88元,剩余1.48元,剩余8个红包 第3人:抢到0.58元,当前红包发放1.46元,剩余1.23元,剩余7个红包 第4人:抢到0.61元,当前红包发放2.07元,剩余0.95元,剩余6个红包 第5人:抢到0.5元,当前红包发放2.57元,剩余0.78元,剩余5个红包 第6人:抢到0.38元,当前红包发放2.95元,剩余0.73元,剩余4个红包 第7人:抢到0.57元,当前红包发放3.52元,剩余0.49元,剩余3个红包 第8人:抢到0.63元,当前红包发放4.15元,剩余0.19元,剩余2个红包 第9人:抢到0.35元,当前红包发放4.50元,剩余0.17元,剩余1个红包 第10人:抢到0.5元,当前红包发放5.00元,剩余0.0元,剩余0个红包 发放红包总金额5.00
补充
-
两者实现本质上一样,通过lua脚本和代码就可以看出来,红包计算全部转化为分最后再转为decimal类型的;
-
程序变量
-
必选参数
- 总红包数量
totalPeople - 总发放金额
remainPocket
- 总红包数量
-
可选参数
- 红包最小单位
minPocket
- 红包最小单位
-
-
当
minPocket*totalPeople=remainPocket,红包会变为均分红包