本文目录导读:

我来为您提供一个完整的Spring Boot文件上传案例,包括单文件上传、多文件上传和文件下载功能。
项目依赖配置 (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>file-upload-demo</artifactId>
<version>1.0.0</version>
<name>file-upload-demo</name>
<description>文件上传下载示例</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
配置文件 (application.yml)
server:
port: 8080
spring:
servlet:
multipart:
# 单个文件最大大小
max-file-size: 10MB
# 单个请求最大大小
max-request-size: 100MB
# 文件上传临时目录
location: ${java.io.tmpdir}
file:
# 文件存储路径
upload-path: D:/uploads/
# 允许的文件类型
allowed-types: jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,txt
# 单个文件最大大小(MB)
max-size: 10
文件上传控制器
package com.example.fileupload.controller;
import com.example.fileupload.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Slf4j
@RestController
@RequestMapping("/file")
public class FileController {
@Value("${file.upload-path}")
private String uploadPath;
@Value("${file.allowed-types}")
private String allowedTypes;
/**
* 单文件上传
*/
@PostMapping("/upload")
public Map<String, Object> uploadFile(@RequestParam("file") MultipartFile file) {
Map<String, Object> result = new HashMap<>();
try {
// 检查文件是否为空
if (file.isEmpty()) {
result.put("code", 400);
result.put("message", "文件不能为空");
return result;
}
// 检查文件大小
long maxSize = 10 * 1024 * 1024; // 10MB
if (file.getSize() > maxSize) {
result.put("code", 400);
result.put("message", "文件大小不能超过10MB");
return result;
}
// 检查文件类型
String originalFilename = file.getOriginalFilename();
String extension = StringUtils.getFilenameExtension(originalFilename);
if (!isAllowedType(extension)) {
result.put("code", 400);
result.put("message", "不支持的文件类型: " + extension);
return result;
}
// 生成唯一文件名
String newFileName = generateFileName(originalFilename);
// 创建目录(如果不存在)
Path uploadDir = Paths.get(uploadPath);
if (!Files.exists(uploadDir)) {
Files.createDirectories(uploadDir);
}
// 保存文件
Path filePath = uploadDir.resolve(newFileName);
file.transferTo(filePath.toFile());
result.put("code", 200);
result.put("message", "上传成功");
result.put("data", new HashMap<String, String>() {{
put("fileName", originalFilename);
put("newFileName", newFileName);
put("fileSize", FileUtil.formatFileSize(file.getSize()));
put("fileUrl", "/file/download/" + newFileName);
}});
} catch (Exception e) {
log.error("文件上传失败", e);
result.put("code", 500);
result.put("message", "文件上传失败: " + e.getMessage());
}
return result;
}
/**
* 多文件上传
*/
@PostMapping("/batch/upload")
public Map<String, Object> batchUpload(@RequestParam("files") MultipartFile[] files) {
Map<String, Object> result = new HashMap<>();
if (files.length == 0) {
result.put("code", 400);
result.put("message", "请选择要上传的文件");
return result;
}
java.util.List<Map<String, Object>> fileList = new java.util.ArrayList<>();
int successCount = 0;
for (MultipartFile file : files) {
Map<String, Object> fileResult = new HashMap<>();
try {
if (file.isEmpty()) {
fileResult.put("status", "failed");
fileResult.put("message", "文件为空");
} else {
String originalFilename = file.getOriginalFilename();
String extension = StringUtils.getFilenameExtension(originalFilename);
if (!isAllowedType(extension)) {
fileResult.put("status", "failed");
fileResult.put("message", "不支持的文件类型: " + extension);
} else {
// 生成唯一文件名
String newFileName = generateFileName(originalFilename);
// 保存文件
Path uploadDir = Paths.get(uploadPath);
if (!Files.exists(uploadDir)) {
Files.createDirectories(uploadDir);
}
Path filePath = uploadDir.resolve(newFileName);
file.transferTo(filePath.toFile());
fileResult.put("status", "success");
fileResult.put("fileName", originalFilename);
fileResult.put("newFileName", newFileName);
fileResult.put("fileUrl", "/file/download/" + newFileName);
successCount++;
}
}
} catch (Exception e) {
log.error("文件上传失败: {}", file.getOriginalFilename(), e);
fileResult.put("status", "failed");
fileResult.put("message", e.getMessage());
}
fileList.add(fileResult);
}
result.put("code", 200);
result.put("message", "上传完成");
result.put("total", files.length);
result.put("successCount", successCount);
result.put("failedCount", files.length - successCount);
result.put("data", fileList);
return result;
}
/**
* 文件下载
*/
@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
try {
Path filePath = Paths.get(uploadPath).resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
// 提取原始文件名(如果有的话)
String originalFileName = getOriginalFileName(fileName);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + URLEncoder.encode(originalFileName, "UTF-8") + "\"")
.body(resource);
} catch (Exception e) {
log.error("文件下载失败", e);
return ResponseEntity.status(500).build();
}
}
/**
* 在线预览文件
*/
@GetMapping("/preview/{fileName}")
public ResponseEntity<Resource> previewFile(@PathVariable String fileName) {
try {
Path filePath = Paths.get(uploadPath).resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
// 根据扩展名判断MIME类型
String contentType = Files.probeContentType(filePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.body(resource);
} catch (Exception e) {
log.error("文件预览失败", e);
return ResponseEntity.status(500).build();
}
}
/**
* 删除文件
*/
@DeleteMapping("/delete/{fileName}")
public Map<String, Object> deleteFile(@PathVariable String fileName) {
Map<String, Object> result = new HashMap<>();
try {
Path filePath = Paths.get(uploadPath).resolve(fileName).normalize();
boolean deleted = Files.deleteIfExists(filePath);
if (deleted) {
result.put("code", 200);
result.put("message", "文件删除成功");
} else {
result.put("code", 404);
result.put("message", "文件不存在");
}
} catch (Exception e) {
log.error("文件删除失败", e);
result.put("code", 500);
result.put("message", "文件删除失败: " + e.getMessage());
}
return result;
}
/**
* 获取文件列表
*/
@GetMapping("/list")
public Map<String, Object> getFileList() {
Map<String, Object> result = new HashMap<>();
try {
Path uploadDir = Paths.get(uploadPath);
if (!Files.exists(uploadDir)) {
result.put("code", 200);
result.put("data", new java.util.ArrayList<>());
return result;
}
java.util.List<Map<String, Object>> fileList = new java.util.ArrayList<>();
Files.list(uploadDir).forEach(path -> {
if (Files.isRegularFile(path)) {
try {
Map<String, Object> fileInfo = new HashMap<>();
fileInfo.put("fileName", path.getFileName().toString());
fileInfo.put("fileSize", FileUtil.formatFileSize(Files.size(path)));
fileInfo.put("createTime", Files.getLastModifiedTime(path).toString());
fileInfo.put("downloadUrl", "/file/download/" + path.getFileName());
fileList.add(fileInfo);
} catch (IOException e) {
log.error("获取文件信息失败", e);
}
}
});
result.put("code", 200);
result.put("data", fileList);
} catch (Exception e) {
log.error("获取文件列表失败", e);
result.put("code", 500);
result.put("message", "获取文件列表失败: " + e.getMessage());
}
return result;
}
/**
* 检查文件类型是否允许
*/
private boolean isAllowedType(String extension) {
if (StringUtils.isEmpty(extension)) {
return false;
}
String[] allowedTypesArray = allowedTypes.split(",");
for (String type : allowedTypesArray) {
if (type.trim().equalsIgnoreCase(extension)) {
return true;
}
}
return false;
}
/**
* 生成唯一文件名
*/
private String generateFileName(String originalFilename) {
String extension = StringUtils.getFilenameExtension(originalFilename);
return UUID.randomUUID().toString().replace("-", "") + "." + extension;
}
/**
* 获取原始文件名(从文件名映射表中获取)
*/
private String getOriginalFileName(String fileName) {
// 这里可以查询数据库获取原始文件名
// 简化版本直接使用文件名
return fileName;
}
}
文件工具类
package com.example.fileupload.util;
import java.text.DecimalFormat;
public class FileUtil {
/**
* 格式化文件大小
*/
public static String formatFileSize(long size) {
if (size <= 0) {
return "0 B";
}
String[] units = {"B", "KB", "MB", "GB", "TB"};
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
}
/**
* 检查文件扩展名
*/
public static boolean checkFileType(String fileName, String[] allowedTypes) {
String extension = getFileExtension(fileName);
if (extension == null || extension.isEmpty()) {
return false;
}
for (String type : allowedTypes) {
if (type.equalsIgnoreCase(extension)) {
return true;
}
}
return false;
}
/**
* 获取文件扩展名
*/
public static String getFileExtension(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return null;
}
int dotIndex = fileName.lastIndexOf(".");
if (dotIndex >= 0 && dotIndex < fileName.length() - 1) {
return fileName.substring(dotIndex + 1);
}
return null;
}
/**
* 生成唯一文件名
*/
public static String generateUniqueFileName(String originalFilename) {
String extension = getFileExtension(originalFilename);
String timestamp = String.valueOf(System.currentTimeMillis());
String randomStr = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
if (extension != null && !extension.isEmpty()) {
return timestamp + "_" + randomStr + "." + extension;
} else {
return timestamp + "_" + randomStr;
}
}
}
上传页面 (HTML)
创建 src/main/resources/templates/upload.html:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">文件上传下载示例</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: auto;
background-color: white;
border-radius: 8px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1, h2 {
color: #333;
}
.section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.form-group {
margin-bottom: 15px;
}
input[type="file"] {
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
width: 100%;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-right: 10px;
}
button:hover {
background-color: #45a049;
}
button.delete {
background-color: #f44336;
}
button.delete:hover {
background-color: #da190b;
}
.message {
padding: 10px;
border-radius: 4px;
margin-top: 10px;
}
.success {
background-color: #d4edda;
border-color: #c3e6cb;
color: #155724;
}
.error {
background-color: #f8d7da;
border-color: #f5c6cb;
color: #721c24;
}
#fileList {
margin-top: 15px;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border: 1px solid #eee;
margin: 5px 0;
border-radius: 4px;
}
.file-info {
flex: 1;
}
.file-actions a, .file-actions button {
margin-left: 10px;
}
.file-actions a {
color: #007bff;
text-decoration: none;
padding: 5px 10px;
border: 1px solid #007bff;
border-radius: 3px;
}
.file-actions a:hover {
background-color: #007bff;
color: white;
}
progress {
width: 100%;
height: 20px;
margin-top: 10px;
}
.progress-container {
display: none;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>文件上传下载示例</h1>
<!-- 单文件上传 -->
<div class="section">
<h2>单文件上传</h2>
<div class="form-group">
<input type="file" id="singleFile">
</div>
<button onclick="uploadSingleFile()">上传文件</button>
<div class="progress-container" id="singleProgressContainer">
<progress id="singleProgress" value="0" max="100"></progress>
</div>
<div id="singleMessage"></div>
</div>
<!-- 多文件上传 -->
<div class="section">
<h2>多文件上传</h2>
<div class="form-group">
<input type="file" id="multiFiles" multiple>
</div>
<button onclick="uploadMultiFiles()">上传文件</button>
<div class="progress-container" id="multiProgressContainer">
<progress id="multiProgress" value="0" max="100"></progress>
</div>
<div id="multiMessage"></div>
</div>
<!-- 文件列表 -->
<div class="section">
<h2>已上传文件</h2>
<button onclick="loadFileList()">刷新列表</button>
<div id="fileList"></div>
</div>
</div>
<script>
// 单文件上传
async function uploadSingleFile() {
const fileInput = document.getElementById('singleFile');
const file = fileInput.files[0];
if (!file) {
showMessage('single', '请先选择文件', 'error');
return;
}
const formData = new FormData();
formData.append('file', file);
showProgress('single', 0);
try {
const response = await fetch('/file/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
showProgress('single', 100);
if (result.code === 200) {
showMessage('single', '上传成功: ' + result.data.fileName, 'success');
loadFileList(); // 刷新列表
} else {
showMessage('single', '上传失败: ' + result.message, 'error');
}
} catch (error) {
showMessage('single', '上传失败: ' + error.message, 'error');
}
// 重置文件输入
fileInput.value = '';
}
// 多文件上传
async function uploadMultiFiles() {
const fileInput = document.getElementById('multiFiles');
const files = fileInput.files;
if (files.length === 0) {
showMessage('multi', '请先选择文件', 'error');
return;
}
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append('files', files[i]);
}
showProgress('multi', 0);
try {
const response = await fetch('/file/batch/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
showProgress('multi', 100);
if (result.code === 200) {
showMessage('multi', '上传完成: 成功' + result.successCount + '个, 失败' + result.failedCount + '个', 'success');
loadFileList(); // 刷新列表
} else {
showMessage('multi', '上传失败: ' + result.message, 'error');
}
} catch (error) {
showMessage('multi', '上传失败: ' + error.message, 'error');
}
// 重置文件输入
fileInput.value = '';
}
// 加载文件列表
async function loadFileList() {
const fileListDiv = document.getElementById('fileList');
try {
const response = await fetch('/file/list');
const result = await response.json();
if (result.code === 200) {
const files = result.data;
if (files.length === 0) {
fileListDiv.innerHTML = '<p>暂无文件</p>';
return;
}
let html = '';
files.forEach(file => {
html += `
<div class="file-item">
<div class="file-info">
<strong>${file.fileName}</strong><br>
<small>大小: ${file.fileSize} | 上传时间: ${formatDate(file.createTime)}</small>
</div>
<div class="file-actions">
<a href="${file.downloadUrl}">下载</a>
<a href="/file/preview/${file.fileName}" target="_blank">预览</a>
<button class="delete" onclick="deleteFile('${file.fileName}')">删除</button>
</div>
</div>
`;
});
fileListDiv.innerHTML = html;
} else {
fileListDiv.innerHTML = '<p>加载文件列表失败</p>';
}
} catch (error) {
fileListDiv.innerHTML = '<p>加载文件列表失败: ' + error.message + '</p>';
}
}
// 删除文件
async function deleteFile(fileName) {
if (!confirm('确定要删除文件: ' + fileName + ' 吗?')) {
return;
}
try {
const response = await fetch('/file/delete/' + encodeURIComponent(fileName), {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 200) {
alert('文件删除成功');
loadFileList();
} else {
alert('删除失败: ' + result.message);
}
} catch (error) {
alert('删除失败: ' + error.message);
}
}
// 显示消息
function showMessage(type, message, status) {
const div = document.getElementById(type + 'Message');
div.className = 'message ' + (status === 'success' ? 'success' : 'error');
div.textContent = message;
}
// 显示进度条
function showProgress(type, percent) {
const container = document.getElementById(type + 'ProgressContainer');
const progress = document.getElementById(type + 'Progress');
if (percent === 100) {
setTimeout(() => {
container.style.display = 'none';
progress.value = 0;
}, 1000);
} else {
container.style.display = 'block';
progress.value = percent;
}
}
// 格式化日期
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleString('zh-CN');
}
// 页面加载时获取文件列表
window.onload = function() {
loadFileList();
};
</script>
</body>
</html>
主页控制器
package com.example.fileupload.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class PageController {
@GetMapping("/")
public String index() {
return "upload";
}
}
启动类
package com.example.fileupload;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FileUploadApplication {
public static void main(String[] args) {
SpringApplication.run(FileUploadApplication.class, args);
}
}
测试说明
启动应用后,访问 http://localhost:8080 即可看到上传页面。
主要功能:
- 单文件上传:支持单个文件的上传
- 多文件上传:支持多个文件同时上传
- 文件下载:支持文件下载
- 文件预览:支持图片、PDF等文件在线预览
- 文件删除:支持删除已上传的文件
- 文件列表:显示所有已上传的文件
API接口:
POST /file/upload- 单文件上传POST /file/batch/upload- 多文件上传GET /file/list- 获取文件列表GET /file/download/{fileName}- 下载文件GET /file/preview/{fileName}- 预览文件DELETE /file/delete/{fileName}- 删除文件
配置说明:
在 application.yml 中可以配置:
- 文件上传路径
- 允许的文件类型
- 单个文件大小限制
- 请求总大小限制
这样就完成了Spring Boot的文件上传下载功能,包含了完整的CRUD操作,您可以根据实际需求进行修改和扩展。
版权声明: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直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播