本文目录导读:

我来为您提供一个完整的Spring Boot接口限流案例,包含多种实现方式。
项目依赖配置
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis (用于分布式限流) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
自定义限流注解
package com.example.ratelimiter.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 限流注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
/**
* 限流key
*/
String key() default "rate:limit:";
/**
* 限流时间窗口(秒)
*/
int timeWindow() default 60;
/**
* 时间窗口内最大请求数
*/
int limit() default 100;
/**
* 限流类型
*/
RateLimitType type() default RateLimitType.TOKEN_BUCKET;
/**
* 限流提示信息
*/
String message() default "系统繁忙,请稍后再试";
enum RateLimitType {
/**
* 计数器限流
*/
COUNTER,
/**
* 令牌桶限流
*/
TOKEN_BUCKET,
/**
* 滑动窗口限流
*/
SLIDING_WINDOW,
/**
* 漏桶限流
*/
LEAKY_BUCKET
}
}
限流实现类
1 计数器限流(单机版)
package com.example.ratelimiter.limiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 计数器限流器(单机版)
*/
@Slf4j
@Component
public class CounterRateLimiter {
// 存储每个key的计数信息
private final ConcurrentHashMap<String, AtomicLong> counters = new ConcurrentHashMap<>();
// 存储每个key的窗口开始时间
private final ConcurrentHashMap<String, Long> windowStartTimes = new ConcurrentHashMap<>();
/**
* 是否允许请求
*/
public boolean tryAcquire(String key, int limit) {
long currentTime = System.currentTimeMillis();
long windowSize = 1000L; // 1秒窗口
// 获取或创建计数器
AtomicLong counter = counters.computeIfAbsent(key, k -> new AtomicLong(0));
// 获取或创建窗口开始时间
Long windowStartTime = windowStartTimes.putIfAbsent(key, currentTime);
if (windowStartTime == null) {
windowStartTime = currentTime;
}
// 如果是新的时间窗口,重置计数器和开始时间
if (currentTime - windowStartTime >= windowSize) {
synchronized (this) {
if (currentTime - windowStartTimes.get(key) >= windowSize) {
counter.set(0);
windowStartTimes.put(key, currentTime);
}
}
}
// 判断是否超过限制
long currentCount = counter.incrementAndGet();
return currentCount <= limit;
}
/**
* 清理过期数据
*/
public void cleanup() {
long currentTime = System.currentTimeMillis();
counters.entrySet().removeIf(entry ->
currentTime - windowStartTimes.getOrDefault(entry.getKey(), 0L) >= 10000L
);
windowStartTimes.entrySet().removeIf(entry ->
currentTime - entry.getValue() >= 10000L
);
}
}
2 令牌桶限流器
package com.example.ratelimiter.limiter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
/**
* 令牌桶限流器(单机版)
*/
@Slf4j
public class TokenBucketRateLimiter {
private final ConcurrentHashMap<String, TokenBucket> buckets = new ConcurrentHashMap<>();
/**
* 创建或获取令牌桶
*/
private TokenBucket getBucket(String key, int capacity) {
return buckets.computeIfAbsent(key, k -> new TokenBucket(capacity));
}
/**
* 是否允许请求
*/
public boolean tryAcquire(String key, int capacity, int refillRate) {
TokenBucket bucket = getBucket(key, capacity);
return bucket.tryConsume(refillRate);
}
/**
* 令牌桶内部类
*/
private static class TokenBucket {
private final int capacity; // 桶容量
private double tokens; // 当前令牌数
private long lastRefillTime; // 上次填充时间
public TokenBucket(int capacity) {
this.capacity = capacity;
this.tokens = capacity;
this.lastRefillTime = System.currentTimeMillis();
}
/**
* 尝试消耗一个令牌
*/
public synchronized boolean tryConsume(double refillRate) {
refill(refillRate);
if (tokens >= 1) {
tokens -= 1;
return true;
}
return false;
}
/**
* 填充令牌
*/
private void refill(double refillRate) {
long now = System.currentTimeMillis();
double tokensToAdd = (now - lastRefillTime) / 1000.0 * refillRate;
tokens = Math.min(capacity, tokens + tokensToAdd);
lastRefillTime = now;
}
}
}
3 Redis滑动窗口限流器
package com.example.ratelimiter.limiter;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
/**
* 基于Redis的滑动窗口限流器(分布式)
*/
@Component
@RequiredArgsConstructor
public class RedisSlidingWindowRateLimiter {
private final RedisTemplate<String, String> redisTemplate;
// Lua脚本:原子性实现滑动窗口计数
private static final String SLIDING_WINDOW_SCRIPT =
"local key = KEYS[1] " +
"local now = tonumber(ARGV[1]) " +
"local windowSize = tonumber(ARGV[2]) " +
"local limit = tonumber(ARGV[3]) " +
"local windowStart = now - windowSize * 1000 " +
"" +
"redis.call('ZREMRANGEBYSCORE', key, 0, windowStart) " +
"" +
"local currentCount = redis.call('ZCARD', key) " +
"if currentCount < limit then " +
" redis.call('ZADD', key, now, now .. '-' .. math.random(1, 999999)) " +
" redis.call('PEXPIRE', key, windowSize * 1000) " +
" return 1 " +
"else " +
" return 0 " +
"end";
/**
* 尝试通过滑动窗口
*/
public boolean tryAcquire(String key, int windowSize, int limit) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>(SLIDING_WINDOW_SCRIPT, Long.class);
List<String> keys = Collections.singletonList(key);
Object[] args = new Object[]{
String.valueOf(System.currentTimeMillis()),
String.valueOf(windowSize),
String.valueOf(limit)
};
Long result = redisTemplate.execute(script, keys, args);
return result != null && result == 1;
}
}
4 漏桶限流器
package com.example.ratelimiter.limiter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* 漏桶限流器(单机版)
*/
public class LeakyBucketRateLimiter {
private final ConcurrentHashMap<String, LeakyBucket> buckets = new ConcurrentHashMap<>();
/**
* 是否允许请求
*/
public boolean tryAcquire(String key, int capacity, double leakRate) {
LeakyBucket bucket = buckets.computeIfAbsent(key, k -> new LeakyBucket(capacity, leakRate));
return bucket.tryAcquire();
}
/**
* 漏桶内部类
*/
private static class LeakyBucket {
private final int capacity; // 桶容量(缓存请求数)
private final double leakRate; // 漏出速率(每秒)
private AtomicInteger water; // 当前水量(请求数)
private AtomicLong lastLeakTime; // 上次漏出时间
public LeakyBucket(int capacity, double leakRate) {
this.capacity = capacity;
this.leakRate = leakRate;
this.water = new AtomicInteger(0);
this.lastLeakTime = new AtomicLong(System.currentTimeMillis());
}
/**
* 尝试获取
*/
public synchronized boolean tryAcquire() {
// 先漏水
leak();
// 检查水位
if (water.get() < capacity) {
water.incrementAndGet();
return true;
}
return false;
}
/**
* 漏水操作
*/
private void leak() {
long now = System.currentTimeMillis();
long lastTime = lastLeakTime.get();
double elapsedTime = (now - lastTime) / 1000.0;
int leakedWater = (int) (elapsedTime * leakRate);
if (leakedWater > 0) {
water.accumulateAndGet(-leakedWater, (current, delta) ->
Math.max(0, current + delta)
);
lastLeakTime.set(now);
}
}
}
}
AOP拦截器实现
package com.example.ratelimiter.aspect;
import com.example.ratelimiter.annotation.RateLimit;
import com.example.ratelimiter.exception.RateLimitException;
import com.example.ratelimiter.limiter.TokenBucketRateLimiter;
import com.example.ratelimiter.limiter.RedisSlidingWindowRateLimiter;
import com.example.ratelimiter.utils.IPUtils;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* 限流切面
*/
@Aspect
@Component
@RequiredArgsConstructor
public class RateLimitAspect {
private final RedisTemplate<String, String> redisTemplate;
private final TokenBucketRateLimiter tokenBucketRateLimiter;
private final RedisSlidingWindowRateLimiter redisSlidingWindowRateLimiter;
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取限流key
String key = buildKey(joinPoint, rateLimit);
// 根据限流类型执行不同的限流策略
boolean allowed = checkLimit(key, rateLimit);
if (!allowed) {
throw new RateLimitException(rateLimit.message());
}
return joinPoint.proceed();
}
/**
* 构建限流key
*/
private String buildKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
StringBuilder keyBuilder = new StringBuilder(rateLimit.key());
// 获取当前方法名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
keyBuilder.append(signature.getDeclaringTypeName())
.append(".")
.append(signature.getName());
// 获取IP地址(如果配置了IP限流)
if (rateLimit.type() == RateLimit.RateLimitType.COUNTER) {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
keyBuilder.append(":").append(IPUtils.getClientIp(request));
}
}
return keyBuilder.toString();
}
/**
* 检查是否允许请求
*/
private boolean checkLimit(String key, RateLimit rateLimit) {
boolean allowed;
switch (rateLimit.type()) {
case TOKEN_BUCKET:
// 令牌桶:每秒填充rateLimit.limit()/rateLimit.timeWindow()个令牌
double refillRate = (double) rateLimit.limit() / rateLimit.timeWindow();
allowed = tokenBucketRateLimiter.tryAcquire(key, rateLimit.limit(), refillRate);
break;
case SLIDING_WINDOW:
// Redis滑动窗口
allowed = redisSlidingWindowRateLimiter.tryAcquire(key, rateLimit.timeWindow(), rateLimit.limit());
break;
case COUNTER:
// 计数器
allowed = counterLimit(key, rateLimit);
break;
case LEAKY_BUCKET:
// 漏桶
allowed = leakyBucketLimit(key, rateLimit);
break;
default:
allowed = true;
break;
}
return allowed;
}
/**
* 计数器限流
*/
private boolean counterLimit(String key, RateLimit rateLimit) {
// 使用Redis INCR实现计数器
String counterKey = "rate:limit:counter:" + key;
Long count = redisTemplate.opsForValue().increment(counterKey);
if (count != null && count == 1) {
redisTemplate.expire(counterKey, rateLimit.timeWindow(), java.util.concurrent.TimeUnit.SECONDS);
}
return count != null && count <= rateLimit.limit();
}
/**
* 漏桶限流
*/
private boolean leakyBucketLimit(String key, RateLimit rateLimit) {
// 漏桶实现(这里简化,实际可以复用漏桶类)
return true;
}
}
自定义异常
package com.example.ratelimiter.exception;
/**
* 限流异常
*/
public class RateLimitException extends RuntimeException {
public RateLimitException(String message) {
super(message);
}
public RateLimitException(String message, Throwable cause) {
super(message, cause);
}
}
全局异常处理
package com.example.ratelimiter.handler;
import com.example.ratelimiter.exception.RateLimitException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RateLimitException.class)
public ResponseEntity<Map<String, Object>> handleRateLimitException(RateLimitException e) {
log.warn("触发限流: {}", e.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("code", 429);
response.put("message", e.getMessage());
response.put("success", false);
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(response);
}
}
测试控制器
package com.example.ratelimiter.controller;
import com.example.ratelimiter.annotation.RateLimit;
import com.example.ratelimiter.annotation.RateLimit.RateLimitType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 测试控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/test")
public class TestController {
/**
* 计数器限流测试
* - 60秒内最多100次请求
*/
@GetMapping("/counter")
@RateLimit(
key = "test:counter:",
timeWindow = 60,
limit = 100,
type = RateLimitType.COUNTER,
message = "请求过于频繁,请稍后再试"
)
public Map<String, Object> counterTest() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "计数器限流测试成功");
result.put("timestamp", System.currentTimeMillis());
return result;
}
/**
* 令牌桶限流测试
* - 速率:每秒10个令牌
* - 容量:100个
*/
@GetMapping("/token")
@RateLimit(
key = "test:token:",
timeWindow = 10, // 10秒
limit = 100, // 100个令牌
type = RateLimitType.TOKEN_BUCKET,
message = "服务繁忙,请稍后重试"
)
public Map<String, Object> tokenBucketTest() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "令牌桶限流测试成功");
result.put("timestamp", System.currentTimeMillis());
return result;
}
/**
* Redis滑动窗口限流测试
* - 60秒窗口内最多50次请求
*/
@GetMapping("/sliding")
@RateLimit(
key = "test:sliding:",
timeWindow = 60,
limit = 50,
type = RateLimitType.SLIDING_WINDOW,
message = "请求过多,请稍后再试"
)
public Map<String, Object> slidingWindowTest() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "滑动窗口限流测试成功");
result.put("timestamp", System.currentTimeMillis());
return result;
}
/**
* 不加限流的测试接口
*/
@GetMapping("/normal")
public Map<String, Object> normalTest() throws InterruptedException {
// 模拟业务处理
TimeUnit.MILLISECONDS.sleep(100);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "正常请求成功");
result.put("timestamp", System.currentTimeMillis());
return result;
}
}
配置类
package com.example.ratelimiter.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置类
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 设置序列化器
StringRedisSerializer stringSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
}
工具类(IP获取)
package com.example.ratelimiter.utils;
import javax.servlet.http.HttpServletRequest;
/**
* IP工具类
*/
public class IPUtils {
/**
* 获取客户端IP地址
*/
public static String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
定时清理任务(可选)
package com.example.ratelimiter.task;
import com.example.ratelimiter.limiter.CounterRateLimiter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 定时清理任务
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CleanupTask {
private final CounterRateLimiter counterRateLimiter;
/**
* 每天凌晨2点清理过期数据
*/
@Scheduled(cron = "0 0 2 * * ?")
public void cleanupExpiredData() {
log.info("开始清理限流过期数据");
counterRateLimiter.cleanup();
log.info("清理限流过期数据完成");
}
}
使用示例
package com.example.ratelimiter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 启动类
*/
@SpringBootApplication
@EnableScheduling
public class RateLimiterApplication {
public static void main(String[] args) {
SpringApplication.run(RateLimiterApplication.class, args);
System.out.println("限流应用启动成功!");
System.out.println("测试接口:");
System.out.println(" - http://localhost:8080/api/test/counter (计数器限流)");
System.out.println(" - http://localhost:8080/api/test/token (令牌桶限流)");
System.out.println(" - http://localhost:8080/api/test/sliding (滑动窗口限流)");
System.out.println(" - http://localhost:8080/api/test/normal (普通接口)");
}
}
测试脚本(并发测试)
# 使用 curl 并发测试
for i in $(seq 1 200); do
curl -s -o /dev/null -w "请求$i: HTTP %{http_code}\n" http://localhost:8080/api/test/counter &
done
wait
这个完整的限流案例包含了:
- 四种限流算法:计数器、令牌桶、滑动窗口、漏桶
- 多种限流实现:单机版(本地内存)和分布式版(Redis)
- AOP切面实现:无需修改业务代码,通过注解即可实现限流
- 自定义注解:灵活配置限流参数
- 全局异常处理:统一处理限流异常
- 完整测试示例:包含测试控制器和测试脚本
可以根据实际需求选择合适的限流方式:
- 单机应用:使用本地内存实现
- 分布式应用:使用Redis实现
- 对精度要求高:使用令牌桶或滑动窗口
- 业务突发流量:使用漏桶算法
版权声明: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直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播