功能概述
趣猜功能是"东莞梦幻网络科技"体育直播系统源码中的互动功能,主播可以发起竞猜题目,观众使用虚拟货币进行投注,增加直播间的互动性和趣味性。所有货币均为虚拟货币,通过系统活动获取,不可充值提现。
数据库设计 (MySQL)
php
-- 趣猜表
CREATE TABLE `live_quiz` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`live_id` int(11) NOT NULL COMMENT '直播间ID',
`anchor_id` int(11) NOT NULL COMMENT '主播ID',
`title` varchar(255) NOT NULL COMMENT '趣猜主题',
`option_a` varchar(100) NOT NULL COMMENT '选项A',
`option_b` varchar(100) NOT NULL COMMENT '选项B',
`odds_a` decimal(5,2) NOT NULL DEFAULT '1.00' COMMENT 'A选项赔率',
`odds_b` decimal(5,2) NOT NULL DEFAULT '1.00' COMMENT 'B选项赔率',
`end_time` int(11) NOT NULL COMMENT '截止时间',
`result` tinyint(1) DEFAULT NULL COMMENT '结果:0-A赢,1-B赢,NULL-未开奖',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态:0-进行中,1-已结束,2-已开奖',
`create_time` int(11) NOT NULL,
`update_time` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `live_id` (`live_id`),
KEY `anchor_id` (`anchor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='直播间趣猜表';
-- 用户投注表
CREATE TABLE `live_quiz_bet` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`quiz_id` int(11) NOT NULL COMMENT '趣猜ID',
`user_id` int(11) NOT NULL COMMENT '用户ID',
`option` tinyint(1) NOT NULL COMMENT '投注选项:0-A,1-B',
`amount` int(11) NOT NULL COMMENT '投注金额',
`potential_win` int(11) NOT NULL COMMENT '潜在收益',
`is_win` tinyint(1) DEFAULT NULL COMMENT '是否赢:0-输,1-赢,NULL-未开奖',
`win_amount` int(11) DEFAULT NULL COMMENT '实际赢取金额',
`create_time` int(11) NOT NULL,
`update_time` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `quiz_id` (`quiz_id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户投注表';
-- 用户虚拟货币表
CREATE TABLE `user_virtual_currency` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`balance` int(11) NOT NULL DEFAULT '0' COMMENT '余额',
`total_earn` int(11) NOT NULL DEFAULT '0' COMMENT '累计获得',
`total_spend` int(11) NOT NULL DEFAULT '0' COMMENT '累计消费',
`create_time` int(11) NOT NULL,
`update_time` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户虚拟货币表';
PHP后端实现 (ThinkPHP)
控制器 LiveQuizController.php
php
<?php
namespace app\api\controller;
use think\Controller;
use think\Request;
use app\common\model\LiveQuiz;
use app\common\model\LiveQuizBet;
use app\common\model\UserVirtualCurrency;
class LiveQuizController extends Controller
{
// 主播发起趣猜
public function create(Request $request)
{
$user = $request->user;
$data = $request->only(['live_id', 'title', 'option_a', 'option_b', 'odds_a', 'odds_b', 'end_time']);
// 验证数据
$validate = new \think\Validate([
'live_id' => 'require|number',
'title' => 'require|max:255',
'option_a' => 'require|max:100',
'option_b' => 'require|max:100',
'odds_a' => 'require|float|>:0',
'odds_b' => 'require|float|>:0',
'end_time' => 'require|number|>:time'
]);
if (!$validate->check($data)) {
return json(['code' => 400, 'msg' => $validate->getError()]);
}
$data['anchor_id'] = $user->id;
$data['create_time'] = time();
$data['update_time'] = time();
$quiz = LiveQuiz::create($data);
// 广播消息到直播间
$this->broadcastQuizCreate($quiz);
return json(['code' => 200, 'msg' => '趣猜创建成功', 'data' => $quiz]);
}
// 用户投注
public function bet(Request $request)
{
$user = $request->user;
$data = $request->only(['quiz_id', 'option', 'amount']);
// 验证数据
$validate = new \think\Validate([
'quiz_id' => 'require|number',
'option' => 'require|in:0,1',
'amount' => 'require|number|>:0'
]);
if (!$validate->check($data)) {
return json(['code' => 400, 'msg' => $validate->getError()]);
}
// 检查趣猜是否存在且可投注
$quiz = LiveQuiz::where('id', $data['quiz_id'])
->where('status', 0)
->where('end_time', '>', time())
->find();
if (!$quiz) {
return json(['code' => 400, 'msg' => '该趣猜已结束或不存在']);
}
// 检查用户余额
$currency = UserVirtualCurrency::where('user_id', $user->id)->find();
if (!$currency || $currency->balance < $data['amount']) {
return json(['code' => 400, 'msg' => '虚拟货币不足']);
}
// 计算潜在收益
$odds = $data['option'] == 0 ? $quiz->odds_a : $quiz->odds_b;
$potential_win = floor($data['amount'] * $odds);
// 开始事务
Db::startTrans();
try {
// 扣除用户余额
UserVirtualCurrency::where('user_id', $user->id)
->update([
'balance' => Db::raw('balance-'.$data['amount']),
'total_spend' => Db::raw('total_spend+'.$data['amount']),
'update_time' => time()
]);
// 创建投注记录
$bet = LiveQuizBet::create([
'quiz_id' => $data['quiz_id'],
'user_id' => $user->id,
'option' => $data['option'],
'amount' => $data['amount'],
'potential_win' => $potential_win,
'create_time' => time(),
'update_time' => time()
]);
// 广播投注消息到直播间
$this->broadcastBet($quiz->live_id, [
'user_id' => $user->id,
'nickname' => $user->nickname,
'option' => $data['option'],
'amount' => $data['amount']
]);
Db::commit();
return json(['code' => 200, 'msg' => '投注成功', 'data' => $bet]);
} catch (\Exception $e) {
Db::rollback();
return json(['code' => 500, 'msg' => '投注失败:'.$e->getMessage()]);
}
}
// 主播开奖
public function settle(Request $request)
{
$user = $request->user;
$quiz_id = $request->param('quiz_id');
$result = $request->param('result');
// 验证数据
if (!in_array($result, [0, 1])) {
return json(['code' => 400, 'msg' => '无效的结果']);
}
// 检查趣猜是否存在且可开奖
$quiz = LiveQuiz::where('id', $quiz_id)
->where('anchor_id', $user->id)
->where('status', 0)
->where('end_time', '<', time())
->find();
if (!$quiz) {
return json(['code' => 400, 'msg' => '该趣猜不能开奖']);
}
// 开始事务
Db::startTrans();
try {
// 更新趣猜结果
$quiz->result = $result;
$quiz->status = 2;
$quiz->update_time = time();
$quiz->save();
// 获取所有赢的投注
$winBets = LiveQuizBet::where('quiz_id', $quiz_id)
->where('option', $result)
->select();
// 发放奖励
foreach ($winBets as $bet) {
$winAmount = $bet->potential_win;
// 更新投注记录
$bet->is_win = 1;
$bet->win_amount = $winAmount;
$bet->update_time = time();
$bet->save();
// 增加用户余额
UserVirtualCurrency::where('user_id', $bet->user_id)
->update([
'balance' => Db::raw('balance+'.$winAmount),
'total_earn' => Db::raw('total_earn+'.$winAmount),
'update_time' => time()
]);
}
// 更新输的投注
LiveQuizBet::where('quiz_id', $quiz_id)
->where('option', $result == 0 ? 1 : 0)
->update([
'is_win' => 0,
'win_amount' => 0,
'update_time' => time()
]);
// 广播开奖消息到直播间
$this->broadcastSettle($quiz->live_id, [
'quiz_id' => $quiz->id,
'result' => $result,
'option_a' => $quiz->option_a,
'option_b' => $quiz->option_b
]);
Db::commit();
return json(['code' => 200, 'msg' => '开奖成功']);
} catch (\Exception $e) {
Db::rollback();
return json(['code' => 500, 'msg' => '开奖失败:'.$e->getMessage()]);
}
}
// 获取趣猜列表
public function list(Request $request)
{
$live_id = $request->param('live_id');
$status = $request->param('status', 0);
$list = LiveQuiz::where('live_id', $live_id)
->where('status', $status)
->order('create_time', 'desc')
->select();
return json(['code' => 200, 'msg' => 'success', 'data' => $list]);
}
// 获取趣猜详情
public function detail(Request $request)
{
$quiz_id = $request->param('quiz_id');
$user_id = $request->user->id;
$quiz = LiveQuiz::find($quiz_id);
if (!$quiz) {
return json(['code' => 404, 'msg' => '趣猜不存在']);
}
// 获取投注统计
$betStats = LiveQuizBet::where('quiz_id', $quiz_id)
->field('option, count(*) as bet_count, sum(amount) as total_amount')
->group('option')
->select();
$stats = [
'option_a' => ['bet_count' => 0, 'total_amount' => 0],
'option_b' => ['bet_count' => 0, 'total_amount' => 0]
];
foreach ($betStats as $stat) {
if ($stat['option'] == 0) {
$stats['option_a'] = [
'bet_count' => $stat['bet_count'],
'total_amount' => $stat['total_amount']
];
} else {
$stats['option_b'] = [
'bet_count' => $stat['bet_count'],
'total_amount' => $stat['total_amount']
];
}
}
// 获取用户投注
$userBet = LiveQuizBet::where('quiz_id', $quiz_id)
->where('user_id', $user_id)
->find();
$quiz->stats = $stats;
$quiz->user_bet = $userBet;
return json(['code' => 200, 'msg' => 'success', 'data' => $quiz]);
}
// 广播消息方法
private function broadcastQuizCreate($quiz)
{
// 这里实现WebSocket或其它方式的消息广播
// 实际项目中可以使用Swoole、Workerman或第三方推送服务
}
private function broadcastBet($live_id, $data)
{
// 广播投注消息
}
private function broadcastSettle($live_id, $data)
{
// 广播开奖消息
}
}
前端Vue.js实现
主播端组件 AnchorQuizPanel.vue
php
<template>
<div class="quiz-panel">
<h3>发起趣猜</h3>
<el-form :model="quizForm" :rules="rules" ref="quizForm" label-width="100px">
<el-form-item label="趣猜主题" prop="title">
<el-input v-model="quizForm.title" placeholder="例如:本场比赛哪队会获胜?"></el-input>
</el-form-item>
<el-form-item label="选项A" prop="option_a">
<el-input v-model="quizForm.option_a" placeholder="例如:主队"></el-input>
</el-form-item>
<el-form-item label="选项B" prop="option_b">
<el-input v-model="quizForm.option_b" placeholder="例如:客队"></el-input>
</el-form-item>
<el-form-item label="赔率A" prop="odds_a">
<el-input-number v-model="quizForm.odds_a" :min="1" :step="0.1" :precision="2"></el-input-number>
</el-form-item>
<el-form-item label="赔率B" prop="odds_b">
<el-input-number v-model="quizForm.odds_b" :min="1" :step="0.1" :precision="2"></el-input-number>
</el-form-item>
<el-form-item label="截止时间" prop="end_time">
<el-date-picker
v-model="quizForm.end_time"
type="datetime"
placeholder="选择截止时间"
:picker-options="pickerOptions">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitQuiz">发起趣猜</el-button>
</el-form-item>
</el-form>
<div class="active-quiz-list" v-if="activeQuizzes.length > 0">
<h3>进行中的趣猜</h3>
<div class="quiz-item" v-for="quiz in activeQuizzes" :key="quiz.id">
<div class="quiz-title">{{ quiz.title }}</div>
<div class="quiz-options">
<span class="option-a">{{ quiz.option_a }} (赔率:{{ quiz.odds_a }})</span>
<span class="vs">VS</span>
<span class="option-b">{{ quiz.option_b }} (赔率:{{ quiz.odds_b }})</span>
</div>
<div class="quiz-endtime">截止时间: {{ formatTime(quiz.end_time) }}</div>
<div class="quiz-stats">
<span>A: {{ quiz.stats.option_a.bet_count }}人投注, {{ quiz.stats.option_a.total_amount }}币</span>
<span>B: {{ quiz.stats.option_b.bet_count }}人投注, {{ quiz.stats.option_b.total_amount }}币</span>
</div>
<el-button
type="success"
size="small"
@click="settleQuiz(quiz.id, 0)"
:disabled="quiz.end_time > (Date.now()/1000)">
开奖: {{ quiz.option_a }}
</el-button>
<el-button
type="danger"
size="small"
@click="settleQuiz(quiz.id, 1)"
:disabled="quiz.end_time > (Date.now()/1000)">
开奖: {{ quiz.option_b }}
</el-button>
</div>
</div>
</div>
</template>
<script>
import { createQuiz, settleQuiz, getActiveQuizzes } from '@/api/liveQuiz';
export default {
props: {
liveId: {
type: Number,
required: true
}
},
data() {
return {
quizForm: {
live_id: this.liveId,
title: '',
option_a: '',
option_b: '',
odds_a: 1.8,
odds_b: 1.8,
end_time: new Date(Date.now() + 30 * 60 * 1000) // 默认30分钟后截止
},
rules: {
title: [{ required: true, message: '请输入趣猜主题', trigger: 'blur' }],
option_a: [{ required: true, message: '请输入选项A', trigger: 'blur' }],