功能概述
趣猜功能是“东莞梦幻网络科技”体育直播系统源码中的互动功能,主播可以发起竞猜题目,观众使用虚拟货币进行投注,增加直播间的互动性和趣味性。所有货币均为虚拟货币,通过系统活动获取,不可充值提现。
数据库设计 (MySQL)
-- 趣猜表
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
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
<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-pickerv-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' }],