完善项目

This commit is contained in:
2025-04-21 11:17:19 +08:00
parent 11a207a10a
commit f77777a055
24 changed files with 747 additions and 25 deletions

View File

@@ -48,8 +48,8 @@
<option name="tableUIInfoList"> <option name="tableUIInfoList">
<list> <list>
<TableUIInfo> <TableUIInfo>
<option name="className" value="Urls" /> <option name="className" value="Games" />
<option name="tableName" value="urls" /> <option name="tableName" value="games" />
</TableUIInfo> </TableUIInfo>
</list> </list>
</option> </option>

View File

@@ -53,6 +53,15 @@
<artifactId>spring-boot-starter-validation</artifactId> <artifactId>spring-boot-starter-validation</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.google.code.gson</groupId>-->
<!-- <artifactId>gson</artifactId>-->
<!-- <version>2.10.1</version>-->
<!-- </dependency>-->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>

View File

@@ -2,14 +2,27 @@ package com.ping.study.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class WebMvc implements WebMvcConfigurer { public class WebMvc implements WebMvcConfigurer {
@Bean // 原有的 NBA WebClient
public WebClient webClient() { @Bean("nbaWebClient")
public WebClient nbaWebClient() {
return WebClient.create("https://api.nba.cn/sib/v2"); return WebClient.create("https://api.nba.cn/sib/v2");
} }
// 新增的腾讯视频 WebClient
@Bean("tencentWebClient")
public WebClient tencentWebClient() {
return WebClient.create("https://infozb6.video.qq.com");
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
} }

View File

@@ -48,6 +48,12 @@ public class NbaController {
log.info("执行定时方法添加当天赛程"); log.info("执行定时方法添加当天赛程");
return nbaApi.addGames(); return nbaApi.addGames();
} }
@Scheduled(cron = "0 0 0 * * ?")
@RequestMapping("/updateLive")
public void updateLive() {
log.info("执行定时方法更新当天赛程直播链接");
}
@RequestMapping("/games") @RequestMapping("/games")
public List<Games> getGames() { public List<Games> getGames() {
log.info("获取所有赛程"); log.info("获取所有赛程");

View File

@@ -0,0 +1,24 @@
package com.ping.study.controller.tx;
import com.ping.study.service.tx.LiveInfoService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/tx/nba")
public class LiveInfoController {
private final LiveInfoService liveInfoService;
public LiveInfoController(LiveInfoService liveInfoService) {
this.liveInfoService = liveInfoService;
}
@GetMapping("/live/{cnlid}")
public String getLiveInfo(@PathVariable String cnlid) {
return liveInfoService.getLiveInfo(cnlid);
}
}

View File

@@ -0,0 +1,50 @@
package com.ping.study.controller.tx;
import com.ping.study.model.dto.tx.MatchListRequest;
import com.ping.study.service.tx.MatchService;
import com.ping.study.service.tx.SportsQqService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import java.time.LocalDate;
import java.util.List;
@RestController
@RequestMapping("/tx/nba")
public class MatchController {
private final SportsQqService sportsQqService;
private final MatchService matchService;
public MatchController(MatchService matchService,SportsQqService sportsQqService) {
this.sportsQqService = sportsQqService;
this.matchService = matchService;
}
@GetMapping("/matches")
public Mono<String> getMatches(
@RequestParam(required = false, defaultValue = "100000") Integer columnId,
@RequestParam String startTime,
@RequestParam String endTime) {
MatchListRequest request = new MatchListRequest();
request.setColumnId(columnId);
request.setStartTime(LocalDate.parse(startTime));
request.setEndTime(LocalDate.parse(endTime));
return sportsQqService.getMatchList(request);
}
@GetMapping("/lives")
public List<String> getPlayoffLiveIds(
@RequestParam String startTime,
@RequestParam String endTime) {
LocalDate startDate = LocalDate.parse(startTime);
LocalDate endDate = LocalDate.parse(endTime);
return matchService.getPlayoffLiveIdsBlocking(startDate, endDate);
}
}

View File

@@ -0,0 +1,22 @@
package com.ping.study.controller.tx;
import com.ping.study.service.tx.TxSportTokenRefreshService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/tx")
public class TokenRefreshController {
@Autowired
private TxSportTokenRefreshService txSportTokenRefreshService;
@RequestMapping("/refresh")
public Boolean refreshToken()
{
return txSportTokenRefreshService.refreshCookies();
}
}

View File

@@ -5,9 +5,9 @@ import com.ping.study.pojo.Games;
import java.util.List; import java.util.List;
/** /**
* @author Administrator * @author Ping01
* @description 针对表【games】的数据库操作Mapper * @description 针对表【games】的数据库操作Mapper
* @createDate 2025-04-17 21:23:08 * @createDate 2025-04-20 20:13:51
* @Entity com.ping.study.pojo.Games * @Entity com.ping.study.pojo.Games
*/ */
public interface GamesMapper { public interface GamesMapper {
@@ -30,4 +30,5 @@ public interface GamesMapper {
//删除所有赛程 //删除所有赛程
void deleteAllGames(); void deleteAllGames();
} }

View File

@@ -0,0 +1,42 @@
package com.ping.study.model.dto.tx;
import lombok.Data;
import java.time.DayOfWeek;
import java.util.Map;
@Data
public class LiveInfoRequest {
private String ckey;
private String encryptVer;
private String platform;
private String tm;
private String cnlid;
//private String cookie =
public static final String getCookie() {
return "pgv_pvid=67939534; fqm_pvqid=5e0a1a80-2ffb-42c9-8878-99ef08c0bd21; RK=dudkRjOGUW; ptcz=d9e319494b15cb621d9f54a8e03a1b55fb6c6c3564ff6c4985d1ff7b4797ce4e; video_platform=2; qq_domain_video_guid_verify=119f9c981e9ffd34; _qimei_uuid42=184170b170810037e9eacb93c4fe35a57c7df28f38; _qimei_q36=; _qimei_h38=215914e0566ee066f810eb4b0200000e81840b; video_guid=119f9c981e9ffd34; tvfe_boss_uuid=d41b10b066856291; eas_sid=E1i7L1k4j4D7q1D0Q5J894p636; livelink_pvid=7900946432; check_16=584445b5d815fd331bb7206205f8c603; _qimei_fingerprint=3c06d28104a1e802703470a164d81dca; vqq_refresh_token=; vqq_next_refresh_time=6196; vqq_login_time_init=1745153828; v_qq_com_session_lapse_time=1745160024778; vqq_appid=101481799; vqq_openid=2E6A77E3002C87A9CA68908FB058D4E5; vqq_access_token=308C7CAFBC867AF65212034AC90E71D9; vqq_vuserid=3468482246; vqq_vusession=Kcz0B9FBUDZuIvwHKErD3g.N";
}
// 密钥映射表
private static final Map<DayOfWeek, String> WEEKDAY_KEYS = Map.of(
DayOfWeek.MONDAY, "06fc1464",
DayOfWeek.TUESDAY, "4244ce1b",
DayOfWeek.WEDNESDAY, "77de31c5",
DayOfWeek.THURSDAY, "e0149fa2",
DayOfWeek.FRIDAY, "60394ced",
DayOfWeek.SATURDAY, "2da639f0",
DayOfWeek.SUNDAY, "c2f0cf9f"
);
// 生成加密版本号
public static String generateEncryptVer(DayOfWeek dayOfWeek) {
int dayValue = dayOfWeek == DayOfWeek.SUNDAY ? 7 : dayOfWeek.getValue();
return "7." + dayValue;
}
// 获取当天密钥
public static String getTodaySecretKey() {
return WEEKDAY_KEYS.get(DayOfWeek.from(java.time.LocalDate.now()));
}
}

View File

@@ -0,0 +1,18 @@
package com.ping.study.model.dto.tx;
import lombok.Data;
import java.time.LocalDate;
@Data
public class MatchListRequest {
private Integer columnId = 100000; // 默认值
private LocalDate startTime;
private LocalDate endTime;
// 自定义参数校验逻辑(可选)
public void validate() {
if (startTime.isAfter(endTime)) {
throw new IllegalArgumentException("开始时间不能晚于结束时间");
}
}
}

View File

@@ -0,0 +1,11 @@
package com.ping.study.model.vo.tx;
import lombok.Data;
@Data
public class MatchInfo {
private String liveId;
private String matchType;
// 其他需要的字段...
}

View File

@@ -0,0 +1,14 @@
package com.ping.study.model.vo.tx;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class MatchResponse {
private Integer code;
private String msg;
private Map<String, List<MatchInfo>> data;
}

View File

@@ -1,5 +1,6 @@
package com.ping.study.pojo; package com.ping.study.pojo;
import java.io.Serializable;
import java.util.Date; import java.util.Date;
import lombok.Data; import lombok.Data;
@@ -8,7 +9,7 @@ import lombok.Data;
* @TableName games * @TableName games
*/ */
@Data @Data
public class Games { public class Games implements Serializable {
/** /**
* *
*/ */
@@ -24,6 +25,11 @@ public class Games {
*/ */
private String gameId; private String gameId;
/**
*
*/
private String playId;
/** /**
* *
*/ */
@@ -48,4 +54,6 @@ public class Games {
* *
*/ */
private String awayTeamLogoDark; private String awayTeamLogoDark;
private static final long serialVersionUID = 1L;
} }

View File

@@ -0,0 +1,77 @@
package com.ping.study.service.tx;
import com.ping.study.model.dto.tx.LiveInfoRequest;
import com.ping.study.utils.tx.CKeyGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Slf4j
public class LiveInfoService {
private final WebClient webClient;
private final CKeyGenerator cKeyGenerator;
private StringRedisTemplate stringRedisTemplate;
public LiveInfoService(@Qualifier("tencentWebClient")WebClient webClient, CKeyGenerator cKeyGenerator, StringRedisTemplate stringRedisTemplate) {
this.webClient = webClient;
this.cKeyGenerator = cKeyGenerator;
this.stringRedisTemplate = stringRedisTemplate;
}
// 2. 远程调用时拼接 Cookie
public String getCookieForRequest() {
// 读取
String cookie = stringRedisTemplate.opsForValue().get("tx_sports_cookie");
return cookie;
}
public String getLiveInfo(String cnlid) {
String platform = "40201";
String ckey = cKeyGenerator.generateCKey(cnlid, platform);
// 1. 打印请求参数
String queryParams = String.format(
"ckey=%s&encrypt_ver=%s&platform=%s&tm=%d&cnlid=%s",
ckey,
LiveInfoRequest.generateEncryptVer(DayOfWeek.from(LocalDate.now())),
platform,
Instant.now().getEpochSecond(),
cnlid
);
log.info("请求参数: {}", queryParams);
// 2. 打印请求头Cookie
String cookie = getCookieForRequest();
log.info("请求头 Cookie: {}", cookie);
// 3. 发起请求
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/cgi-bin/getliveinfo")
.queryParam("ckey", ckey)
.queryParam("encrypt_ver", LiveInfoRequest.generateEncryptVer(
DayOfWeek.from(LocalDate.now())))
.queryParam("platform", platform)
.queryParam("tm", Instant.now().getEpochSecond())
.queryParam("cnlid", cnlid)
.build())
.header("Cookie", "pgv_pvid=67939534; fqm_pvqid=5e0a1a80-2ffb-42c9-8878-99ef08c0bd21; RK=dudkRjOGUW; ptcz=d9e319494b15cb621d9f54a8e03a1b55fb6c6c3564ff6c4985d1ff7b4797ce4e; video_platform=2; qq_domain_video_guid_verify=119f9c981e9ffd34; _qimei_uuid42=184170b170810037e9eacb93c4fe35a57c7df28f38; _qimei_q36=; _qimei_h38=215914e0566ee066f810eb4b0200000e81840b; video_guid=119f9c981e9ffd34; tvfe_boss_uuid=d41b10b066856291; eas_sid=E1i7L1k4j4D7q1D0Q5J894p636; livelink_pvid=7900946432; check_16=584445b5d815fd331bb7206205f8c603; o2_uin=941039061; uin_cookie=o0941039061; ied_qq=o0941039061; o_cookie=941039061; pac_uid=0_ePEHB57t75p7c; LW_sid=31h7T2M8f3O5O280q0K0L365H2; LW_uid=P1J7P2i8S3R5a2r0j020t3n543; ptui_loginuin=1131302745; qq_nick=A; qq_head=https://tvpic.gtimg.cn/head/dbb128053992612c0c1327e8e32e95f8da39a3ee5e6b4b0d3255bfef95601890afd80709/346; vqq_refresh_token=; vqq_next_refresh_time=6505; vqq_login_time_init=1745154214; _qimei_fingerprint=829539b60aaa7ed28f0e5f604c391a63; main_login=qq; vqq_appid=101481799; vqq_openid=2E6A77E3002C87A9CA68908FB058D4E5; vqq_access_token=308C7CAFBC867AF65212034AC90E71D9; vqq_vuserid=3468482246; vqq_vusession=H1-ZxeRN0vDUVk4_wKLkyA.N")
.retrieve()
.bodyToMono(String.class)
.block();
}
}

View File

@@ -0,0 +1,60 @@
package com.ping.study.service.tx;
import com.ping.study.model.vo.tx.MatchInfo;
import com.ping.study.model.vo.tx.MatchResponse;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class MatchService {
private final WebClient webClient;
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
public MatchService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder
.baseUrl("https://matchweb.sports.qq.com")
.build();
}
/**
* 获取指定日期范围内所有NBA季后赛(matchType=2)的直播ID
*/
public List<String> getPlayoffLiveIdsBlocking(LocalDate startDate, LocalDate endDate) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/matchUnion/list")
.queryParam("columnId", 100000)
.queryParam("startTime", startDate.format(DATE_FORMATTER))
.queryParam("endTime", endDate.format(DATE_FORMATTER))
.build())
.retrieve()
.bodyToMono(MatchResponse.class)
.flatMapMany(response -> {
if (response.getCode() != 0 || response.getData() == null) {
return Flux.error(new RuntimeException("API返回异常: " + response.getMsg()));
}
// 提取所有matchType=2的比赛
List<String> liveIds = response.getData().values().stream()
.flatMap(List::stream)
.filter(match -> "2".equals(match.getMatchType()))
.map(MatchInfo::getLiveId)
.collect(Collectors.toList());
return Flux.fromIterable(liveIds);
})
.collectList()
.block(); // 阻塞等待结果
}
/**
* 获取完整比赛信息过滤matchType=2
*/
}

View File

@@ -0,0 +1,36 @@
package com.ping.study.service.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class QQCookieService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final String REDIS_KEY = "user:nba"; // 固定 Key
// 存储 Cookie无需 uin 参数)
public void saveCookie(Map<String, String> cookies) {
stringRedisTemplate.opsForHash().putAll(REDIS_KEY, cookies);
stringRedisTemplate.expire(REDIS_KEY, 30, TimeUnit.DAYS);
}
// 获取 Cookie无需 uin 参数)
public Map<String, String> getCookie() {
Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(REDIS_KEY);
Map<String, String> result = new HashMap<>();
entries.forEach((key, value) ->
result.put(key.toString(), value.toString())
);
return result;
}
}

View File

@@ -0,0 +1,49 @@
package com.ping.study.service.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.Map;
@Service
public class QQRequestService {
@Autowired
private RestTemplate restTemplate; // 现在可以正常注入
@Autowired
private QQCookieService qqCookieService;
@Autowired
private QQTokenRefreshService tokenRefreshService;
// 发起请求(自动处理 Cookie 和 Token 刷新)
public String fetchData(String uin, String apiUrl) throws IOException {
Map<String, String> cookies = qqCookieService.getCookie();
// 检查 Token 是否即将过期
long expiresIn = Long.parseLong(cookies.getOrDefault("vqq_next_refresh_time", "0"));
if (expiresIn < 300) { // 剩余 <5 分钟时刷新
tokenRefreshService.refreshToken();
cookies = qqCookieService.getCookie(); // 重新加载
}
// 构造请求头(模拟浏览器)
HttpHeaders headers = new HttpHeaders();
headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
cookies.forEach((name, value) -> headers.add("Cookie", name + "=" + value));
// 发起请求
ResponseEntity<String> response = restTemplate.exchange(
apiUrl,
HttpMethod.GET,
new HttpEntity<>(headers),
String.class
);
return response.getBody();
}
}

View File

@@ -0,0 +1,50 @@
package com.ping.study.service.tx;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.Map;
@Service
public class QQTokenRefreshService {
@Autowired
private QQCookieService qqCookieService;
@Autowired
private RestTemplate restTemplate;
// 刷新 Token
public boolean refreshToken() throws IOException {
Map<String, String> cookies = qqCookieService.getCookie();
String refreshToken = cookies.get("vqq_refresh_token");
if (refreshToken == null || refreshToken.isEmpty()) {
throw new IllegalStateException("No refresh_token available, need re-login");
}
// 调用 QQ 的 Token 刷新接口(示例,需替换真实 API
String url = "https://xui.ptlogin2.qq.com/refresh?refresh_token=" + refreshToken;
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
// 解析新 Token 并更新 Cookie
String newAccessToken = parseNewToken(response.getBody());
cookies.put("vqq_access_token", newAccessToken);
cookies.put("vqq_next_refresh_time", "7200"); // 假设新有效期 2 小时
qqCookieService.saveCookie(cookies);
return true;
} else {
return false; // 刷新失败,需重新登录
}
}
private String parseNewToken(String responseBody) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(responseBody);
return root.get("access_token").asText();
}
}

View File

@@ -0,0 +1,36 @@
package com.ping.study.service.tx;
import com.ping.study.model.dto.tx.MatchListRequest;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.time.format.DateTimeFormatter;
@Service
public class SportsQqService {
private final WebClient webClient;
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
public SportsQqService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder
.baseUrl("https://matchweb.sports.qq.com")
.defaultHeader("User-Agent", "YourApp/1.0")
.build();
}
public Mono<String> getMatchList(MatchListRequest request) {
request.validate(); // 参数校验
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/matchUnion/list")
.queryParam("columnId", request.getColumnId())
.queryParam("startTime", request.getStartTime().format(DATE_FORMATTER))
.queryParam("endTime", request.getEndTime().format(DATE_FORMATTER))
.build())
.retrieve()
.bodyToMono(String.class);
}
}

View File

@@ -0,0 +1,108 @@
package com.ping.study.service.tx;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Service
public class TxSportTokenRefreshService {
private final WebClient webClient;
private final StringRedisTemplate redisTemplate;
private static final String REDIS_COOKIE_KEY = "tx_sports_cookie";
public TxSportTokenRefreshService(WebClient.Builder builder,
StringRedisTemplate redisTemplate) {
this.webClient = builder.baseUrl("https://app.sports.qq.com").build();
this.redisTemplate = redisTemplate;
}
/**
* 刷新Cookie并存储到Redis
*/
// 在首次成功时分离并存储基础登录态Cookie
public boolean refreshCookies() {
try {
// 1. 从Redis获取基础登录态Cookie首次需手动初始化
String baseCookie = redisTemplate.opsForValue().get("tx_base_cookies");
if (baseCookie == null) {
baseCookie = "pgv_pvid=67939534; ptui_loginuin=1131302745; ..."; // 你的浏览器完整Cookie
}
// 2. 携带基础Cookie发起请求
String response = webClient.get()
.uri("/init/refresh?os=web")
.header("Cookie", baseCookie) // 关键点:始终携带基础登录态
.retrieve()
.bodyToMono(String.class)
.block();
// 3. 合并新旧Cookie保留基础登录态
JsonNode data = new ObjectMapper().readTree(response).path("data");
String newCookie = extractNewCookies(data); // 提取spt_token等
String finalCookie = mergeCookies(baseCookie, newCookie);
// 4. 存储到Redis
redisTemplate.opsForValue().set(
"tx_sports_cookies",
finalCookie,
Long.parseLong(data.path("tokenTTL").asText()),
TimeUnit.SECONDS
);
return true;
} catch (Exception e) {
log.error("刷新失败", e);
return false;
}
}
// 合并Cookie的工具方法
private String mergeCookies(String base, String news) {
Map<String, String> map = new LinkedHashMap<>();
// 先解析基础Cookie
Arrays.stream(base.split("; "))
.forEach(c -> {
String[] kv = c.split("=", 2);
if (kv.length == 2) map.put(kv[0], kv[1]);
});
// 用新Cookie覆盖
Arrays.stream(news.split("; "))
.forEach(c -> {
String[] kv = c.split("=", 2);
if (kv.length == 2) map.put(kv[0], kv[1]);
});
// 重新拼接
return map.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("; "));
}
/**
* 定时刷新任务每1小时执行一次
*/
// @Scheduled(fixedRate = 3600000) // 单位:毫秒
// public void scheduledRefresh() {
// if (!refreshCookies()) {
// log.warn("定时刷新Cookie失败将重试...");
// // 失败后延迟5分钟重试
// try {
// Thread.sleep(300000);
// refreshCookies();
// } catch (InterruptedException ignored) {}
// }
// }
}

View File

@@ -6,14 +6,17 @@ import com.ping.study.model.vo.Game;
import com.ping.study.model.vo.Group; import com.ping.study.model.vo.Group;
import com.ping.study.pojo.Games; import com.ping.study.pojo.Games;
import com.ping.study.service.GamesService; import com.ping.study.service.GamesService;
import com.ping.study.service.tx.MatchService;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@@ -28,7 +31,10 @@ public class NbaApi {
private WebClient webClient; private WebClient webClient;
public NbaApi(WebClient webClient) { @Autowired
private MatchService matchService;
public NbaApi(@Qualifier("nbaWebClient")WebClient webClient) {
this.webClient = webClient; this.webClient = webClient;
} }
@@ -37,6 +43,8 @@ public class NbaApi {
NbaStatsRequestDto requestParams = new NbaStatsRequestDto(); NbaStatsRequestDto requestParams = new NbaStatsRequestDto();
log.info("{}", requestParams); log.info("{}", requestParams);
log.info("进入{},执行{}", this.getClass().getName(), "getGames"); log.info("进入{},执行{}", this.getClass().getName(), "getGames");
//获取所有当天直播id
List<String> lives = matchService.getPlayoffLiveIdsBlocking(LocalDate.now(), LocalDate.now());
List<Games> gameEntities = webClient.get() List<Games> gameEntities = webClient.get()
.uri("/game/schedule", uriBuilder -> uriBuilder .uri("/game/schedule", uriBuilder -> uriBuilder
.queryParam("app_key", requestParams.getAppKey()) .queryParam("app_key", requestParams.getAppKey())
@@ -60,17 +68,17 @@ public class NbaApi {
List<Games> entities = new ArrayList<>(); List<Games> entities = new ArrayList<>();
// 遍历groups中的games // 遍历groups中的games
for (Group group : response.getData().getGroups()) { for (Group group : response.getData().getGroups()) {
for (Game game : group.getGames()) { for (int i = 0; i < group.getGames().size(); i++){
Games entity = new Games(); Games entity = new Games();
entity.setDate(new SimpleDateFormat("yyyy-MM-dd").format(new Date())); entity.setDate(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
entity.setGameId(game.getGameId()); entity.setGameId(group.getGames().get(i).getGameId());
entity.setHomeTeamName(game.getHomeTeamName()); entity.setHomeTeamName(group.getGames().get(i).getHomeTeamName());
entity.setAwayTeamName(game.getAwayTeamName()); entity.setAwayTeamName(group.getGames().get(i).getAwayTeamName());
entity.setHomeTeamLogoDark(game.getHomeTeamLogoDark()); entity.setHomeTeamLogoDark(group.getGames().get(i).getHomeTeamLogoDark());
entity.setAwayTeamLogoDark(game.getAwayTeamLogoDark()); entity.setAwayTeamLogoDark(group.getGames().get(i).getAwayTeamLogoDark());
entity.setPlayId(lives.get(i));
// 合并日期和时间 // 合并日期和时间
String dateTimeStr = game.getStartDate() + " " + game.getStartTime(); String dateTimeStr = group.getGames().get(i).getStartDate() + " " + group.getGames().get(i).getStartTime();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try { try {
entity.setStartTime(format.parse(dateTimeStr)); entity.setStartTime(format.parse(dateTimeStr));
@@ -78,9 +86,31 @@ public class NbaApi {
// 处理异常或设置默认值 // 处理异常或设置默认值
entity.setStartTime(null); entity.setStartTime(null);
} }
gamesService.insertGames(entity); gamesService.insertGames(entity);
entities.add(entity); entities.add(entity);
} }
// for (Game game : group.getGames()) {
// Games entity = new Games();
// entity.setDate(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
// entity.setGameId(game.getGameId());
// entity.setHomeTeamName(game.getHomeTeamName());
// entity.setAwayTeamName(game.getAwayTeamName());
// entity.setHomeTeamLogoDark(game.getHomeTeamLogoDark());
// entity.setAwayTeamLogoDark(game.getAwayTeamLogoDark());
//
// // 合并日期和时间
// String dateTimeStr = game.getStartDate() + " " + game.getStartTime();
// SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// try {
// entity.setStartTime(format.parse(dateTimeStr));
// } catch (ParseException e) {
// // 处理异常或设置默认值
// entity.setStartTime(null);
// }
// gamesService.insertGames(entity);
// entities.add(entity);
// }
} }
return entities; return entities;
}) })

View File

@@ -0,0 +1,46 @@
package com.ping.study.utils.tx;
import com.ping.study.model.dto.tx.LiveInfoRequest;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.DayOfWeek;
import java.time.Instant;
@Component
public class CKeyGenerator {
public String generateCKey(String cnlid, String platform) {
// 获取当前时间戳(秒级)
String tm = String.valueOf(Instant.now().getEpochSecond());
// 获取当天星期几和密钥
DayOfWeek today = DayOfWeek.from(java.time.LocalDate.now());
String secretKey = LiveInfoRequest.getTodaySecretKey();
String encryptVer = LiveInfoRequest.generateEncryptVer(today);
// 构建输入字符串
String input = secretKey + cnlid + tm + "*#06#" + platform;
try {
// 计算MD5
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
// 转换为16进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
throw new RuntimeException("生成ckey失败", e);
}
}
}

View File

@@ -1,6 +1,10 @@
server: server:
port: 9005 port: 9005
spring: spring:
data:
redis:
host: 110.42.255.182
port: 6739
application: application:
name: NBA name: NBA
datasource: datasource:

View File

@@ -8,6 +8,7 @@
<id property="id" column="id" jdbcType="INTEGER"/> <id property="id" column="id" jdbcType="INTEGER"/>
<result property="date" column="date" jdbcType="VARCHAR"/> <result property="date" column="date" jdbcType="VARCHAR"/>
<result property="gameId" column="game_id" jdbcType="VARCHAR"/> <result property="gameId" column="game_id" jdbcType="VARCHAR"/>
<result property="playId" column="play_id" jdbcType="VARCHAR"/>
<result property="startTime" column="start_time" jdbcType="TIMESTAMP"/> <result property="startTime" column="start_time" jdbcType="TIMESTAMP"/>
<result property="homeTeamName" column="home_team_name" jdbcType="VARCHAR"/> <result property="homeTeamName" column="home_team_name" jdbcType="VARCHAR"/>
<result property="awayTeamName" column="away_team_name" jdbcType="VARCHAR"/> <result property="awayTeamName" column="away_team_name" jdbcType="VARCHAR"/>
@@ -17,8 +18,8 @@
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id,date,game_id, id,date,game_id,
start_time,home_team_name,away_team_name, play_id,start_time,home_team_name,
home_team_logo_dark,away_team_logo_dark away_team_name,home_team_logo_dark,away_team_logo_dark
</sql> </sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap"> <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
@@ -35,11 +36,13 @@
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.ping.study.pojo.Games" useGeneratedKeys="true"> <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.ping.study.pojo.Games" useGeneratedKeys="true">
insert into games insert into games
( id,date,game_id ( id,date,game_id
,start_time,home_team_name,away_team_name ,play_id,start_time,home_team_name
,home_team_logo_dark,away_team_logo_dark) ,away_team_name,home_team_logo_dark,away_team_logo_dark
)
values (#{id,jdbcType=INTEGER},#{date,jdbcType=VARCHAR},#{gameId,jdbcType=VARCHAR} values (#{id,jdbcType=INTEGER},#{date,jdbcType=VARCHAR},#{gameId,jdbcType=VARCHAR}
,#{startTime,jdbcType=TIMESTAMP},#{homeTeamName,jdbcType=VARCHAR},#{awayTeamName,jdbcType=VARCHAR} ,#{playId,jdbcType=VARCHAR},#{startTime,jdbcType=TIMESTAMP},#{homeTeamName,jdbcType=VARCHAR}
,#{homeTeamLogoDark,jdbcType=VARCHAR},#{awayTeamLogoDark,jdbcType=VARCHAR}) ,#{awayTeamName,jdbcType=VARCHAR},#{homeTeamLogoDark,jdbcType=VARCHAR},#{awayTeamLogoDark,jdbcType=VARCHAR}
)
</insert> </insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.ping.study.pojo.Games" useGeneratedKeys="true"> <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.ping.study.pojo.Games" useGeneratedKeys="true">
insert into games insert into games
@@ -47,6 +50,7 @@
<if test="id != null">id,</if> <if test="id != null">id,</if>
<if test="date != null">date,</if> <if test="date != null">date,</if>
<if test="gameId != null">game_id,</if> <if test="gameId != null">game_id,</if>
<if test="playId != null">play_id,</if>
<if test="startTime != null">start_time,</if> <if test="startTime != null">start_time,</if>
<if test="homeTeamName != null">home_team_name,</if> <if test="homeTeamName != null">home_team_name,</if>
<if test="awayTeamName != null">away_team_name,</if> <if test="awayTeamName != null">away_team_name,</if>
@@ -57,6 +61,7 @@
<if test="id != null">#{id,jdbcType=INTEGER},</if> <if test="id != null">#{id,jdbcType=INTEGER},</if>
<if test="date != null">#{date,jdbcType=VARCHAR},</if> <if test="date != null">#{date,jdbcType=VARCHAR},</if>
<if test="gameId != null">#{gameId,jdbcType=VARCHAR},</if> <if test="gameId != null">#{gameId,jdbcType=VARCHAR},</if>
<if test="playId != null">#{playId,jdbcType=VARCHAR},</if>
<if test="startTime != null">#{startTime,jdbcType=TIMESTAMP},</if> <if test="startTime != null">#{startTime,jdbcType=TIMESTAMP},</if>
<if test="homeTeamName != null">#{homeTeamName,jdbcType=VARCHAR},</if> <if test="homeTeamName != null">#{homeTeamName,jdbcType=VARCHAR},</if>
<if test="awayTeamName != null">#{awayTeamName,jdbcType=VARCHAR},</if> <if test="awayTeamName != null">#{awayTeamName,jdbcType=VARCHAR},</if>
@@ -73,6 +78,9 @@
<if test="gameId != null"> <if test="gameId != null">
game_id = #{gameId,jdbcType=VARCHAR}, game_id = #{gameId,jdbcType=VARCHAR},
</if> </if>
<if test="playId != null">
play_id = #{playId,jdbcType=VARCHAR},
</if>
<if test="startTime != null"> <if test="startTime != null">
start_time = #{startTime,jdbcType=TIMESTAMP}, start_time = #{startTime,jdbcType=TIMESTAMP},
</if> </if>
@@ -96,6 +104,7 @@
set set
date = #{date,jdbcType=VARCHAR}, date = #{date,jdbcType=VARCHAR},
game_id = #{gameId,jdbcType=VARCHAR}, game_id = #{gameId,jdbcType=VARCHAR},
play_id = #{playId,jdbcType=VARCHAR},
start_time = #{startTime,jdbcType=TIMESTAMP}, start_time = #{startTime,jdbcType=TIMESTAMP},
home_team_name = #{homeTeamName,jdbcType=VARCHAR}, home_team_name = #{homeTeamName,jdbcType=VARCHAR},
away_team_name = #{awayTeamName,jdbcType=VARCHAR}, away_team_name = #{awayTeamName,jdbcType=VARCHAR},
@@ -104,7 +113,6 @@
where id = #{id,jdbcType=INTEGER} where id = #{id,jdbcType=INTEGER}
</update> </update>
<select id="selectByGameId" resultMap="BaseResultMap"> <select id="selectByGameId" resultMap="BaseResultMap">
select select
<include refid="Base_Column_List" /> <include refid="Base_Column_List" />