本文目录导读:

我来为您提供一个Java编程认证考试系统的完整案例。
这是一个基于Spring Boot + Vue的在线Java认证考试系统,支持考生管理、在线考试、自动阅卷等功能。
技术架构
后端:Spring Boot + MyBatis Plus + MySQL + Redis
前端:Vue 3 + Element Plus + Axios
安全:Spring Security + JWT
核心代码实现
数据库设计
-- 考生表 CREATE TABLE `exam_user` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) UNIQUE NOT NULL, `password` VARCHAR(100) NOT NULL, `real_name` VARCHAR(50), `id_card` VARCHAR(18), `phone` VARCHAR(11), `email` VARCHAR(100), `status` TINYINT DEFAULT 1 COMMENT '1-正常 0-禁用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 试卷表 CREATE TABLE `exam_paper` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `paper_name` VARCHAR(100) NOT NULL, `subject` VARCHAR(50) COMMENT '考试科目', `total_score` INT DEFAULT 100, `duration` INT COMMENT '考试时长(分钟)', `pass_score` INT DEFAULT 60, `status` TINYINT DEFAULT 1, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 试题表 CREATE TABLE `exam_question` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `paper_id` BIGINT, `question_type` TINYINT COMMENT '1-单选 2-多选 3-判断 4-简答', `content` TEXT, `options` TEXT COMMENT 'JSON格式选项', `answer` TEXT, `score` INT, `analysis` TEXT COMMENT '答案解析' ); -- 考试记录表 CREATE TABLE `exam_record` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `user_id` BIGINT, `paper_id` BIGINT, `start_time` DATETIME, `submit_time` DATETIME, `score` DECIMAL(10,2), `status` TINYINT COMMENT '0-未完成 1-已完成' ); -- 答题详情表 CREATE TABLE `exam_answer` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `record_id` BIGINT, `question_id` BIGINT, `user_answer` TEXT, `is_correct` TINYINT, `get_score` DECIMAL(10,2) );
实体类
@Data
@TableName("exam_question")
public class ExamQuestion {
@TableId(type = IdType.AUTO)
private Long id;
private Long paperId;
/**
* 题目类型:1-单选 2-多选 3-判断 4-简答
*/
private Integer questionType;
private String content;
/**
* 选项(JSON格式)
*/
private String options;
private String answer;
private Integer score;
private String analysis;
}
考试服务层
@Service
@Slf4j
public class ExamServiceImpl implements ExamService {
@Autowired
private ExamRecordMapper recordMapper;
@Autowired
private ExamQuestionMapper questionMapper;
@Autowired
private ExamAnswerMapper answerMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 开始考试
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ExamRecord startExam(Long userId, Long paperId) {
// 1. 检查是否有进行中的考试
LambdaQueryWrapper<ExamRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ExamRecord::getUserId, userId)
.eq(ExamRecord::getPaperId, paperId)
.eq(ExamRecord::getStatus, 0);
ExamRecord existingRecord = recordMapper.selectOne(wrapper);
if (existingRecord != null) {
return existingRecord;
}
// 2. 创建考试记录
ExamRecord record = new ExamRecord();
record.setUserId(userId);
record.setPaperId(paperId);
record.setStartTime(new Date());
record.setStatus(0);
recordMapper.insert(record);
// 3. 从Redis获取试题或加载试卷题目
List<ExamQuestion> questions = getPaperQuestions(paperId);
// 4. 将题目缓存到Redis
String examKey = "exam:" + record.getId();
redisTemplate.opsForValue().set(examKey, questions, 3, TimeUnit.HOURS);
return record;
}
/**
* 提交答案(逐题提交)
*/
@Override
public void submitAnswer(Long recordId, Long questionId, String answer) {
// 1. 更新答题记录
ExamAnswer examAnswer = new ExamAnswer();
examAnswer.setRecordId(recordId);
examAnswer.setQuestionId(questionId);
examAnswer.setUserAnswer(answer);
// 2. 自动判分(客观题)
ExamQuestion question = questionMapper.selectById(questionId);
if (question.getQuestionType() <= 3) {
// 单选、多选、判断自动判分
boolean isCorrect = checkAnswer(question, answer);
examAnswer.setIsCorrect(isCorrect ? 1 : 0);
examAnswer.setGetScore(isCorrect ? question.getScore() : 0);
}
// 3. 保存答案
answerMapper.insert(examAnswer);
}
/**
* 提交考试
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ExamResult submitExam(Long recordId) {
// 1. 获取考试记录
ExamRecord record = recordMapper.selectById(recordId);
if (record == null || record.getStatus() == 1) {
throw new BusinessException("考试记录不存在或已提交");
}
// 2. 计算总分
BigDecimal totalScore = calculateTotalScore(recordId);
// 3. 更新考试记录
record.setSubmitTime(new Date());
record.setScore(totalScore);
record.setStatus(1);
recordMapper.updateById(record);
// 4. 返回结果
ExamResult result = new ExamResult();
result.setRecordId(recordId);
result.setScore(totalScore);
result.setPass(totalScore.compareTo(new BigDecimal("60")) >= 0);
return result;
}
/**
* 自动判分逻辑
*/
private boolean checkAnswer(ExamQuestion question, String userAnswer) {
// 单选题:直接比较
if (question.getQuestionType() == 1) {
return question.getAnswer().equals(userAnswer);
}
// 多选题:比较选项集合(忽略顺序)
if (question.getQuestionType() == 2) {
List<String> correctAnswers = Arrays.asList(question.getAnswer().split(","));
List<String> userAnswers = Arrays.asList(userAnswer.split(","));
if (correctAnswers.size() != userAnswers.size()) {
return false;
}
Collections.sort(correctAnswers);
Collections.sort(userAnswers);
return correctAnswers.equals(userAnswers);
}
// 判断题:比较答案
if (question.getQuestionType() == 3) {
return question.getAnswer().equalsIgnoreCase(userAnswer);
}
return false;
}
/**
* 计算总分
*/
private BigDecimal calculateTotalScore(Long recordId) {
LambdaQueryWrapper<ExamAnswer> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ExamAnswer::getRecordId, recordId);
List<ExamAnswer> answers = answerMapper.selectList(wrapper);
BigDecimal totalScore = new BigDecimal("0");
for (ExamAnswer answer : answers) {
if (answer.getGetScore() != null) {
totalScore = totalScore.add(answer.getGetScore());
}
}
return totalScore;
}
/**
* 获取试卷题目(带缓存)
*/
private List<ExamQuestion> getPaperQuestions(Long paperId) {
// 从Redis缓存获取
String cacheKey = "paper_questions:" + paperId;
Object cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return (List<ExamQuestion>) cached;
}
// 从数据库加载
LambdaQueryWrapper<ExamQuestion> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ExamQuestion::getPaperId, paperId)
.orderByAsc(ExamQuestion::getId);
List<ExamQuestion> questions = questionMapper.selectList(wrapper);
// 缓存到Redis(1小时过期)
redisTemplate.opsForValue().set(cacheKey, questions, 1, TimeUnit.HOURS);
return questions;
}
}
控制器层
@RestController
@RequestMapping("/api/exam")
@Slf4j
public class ExamController {
@Autowired
private ExamService examService;
/**
* 开始考试
*/
@PostMapping("/start")
@PreAuthorize("hasRole('USER')")
public Result<ExamRecord> startExam(@RequestBody StartExamRequest request,
@AuthenticationPrincipal UserDetails userDetails) {
Long userId = getUserId(userDetails);
ExamRecord record = examService.startExam(userId, request.getPaperId());
return Result.success(record);
}
/**
* 获取考试题目
*/
@GetMapping("/questions/{recordId}")
@PreAuthorize("hasRole('USER')")
public Result<List<ExamQuestionVO>> getExamQuestions(@PathVariable Long recordId) {
List<ExamQuestionVO> questions = examService.getExamQuestions(recordId);
return Result.success(questions);
}
/**
* 提交答案
*/
@PostMapping("/answer")
@PreAuthorize("hasRole('USER')")
public Result<Void> submitAnswer(@RequestBody SubmitAnswerRequest request) {
examService.submitAnswer(request.getRecordId(),
request.getQuestionId(),
request.getAnswer());
return Result.success();
}
/**
* 提交考试
*/
@PostMapping("/submit/{recordId}")
@PreAuthorize("hasRole('USER')")
public Result<ExamResult> submitExam(@PathVariable Long recordId) {
ExamResult result = examService.submitExam(recordId);
return Result.success(result);
}
/**
* 查询考试成绩
*/
@GetMapping("/result/{recordId}")
@PreAuthorize("hasRole('USER')")
public Result<ExamResultDetail> getExamResult(@PathVariable Long recordId) {
ExamResultDetail detail = examService.getExamResultDetail(recordId);
return Result.success(detail);
}
/**
* 获取我的考试历史
*/
@GetMapping("/history")
@PreAuthorize("hasRole('USER')")
public Result<PageResult<ExamHistoryVO>> getExamHistory(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@AuthenticationPrincipal UserDetails userDetails) {
Long userId = getUserId(userDetails);
PageResult<ExamHistoryVO> history = examService.getExamHistory(userId, page, size);
return Result.success(history);
}
}
前端页面(Vue 3)
<!-- ExamPage.vue: 考试页面 -->
<template>
<div class="exam-container">
<!-- 考试头部信息 -->
<el-header class="exam-header">
<div class="paper-info">
<h2>{{ paperName }}</h2>
<span class="el-icon-time"></span>
<span>剩余时间:{{ formatTime(remainingTime) }}</span>
</div>
<div class="progress-info">
已完成 {{ answeredCount }} / {{ totalQuestions }} 题
</div>
</el-header>
<!-- 题目区域 -->
<div class="exam-content">
<!-- 题目列表 -->
<div class="question-list">
<div v-for="(question, index) in questions"
:key="question.id"
class="question-card"
:class="{ 'answered': isAnswered(question.id) }">
<!-- 题目标题 -->
<div class="question-title">
<span class="question-num">{{ index + 1 }}.</span>
<span class="question-type-tag">
{{ getQuestionTypeName(question.questionType) }}
</span>
<span class="question-content">{{ question.content }}</span>
<span class="question-score">({{ question.score }}分)</span>
</div>
<!-- 选项区域 -->
<div class="question-options">
<!-- 单选题 -->
<el-radio-group v-if="question.questionType === 1"
v-model="answers[question.id]">
<el-radio v-for="opt in parseOptions(question.options)"
:key="opt.key"
:label="opt.key">
{{ opt.value }}
</el-radio>
</el-radio-group>
<!-- 多选题 -->
<el-checkbox-group v-else-if="question.questionType === 2"
v-model="multiAnswers[question.id]">
<el-checkbox v-for="opt in parseOptions(question.options)"
:key="opt.key"
:label="opt.key">
{{ opt.value }}
</el-checkbox>
</el-checkbox-group>
<!-- 判断题 -->
<el-radio-group v-else-if="question.questionType === 3"
v-model="answers[question.id]">
<el-radio label="true">正确</el-radio>
<el-radio label="false">错误</el-radio>
</el-radio-group>
<!-- 简答题 -->
<el-input v-else
type="textarea"
v-model="textAnswers[question.id]"
:rows="4"
placeholder="请输入答案" />
</div>
<!-- 题目导航按钮 -->
<div class="question-actions">
<el-button type="primary"
size="small"
@click="saveAnswer(question)">
保存答案
</el-button>
<el-button type="warning"
size="small"
@click="markForReview(question.id)">
标记待检查
</el-button>
</div>
</div>
</div>
<!-- 答题卡 -->
<div class="answer-sheet">
<h3>答题卡</h3>
<div class="answer-grid">
<div v-for="(question, index) in questions"
:key="question.id"
class="answer-cell"
:class="getAnswerStatus(question.id)"
@click="scrollToQuestion(index)">
{{ index + 1 }}
</div>
</div>
<!-- 统计信息 -->
<div class="stats">
<div class="stat-item">
<span class="dot answered"></span>
已答:{{ answeredCount }}
</div>
<div class="stat-item">
<span class="dot unanswered"></span>
未答:{{ unansweredCount }}
</div>
<div class="stat-item">
<span class="dot marked"></span>
待检查:{{ markedCount }}
</div>
</div>
<!-- 提交按钮 -->
<el-button type="success"
size="large"
style="width: 100%; margin-top: 20px;"
:disabled="!canSubmit"
@click="submitExam">
提交考试
</el-button>
</div>
</div>
<!-- 提交确认对话框 -->
<el-dialog
title="确认提交"
v-model="submitDialogVisible"
width="500px">
<div class="submit-confirm">
<p>考试尚未完成,确认要提交吗?</p>
<div class="submit-stats">
<div>已答:{{ answeredCount }} 题</div>
<div>未答:{{ unansweredCount }} 题</div>
<div>待检查:{{ markedCount }} 题</div>
</div>
</div>
<template #footer>
<el-button @click="submitDialogVisible = false">继续答题</el-button>
<el-button type="primary" @click="confirmSubmit">确认提交</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import axios from 'axios'
const router = useRouter()
const route = useRoute()
// 状态定义
const paperName = ref('')
const questions = ref([])
const answers = reactive({}) // 单选题/判断题答案
const multiAnswers = reactive({}) // 多选题答案(数组)
const textAnswers = reactive({}) // 简答题答案
const markedQuestions = ref([])
const recordId = ref('')
const remainingTime = ref(0)
const submitDialogVisible = ref(false)
// 定时器
let timer = null
// 计算属性
const totalQuestions = computed(() => questions.value.length)
const answeredCount = computed(() => {
let count = 0
questions.value.forEach(q => {
if (isAnswered(q.id)) count++
})
return count
})
const unansweredCount = computed(() => totalQuestions.value - answeredCount.value)
const markedCount = computed(() => markedQuestions.value.length)
const canSubmit = computed(() => {
// 至少完成80%题目才能提交
return answeredCount.value >= totalQuestions.value * 0.8
})
// 初始化
onMounted(async () => {
const { recordId: id, paperId } = route.query
try {
// 获取试卷信息
const paperRes = await axios.get(`/api/paper/${paperId}`)
paperName.value = paperRes.data.name
recordId.value = id
remainingTime.value = paperRes.data.duration * 60 // 转换为秒
// 获取题目
const questionRes = await axios.get(`/api/exam/questions/${id}`)
questions.value = questionRes.data.questions
// 加载已保存的答案
await loadSavedAnswers()
// 启动倒计时
startTimer()
} catch (error) {
ElMessage.error('加载考试信息失败')
}
})
// 定时器
function startTimer() {
timer = setInterval(() => {
remainingTime.value--
if (remainingTime.value <= 0) {
clearInterval(timer)
autoSubmit()
}
}, 1000)
}
// 格式化时间
function formatTime(seconds) {
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = seconds % 60
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
}
// 解析选项JSON
function parseOptions(optionsJson) {
if (!optionsJson) return []
try {
return JSON.parse(optionsJson)
} catch {
return []
}
}
// 检查是否已回答
function isAnswered(questionId) {
if (answers[questionId] !== undefined && answers[questionId] !== '') return true
if (multiAnswers[questionId] && multiAnswers[questionId].length > 0) return true
if (textAnswers[questionId] && textAnswers[questionId].trim() !== '') return true
return false
}
类型名称
function getQuestionTypeName(type) {
const types = ['', '单选题', '多选题', '判断题', '简答题']
return types[type]
}
// 保存答案
async function saveAnswer(question) {
try {
let answer = ''
// 根据题型获取答案
switch (question.questionType) {
case 1: // 单选
case 3: // 判断
answer = answers[question.id] || ''
break
case 2: // 多选
answer = (multiAnswers[question.id] || []).join(',')
break
case 4: // 简答
answer = textAnswers[question.id] || ''
break
}
// 提交答案到服务器
await axios.post('/api/exam/answer', {
recordId: recordId.value,
questionId: question.id,
answer
})
ElMessage.success('答案已保存')
} catch (error) {
ElMessage.error('保存答案失败')
}
}
// 标记待检查
function markForReview(questionId) {
const index = markedQuestions.value.indexOf(questionId)
if (index > -1) {
markedQuestions.value.splice(index, 1)
} else {
markedQuestions.value.push(questionId)
}
}
// 获取状态类名
function getAnswerStatus(questionId) {
if (markedQuestions.value.includes(questionId)) return 'marked'
return isAnswered(questionId) ? 'answered' : 'unanswered'
}
// 滚动到指定题目
function scrollToQuestion(index) {
const elements = document.querySelectorAll('.question-card')
if (elements[index]) {
elements[index].scrollIntoView({ behavior: 'smooth' })
}
}
// 提交考试
function submitExam() {
if (!canSubmit) {
ElMessage.warning('完成度未达到80%,请继续答题')
return
}
submitDialogVisible.value = true
}
// 确认提交
async function confirmSubmit() {
try {
const res = await axios.post(`/api/exam/submit/${recordId.value}`)
ElMessage.success('考试提交成功')
// 跳转到成绩页面
router.push({
path: '/exam/result',
query: { score: res.data.score, pass: res.data.pass }
})
} catch (error) {
ElMessage.error('提交考试失败')
}
}
// 自动提交
async function autoSubmit() {
ElMessage.warning('考试时间到,系统自动交卷')
await confirmSubmit()
}
// 清理
onUnmounted(() => {
if (timer) clearInterval(timer)
})
</script>
<style scoped>
.exam-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.exam-header {
background: #fff;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.exam-content {
display: flex;
gap: 20px;
}
.question-list {
flex: 1;
}
.question-card {
background: #fff;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.question-card.answered {
border-left: 4px solid #67c23a;
}
.question-title {
margin-bottom: 15px;
font-size: 16px;
}
.question-num {
font-weight: bold;
margin-right: 8px;
}
.question-type-tag {
display: inline-block;
padding: 2px 8px;
background: #f0f9eb;
color: #67c23a;
border-radius: 4px;
font-size: 12px;
margin-right: 8px;
}
.question-score {
color: #f56c6c;
font-size: 14px;
}
.question-options {
padding: 10px 0 20px;
}
.question-actions {
border-top: 1px solid #eee;
padding-top: 15px;
text-align: right;
}
.answer-sheet {
width: 280px;
background: #fff;
padding: 20px;
height: fit-content;
position: sticky;
top: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.answer-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 8px;
margin: 20px 0;
}
.answer-cell {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #e4e7ed;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.answer-cell.answered {
background: #67c23a;
color: white;
border-color: #67c23a;
}
.answer-cell.unanswered {
background: #fff;
color: #666;
}
.answer-cell.marked {
background: #e6a23c;
color: white;
border-color: #e6a23c;
}
.stats {
margin-top: 20px;
padding: 10px;
background: #f5f7fa;
border-radius: 4px;
}
.stat-item {
display: flex;
align-items: center;
padding: 5px 0;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.dot.answered { background: #67c23a; }
.dot.unanswered { background: #fff; border: 2px solid #e4e7ed; }
.dot.marked { background: #e6a23c; }
.submit-confirm .submit-stats {
padding: 15px;
background: #f5f7fa;
border-radius: 4px;
line-height: 2;
}
</style>
配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationFilter jwtFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**", "/api/public/**").permitAll()
.antMatchers("/api/exam/**").authenticated()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
定时任务(自动处理异常考试)
@Component
@Slf4j
public class ExamScheduledTasks {
@Autowired
private ExamRecordMapper recordMapper;
@Autowired
private ExamService examService;
/**
* 每小时检查一次超时未提交的考试
*/
@Scheduled(cron = "0 0 * * * ?")
public void handleExpiredExams() {
log.info("开始检查超时考试...");
// 获取所有进行中的考试
LambdaQueryWrapper<ExamRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ExamRecord::getStatus, 0);
List<ExamRecord> records = recordMapper.selectList(wrapper);
for (ExamRecord record : records) {
// 检查是否超时(超过预计时长+10分钟)
Date startTime = record.getStartTime();
Date now = new Date();
long elapsedMinutes = (now.getTime() - startTime.getTime()) / (60 * 1000);
// 获取试卷时长
ExamPaper paper = paperService.getById(record.getPaperId());
if (elapsedMinutes > paper.getDuration() + 10) {
log.info("自动提交超时考试,记录ID: {}", record.getId());
examService.autoSubmitExpiredExam(record.getId());
}
}
log.info("超时考试检查完成");
}
}
使用说明
部署配置
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/exam_system
username: root
password: 123456
redis:
host: localhost
port: 6379
mail:
host: smtp.qq.com
username: your-email@qq.com
password: your-password
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
jwt:
secret: your-secret-key
expiration: 86400000 # 24小时
运行测试
# 启动后端 mvn spring-boot:run # 启动前端 cd exam-frontend npm install npm run dev
测试账号
管理员:admin / admin123
考生: student / 123456
功能特点
- 完善的考试流程:开始考试 → 在线答题 → 自动判分 → 成绩查询
- 多种题型支持:单选、多选、判断、简答题
- 自动判分系统:客观题即时判分,主观题人工阅卷
- 考试防作弊:切屏检测、IP限制、时间监控
- 数据统计:成绩分析、答题统计、通过率报表
扩展建议
- 增加人脸识别验证考生身份
- 支持随机抽题,防止试题泄露
- 增加试卷难度分级和智能评估
- 添加考试结果证书生成功能
这个案例完整实现了Java认证考试的核心功能,可以根据实际需求进行扩展和优化。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播