本文目录导读:

我来为您详细讲解Spring Boot自定义Starter的完整案例,这个案例将创建一个短信发送的Starter,包含自动配置、属性绑定和条件装配等功能。
项目结构
sms-spring-boot-starter/ ├── pom.xml ├── src/ │ └── main/ │ ├── java/ │ │ └── com/example/sms/ │ │ ├── SmsAutoConfiguration.java │ │ ├── SmsProperties.java │ │ ├── SmsService.java │ │ ├── SmsSender.java │ │ ├── SmsType.java │ │ └── condition/ │ │ └── SmsCondition.java │ └── resources/ │ └── META-INF/ │ └── spring.factories └── ...
创建Starter模块
1 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>
<groupId>com.example</groupId>
<artifactId>sms-spring-boot-starter</artifactId>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
<spring.boot.version>2.7.0</spring.boot.version>
</properties>
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
<!-- Spring Boot Autoconfigure -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
<!-- Configuration Properties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<optional>true</optional>
</dependency>
<!-- Gson for JSON -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
2 配置属性类
// SmsProperties.java
package com.example.sms;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* 短信配置属性
*/
@ConfigurationProperties(prefix = "sms")
@EnableConfigurationProperties(SmsProperties.class)
public class SmsProperties {
/**
* 是否启用短信服务
*/
private boolean enabled = true;
/**
* 短信服务提供商
*/
private Provider provider = Provider.ALIYUN;
/**
* 阿里云配置
*/
private AliyunConfig aliyun = new AliyunConfig();
/**
* 腾讯云配置
*/
private TencentConfig tencent = new TencentConfig();
/**
* 短信默认签名
*/
private String signName = "测试签名";
/**
* 短信模板ID
*/
private String templateCode = "SMS_000000";
/**
* 短信服务提供商枚举
*/
public enum Provider {
ALIYUN, TENCENT
}
/**
* 阿里云配置
*/
public static class AliyunConfig {
private String accessKeyId = "your-access-key-id";
private String accessKeySecret = "your-access-key-secret";
private String endpoint = "dysmsapi.aliyuncs.com";
// Getters and Setters
}
/**
* 腾讯云配置
*/
public static class TencentConfig {
private String secretId = "your-secret-id";
private String secretKey = "your-secret-key";
private String region = "ap-guangzhou";
// Getters and Setters
}
// Getters and Setters
}
3 短信接口和实现
// SmsSender.java
package com.example.sms;
/**
* 短信发送接口
*/
public interface SmsSender {
/**
* 发送短信
* @param phone 手机号
* @param content 短信内容
* @return 发送结果
*/
boolean send(String phone, String content);
}
// AliyunSmsSender.java
package com.example.sms;
/**
* 阿里云短信发送实现
*/
public class AliyunSmsSender implements SmsSender {
private final SmsProperties properties;
public AliyunSmsSender(SmsProperties properties) {
this.properties = properties;
}
@Override
public boolean send(String phone, String content) {
// 模拟阿里云短信发送
System.out.println("使用阿里云发送短信");
System.out.println("手机号: " + phone);
System.out.println("内容: " + content);
System.out.println("签名: " + properties.getSignName());
System.out.println("模板: " + properties.getTemplateCode());
System.out.println("AccessKey: " + properties.getAliyun().getAccessKeyId());
// 这里应该调用阿里云SDK发送短信
return true;
}
}
// TencentSmsSender.java
package com.example.sms;
/**
* 腾讯云短信发送实现
*/
public class TencentSmsSender implements SmsSender {
private final SmsProperties properties;
public TencentSmsSender(SmsProperties properties) {
this.properties = properties;
}
@Override
public boolean send(String phone, String content) {
// 模拟腾讯云短信发送
System.out.println("使用腾讯云发送短信");
System.out.println("手机号: " + phone);
System.out.println("内容: " + content);
System.out.println("签名: " + properties.getSignName());
System.out.println("模板: " + properties.getTemplateCode());
System.out.println("SecretId: " + properties.getTencent().getSecretId());
// 这里应该调用腾讯云SDK发送短信
return true;
}
}
4 短信服务门面
// SmsService.java
package com.example.sms;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 短信服务门面
*/
public class SmsService {
private final SmsProperties properties;
private final SmsSender smsSender;
@Autowired
public SmsService(SmsProperties properties, SmsSender smsSender) {
this.properties = properties;
this.smsSender = smsSender;
}
/**
* 发送短信
*/
@SuppressWarnings("unused")
public boolean sendSms(String phone, String content) {
return smsSender.send(phone, content);
}
/**
* 发送验证码
*/
public boolean sendVerificationCode(String phone, String code) {
String content = "您的验证码是:" + code + ",请在10分钟内完成验证。";
return sendSms(phone, content);
}
/**
* 发送营销短信
*/
public boolean sendMarketingMessage(String phone, String content) {
String marketingContent = "【营销】" + content;
return sendSms(phone, marketingContent);
}
/**
* 获取当前短信服务提供商配置
*/
public String getCurrentProviderInfo() {
if (properties.getProvider() == SmsProperties.Provider.ALIYUN) {
return "当前使用阿里云短信服务:AccessKey=" + properties.getAliyun().getAccessKeyId();
} else {
return "当前使用腾讯云短信服务:SecretId=" + properties.getTencent().getSecretId();
}
}
}
5 构造器绑定类
// SmsBuilder.java
package com.example.sms;
/**
* 短信构建器
*/
@SuppressWarnings("unused")
public class SmsBuilder {
private final SmsSender smsSender;
private final SmsProperties properties;
public SmsBuilder(SmsSender smsSender, SmsProperties properties) {
this.smsSender = smsSender;
this.properties = properties;
}
/**
* 构建短信服务
*/
public SmsService build() {
return new SmsService(properties, smsSender);
}
/**
* 构建验证码发送器
*/
public VerificationCodeSender buildVerificationCodeSender(int expireMinutes) {
return new VerificationCodeSender(smsSender, expireMinutes);
}
}
/**
* 验证码发送器
*/
class VerificationCodeSender {
private final SmsSender smsSender;
private final int expireMinutes;
private final Map<String, String> codes = new ConcurrentHashMap<>();
public VerificationCodeSender(SmsSender smsSender, int expireMinutes) {
this.smsSender = smsSender;
this.expireMinutes = expireMinutes;
}
public boolean sendCode(String phone) {
String code = generateCode();
codes.put(phone, code);
return smsSender.send(phone, "您的验证码是:" + code + ",有效期" + expireMinutes + "分钟");
}
public boolean verifyCode(String phone, String inputCode) {
String savedCode = codes.get(phone);
return savedCode != null && savedCode.equals(inputCode);
}
private String generateCode() {
return String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
}
}
6 条件注解
// SmsCondition.java
package com.example.sms.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.StringUtils;
/**
* 短信服务启用条件
*/
public class SmsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String enabled = context.getEnvironment().getProperty("sms.enabled", "true");
if (StringUtils.hasText(enabled)) {
return Boolean.parseBoolean(enabled);
}
return true;
}
}
7 自动配置类
// SmsAutoConfiguration.java
package com.example.sms;
import com.example.sms.condition.SmsCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* 短信自动配置类
*/
@Configuration
@EnableConfigurationProperties(SmsProperties.class)
@ConditionalOnProperty(prefix = "sms", name = "enabled", havingValue = "true", matchIfMissing = true)
public class SmsAutoConfiguration {
/**
* 创建阿里云短信发送器
*/
@Bean
@ConditionalOnMissingBean
@Conditional(SmsCondition.class)
public SmsSender aliyunSmsSender(SmsProperties properties) {
if (properties.getProvider() == SmsProperties.Provider.ALIYUN) {
return new AliyunSmsSender(properties);
}
return null;
}
/**
* 创建腾讯云短信发送器
*/
@Bean
@ConditionalOnMissingBean
@Conditional(SmsCondition.class)
public SmsSender tencentSmsSender(SmsProperties properties) {
if (properties.getProvider() == SmsProperties.Provider.TENCENT) {
return new TencentSmsSender(properties);
}
return null;
}
/**
* 创建短信服务门面
*/
@Bean
@ConditionalOnMissingBean
public SmsService smsService(SmsProperties properties, SmsSender smsSender) {
return new SmsService(properties, smsSender);
}
/**
* 创建短信构建器
*/
@Bean
@ConditionalOnMissingBean
public SmsBuilder smsBuilder(SmsSender smsSender, SmsProperties properties) {
return new SmsBuilder(smsSender, properties);
}
}
8 自动配置注册
使用 spring.factories(推荐)
# src/main/resources/META-INF/spring.factories org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.sms.SmsAutoConfiguration
使用 AutoConfiguration.imports(Spring Boot 2.7+)
# src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports com.example.sms.SmsAutoConfiguration
创建使用示例项目
1 示例项目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.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>sms-starter-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 我们的自定义Starter -->
<dependency>
<groupId>com.example</groupId>
<artifactId>sms-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2 应用配置
# application.yml
server:
port: 8080
sms:
enabled: true
provider: ALIYUN # 或 TENCENT
sign-name: 测试签名
template-code: SMS_123456
aliyun:
access-key-id: LTAI5tXXXXXXX
access-key-secret: your-secret
endpoint: dysmsapi.aliyuncs.com
tencent:
secret-id: AKIDXXXX
secret-key: your-secret-key
region: ap-guangzhou
3 应用启动类和控制器
// Application.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// 获取短信服务并测试
SmsService smsService = context.getBean(SmsService.class);
System.out.println(smsService.getCurrentProviderInfo());
}
}
// SmsController.java
package com.example.demo.controller;
import com.example.sms.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 短信控制层
*/
@RestController
@RequestMapping("/api/sms")
public class SmsController {
@Autowired
private SmsService smsService;
/**
* 发送短信
*/
@PostMapping("/send")
public String sendSms(@RequestParam String phone, @RequestParam String content) {
boolean result = smsService.sendSms(phone, content);
return result ? "发送成功" : "发送失败";
}
/**
* 发送验证码
*/
@PostMapping("/verification")
public String sendVerificationCode(@RequestParam String phone) {
String code = generateCode();
boolean result = smsService.sendVerificationCode(phone, code);
return result ? "验证码发送成功: " + code : "发送失败";
}
/**
* 发送营销短信
*/
@PostMapping("/marketing")
public String sendMarketing(@RequestParam String phone, @RequestParam String content) {
boolean result = smsService.sendMarketingMessage(phone, content);
return result ? "营销短信发送成功" : "发送失败";
}
/**
* 获取当前配置信息
*/
@GetMapping("/config")
public String getConfig() {
return smsService.getCurrentProviderInfo();
}
private String generateCode() {
return String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
}
}
4 高级用法示例
// SmsBuilderDemo.java
package com.example.demo;
import com.example.sms.SmsBuilder;
import com.example.sms.SmsService;
import com.example.sms.VerificationCodeSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SmsBuilderDemo {
@Autowired
private SmsBuilder smsBuilder;
public void demoBuilder() {
// 构建自定义验证码发送器
VerificationCodeSender codeSender = smsBuilder.buildVerificationCodeSender(5);
// 发送验证码
boolean sent = codeSender.sendCode("13800138000");
// 验证验证码
boolean verified = codeSender.verifyCode("13800138000", "123456");
}
public void demoSmsService() {
// 通过SmsService直接发送
SmsService smsService = smsBuilder.build();
smsService.sendSms("13800138000", "Hello Spring Boot Starter!");
}
}
Starter优化选项
1 添加监控端点
// SmsHealthIndicator.java
package com.example.sms.actuator;
import com.example.sms.SmsService;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
/**
* 短信服务健康检查
*/
@Component
public class SmsHealthIndicator implements HealthIndicator {
private final SmsService smsService;
public SmsHealthIndicator(SmsService smsService) {
this.smsService = smsService;
}
@Override
public Health health() {
try {
// 模拟检查短信服务
String info = smsService.getCurrentProviderInfo();
return Health.up()
.withDetail("smsProvider", info)
.withDetail("status", "短信服务运行正常")
.build();
} catch (Exception e) {
return Health.down(e).withDetail("smsProvider", "短信服务不可用").build();
}
}
}
2 支持多配置源
// SmsConfigMerger.java
package com.example.sms.support;
import com.example.sms.SmsProperties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 配置合并器
*/
@Component
public class SmsConfigMerger {
@Value("${sms.provider:ALIYUN}")
private String provider;
/**
* 合并配置优先级:命令行 > 配置文件 > 默认配置
*/
public SmsProperties mergeConfig(SmsProperties properties) {
// 可以根据实际需求实现复杂的配置合并逻辑
return properties;
}
}
实际使用验证
1 编写单元测试
// SmsServiceTest.java
package com.example.demo;
import com.example.sms.SmsService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
public class SmsServiceTest {
@Autowired
private SmsService smsService;
@Test
public void testSendSms() {
boolean result = smsService.sendSms("13800138000", "Test message");
assertTrue(result);
}
@Test
public void testSendVerificationCode() {
boolean result = smsService.sendVerificationCode("13800138000", "123456");
assertTrue(result);
}
@Test
public void testGetProviderInfo() {
String info = smsService.getCurrentProviderInfo();
System.out.println(info);
assertTrue(!info.isEmpty());
}
}
2 使用curl测试
# 发送普通短信 curl -X POST "http://localhost:8080/api/sms/send?phone=13800138000&content=Hello" # 发送验证码 curl -X POST "http://localhost:8080/api/sms/verification?phone=13800138000" # 发送营销短信 curl -X POST "http://localhost:8080/api/sms/marketing?phone=13800138000&content=促销活动" # 获取配置信息 curl GET "http://localhost:8080/api/sms/config"
部署和安装Starter
1 安装到本地Maven仓库
cd sms-spring-boot-starter mvn clean install
2 部署到远程仓库
在pom.xml中添加部署配置:
<distributionManagement>
<repository>
<id>releases</id>
<url>http://your-nexus/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<url>http://your-nexus/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
然后执行:
mvn deploy
1 Starter的关键部分
- 自动配置类:提供自动配置逻辑
- 属性绑定类:读取配置并绑定到Java对象
- 条件注解:控制配置何时生效
- 服务实现:提供业务功能实现
- spring.factories:注册自动配置类
2 设计原则
- 命名规范:
xxx-spring-boot-starter - 条件装配:使用
@ConditionalOn*注解 - 属性设置:使用
@ConfigurationProperties - 自动配置:通过
@EnableAutoConfiguration自动生效 - 可选依赖:使用
optional标记依赖 - 简化使用:让使用者通过极少的配置即可使用
3 使用建议
- 配置属性要有默认值
- 提供必要的校验逻辑
- 支持自动配置和手动配置的切换
- 提供良好的文档和示例
- 做好向后兼容性
这个完整的Starter案例展示了如何创建一个功能完整、可配置、易用的Spring Boot Starter,包含了实际项目中常用的各种模式和最佳实践。
版权声明: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直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播