本文目录导读:

我来详细介绍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
];
}
}
注意事项
-
安全性考虑
- 抽奖逻辑必须在后端执行
- 使用HTTPS传输数据
- 限制IP和用户抽奖频率
-
性能优化
- 使用Redis缓存奖品配置
- 异步处理抽奖记录
- 使用消息队列处理高并发
-
用户体验
- 添加loading效果
- 中奖后弹出精美提示
- 支持分享到社交平台
-
数据统计
- 记录抽奖参与人数
- 统计中奖概率分布
- 监控奖品库存变化
这个实现方案可以根据你的具体需求进行调整和优化。