国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

SpringBoot 實現多場景抽獎活動全攻略

開發 前端
本文將基于?SpringBoot?框架,詳細介紹如何實現多種常見的抽獎活動,提供開箱即用的技術方案,助力開發者快速搭建抽獎系統。

前言

在互聯網營銷場景中,抽獎活動是吸引用戶、提升用戶活躍度的有效方式。從簡單的隨機抽獎到復雜的概率抽獎、階梯抽獎等,不同類型的抽獎活動能滿足多樣化的運營需求。

本文將基于 SpringBoot 框架,詳細介紹如何實現多種常見的抽獎活動,提供開箱即用的技術方案,助力開發者快速搭建抽獎系統。

實現

效果圖

圖片圖片

數據庫設計

#活動 ID、活動名稱、活動開始時間、活動結束時間、活動狀態(進行中、已結束等)、抽獎次數限制、活動描述
CREATE TABLE lottery_activity (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    activity_name VARCHAR(255) NOT NULL,
    start_time DATETIME NOT NULL,
    end_time DATETIME NOT NULL,
    status TINYINT NOT NULL COMMENT '0:進行中, 1:已結束',
    description TEXT,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

#獎品 ID、活動 ID(關聯活動表)、獎品名稱、獎品數量、獎品圖片地址、中獎概率
CREATE TABLE lottery_prize (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    activity_id BIGINT NOT NULL,
    prize_name VARCHAR(255) NOT NULL,
    prize_count INT NOT NULL,
    image_url VARCHAR(255),
    probability DECIMAL(5, 2) NOT NULL COMMENT '中獎概率,范圍0-1',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (activity_id) REFERENCES lottery_activity(id)
);

#參與 ID、用戶 ID、活動 ID、抽獎時間、是否中獎、抽中獎品 ID
CREATE TABLE lottery_participation (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    activity_id BIGINT NOT NULL,
    participate_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    is_winner TINYINT NOT NULL COMMENT '0:未中獎, 1:中獎',
    prize_id BIGINT,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (activity_id) REFERENCES lottery_activity(id),
    FOREIGN KEY (prize_id) REFERENCES lottery_prize(id)
);

功能實現

隨機抽獎

隨機抽獎是最基礎的抽獎方式,從所有獎品中隨機選取一個作為中獎結果

@Service
public class RandomLotteryService {

    public LotteryPrize randomDraw(List<LotteryPrize> prizes) {
        if (prizes == null || prizes.isEmpty()) {
            return null;
        }
        Random random = new Random();
        int index = random.nextInt(prizes.size());
        return prizes.get(index);
    }
}
概率抽獎

概率抽獎根據每個獎品設置的中獎概率,按照概率分布決定用戶是否中獎以及抽中哪個獎品

@Service
public class ProbabilityLotteryService {

    public LotteryPrize probabilityDraw(List<LotteryPrize> prizes) {
        if (prizes == null || prizes.isEmpty()) {
            return null;
        }
        // 計算總概率
        double totalProbability = 0;
        for (LotteryPrize prize : prizes) {
            totalProbability += prize.getProbability();
        }

        // 生成0到總概率之間的隨機數
        Random random = new Random();
        double randomValue = random.nextDouble() * totalProbability;

        // 根據概率權重選擇獎品
        double currentProbability = 0;
        for (LotteryPrize prize : prizes) {
            currentProbability += prize.getProbability();
            if (randomValue < currentProbability) {
                return prize;
            }
        }
        return null;
    }
}
階梯抽獎

階梯抽獎根據用戶的參與次數或消費金額等條件,設置不同的抽獎階梯,每個階梯對應不同的獎品池

@Service
public class TieredLotteryService {

    public LotteryPrize tieredDraw(int drawCount, Map<Integer, List<LotteryPrize>> tieredPrizes) {
        for (Map.Entry<Integer, List<LotteryPrize>> entry : tieredPrizes.entrySet()) {
            if (drawCount >= entry.getKey()) {
                List<LotteryPrize> prizes = entry.getValue();
                if (prizes != null &&!prizes.isEmpty()) {
                    // 可以結合隨機抽獎或概率抽獎從對應獎品池中抽取獎品
                    RandomLotteryService randomLotteryService = new RandomLotteryService();
                    return randomLotteryService.randomDraw(prizes);
                }
            }
        }
        return null;
    }
}
抽獎流程整合
  • 抽獎次數限制:通過 Redis 原子操作記錄和檢查用戶抽獎次數
  • 分布式鎖:使用 Redisson 實現分布式鎖,防止并發問題
  • 緩存預熱:活動開始前將獎品庫存和配置加載到 Redis
  • Redis 庫存扣減:使用 Lua 腳本實現原子性庫存扣減,避免超賣
  • 異步庫存更新:Redis 扣減后異步更新數據庫,減輕數據庫壓力
@Service
public class LotteryProcessService {

    public static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyyMMdd");
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private DefaultRedisScript<Long> stockDeductLuaScript;

    @Autowired
    private DefaultRedisScript<Long> incrementIfNotExistLuaScript;

    @Autowired
    private RandomLotteryService randomLotteryService;

    @Autowired
    private ProbabilityLotteryService probabilityLotteryService;

    @Autowired
    private TieredLotteryService tieredLotteryService;

    @Autowired
    private LotteryActivityService lotteryActivityService;

    @Autowired
    private LotteryPrizeService lotteryPrizeService;

    @Autowired
    private LotteryParticipationService lotteryParticipationService;

    @Autowired
    private RedissonClient redissonClient;

    private static final String STOCK_KEY_PREFIX = "lottery:stock:";
    private static final String DRAW_COUNT_KEY_PREFIX = "lottery:drawCount:";
    private static final String LOCK_KEY_PREFIX = "lottery:lock:";

    public LotteryResult draw(Long userId, Long activityId, String lotteryType) {
        // 活動校驗
        LotteryActivity activity = lotteryActivityService.getById(activityId);
        if (activity == null || activity.getStatus() != 0) {
            return new LotteryResult(false, null, "活動不存在或已結束");
        }

        // 檢查用戶抽獎次數
        if (!checkUserDrawCount(userId, activityId, activity.getDailyDrawLimit())) {
            return new LotteryResult(false, null, "今日抽獎次數已用完");
        }

        // 獲取獎品列表
        List<LotteryPrize> prizes = lotteryPrizeService.listByActivityId(activityId);
        if (prizes == null || prizes.isEmpty()) {
            return new LotteryResult(false, null, "獎品列表為空");
        }

        // 預熱獎品庫存到Redis
        warmUpPrizesStock(activityId, prizes);

        // 執行抽獎邏輯
        RLock lock = redissonClient.getLock(LOCK_KEY_PREFIX + activityId + ":" + userId);
        try {
            // 嘗試獲取鎖,等待10秒,自動釋放時間30秒
            boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
            if (!locked) {
                return new LotteryResult(false, null, "系統繁忙,請稍后再試");
            }

            // 從Redis中扣減庫存
            LotteryPrize prize = deductStockAndDraw(userId, activityId, prizes, lotteryType);
            if (prize == null) {
                return new LotteryResult(false, null, "獎品已抽完");
            }

            // 記錄用戶參與抽獎信息
            recordLotteryParticipation(userId, activityId, prize);

            // 異步更新數據庫庫存
            asyncUpdateDbStock(prize.getId(), 1);

            return new LotteryResult(true, prize, "抽獎成功");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return new LotteryResult(false, null, "系統繁忙,請稍后再試");
        } finally {
            lock.unlock();
        }
    }

    /**
     * 檢查用戶抽獎次數
     */
    private boolean checkUserDrawCount(Long userId, Long activityId, int dailyLimit) {
        String drawCountKey = DRAW_COUNT_KEY_PREFIX + userId + ":" + activityId + ":" + DTF.format(LocalDate.now());

        // 使用Lua腳本原子性檢查并增加計數
        Long count = redisTemplate.execute(incrementIfNotExistLuaScript,
                Arrays.asList(drawCountKey),  24);

        return count != null && count <= dailyLimit;
    }

    /**
     * 預熱獎品庫存到Redis
     */
    private void warmUpPrizesStock(Long activityId, List<LotteryPrize> prizes) {
        for (LotteryPrize prize : prizes) {
            String stockKey = STOCK_KEY_PREFIX + activityId + ":" + prize.getId();
            // 如果Redis中不存在該獎品庫存,則從數據庫加載
            if (!redisTemplate.hasKey(stockKey)) {
                // 考慮到并發預熱,使用分布式鎖
                RLock lock = redissonClient.getLock("lottery:warmup:" + stockKey);
                try {
                    if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                        // 再次檢查,避免其他線程已經預熱
                        if (!redisTemplate.hasKey(stockKey)) {
                            // 從數據庫獲取當前庫存
                            Integer dbStock = lotteryPrizeService.getStockById(prize.getId());
                            if (dbStock != null) {
                                redisTemplate.opsForValue().set(stockKey, dbStock);
                            }
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 扣減庫存并抽獎
     */
    private LotteryPrize deductStockAndDraw(Long userId, Long activityId,
                                            List<LotteryPrize> prizes, String lotteryType) {
        // 過濾掉庫存為0的獎品
        List<LotteryPrize> availablePrizes = new ArrayList<>();
        for (LotteryPrize prize : prizes) {
            String stockKey = STOCK_KEY_PREFIX + activityId + ":" + prize.getId();
            Integer stock = (Integer) redisTemplate.opsForValue().get(stockKey);
            if (stock != null && stock > 0) {
                availablePrizes.add(prize);
            }
        }

        if (availablePrizes.isEmpty()) {
            return null;
        }

        // 根據抽獎類型選擇抽獎算法
        LotteryPrize selectedPrize;
        switch (lotteryType) {
            case"random":
                selectedPrize = randomLotteryService.randomDraw(availablePrizes);
                break;
            case"probability":
                selectedPrize = probabilityLotteryService.probabilityDraw(availablePrizes);
                break;
            case"tiered":
                // 獲取用戶抽獎次數,用于階梯抽獎
                String drawCountKey = DRAW_COUNT_KEY_PREFIX + userId + ":" + activityId + ":" + LocalDate.now();
                Integer drawCount = (Integer) redisTemplate.opsForValue().get(drawCountKey);
                if (drawCount == null) drawCount = 0;

                // 獲取階梯獎品配置
                Map<Integer, List<LotteryPrize>> tieredPrizes = getTieredPrizes(activityId);
                selectedPrize = tieredLotteryService.tieredDraw(drawCount, tieredPrizes);
                break;
            default:
                selectedPrize = null;
        }

        if (selectedPrize == null) {
            return null;
        }

        // 使用Lua腳本原子性扣減庫存
        String stockKey = STOCK_KEY_PREFIX + activityId + ":" + selectedPrize.getId();
        Long result = redisTemplate.execute(stockDeductLuaScript, Arrays.asList(stockKey), 1);

        // 返回扣減成功的獎品
        return result != null && result > 0 ? selectedPrize : null;
    }

    /**
     * 記錄用戶參與抽獎信息
     */
    @Transactional
    public void recordLotteryParticipation(Long userId, Long activityId, LotteryPrize prize) {
        LotteryParticipation participation = new LotteryParticipation();
        participation.setUserId(userId);
        participation.setActivityId(activityId);
        participation.setWinner(prize != null ? 1 : 0);
        participation.setPrizeId(prize != null ? prize.getId() : null);
        lotteryParticipationService.save(participation);
    }

    /**
     * 異步更新數據庫庫存
     */
    @Async
    public void asyncUpdateDbStock(Long prizeId, int deductCount) {
        lotteryPrizeService.deductStock(prizeId, deductCount);
    }

    /**
     * 獲取階梯獎品配置
     */
    private Map<Integer, List<LotteryPrize>> getTieredPrizes(Long activityId) {
        // 從Redis緩存或數據庫獲取階梯獎品配置
        String tieredPrizesKey = "lottery:tieredPrizes:" + activityId;
        Map<Integer, List<LotteryPrize>> tieredPrizes =
                (Map<Integer, List<LotteryPrize>>) redisTemplate.opsForValue().get(tieredPrizesKey);

        if (tieredPrizes == null) {
            // 從數據庫加載
            tieredPrizes = lotteryPrizeService.getTieredPrizesByActivityId(activityId);
            // 緩存到Redis,有效期24小時
            if (tieredPrizes != null) {
                redisTemplate.opsForValue().set(tieredPrizesKey, tieredPrizes, 24, TimeUnit.HOURS);
            }
        }

        return tieredPrizes;
    }
}


責任編輯:武曉燕 來源: 一安未來
相關推薦

2013-06-08 11:13:00

Android開發XML解析

2013-04-15 10:48:16

Xcode ARC詳解iOS ARC使用

2024-05-07 09:01:21

Queue 模塊Python線程安全隊列

2010-04-23 14:04:23

Oracle日期操作

2010-05-18 11:12:21

2009-12-14 14:32:38

動態路由配置

2014-03-19 17:22:33

2009-10-19 15:20:01

家庭綜合布線

2009-02-20 11:43:22

UNIXfish全攻略

2021-02-06 06:05:51

抖音APP瓜分紅包

2009-02-12 10:12:00

NAT配置

2019-06-27 11:47:21

Wordpress容器化HTTPS

2020-11-23 15:21:12

Linux環境變量

2009-11-10 12:08:15

2009-12-17 16:15:00

CCNA640-810

2010-08-25 14:36:02

DHCP服務器

2009-07-17 17:43:49

Jruby開發Web

2024-10-25 15:25:42

2015-03-04 13:53:33

MySQL數據庫優化SQL優化

2010-10-11 13:54:03

Windows Ser
點贊
收藏

51CTO技術棧公眾號

欧美影院一区二区| 91偷拍一区二区三区精品| 国产精品二区影院| 欧美xxxx老人做受| 在线看视频不卡| 国产福利一区二区三区在线播放| 国产一二精品视频| 亚洲午夜国产成人| 欧美高清在线观看| 国产免费av高清在线| 国产精品嫩草99a| 可以在线看黄的网站| 日本va欧美va精品发布| 九九九九精品| 青青草91视频| 杨幂一区欧美专区| 亚洲黄页一区| 欧美高跟鞋交xxxxhd| 风间由美一区| 嫩草伊人久久精品少妇av杨幂| 综合欧美一区二区三区| 99re视频在线播放| 婷婷激情成人| 在线观看日韩精品| 中文字幕久久综合| 国产精品手机在线播放| 亚洲成人aaa| 国产精品毛片无码| 亚洲精品乱码久久久久久金桔影视 | av在线观看地址| 香蕉视频在线观看免费| 欧美大片免费观看网址| 欧美日韩一区精品| 图片区小说区亚洲| 日韩1区在线| 51蜜桃传媒精品一区二区| 久久av资源网| 中文字幕高清20页| 国产婷婷色综合av蜜臀av| 久久久人成影片一区二区三区在哪下载 | 久久伊人亚洲| 国产91精品在线播放| 久久香蕉精品香蕉| 亚洲精品在线不卡| 国产精品秘入口| 欧美视频一区二区三区| 超碰在线中文| 激情综合色综合久久| 国产精品久久不能| 欧美猛男同性videos| 99蜜桃在线观看免费视频网站| 超碰免费在线播放| 亚洲美女久久久| 国产aⅴ精品一区二区三区久久| 国产精品不卡在线| 色黄视频在线| 日韩美女写真福利在线观看| 韩国v欧美v日本v亚洲v| 三级国产在线观看| 成人在线视频网站| 亚洲一区在线电影| 亚洲在线久久| 欧美日韩免费做爰大片| 成人444kkkk在线观看| 精品国产一区二区三区av片| 欧美激情网站在线观看| 精品99re| 久久综合狠狠综合久久综青草| 九九视频精品免费| 亚洲黄色小视频在线观看| 亚洲激情中文1区| 免费电影日韩网站| 国产精品视频在线观看| **女人18毛片一区二区| 国产91精品入口17c| 久久网站热最新地址| 亚洲va久久久噜噜噜久久| 国产欧美日韩免费看aⅴ视频| 蜜臀99久久精品久久久久久软件| 日韩精品一区二区三区色偷偷| 久久97视频| 日韩黄色大片| 国产精品激情自拍| 国产大片一区| 亚洲一区在线免费| 欧美不卡视频一区| 影音先锋久久久| 日韩欧美在线观看| 在线电影一区二区| 日韩高清不卡| 亚洲娇小娇小娇小| 国产精品传媒在线| 黄色国产精品| 秋霞伦理一区| 国产精品自拍片| 欧美激情手机在线视频| 亚洲欧洲精品成人久久奇米网| 成人免费高清| 国产一区二区高清视频| 五月婷婷久久丁香| 免费av一区二区三区四区| 成年人看的毛片| 亚洲国产精品人久久电影| 日韩亚洲国产欧美| 91在线中文| 久久久久久国产精品一区| 亚洲精品免费电影| 97久久超碰| 又色又爽又高潮免费视频国产| 欧美成人一区二区三区片免费| 久久精品网站免费观看| 精品亚洲二区| 日韩精品成人一区二区在线观看| 精品播放一区二区| 国产亚洲亚洲| 日韩久久在线| 国产精品久久久久影院色老大| 成人影院大全| 成人18网址在线观看| 日韩视频在线免费播放| 国产成人精品三级| 欧美人与性动交α欧美精品济南到| 97福利电影| 久久天堂国产精品| 国产免费一区二区三区香蕉精| 亚洲性生活视频| 亚洲成人久久电影| 在线亚洲人成电影网站色www| 亚洲国产精品传媒在线观看| 青青青伊人色综合久久| 在线看片不卡| 一个人www视频在线免费观看| 在线激情视频| 激情视频一区二区| 国产毛片精品国产一区二区三区| 免费理论片在线观看播放老| 视频一区三区| 欧美二级三级| 成人黄色片在线| 成人午夜一级二级三级| 国产精品老女人视频| 日本一本a高清免费不卡| 欧美激情精品久久久久久大尺度| 亚洲免费av网址| 日韩精品中文字幕视频在线| 亚洲私人黄色宅男| 不卡一区二区三区四区| 黄色三级视频片| 致1999电视剧免费观看策驰影院| 国产不卡一区二区在线播放| 欧美日产国产精品| 久久蜜臀精品av| 经典三级在线一区| 天天做夜夜做人人爱精品| 4438五月综合| 偷窥自拍亚洲色图| 久色视频在线播放| 欧美午夜性视频| 国产真实生活伦对白| 亚洲成av人乱码色午夜| 精品久久久视频| 精品日本高清在线播放| 这里只有精品电影| 亚洲电影中文字幕| 久久久噜噜噜久久| 91免费精品国偷自产在线| 91久久精品国产91久久性色| gogogo免费高清日本写真| 国产原创popny丨九色| 蜜桃视频中文字幕| 三级国产在线观看| 色琪琪丁香婷婷综合久久| 一级特黄性色生活片| 一本二本三本亚洲码| 欧美日韩精品在线一区二区| 邪恶网站在线观看| 97精品欧美一区二区三区| 亚洲视频在线免费看| 国产视频久久久久| 中文欧美在线视频| 精品欧美一区二区三区精品久久| 色女孩综合影院| 亚洲精品一区二区在线观看| 91国模大尺度私拍在线视频| 欧美一区二区三区在线观看视频| 精品久久香蕉国产线看观看gif| 日韩精品福利网站| 日韩美女视频在线观看| 欧洲美女和动交zoz0z| 欧美日韩在线亚洲一区蜜芽| 欧美精品久久天天躁| 欧美精品九九久久| 一区二区在线不卡| 四虎影院在线播放| 国产激情精品一区二区三区| 丝袜国产日韩另类美女| 色综合天天做天天爱| 91精品国产高清久久久久久91| 欧美日韩不卡在线视频| 亚洲男人的天堂av|