抢红包随着电子支付的流行,目前在微信钉钉等社交软件都非常的受欢迎。

发红包金额一般不需要很大,就可以达到提升大家积极性的方式。抢红包其实更多是一种消遣,不劳而获的快乐,快乐到无法想象。

你抢了那么多红包,知道它实现的原理吗?_第1张图片

红包作为我们的传统文化流传至今,那么如果用程序该怎么实现呢?

大概是自己 5 年前实现过一次,时间太久源码都没了,今天重新整理一下,希望对你有所启发。

lucky-money

lucky-money 是我为这款红包小工具起的名字,好的名字是成功的一半。

期间也看了几种翻译,就直接拼音翻译的,HongBao,或者是直译的 red-packet。

但还是觉得 lucky-money 比较符合自己的预期,因为 lucky 这个词意味着运气,讨个好彩头。

抢过红包的我们都知道,运气在随机红包中非常重要。

lucky,才是这个红包程序的灵魂。

一睹为快

需求

你的产品经理“很简单”在你下班之前又来到你的工位旁边,给你提了一个简单的需求。

类似微信红包,发给多个人,每个人抢的金额不同。对了,后天上线啊,这个很简单的。

你本来想着下班回家的快乐瞬间被冲淡了一半,开始考虑怎么实现这个发红包的需求。

拿来主义

追求高效率的你立刻就想了 github,随手一查就发现了一个还不错的工具 lucky-money。

  • 引入

    com.github.houbb
    lucky-money
    0.0.2
  • 随机

随机将 1000 分(10 元)分给 5 个人。

List resultList = LuckyMoneyHelper.luckyMoney(1000, 5);

输出如下:

[253, 246, 272, 195, 34]

轻松搞定,剩下的封装下接口,整理下文档。

到时候和前端 ui 沟通一下,后天上线问题不大。

指定范围

“很简单”产品回去想了想,抢红包的时候希望可以雨露均沾,比如 10 元分给 5 个人,每个人不能少于 1 元。

并且花 5min 将这个需求改完扔给了你,补了一句改动不大,上线时间不变哦。

灵活配置

你看了下文档中 lucky-money 已经提供了这个特性,于是三下五除二搞定:

List resultList = LuckyMoneyBs.newInstance()
                .range(100, 300)
                .luckyMoney(1000, 5);

指定随机的金额在 [100, 300] 之间。

  • 输出结果
[175, 371, 163, 122, 169]

于是项目还是顺利的上线了,你也并没有为此花费太多的精力。

知其所以然

多变的产品设计

产品经理“很简单”上线完不久觉得还挺满意,于是开始想各种红包新花样。

并又给你提了一些新需求,你无奈的摇了摇头,盼着下班的心情又淡了几分。

心想有时间不如简单看下 lucky-money 的实现原理吧,后面才能应对这多变的产品设计。

LuckyMoneyHelper 工具类

工具类作为核心方法的入口,于是你从这里开始看起。

金额是分,因为国内各种移动支付最小单位就是分。

总人数是一个 int 类型,其实一般情况不会给很多人发红包。你想了想也是,有时候 1 个群几百人,发个红包都抢不到什么了。

/**
 * 分发
 * 1. 人数使用整数,21E 够用了
 * @param totalAmountFen 总金额
 * @param totalPerson 总人数
 * @return 结果
 * @since 0.0.1
 */
public static List luckyMoney(final long totalAmountFen,
                                    final int totalPerson) {
    return LuckyMoneyBs.newInstance().luckyMoney(totalAmountFen, totalPerson);
}

LuckyMoneyBs 引导类

这个类作为引导类,便于各种配置的灵活指定。

你看了下知道了金额的范围不指定时是有默认值的。

当然这个最大值的计算 calcDynamicMax() 后期可以根据自己的实际需要来调整。

/**
 * 随机分发
 * 1. totalPerson GET 1
 * 2. totalAmount GET totalPerson
 * 3. maxAmount LTE totalAmount
 * @param totalAmount 总金额
 * @param totalPerson 总人数
 * @return 结果
 * @since 0.0.1
 */
public List luckyMoney(final long totalAmount,
                             final int totalPerson) {
    ArgUtil.gte("totalPerson", totalPerson, 1);
    ArgUtil.gte("totalAmount", totalAmount, totalPerson);
    //1. 构建上下文
    LuckyMoneyContext context = LuckyMoneyContext.newInstance()
            .totalAmount(totalAmount)
            .totalPerson(totalPerson);
    //2. 自动计算
    //2.1 最小默认为1
    if(this.minAmount <= 0) {
        this.minAmount = 1;
    }
    //2.2 最大默认为所有
    if(this.maxAmount <= 0) {
        this.maxAmount = this.calcDynamicMax(totalAmount);
    } else {
        //2.3 自定义参数校验
        ArgUtil.lte("maxAmount", maxAmount, totalAmount);
    }
    context.minAmount(minAmount).maxAmount(maxAmount);
    //3. 结果
    return this.luckyMoney.luckyMoney(context);
}

ILuckyMoney 核心实现

接口定义

这里是一个接口,后续如果产品想要一个普通均分的红包,自己实现拓展一下即可。

public interface ILuckyMoney {

    /**
     * 结果列表
     * @param context 上下文
     * @return 结果
     * @since 0.0.1
     */
    List luckyMoney(final ILuckyMoneyContext context);

}

内置实现

  • 基本判断

你看了下内置的实现,发现非常的简单。

而且都有注释,自己写一个也不难。

//1. basic
final int totalPerson = context.totalPerson();
final long totalAmount = context.totalAmount();
long minAmount = context.minAmount();
long maxAmount = context.maxAmount();
List resultList = Guavas.newArrayList(totalPerson);

//2. 总金额
if(totalAmount < totalPerson) {
    throw new LuckyMoneyException("Total amount must be great than " + totalPerson);
}

//3. 低保(min * total)
long totalMinAmount = minAmount * totalPerson;
if(totalAmount < totalMinAmount) {
    throw new LuckyMoneyException("Total amount must be great than " + totalMinAmount);
}

这里为了让抢红包的人不出现抢到 0 元的情况,所以每一个人都享受“低保”。

最低就是 1 分钱,也可以根据需求指定

  • 欧皇

如果一个人非常欧,那可能就是其他人全吃低保,他一个人把剩下的钱直接抢完了~~

long totalRemains = totalAmount - totalMinAmount;
if(maxAmount > totalRemains) {
    // 不能再多了,全部给你
    maxAmount = totalRemains;
}
  • 开抢

抢多抢少,各凭运气。

这里只计算前面几个人的,最后一个人就是剩下的金额。

for(int i = 0; i < totalPerson-1; i++) {
    //5.1 低保
    long result = minAmount;
    //5.2 随机
    long random = random(totalRemains, maxAmount);
    //5.3 计算更新
    totalRemains -= random;
    result += random;
    resultList.add(result);
}
long lastRemains = totalRemains + minAmount;
resultList.add(lastRemains);
  • random()

随机方法源码如下:

/**
 * 随机金额
 * @param totalRemains 总剩余
 * @param maxAmount 最大额度
 * @return 结果
 * @since 0.0.1
 */
private long random(final long totalRemains,
                    final long maxAmount) {
    if(totalRemains <= 0) {
        return 0;
    }
    ThreadLocalRandom random = ThreadLocalRandom.current();
    long randomAmount = random.nextLong(maxAmount);
    if(randomAmount > totalRemains) {
        // 剩下的全部
        randomAmount = totalRemains;
    }
    return randomAmount;
}

收获

读到这里你发现自己还是喜欢这种知其然,知其所以然的感觉。

也感谢这个作者帮助自己可以早早下班,于是你给 lucky-money 点了个 Star,并推荐给了其他需要的小伙伴~~