你搶了那麼多紅包,知道它實現的原理嗎?

搶紅包隨着電子支付的流行,目前在微信釘釘等社交軟件都非常的受歡迎。

發紅包金額一般不需要很大,就可以達到提升大家積極性的方式。搶紅包其實更多是一種消遣,不勞而獲的快樂,快樂到無法想象。

timg.jpg

紅包作爲我們的傳統文化流傳至今,那麼如果用程序該怎麼實現呢?

大概是自己 5 年前實現過一次,時間太久源碼都沒了,今天重新整理一下,希望對你有所啓發。

lucky-money

lucky-money 是我爲這款紅包小工具起的名字,好的名字是成功的一半。

期間也看了幾種翻譯,就直接拼音翻譯的,HongBao,或者是直譯的 red-packet。

但還是覺得 lucky-money 比較符合自己的預期,因爲 lucky 這個詞意味着運氣,討個好彩頭。

搶過紅包的我們都知道,運氣在隨機紅包中非常重要。

lucky,纔是這個紅包程序的靈魂。

一睹爲快

需求

你的產品經理“很簡單”在你下班之前又來到你的工位旁邊,給你提了一個簡單的需求。

類似微信紅包,發給多個人,每個人搶的金額不同。對了,後天上線啊,這個很簡單的。

你本來想着下班回家的快樂瞬間被沖淡了一半,開始考慮怎麼實現這個發紅包的需求。

拿來主義

追求高效率的你立刻就想了 github,隨手一查就發現了一個還不錯的工具 lucky-money

  • 引入
<dependency>
    <group>com.github.houbb</group>
    <artifact>lucky-money</artifact>
    <version>0.0.2</version>
</dependency>
  • 隨機

隨機將 1000 分(10 元)分給 5 個人。

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

輸出如下:

[253, 246, 272, 195, 34]

輕鬆搞定,剩下的封裝下接口,整理下文檔。

到時候和前端 ui 溝通一下,後天上線問題不大。

指定範圍

“很簡單”產品回去想了想,搶紅包的時候希望可以雨露均沾,比如 10 元分給 5 個人,每個人不能少於 1 元。

並且花 5min 將這個需求改完扔給了你,補了一句改動不大,上線時間不變哦。

靈活配置

你看了下文檔中 lucky-money 已經提供了這個特性,於是三下五除二搞定:

List<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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,並推薦給了其他需要的小夥伴~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章