PHP项目如何实现大转盘?

wen java案例 2

本文目录导读:

PHP项目如何实现大转盘?

  1. 方案一:纯后端控制
  2. 方案二:前端+后端配合
  3. 方案三:使用Redis控制并发
  4. 注意事项

我来详细介绍PHP实现大转盘抽奖的几种方案。

纯后端控制

数据库设计

CREATE TABLE `prizes` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(50) NOT NULL COMMENT '奖品名称',
    `level` tinyint(4) NOT NULL COMMENT '奖项等级(1-8)',
    `probability` decimal(5,2) NOT NULL COMMENT '中奖概率(%)',
    `total` int(11) NOT NULL DEFAULT 0 COMMENT '总数量',
    `remaining` int(11) NOT NULL DEFAULT 0 COMMENT '剩余数量',
    `sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
    `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态 1启用 0禁用',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `draw_logs` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `user_id` int(11) NOT NULL,
    `prize_id` int(11) DEFAULT NULL,
    `prize_name` varchar(50) DEFAULT NULL,
    `result` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0未中奖 1中奖',
    `create_time` datetime NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

PHP后端抽奖逻辑

<?php
class LuckyDraw {
    private $db; // 数据库连接
    public function __construct($db) {
        $this->db = $db;
    }
    /**
     * 执行抽奖
     */
    public function draw($userId) {
        // 1. 获取所有可用奖品
        $prizes = $this->getAvailablePrizes();
        // 2. 计算概率
        $result = $this->calculateProbability($prizes);
        // 3. 记录抽奖结果
        $this->saveDrawLog($userId, $result);
        // 4. 更新奖品库存
        if ($result['win']) {
            $this->updatePrizeStock($result['prize_id']);
        }
        return $result;
    }
    /**
     * 获取可用奖品
     */
    private function getAvailablePrizes() {
        $sql = "SELECT * FROM prizes WHERE status = 1 AND remaining > 0 ORDER BY sort ASC";
        return $this->db->query($sql)->fetchAll();
    }
    /**
     * 计算中奖概率
     */
    private function calculateProbability($prizes) {
        $randNum = mt_rand(1, 10000); // 生成1-10000的随机数
        $probability = 0;
        foreach ($prizes as $prize) {
            // 将百分比转换为万分比
            $probability += $prize['probability'] * 100;
            if ($randNum <= $probability) {
                return [
                    'win' => true,
                    'prize_id' => $prize['id'],
                    'prize_name' => $prize['name'],
                    'level' => $prize['level']
                ];
            }
        }
        return [
            'win' => false,
            'prize_id' => null,
            'prize_name' => '谢谢参与',
            'level' => 0
        ];
    }
    /**
     * 保存抽奖记录
     */
    private function saveDrawLog($userId, $result) {
        $sql = "INSERT INTO draw_logs (user_id, prize_id, prize_name, result, create_time) 
                VALUES (?, ?, ?, ?, NOW())";
        $this->db->query($sql, [
            $userId,
            $result['prize_id'],
            $result['prize_name'],
            $result['win'] ? 1 : 0
        ]);
    }
    /**
     * 更新奖品库存
     */
    private function updatePrizeStock($prizeId) {
        $sql = "UPDATE prizes SET remaining = remaining - 1 WHERE id = ? AND remaining > 0";
        $this->db->query($sql, [$prizeId]);
    }
}

API接口

// draw.php
<?php
header('Content-Type: application/json; charset=utf-8');
// 验证用户登录
session_start();
if (!isset($_SESSION['user_id'])) {
    echo json_encode(['code' => -1, 'msg' => '请先登录']);
    exit;
}
// 检查抽奖次数
$remainingDraws = checkUserDraws($_SESSION['user_id']);
if ($remainingDraws <= 0) {
    echo json_encode(['code' => -2, 'msg' => '今日抽奖次数已用完']);
    exit;
}
// 执行抽奖
$luckyDraw = new LuckyDraw($db);
$result = $luckyDraw->draw($_SESSION['user_id']);
// 返回结果
echo json_encode([
    'code' => 0,
    'msg' => 'success',
    'data' => [
        'win' => $result['win'],
        'prize_name' => $result['prize_name'],
        'level' => $result['level'],
        'remaining_draws' => $remainingDraws - 1
    ]
]);

前端+后端配合

前端HTML结构

<!DOCTYPE html>
<html>
<head>大转盘抽奖</title>
    <style>
        .wheel-container {
            position: relative;
            width: 400px;
            height: 400px;
            margin: 0 auto;
        }
        .wheel {
            width: 100%;
            height: 100%;
            border-radius: 50%;
            position: relative;
            overflow: hidden;
        }
        .wheel-pointer {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 80px;
            height: 80px;
            background: #ff4444;
            border-radius: 50%;
            z-index: 10;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 16px;
            font-weight: bold;
        }
        .prize-item {
            position: absolute;
            width: 50%;
            height: 50%;
            transform-origin: bottom right;
            display: flex;
            align-items: center;
            justify-content: center;
        }
    </style>
</head>
<body>
    <div class="wheel-container">
        <canvas id="wheelCanvas" width="400" height="400"></canvas>
        <div class="wheel-pointer" onclick="startDraw()">抽奖</div>
    </div>
    <div id="result"></div>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        // 奖品配置(从后端获取)
        let prizes = [];
        // 初始化转盘
        function initWheel() {
            $.ajax({
                url: 'get_prizes.php',
                type: 'GET',
                success: function(response) {
                    prizes = response.data;
                    drawWheel();
                }
            });
        }
        // 绘制转盘
        function drawWheel() {
            const canvas = document.getElementById('wheelCanvas');
            const ctx = canvas.getContext('2d');
            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;
            const radius = 180;
            const sliceAngle = (2 * Math.PI) / prizes.length;
            // 清除画布
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            // 绘制每个扇区
            prizes.forEach((prize, index) => {
                const startAngle = index * sliceAngle - Math.PI / 2;
                const endAngle = startAngle + sliceAngle;
                // 绘制扇区
                ctx.beginPath();
                ctx.moveTo(centerX, centerY);
                ctx.arc(centerX, centerY, radius, startAngle, endAngle);
                ctx.closePath();
                // 填充颜色
                ctx.fillStyle = index % 2 === 0 ? '#ff6b6b' : '#ffa500';
                ctx.fill();
                // 绘制文字
                ctx.save();
                ctx.translate(centerX, centerY);
                ctx.rotate(startAngle + sliceAngle / 2);
                ctx.textAlign = 'right';
                ctx.fillStyle = '#ffffff';
                ctx.font = '14px Arial';
                ctx.fillText(prize.name, radius - 20, 5);
                ctx.restore();
            });
        }
        // 执行抽奖
        function startDraw() {
            // 禁用按钮
            $('.wheel-pointer').css('pointer-events', 'none');
            $.ajax({
                url: 'draw.php',
                type: 'POST',
                data: {
                    action: 'draw'
                },
                success: function(response) {
                    if (response.code === 0) {
                        // 计算旋转角度
                        const targetAngle = calculateTargetAngle(response.data.level);
                        // 执行旋转动画
                        rotateWheel(targetAngle, function() {
                            // 显示结果
                            showResult(response.data);
                            // 启用按钮
                            $('.wheel-pointer').css('pointer-events', 'auto');
                        });
                    } else {
                        alert(response.msg);
                        $('.wheel-pointer').css('pointer-events', 'auto');
                    }
                }
            });
        }
        // 计算目标角度
        function calculateTargetAngle(level) {
            const sliceAngle = 360 / prizes.length;
            // 根据奖项等级计算停留位置
            const target = level * sliceAngle;
            // 添加随机偏移,增加真实感
            const offset = Math.random() * 20 - 10;
            return 360 * 5 + target + offset; // 旋转5圈以上
        }
        // 旋转动画
        function rotateWheel(targetAngle, callback) {
            const canvas = document.getElementById('wheelCanvas');
            let currentAngle = 0;
            const duration = 3000; // 3秒
            const startTime = Date.now();
            function animate() {
                const elapsed = Date.now() - startTime;
                const progress = Math.min(elapsed / duration, 1);
                // 缓出函数
                const easeOut = 1 - Math.pow(1 - progress, 3);
                currentAngle = targetAngle * easeOut;
                // 更新旋转
                canvas.style.transform = `rotate(${currentAngle}deg)`;
                if (progress < 1) {
                    requestAnimationFrame(animate);
                } else {
                    callback();
                }
            }
            animate();
        }
        // 显示结果
        function showResult(data) {
            let message = '很遗憾,未中奖';
            if (data.win) {
                message = `恭喜获得:${data.prize_name}`;
            }
            $('#result').text(message).fadeIn();
        }
        // 初始化
        $(document).ready(function() {
            initWheel();
        });
    </script>
</body>
</html>

获取奖品列表接口

// get_prizes.php
<?php
header('Content-Type: application/json; charset=utf-8');
$sql = "SELECT id, name, level, sort FROM prizes WHERE status = 1 ORDER BY sort ASC";
$prizes = $db->query($sql)->fetchAll();
// 不返回概率和库存信息给前端
$data = [];
foreach ($prizes as $prize) {
    $data[] = [
        'id' => $prize['id'],
        'name' => $prize['name'],
        'level' => $prize['level']
    ];
}
echo json_encode([
    'code' => 0,
    'msg' => 'success',
    'data' => $data
]);

使用Redis控制并发

<?php
class RedisLuckyDraw {
    private $redis;
    public function __construct() {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    /**
     * 使用Redis控制抽奖并发
     */
    public function draw($userId) {
        // 获取分布式锁
        $lockKey = "draw_lock:{$userId}";
        $lockValue = uniqid();
        if (!$this->redis->set($lockKey, $lockValue, ['nx', 'ex' => 10])) {
            return ['code' => -3, 'msg' => '操作太频繁'];
        }
        try {
            // 检查用户每日抽奖次数
            $dailyKey = "draw_daily:{$userId}:" . date('Ymd');
            $dailyCount = $this->redis->incr($dailyKey);
            if ($dailyCount === 1) {
                $this->redis->expire($dailyKey, 86400); // 24小时过期
            }
            if ($dailyCount > 3) { // 每天限制3次
                return ['code' => -2, 'msg' => '今日抽奖次数已用完'];
            }
            // 执行抽奖
            return $this->doDraw($userId);
        } finally {
            // 释放锁
            if ($this->redis->get($lockKey) === $lockValue) {
                $this->redis->del($lockKey);
            }
        }
    }
    /**
     * 执行抽奖
     */
    private function doDraw($userId) {
        // 获取奖品列表(带概率)
        $prizes = $this->getPrizesWithProbability();
        // 抽奖逻辑
        $result = $this->calculatePrize($prizes);
        // 记录结果
        $this->saveResult($userId, $result);
        return [
            'code' => 0,
            'msg' => 'success',
            'data' => $result
        ];
    }
}

注意事项

  1. 安全性考虑

    • 抽奖逻辑必须在后端执行
    • 使用HTTPS传输数据
    • 限制IP和用户抽奖频率
  2. 性能优化

    • 使用Redis缓存奖品配置
    • 异步处理抽奖记录
    • 使用消息队列处理高并发
  3. 用户体验

    • 添加loading效果
    • 中奖后弹出精美提示
    • 支持分享到社交平台
  4. 数据统计

    • 记录抽奖参与人数
    • 统计中奖概率分布
    • 监控奖品库存变化

这个实现方案可以根据你的具体需求进行调整和优化。

抱歉,评论功能暂时关闭!