天天看點

幸運大轉盤抽獎邏輯實作

@[toc]

1. 需求

幸運大轉盤抽獎邏輯實作

如圖,實作一個轉盤抽獎,

有個n個獎項,每個獎項配置中獎機率,點選開始抽獎進行抽獎;

本文主要講後端抽獎邏輯算法;

2. 實作思路

2.1 實作邏輯步驟

  • 前端使用現有的轉盤插件 如 https://github.com/LuckDraw/vue-luck-draw
  • 背景能夠添加配置抽獎的獎項,以及每個獎項的名稱、數量、中獎機率
  • 前端調用接口查詢所有獎項,初始化轉盤
  • 點選開始抽獎,調用後端接口,接口計算中獎機率得到中獎獎項,傳回中獎的獎項給前端,前端提示中獎

2.2 計算中獎機率算法

下面會貼出機率計算的代碼,封裝成了工具類,可以直接在項目裡面用;

這裡講思路。

舉例: 獎項及中獎機率 蘋果 10%,橘子 20%, 香蕉 15%, 檸檬 5%, 謝謝參與 35%

  1. 根據獎項機率生成區間(從0開始,累加目前獎項的機率)

    分别如下:

    蘋果 [0,10) 橘子 [10,30) 香蕉 [30,45) 檸檬 [45,50) 謝謝參與 [50,85)

  2. 生成随機數,落在哪個區間,就傳回對應的中獎項

3. 實作代碼

調用方式

public static void main(String[] args) {
    //實際上我們的獎項可能是個實體類;這面的map就是HashMap<獎項類, Integer>
    HashMap<String, Integer> map = new HashMap<>();
    map.put("蘋果", 10);
    map.put("橘子", 20);
    map.put("香蕉", 15);
    map.put("檸檬", 5);
    map.put("波羅", 15);
    map.put("謝謝參與", 35);
    //直接調用工具類 RandomUtil
    WeightMeta<String> md = RandomUtil.buildWeightMeta(map);
    //md.random()的結果就是獎項; 
    System.out.println("恭喜你抽到了:"+md.random());
}           

機率計算工具類,調用方式參考裡面的main方法

RandomUtil.java

public class RandomUtil {

    /***
     * @param weightMap key是獎項,value是機率
     * @param <T>
     * @return
     */
    public static <T> WeightMeta<T> buildWeightMeta(final Map<T, Integer> weightMap) {
        final int size = weightMap.size();
        Object[] nodes = new Object[size];
        int[] weights = new int[size];
        int index = 0;
        int weightAdder = 0;
        for (Map.Entry<T, Integer> each : weightMap.entrySet()) {
            nodes[index] = each.getKey();
            weights[index++] = (weightAdder = weightAdder + each.getValue());
        }
        return new WeightMeta<T>((T[]) nodes, weights);
    }
}           

獎項權重計算類

WeightMeta.java

package com.happy.netshop.wechat.helper;

import java.util.Arrays;
import java.util.Random;

public class WeightMeta<T> {
    private final Random ran = new Random();
    private final T[] nodes;
    private final int[] weights;
    private final int maxW;

    public WeightMeta(T[] nodes, int[] weights) {
        this.nodes = nodes;
        this.weights = weights;
        this.maxW = weights[weights.length - 1];
    }

    /**
     * 該方法傳回權重随機對象
     * @return
     */
    public T random() {
        int index = Arrays.binarySearch(weights, ran.nextInt(maxW) + 1);
        if (index < 0) {
            index = -1 - index;
        }
        return nodes[index];
    }

    public T random(int ranInt) {
        if (ranInt > maxW) {
            ranInt = maxW;
        } else if(ranInt < 0){
            ranInt = 1;
        } else {
            ranInt ++;
        }
        int index = Arrays.binarySearch(weights, ranInt);
        if (index < 0) {
            index = -1 - index;
        }
        return nodes[index];
    }

    @Override
    public String toString() {
        StringBuilder l1 = new StringBuilder();
        StringBuilder l2 = new StringBuilder("[random]\t");
        StringBuilder l3 = new StringBuilder("[node]\t\t");
        l1.append(this.getClass().getName()).append(":").append(this.hashCode()).append(":\n").append("[index]\t\t");
        for (int i = 0; i < weights.length; i++) {
            l1.append(i).append("\t");
            l2.append(weights[i]).append("\t");
            l3.append(nodes[i]).append("\t");
        }
        l1.append("\n");
        l2.append("\n");
        l3.append("\n");
        return l1.append(l2).append(l3).toString();
    }
}           

4. 其它問題處理

實際上抽獎并不是公平的按照機率去抽的;會有一些黑幕操作在裡面;

例如:

4.1 獎項設定有次數限制

例如 個别獎項,設定三天内隻能抽到2次

思路1:
簡單點直接存redis,抽中的時候存入redis,記錄次數,key到期時間設定3天;抽中的時候擷取redis存的次數,判斷達到2次,就直接傳回謝謝參與
思路2:
存資料庫,抽中的時候查詢三天内已經抽中的次數,大于2次就直接傳回謝謝參與獎項           

4.2 獎項設定有總數量

例如 個别獎項,設定總共有5個

跟上邊思路是一樣的;做好庫存管理,沒有數量了就傳回謝謝參與

4.3 讓大獎在前半段時間内不被抽到

實作思路就是:
1. 預設機率最低的獎項就是大獎或者在背景配置得知哪個是大獎;
2. 如果抽到的是大獎,則計算目前時間是否在整個活動時間的1/2時間内,如果是的話,就傳回謝謝參與           

5.總結

這裡主要是記錄下這次做的功能,以後用到了可以直接使用上面的工具類計算機率,比較友善。