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

功能概述

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

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

相关推荐
小蜜蜂嗡嗡19 分钟前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0025 分钟前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil2 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你2 小时前
Android View的绘制原理详解
android
移动开发者1号5 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号5 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best10 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk10 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭15 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin
aqi0015 小时前
FFmpeg开发笔记(七十七)Android的开源音视频剪辑框架RxFFmpeg
android·ffmpeg·音视频·流媒体