偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

SpringBoot 實(shí)現(xiàn)多場景抽獎活動全攻略

開發(fā) 前端
本文將基于?SpringBoot?框架,詳細(xì)介紹如何實(shí)現(xiàn)多種常見的抽獎活動,提供開箱即用的技術(shù)方案,助力開發(fā)者快速搭建抽獎系統(tǒng)。

前言

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

本文將基于 SpringBoot 框架,詳細(xì)介紹如何實(shí)現(xiàn)多種常見的抽獎活動,提供開箱即用的技術(shù)方案,助力開發(fā)者快速搭建抽獎系統(tǒng)。

實(shí)現(xiàn)

效果圖

圖片圖片

數(shù)據(jù)庫設(shè)計(jì)

#活動 ID、活動名稱、活動開始時(shí)間、活動結(jié)束時(shí)間、活動狀態(tài)(進(jìn)行中、已結(jié)束等)、抽獎次數(shù)限制、活動描述
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:進(jìn)行中, 1:已結(jié)束',
    description TEXT,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

#獎品 ID、活動 ID(關(guān)聯(lián)活動表)、獎品名稱、獎品數(shù)量、獎品圖片地址、中獎概率
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、抽獎時(shí)間、是否中獎、抽中獎品 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)
);

功能實(shí)現(xiàn)

隨機(jī)抽獎

隨機(jī)抽獎是最基礎(chǔ)的抽獎方式,從所有獎品中隨機(jī)選取一個作為中獎結(jié)果

@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);
    }
}
概率抽獎

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

@Service
public class ProbabilityLotteryService {

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

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

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

階梯抽獎根據(jù)用戶的參與次數(shù)或消費(fèi)金額等條件,設(shè)置不同的抽獎階梯,每個階梯對應(yīng)不同的獎品池

@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()) {
                    // 可以結(jié)合隨機(jī)抽獎或概率抽獎從對應(yīng)獎品池中抽取獎品
                    RandomLotteryService randomLotteryService = new RandomLotteryService();
                    return randomLotteryService.randomDraw(prizes);
                }
            }
        }
        return null;
    }
}
抽獎流程整合
  • 抽獎次數(shù)限制:通過 Redis 原子操作記錄和檢查用戶抽獎次數(shù)
  • 分布式鎖:使用 Redisson 實(shí)現(xiàn)分布式鎖,防止并發(fā)問題
  • 緩存預(yù)熱:活動開始前將獎品庫存和配置加載到 Redis
  • Redis 庫存扣減:使用 Lua 腳本實(shí)現(xiàn)原子性庫存扣減,避免超賣
  • 異步庫存更新:Redis 扣減后異步更新數(shù)據(jù)庫,減輕數(shù)據(jù)庫壓力
@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) {
        // 活動校驗(yàn)
        LotteryActivity activity = lotteryActivityService.getById(activityId);
        if (activity == null || activity.getStatus() != 0) {
            return new LotteryResult(false, null, "活動不存在或已結(jié)束");
        }

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

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

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

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

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

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

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

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

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

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

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

    /**
     * 預(yù)熱獎品庫存到Redis
     */
    private void warmUpPrizesStock(Long activityId, List<LotteryPrize> prizes) {
        for (LotteryPrize prize : prizes) {
            String stockKey = STOCK_KEY_PREFIX + activityId + ":" + prize.getId();
            // 如果Redis中不存在該獎品庫存,則從數(shù)據(jù)庫加載
            if (!redisTemplate.hasKey(stockKey)) {
                // 考慮到并發(fā)預(yù)熱,使用分布式鎖
                RLock lock = redissonClient.getLock("lottery:warmup:" + stockKey);
                try {
                    if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                        // 再次檢查,避免其他線程已經(jīng)預(yù)熱
                        if (!redisTemplate.hasKey(stockKey)) {
                            // 從數(shù)據(jù)庫獲取當(dāng)前庫存
                            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;
        }

        // 根據(jù)抽獎類型選擇抽獎算法
        LotteryPrize selectedPrize;
        switch (lotteryType) {
            case"random":
                selectedPrize = randomLotteryService.randomDraw(availablePrizes);
                break;
            case"probability":
                selectedPrize = probabilityLotteryService.probabilityDraw(availablePrizes);
                break;
            case"tiered":
                // 獲取用戶抽獎次數(shù),用于階梯抽獎
                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);
    }

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

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

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

        return tieredPrizes;
    }
}


責(zé)任編輯:武曉燕 來源: 一安未來
相關(guān)推薦

2013-06-08 11:13:00

Android開發(fā)XML解析

2024-05-07 09:01:21

Queue 模塊Python線程安全隊(duì)列

2013-04-15 10:48:16

Xcode ARC詳解iOS ARC使用

2010-04-23 14:04:23

Oracle日期操作

2010-05-18 11:12:21

2014-03-19 17:22:33

2009-12-14 14:32:38

動態(tài)路由配置

2009-10-19 15:20:01

家庭綜合布線

2009-02-20 11:43:22

UNIXfish全攻略

2021-02-06 06:05:51

抖音APP瓜分紅包

2020-11-23 15:21:12

Linux環(huán)境變量

2009-07-17 17:43:49

Jruby開發(fā)Web

2009-11-10 12:08:15

2019-06-27 11:47:21

Wordpress容器化HTTPS

2009-12-17 16:15:00

CCNA640-810

2024-10-25 15:25:42

2010-08-25 14:36:02

DHCP服務(wù)器

2009-02-12 10:12:00

NAT配置

2015-03-04 13:53:33

MySQL數(shù)據(jù)庫優(yōu)化SQL優(yōu)化

2009-10-12 15:06:59

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號