体育直播系统趣猜功能开发技术实现方案

功能概述

趣猜功能是"东莞梦幻网络科技"体育直播系统源码中的互动功能,主播可以发起竞猜题目,观众使用虚拟货币进行投注,增加直播间的互动性和趣味性。所有货币均为虚拟货币,通过系统活动获取,不可充值提现。

数据库设计 (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' }],

相关推荐
每次的天空43 分钟前
Android面试总结之Android RecyclerView:从基础机制到缓存优化
android
该怎么办呢2 小时前
原生android实现定位java实现
android·java·gitee
Android小码家3 小时前
Live555+Windows+MSys2 编译Androidso库和运行使用(三,实战篇)
android·live555
Tsing7223 小时前
Android vulkan示例
android
每次的天空4 小时前
高性能 Android 自定义 View:数据渲染与事件分发的双重优化
android
KdanMin4 小时前
Android 13组合键截屏功能的彻底移除实战
android
_祝你今天愉快4 小时前
安卓源码学习之【导航方式切换分析及实战】
android·源码
&有梦想的咸鱼&4 小时前
Android Compose 框架物理动画之弹簧动画(Spring、SpringSpec)深入剖析(二十七)
android·java·spring
Wgllss4 小时前
Android Compose轻松绘制地图可视化图表,带点击事件,可扩展二次开发
android·架构·android jetpack
SHUIPING_YANG5 小时前
MySQL 慢查询日志开启与问题排查指南
android·mysql·adb