最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

开发体育赛事平台:专家预测系统功能模块解析与技术实现全方案

网站源码admin2浏览0评论

开发体育赛事平台:专家预测系统功能模块解析与技术实现全方案

一、系统概述

专家预测系统是体育直播平台核心商业化功能之一,在 “东莞梦幻网络科技” 开发的体育直播系统中,专家功能是平台商业化的重要组成部分,允许普通用户申请成为“专家”,在平台内发布赛事预测内容,其他用户可付费查看,平台从中抽成。系统需支持:

模块

功能描述

专家认证

用户申请专家,后台审核通过后赋予专家身份

内容发布

专家发布赛事分析与预测文章,设置是否收费

付费阅读

普通用户购买后才能查看专家付费内容

数据统计

实时统计专家收益、预测胜率、粉丝量

下文将详细介绍该模块的核心功能设计与技术实现方案,包括专家审核流程、预测内容管理机制、付费系统集成、胜率与收益统计逻辑等关键技术点。

二、数据库设计(MySQL)

代码语言:sql复制
-- 用户表(简化)
CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50),
  is_expert TINYINT DEFAULT 0, -- 是否为专家
  expert_status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending'
);

-- 专家文章表
CREATE TABLE expert_articles (
  id INT PRIMARY KEY AUTO_INCREMENT,
  expert_id INT,
  title VARCHAR(100),
  content TEXT,
  is_paid TINYINT DEFAULT 0,
  price DECIMAL(10,2) DEFAULT 0.00,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 用户购买记录
CREATE TABLE article_purchases (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT,
  article_id INT,
  purchased_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 预测结果记录表(用于计算胜率)
CREATE TABLE expert_predictions (
  id INT PRIMARY KEY AUTO_INCREMENT,
  article_id INT,
  result ENUM('win', 'lose', 'pending') DEFAULT 'pending'
);

三、后台管理端(PHP + ThinkPHP)

1. 用户申请专家接口(用户端发起)

代码语言:php复制
// 控制器:ExpertController.php
public function applyExpert() {
    $userId = session('user_id');
    Db::name('users')->where('id', $userId)->update([
        'expert_status' => 'pending'
    ]);
    return json(['code' => 1, 'msg' => '申请已提交,等待审核']);
}

2. 后台审核专家申请

代码语言:php复制
// 控制器:AdminExpertController.php
public function auditExpert($userId, $status) {
    if (!in_array($status, ['approved', 'rejected'])) {
        return json(['code' => 0, 'msg' => '非法状态']);
    }

    Db::name('users')->where('id', $userId)->update([
        'is_expert' => $status == 'approved' ? 1 : 0,
        'expert_status' => $status
    ]);

    return json(['code' => 1, 'msg' => '审核完成']);
}

四、专家内容发布功能

1. 专家发布文章

代码语言:php复制
public function publishArticle(Request $request) {
    $expertId = session('user_id');

    // 验证专家身份
    $user = Db::name('users')->find($expertId);
    if (!$user || !$user['is_expert']) {
        return json(['code' => 0, 'msg' => '非专家用户']);
    }

    // 获取内容
    $title = $request->post('title');
    $content = $request->post('content');
    $isPaid = $request->post('is_paid', 0);
    $price = $request->post('price', 0.00);

    Db::name('expert_articles')->insert([
        'expert_id' => $expertId,
        'title' => $title,
        'content' => $content,
        'is_paid' => $isPaid,
        'price' => $price,
        'created_at' => date('Y-m-d H:i:s')
    ]);

    return json(['code' => 1, 'msg' => '发布成功']);
}

五、用户付费查看机制

1. 前端判断是否已购买

代码语言:php复制
public function getArticle($articleId) {
    $userId = session('user_id');
    $article = Db::name('expert_articles')->find($articleId);

    if ($article['is_paid']) {
        $hasPurchased = Db::name('article_purchases')
            ->where(['user_id' => $userId, 'article_id' => $articleId])
            ->count();
        if (!$hasPurchased) {
            return json(['code' => 2, 'msg' => '请先付费购买']);
        }
    }

    return json(['code' => 1, 'data' => $article]);
}

2. 用户付费接口

代码语言:php复制
public function buyArticle($articleId) {
    $userId = session('user_id');

    // 假设余额足够,直接扣费逻辑略
    Db::name('article_purchases')->insert([
        'user_id' => $userId,
        'article_id' => $articleId
    ]);

    return json(['code' => 1, 'msg' => '购买成功']);
}

六、专家胜率与收益统计

1. 胜率统计逻辑(后台或API)

代码语言:php复制
public function getExpertStats($expertId) {
    $articles = Db::name('expert_articles')->where('expert_id', $expertId)->column('id');
    if (!$articles) return json(['code' => 1, 'data' => ['win_rate' => '0%', 'income' => 0]]);

    $total = Db::name('expert_predictions')->whereIn('article_id', $articles)->count();
    $wins = Db::name('expert_predictions')->whereIn('article_id', $articles)->where('result', 'win')->count();

    $income = Db::name('article_purchases')
        ->whereIn('article_id', $articles)
        ->count() * Db::name('expert_articles')->whereIn('id', $articles)->sum('price');

    return json([
        'code' => 1,
        'data' => [
            'win_rate' => $total ? round($wins / $total * 100, 2) . '%' : '0%',
            'income' => $income
        ]
    ]);
}

七、Java Android客户端实现

专家预测发布Activity CreatePredictionActivity.java:

代码语言:java复制
public class CreatePredictionActivity extends AppCompatActivity {
    private EditText titleEditText;
    private EditText contentEditText;
    private EditText priceEditText;
    private SwitchCompat freeSwitch;
    private Spinner matchSpinner;
    private Button submitButton;
    
    private ApiService apiService;
    private List<Match> matches = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_create_prediction);
        
        apiService = RetrofitClient.getApiService();
        
        titleEditText = findViewById(R.id.titleEditText);
        contentEditText = findViewById(R.id.contentEditText);
        priceEditText = findViewById(R.id.priceEditText);
        freeSwitch = findViewById(R.id.freeSwitch);
        matchSpinner = findViewById(R.id.matchSpinner);
        submitButton = findViewById(R.id.submitButton);
        
        freeSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
            priceEditText.setEnabled(!isChecked);
            if (isChecked) {
                priceEditText.setText("0");
            }
        });
        
        loadMatches();
        
        submitButton.setOnClickListener(v -> submitPrediction());
    }
    
    private void loadMatches() {
        apiService.getUpcomingMatches().enqueue(new Callback<List<Match>>() {
            @Override
            public void onResponse(Call<List<Match>> call, Response<List<Match>> response) {
                if (response.isSuccessful() && response.body() != null) {
                    matches = response.body();
                    ArrayAdapter<Match> adapter = new ArrayAdapter<>(
                        CreatePredictionActivity.this,
                        android.R.layout.simple_spinner_item,
                        matches
                    );
                    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                    matchSpinner.setAdapter(adapter);
                }
            }
            
            @Override
            public void onFailure(Call<List<Match>> call, Throwable t) {
                Toast.makeText(CreatePredictionActivity.this, "加载赛事失败", Toast.LENGTH_SHORT).show();
            }
        });
    }
    
    private void submitPrediction() {
        String title = titleEditText.getText().toString().trim();
        String content = contentEditText.getText().toString().trim();
        String priceStr = priceEditText.getText().toString().trim();
        boolean isFree = freeSwitch.isChecked();
        Match selectedMatch = (Match) matchSpinner.getSelectedItem();
        
        if (title.isEmpty() || content.isEmpty()) {
            Toast.makeText(this, "请填写标题和内容", Toast.LENGTH_SHORT).show();
            return;
        }
        
        double price = 0;
        if (!isFree) {
            try {
                price = Double.parseDouble(priceStr);
                if (price <= 0) {
                    Toast.makeText(this, "价格必须大于0", Toast.LENGTH_SHORT).show();
                    return;
                }
            } catch (NumberFormatException e) {
                Toast.makeText(this, "请输入有效的价格", Toast.LENGTH_SHORT).show();
                return;
            }
        }
        
        CreatePredictionRequest request = new CreatePredictionRequest();
        request.setTitle(title);
        request.setContent(content);
        request.setPrice(price);
        request.setFree(isFree);
        if (selectedMatch != null) {
            request.setMatchId(selectedMatch.getId());
        }
        
        apiService.createPrediction(request).enqueue(new Callback<ApiResponse>() {
            @Override
            public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
                if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
                    Toast.makeText(CreatePredictionActivity.this, "预测发布成功", Toast.LENGTH_SHORT).show();
                    finish();
                } else {
                    Toast.makeText(CreatePredictionActivity.this, "预测发布失败", Toast.LENGTH_SHORT).show();
                }
            }
            
            @Override
            public void onFailure(Call<ApiResponse> call, Throwable t) {
                Toast.makeText(CreatePredictionActivity.this, "网络错误", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

八、Vue.js H5移动端实现 - 付费阅读组件

PredictionDetail.vue:

代码语言:html复制
<template>
  <div class="prediction-detail">
    <div v-if="loading" class="loading">加载中...</div>
    <div v-else>
      <h1>{{ prediction.title }}</h1>
      <div class="expert-info">
        <img :src="prediction.expert.avatar" class="avatar">
        <span>{{ prediction.expert.name }}</span>
        <span>胜率: {{ prediction.expert.winRate }}%</span>
      </div>
      
      <div v-if="hasPurchased || prediction.isFree" class="content">
        <div v-html="prediction.content"></div>
        <div v-if="prediction.match" class="match-info">
          <h3>相关赛事: {{ prediction.match.name }}</h3>
          <p>时间: {{ prediction.match.time }}</p>
        </div>
      </div>
      <div v-else class="pay-wall">
        <p>此内容需要付费查看</p>
        <p>价格: {{ prediction.price }}元</p>
        <button @click="purchase" :disabled="purchasing">
          {{ purchasing ? '购买中...' : '立即购买' }}
        </button>
      </div>
      
      <div class="stats">
        <span>浏览: {{ prediction.viewCount }}</span>
        <span>购买: {{ prediction.purchaseCount }}</span>
      </div>
    </div>
  </div>
</template>

<script>
import { getPredictionDetail, purchasePrediction } from '@/api/expert';

export default {
  data() {
    return {
      prediction: {},
      loading: true,
      hasPurchased: false,
      purchasing: false
    };
  },
  async created() {
    const id = this.$route.params.id;
    try {
      const response = await getPredictionDetail(id);
      this.prediction = response.data;
      this.hasPurchased = response.data.hasPurchased;
      this.loading = false;
      
      // 记录浏览
      this.recordView(id);
    } catch (error) {
      console.error(error);
      this.$toast.error('加载预测详情失败');
    }
  },
  methods: {
    async purchase() {
      if (this.purchasing) return;
      
      this.purchasing = true;
      try {
        const response = await purchasePrediction(this.prediction.id);
        if (response.success) {
          this.hasPurchased = true;
          this.prediction.purchaseCount += 1;
          this.$toast.success('购买成功');
        } else {
          this.$toast.error(response.message || '购买失败');
        }
      } catch (error) {
        console.error(error);
        this.$toast.error('网络错误');
      } finally {
        this.purchasing = false;
      }
    },
    async recordView(id) {
      try {
        await recordPredictionView(id);
      } catch (error) {
        console.error('记录浏览失败', error);
      }
    }
  }
};
</script>

<style scoped>
.prediction-detail {
  padding: 15px;
}
.expert-info {
  display: flex;
  align-items: center;
  margin-bottom: 15px;
}
.avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  margin-right: 10px;
}
.pay-wall {
  text-align: center;
  padding: 30px 0;
  background: #f5f5f5;
  border-radius: 5px;
  margin: 20px 0;
}
.pay-wall button {
  background: #ff6b00;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 5px;
  font-size: 16px;
  cursor: pointer;
}
.stats {
  margin-top: 20px;
  color: #999;
  font-size: 14px;
}
.stats span {
  margin-right: 15px;
}
</style>
发布评论

评论列表(0)

  1. 暂无评论