完善项目
This commit is contained in:
4
.idea/mybatisx/templates.xml
generated
4
.idea/mybatisx/templates.xml
generated
@@ -48,8 +48,8 @@
|
||||
<option name="tableUIInfoList">
|
||||
<list>
|
||||
<TableUIInfo>
|
||||
<option name="className" value="Urls" />
|
||||
<option name="tableName" value="urls" />
|
||||
<option name="className" value="Games" />
|
||||
<option name="tableName" value="games" />
|
||||
</TableUIInfo>
|
||||
</list>
|
||||
</option>
|
||||
|
||||
9
pom.xml
9
pom.xml
@@ -53,6 +53,15 @@
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</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>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
||||
@@ -2,14 +2,27 @@ package com.ping.study.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebMvc implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
public WebClient webClient() {
|
||||
// 原有的 NBA WebClient
|
||||
@Bean("nbaWebClient")
|
||||
public WebClient nbaWebClient() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,12 @@ public class NbaController {
|
||||
log.info("执行定时方法添加当天赛程");
|
||||
return nbaApi.addGames();
|
||||
}
|
||||
@Scheduled(cron = "0 0 0 * * ?")
|
||||
@RequestMapping("/updateLive")
|
||||
public void updateLive() {
|
||||
log.info("执行定时方法更新当天赛程直播链接");
|
||||
|
||||
}
|
||||
@RequestMapping("/games")
|
||||
public List<Games> getGames() {
|
||||
log.info("获取所有赛程");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import com.ping.study.pojo.Games;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
* @author Ping01
|
||||
* @description 针对表【games】的数据库操作Mapper
|
||||
* @createDate 2025-04-17 21:23:08
|
||||
* @createDate 2025-04-20 20:13:51
|
||||
* @Entity com.ping.study.pojo.Games
|
||||
*/
|
||||
public interface GamesMapper {
|
||||
@@ -30,4 +30,5 @@ public interface GamesMapper {
|
||||
|
||||
//删除所有赛程
|
||||
void deleteAllGames();
|
||||
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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("开始时间不能晚于结束时间");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/main/java/com/ping/study/model/vo/tx/MatchInfo.java
Normal file
11
src/main/java/com/ping/study/model/vo/tx/MatchInfo.java
Normal 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;
|
||||
// 其他需要的字段...
|
||||
}
|
||||
14
src/main/java/com/ping/study/model/vo/tx/MatchResponse.java
Normal file
14
src/main/java/com/ping/study/model/vo/tx/MatchResponse.java
Normal 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;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.ping.study.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -8,7 +9,7 @@ import lombok.Data;
|
||||
* @TableName games
|
||||
*/
|
||||
@Data
|
||||
public class Games {
|
||||
public class Games implements Serializable {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -24,6 +25,11 @@ public class Games {
|
||||
*/
|
||||
private String gameId;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String playId;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -48,4 +54,6 @@ public class Games {
|
||||
*
|
||||
*/
|
||||
private String awayTeamLogoDark;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
77
src/main/java/com/ping/study/service/tx/LiveInfoService.java
Normal file
77
src/main/java/com/ping/study/service/tx/LiveInfoService.java
Normal 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();
|
||||
}
|
||||
}
|
||||
60
src/main/java/com/ping/study/service/tx/MatchService.java
Normal file
60
src/main/java/com/ping/study/service/tx/MatchService.java
Normal 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)
|
||||
*/
|
||||
}
|
||||
36
src/main/java/com/ping/study/service/tx/QQCookieService.java
Normal file
36
src/main/java/com/ping/study/service/tx/QQCookieService.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
36
src/main/java/com/ping/study/service/tx/SportsQqService.java
Normal file
36
src/main/java/com/ping/study/service/tx/SportsQqService.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -6,14 +6,17 @@ import com.ping.study.model.vo.Game;
|
||||
import com.ping.study.model.vo.Group;
|
||||
import com.ping.study.pojo.Games;
|
||||
import com.ping.study.service.GamesService;
|
||||
import com.ping.study.service.tx.MatchService;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -28,7 +31,10 @@ public class NbaApi {
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
public NbaApi(WebClient webClient) {
|
||||
@Autowired
|
||||
private MatchService matchService;
|
||||
|
||||
public NbaApi(@Qualifier("nbaWebClient")WebClient webClient) {
|
||||
this.webClient = webClient;
|
||||
}
|
||||
|
||||
@@ -37,6 +43,8 @@ public class NbaApi {
|
||||
NbaStatsRequestDto requestParams = new NbaStatsRequestDto();
|
||||
log.info("{}", requestParams);
|
||||
log.info("进入{},执行{}", this.getClass().getName(), "getGames");
|
||||
//获取所有当天直播id
|
||||
List<String> lives = matchService.getPlayoffLiveIdsBlocking(LocalDate.now(), LocalDate.now());
|
||||
List<Games> gameEntities = webClient.get()
|
||||
.uri("/game/schedule", uriBuilder -> uriBuilder
|
||||
.queryParam("app_key", requestParams.getAppKey())
|
||||
@@ -60,17 +68,17 @@ public class NbaApi {
|
||||
List<Games> entities = new ArrayList<>();
|
||||
// 遍历groups中的games
|
||||
for (Group group : response.getData().getGroups()) {
|
||||
for (Game game : group.getGames()) {
|
||||
for (int i = 0; i < group.getGames().size(); i++){
|
||||
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());
|
||||
|
||||
entity.setGameId(group.getGames().get(i).getGameId());
|
||||
entity.setHomeTeamName(group.getGames().get(i).getHomeTeamName());
|
||||
entity.setAwayTeamName(group.getGames().get(i).getAwayTeamName());
|
||||
entity.setHomeTeamLogoDark(group.getGames().get(i).getHomeTeamLogoDark());
|
||||
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");
|
||||
try {
|
||||
entity.setStartTime(format.parse(dateTimeStr));
|
||||
@@ -78,9 +86,31 @@ public class NbaApi {
|
||||
// 处理异常或设置默认值
|
||||
entity.setStartTime(null);
|
||||
}
|
||||
|
||||
gamesService.insertGames(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;
|
||||
})
|
||||
|
||||
46
src/main/java/com/ping/study/utils/tx/CKeyGenerator.java
Normal file
46
src/main/java/com/ping/study/utils/tx/CKeyGenerator.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
server:
|
||||
port: 9005
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
host: 110.42.255.182
|
||||
port: 6739
|
||||
application:
|
||||
name: NBA
|
||||
datasource:
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<id property="id" column="id" jdbcType="INTEGER"/>
|
||||
<result property="date" column="date" 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="homeTeamName" column="home_team_name" jdbcType="VARCHAR"/>
|
||||
<result property="awayTeamName" column="away_team_name" jdbcType="VARCHAR"/>
|
||||
@@ -17,8 +18,8 @@
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
id,date,game_id,
|
||||
start_time,home_team_name,away_team_name,
|
||||
home_team_logo_dark,away_team_logo_dark
|
||||
play_id,start_time,home_team_name,
|
||||
away_team_name,home_team_logo_dark,away_team_logo_dark
|
||||
</sql>
|
||||
|
||||
<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 into games
|
||||
( id,date,game_id
|
||||
,start_time,home_team_name,away_team_name
|
||||
,home_team_logo_dark,away_team_logo_dark)
|
||||
,play_id,start_time,home_team_name
|
||||
,away_team_name,home_team_logo_dark,away_team_logo_dark
|
||||
)
|
||||
values (#{id,jdbcType=INTEGER},#{date,jdbcType=VARCHAR},#{gameId,jdbcType=VARCHAR}
|
||||
,#{startTime,jdbcType=TIMESTAMP},#{homeTeamName,jdbcType=VARCHAR},#{awayTeamName,jdbcType=VARCHAR}
|
||||
,#{homeTeamLogoDark,jdbcType=VARCHAR},#{awayTeamLogoDark,jdbcType=VARCHAR})
|
||||
,#{playId,jdbcType=VARCHAR},#{startTime,jdbcType=TIMESTAMP},#{homeTeamName,jdbcType=VARCHAR}
|
||||
,#{awayTeamName,jdbcType=VARCHAR},#{homeTeamLogoDark,jdbcType=VARCHAR},#{awayTeamLogoDark,jdbcType=VARCHAR}
|
||||
)
|
||||
</insert>
|
||||
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.ping.study.pojo.Games" useGeneratedKeys="true">
|
||||
insert into games
|
||||
@@ -47,6 +50,7 @@
|
||||
<if test="id != null">id,</if>
|
||||
<if test="date != null">date,</if>
|
||||
<if test="gameId != null">game_id,</if>
|
||||
<if test="playId != null">play_id,</if>
|
||||
<if test="startTime != null">start_time,</if>
|
||||
<if test="homeTeamName != null">home_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="date != null">#{date,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="homeTeamName != null">#{homeTeamName,jdbcType=VARCHAR},</if>
|
||||
<if test="awayTeamName != null">#{awayTeamName,jdbcType=VARCHAR},</if>
|
||||
@@ -73,6 +78,9 @@
|
||||
<if test="gameId != null">
|
||||
game_id = #{gameId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="playId != null">
|
||||
play_id = #{playId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="startTime != null">
|
||||
start_time = #{startTime,jdbcType=TIMESTAMP},
|
||||
</if>
|
||||
@@ -96,6 +104,7 @@
|
||||
set
|
||||
date = #{date,jdbcType=VARCHAR},
|
||||
game_id = #{gameId,jdbcType=VARCHAR},
|
||||
play_id = #{playId,jdbcType=VARCHAR},
|
||||
start_time = #{startTime,jdbcType=TIMESTAMP},
|
||||
home_team_name = #{homeTeamName,jdbcType=VARCHAR},
|
||||
away_team_name = #{awayTeamName,jdbcType=VARCHAR},
|
||||
@@ -104,7 +113,6 @@
|
||||
where id = #{id,jdbcType=INTEGER}
|
||||
</update>
|
||||
|
||||
|
||||
<select id="selectByGameId" resultMap="BaseResultMap">
|
||||
select
|
||||
<include refid="Base_Column_List" />
|
||||
@@ -114,8 +122,8 @@
|
||||
|
||||
<select id="selectAll" resultMap="BaseResultMap">
|
||||
select
|
||||
<include refid="Base_Column_List" />
|
||||
from games
|
||||
<include refid="Base_Column_List" />
|
||||
from games
|
||||
</select>
|
||||
<delete id="deleteAllGames" >
|
||||
delete from games
|
||||
|
||||
Reference in New Issue
Block a user