首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 抽獎搶購算法實現:從資料庫設計到核心邏輯

     編輯:Java 相關知識

應用場景

  • 單件商品限時搶購(如秒殺)。
  • 多件商品按概率抽獎(可限時、可不限量)。

資料庫設計

1. 獎品資訊表

CREATE TABLE AWARD_INFO (
    ID NUMBER(11) NOT NULL PRIMARY KEY,
    ACT_ID NUMBER(11),           -- 活動 ID
    NUM NUMBER(11),              -- 獎品總量(0 表示不限量)
    REST NUMBER(11),             -- 剩餘量
    ODDS NUMBER(11) DEFAULT 0,   -- 中獎權重/概率
    START_DATE DATE,             -- 開始時間
    END_DATE DATE,               -- 結束時間
    PRODUCT_ID NUMBER(11),       -- 獎品 ID
    STATE NUMBER(5) DEFAULT 0,   -- 狀態:0 有效 1 失效
    INFO_TYPE NUMBER(5) DEFAULT 0 -- 類型:0 普通
);

2. 中獎記錄表

CREATE TABLE AWARD_LOG (
    ID NUMBER(11) PRIMARY KEY,
    ACT_ID NUMBER(11),
    GET_TIME DATE,
    PRODUCT_ID NUMBER(11),
    NUM NUMBER(11) DEFAULT 1,
    PERSON VARCHAR2(50),
    INFO_ID NUMBER(11),
    STATE NUMBER(5)
);

核心算法實現

public static class AwardResult {
    public int ret;      // 返回碼
    public int logId;    // 記錄 ID
}

/**
 * 統一抽獎/搶購入口
 * @param actId 活動 ID
 * @param person 參與者
 * @param productId 指定獎品 ID;-1 表示活動下所有獎品
 * @param excludeIds 排除的獎品 ID 陣列
 * @param checkDate 是否校驗時間窗口
 * @return AwardResult
 */
public static AwardResult getAwardFull(int actId, String person, int productId,
                                       int[] excludeIds, boolean checkDate) throws SQLException {
    AwardResult result = new AwardResult();
    Connection conn = JDBC.getConnection();
    conn.setAutoCommit(false);
    try {
        // 1. 查詢可用獎品(加鎖)
        String baseSql = "SELECT t.id, t.product_id, t.num, t.rest, t.odds, t.info_type " +
                         "FROM award_info t WHERE t.act_id = ? AND t.state = 0 ";
        String checkDateSql = checkDate ? " AND t.start_date <= SYSDATE AND t.end_date >= SYSDATE " : "";
        String sql;
        Object[] params;
        if (productId > 0) { // 搶購模式
            sql = baseSql + " AND t.product_id = ? " + checkDateSql + " FOR UPDATE";
            params = new Object[]{actId, productId};
        } else { // 抽獎模式
            sql = baseSql + checkDateSql + " FOR UPDATE";
            params = new Object[]{actId};
        }
        List<Map<String, Object>> rows = JDBC.getRows(sql, params, conn);
        if (rows.isEmpty()) {
            result.ret = -1; // 無獎品資料
            conn.commit();
            return result;
        }

        // 2. 確定中獎獎品
        int infoId = -1, getProductId = -1, num = -1, rest = -1;
        if (rows.size() == 1) { // 單獎品:搶購
            Map<String, Object> row = rows.get(0);
            infoId = ((Number) row.get("ID")).intValue();
            getProductId = ((Number) row.get("PRODUCT_ID")).intValue();
            num = ((Number) row.get("NUM")).intValue();
            rest = ((Number) row.get("REST")).intValue();
        } else { // 多獎品:按權重抽獎
            int[][] range = new int[rows.size()][3];
            int sum = 0, idx = 0;
            for (int k = 0; k < rows.size(); k++) {
                int odds = ((BigDecimal) rows.get(k).get("ODDS")).intValue();
                range[idx][0] = sum;          // 區間起始
                sum += odds;
                range[idx][1] = sum;          // 區間結束
                range[idx][2] = k;            // 對應 rows 索引
                idx++;
            }
            int r = new Random().nextInt(sum + 1);
            int hitIdx = 0;
            for (int k = 0; k < idx; k++) {
                if (r >= range[k][0] && r <= range[k][1]) {
                    hitIdx = k;
                    break;
                }
            }
            Map<String, Object> hitRow = rows.get(range[hitIdx][2]);
            infoId = ((BigDecimal) hitRow.get("ID")).intValue();
            getProductId = ((BigDecimal) hitRow.get("PRODUCT_ID")).intValue();
            num = ((Number) hitRow.get("NUM")).intValue();
            rest = ((Number) hitRow.get("REST")).intValue();
        }

        // 3. 排除檢查
        if (ArrayUtils.contains(excludeIds, getProductId)) {
            result.ret = -4;
            conn.commit();
            return result;
        }

        // 4. 庫存檢查
        if (num > 0 && rest <= 0) {
            result.ret = -2; // 售罄
            conn.commit();
            return result;
        }

        // 5. 扣減庫存
        if (num > 0) {
            JDBC.update("UPDATE award_info SET rest = rest - 1 WHERE id = ?", new Object[]{infoId}, conn);
        }

        // 6. 記錄中獎
        AwardLog log = new AwardLog();
        log.setActId(actId);
        log.setNum(1);
        log.setPerson(person);
        log.setProductId(getProductId);
        log.setInfoId(infoId);
        Number logId = log.save(conn);
        if (logId == null) throw new SQLException("save award_log error");
        result.logId = logId.intValue();

        conn.commit();
        result.ret = getProductId;
        return result;
    } catch (SQLException e) {
        conn.rollback();
        result.ret = -3;
        return result;
    } finally {
        JDBC.close(conn);
    }
}

關鍵點解析

  1. 行級鎖:FOR UPDATE 確保高併發下庫存扣減原子性。
  2. 權重抽獎:構建累積區間,隨機數落入區間即中獎,支援動態調整概率。
  3. 事務控制:手動提交/回滾,保證獎品扣減與記錄寫入一致性。
  4. 擴展性:支援排除獎品、時間窗口、限量/不限量多種業務模式。

總結

本文提供了一個生產級的抽獎/搶購核心邏輯,涵蓋資料庫設計、併發控制、概率算法與事務管理,可直接應用於營銷活動系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved