This commit is contained in:
2025-04-21 13:14:50 +08:00
parent f77777a055
commit 48c1e03945
8 changed files with 133 additions and 19 deletions

View File

@@ -1,6 +1,7 @@
package com.ping.study.controller.tx; package com.ping.study.controller.tx;
import com.ping.study.service.tx.LiveInfoService; import com.ping.study.service.tx.LiveInfoService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -21,4 +22,12 @@ public class LiveInfoController {
public String getLiveInfo(@PathVariable String cnlid) { public String getLiveInfo(@PathVariable String cnlid) {
return liveInfoService.getLiveInfo(cnlid); return liveInfoService.getLiveInfo(cnlid);
} }
//定时执行更新直播链接
//定时任务 从北京时间凌晨到12点每过半个小时执行一次
@Scheduled(cron = "0 0/30 0-11 * * ?")
@RequestMapping("/live/refresh")
public String refreshLiveInfo() {
return liveInfoService.refreshLiveInfo();
}
} }

View File

@@ -3,6 +3,7 @@ package com.ping.study.controller.tx;
import com.ping.study.service.tx.TxSportTokenRefreshService; import com.ping.study.service.tx.TxSportTokenRefreshService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -14,7 +15,8 @@ public class TokenRefreshController {
private TxSportTokenRefreshService txSportTokenRefreshService; private TxSportTokenRefreshService txSportTokenRefreshService;
@RequestMapping("/refresh")
@RequestMapping("/cookie/refresh")
public Boolean refreshToken() public Boolean refreshToken()
{ {
return txSportTokenRefreshService.refreshCookies(); return txSportTokenRefreshService.refreshCookies();

View File

@@ -36,4 +36,7 @@ public interface UrlsMapper {
void deleteUrlById(Integer id); void deleteUrlById(Integer id);
//删除所有url //删除所有url
void deleteAllUrls(); void deleteAllUrls();
//更新比赛直播链接
void updateUrlsWithGameId(String gameId, String s);
} }

View File

@@ -0,0 +1,18 @@
package com.ping.study.model.vo.live;
import lombok.Data;
import java.util.List;
@Data
public class LiveInfoResponse {
private int retcode;
private List<PlayItem> play_list;
// 其他需要的字段...
@Data
public static class PlayItem {
private List<String> urls;
// 其他需要的字段...
}
}

View File

@@ -1,9 +1,15 @@
package com.ping.study.service.tx; package com.ping.study.service.tx;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ping.study.mapper.GamesMapper;
import com.ping.study.mapper.UrlsMapper;
import com.ping.study.model.dto.tx.LiveInfoRequest; import com.ping.study.model.dto.tx.LiveInfoRequest;
import com.ping.study.model.vo.live.LiveInfoResponse;
import com.ping.study.pojo.Games;
import com.ping.study.utils.tx.CKeyGenerator; import com.ping.study.utils.tx.CKeyGenerator;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
@@ -14,6 +20,8 @@ import reactor.core.publisher.Mono;
import java.time.DayOfWeek; import java.time.DayOfWeek;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -23,6 +31,10 @@ public class LiveInfoService {
private final WebClient webClient; private final WebClient webClient;
private final CKeyGenerator cKeyGenerator; private final CKeyGenerator cKeyGenerator;
@Autowired
private GamesMapper gamesMapper;
@Autowired
private UrlsMapper urlsMapper;
private StringRedisTemplate stringRedisTemplate; private StringRedisTemplate stringRedisTemplate;
@@ -69,9 +81,45 @@ public class LiveInfoService {
.queryParam("tm", Instant.now().getEpochSecond()) .queryParam("tm", Instant.now().getEpochSecond())
.queryParam("cnlid", cnlid) .queryParam("cnlid", cnlid)
.build()) .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") .header("Cookie", "_qimei_q32=b6ff8aedc45c2d93874747a3e7f5e3dd; ptui_loginuin=1131302745; vqq_access_token=308C7CAFBC867AF65212034AC90E71D9; lcad_appuser=CCC4A35B6047F544; tvfe_boss_uuid=d41b10b066856291; _qimei_uuid42=184170b170810037e9eacb93c4fe35a57c7df28f38; LW_sid=31h7T2M8f3O5O280q0K0L365H2; o_cookie=941039061; lcad_LDERturn=503; last_nick=%E8%85%BE%E8%AE%AF%E7%BD%91%E5%8F%8B; uin_cookie=o0941039061; lcad_LPDFturn=43; spt_uid=1829817828670177361; RK=dudkRjOGUW; ied_qq=o0941039061; lcad_LPVLturn=470; lcad_o_minduid=OCcytZR6an_BFYcypXv6ZzuBX65okAHc; pgv_pvid=67939534; lcad_LVINturn=636; pgv_info=ssid=s6516917000; last_main_login=qq; eas_sid=E1i7L1k4j4D7q1D0Q5J894p636; fqm_pvqid=5e0a1a80-2ffb-42c9-8878-99ef08c0bd21; last_avatar=https://mat1.gtimg.com/sports/sportapp/default_profile_big_after_v1_4.png; lcad_LPPBturn=28; lcad_LPSJturn=135; livelink_pvid=7900946432; LW_uid=P1J7P2i8S3R5a2r0j020t3n543; o2_uin=941039061; pac_uid=0_ePEHB57t75p7c; ptcz=d9e319494b15cb621d9f54a8e03a1b55fb6c6c3564ff6c4985d1ff7b4797ce4e; qq_domain_video_guid_verify=119f9c981e9ffd34; vqq_appid=101481799; vqq_openid=2E6A77E3002C87A9CA68908FB058D4E5; vqq_vuserid=3468482246; _qimei_fingerprint=aaf3a442da56251083fbe4352a04dc72; _qimei_q36=09d3b683102573c400e0b201300015518b19; _qimei_h38=fdedada2b12cacb7c731c46802000004418b19; vqq_vusession=jjtKD6NC-SPkVdItUGl3Sg.N; ts_last=sports.qq.com/kbsweb/mycenter.htm; ts_uid=9292458684; ukey=174520991101356433; boss_user=loginType=1&nbaVipType=0&qq=&wxOpenId=2E6A77E3002C87A9CA68908FB058D4E5&wxAppId=101481799&uKey=174520991101356433; ptag=film_video_qq_com|; main_login=qq; spt_session=CLuygiIQ7MONwAYY8ZWXwAYg1wE; spt_token=roxqAw5zstnrHn4H7DMOSbTeSkCVC0JjQGPz6Lp5Jx1-rxtW6mfo-wgg8jq41xEi; next_refresh_time=1745216492421; bossv2_isvip=-1")
.retrieve() .retrieve()
.bodyToMono(String.class) .bodyToMono(String.class)
.block(); .block();
} }
public List<String> extractPlayUrls(String liveInfoJson) throws Exception {
ObjectMapper mapper = new ObjectMapper();
LiveInfoResponse response = mapper.readValue(liveInfoJson, LiveInfoResponse.class);
List<String> urls = new ArrayList<>();
if (response.getPlay_list() != null) {
for (LiveInfoResponse.PlayItem item : response.getPlay_list()) {
if (item.getUrls() != null) {
urls.addAll(item.getUrls());
}
}
}
return urls;
}
public String refreshLiveInfo() {
List<Games> gamesList = gamesMapper.selectAll();
for (Games games : gamesList) {
String playId = games.getPlayId();
String liveInfo = getLiveInfo(playId);
log.info("获取到的直播信息: {}", liveInfo);
try {
List<String> urls = extractPlayUrls(liveInfo);
log.info("提取到的播放URL: {}", urls);
urlsMapper.updateUrlsWithGameId(games.getGameId(), urls.get(0));
// 这里可以保存urls到数据库或其他处理
} catch (Exception e) {
log.error("解析直播信息失败");
}
}
return "刷新完成";
}
} }

View File

@@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.ping.study.model.vo.live.LiveInfoResponse;
import jakarta.annotation.PostConstruct;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
@@ -30,6 +32,12 @@ public class TxSportTokenRefreshService {
this.redisTemplate = redisTemplate; this.redisTemplate = redisTemplate;
} }
@PostConstruct
public void initBaseCookies() {
String baseCookies = "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; ptui_loginuin=1131302745";
redisTemplate.opsForValue().set(REDIS_COOKIE_KEY, baseCookies, 30, TimeUnit.DAYS);
}
/** /**
* 刷新Cookie并存储到Redis * 刷新Cookie并存储到Redis
*/ */
@@ -37,11 +45,8 @@ public class TxSportTokenRefreshService {
public boolean refreshCookies() { public boolean refreshCookies() {
try { try {
// 1. 从Redis获取基础登录态Cookie首次需手动初始化 // 1. 从Redis获取基础登录态Cookie首次需手动初始化
String baseCookie = redisTemplate.opsForValue().get("tx_base_cookies"); String baseCookie = redisTemplate.opsForValue().get(REDIS_COOKIE_KEY);
if (baseCookie == null) { log.info("当前Cookie: {}", baseCookie);
baseCookie = "pgv_pvid=67939534; ptui_loginuin=1131302745; ..."; // 你的浏览器完整Cookie
}
// 2. 携带基础Cookie发起请求 // 2. 携带基础Cookie发起请求
String response = webClient.get() String response = webClient.get()
.uri("/init/refresh?os=web") .uri("/init/refresh?os=web")
@@ -49,7 +54,6 @@ public class TxSportTokenRefreshService {
.retrieve() .retrieve()
.bodyToMono(String.class) .bodyToMono(String.class)
.block(); .block();
// 3. 合并新旧Cookie保留基础登录态 // 3. 合并新旧Cookie保留基础登录态
JsonNode data = new ObjectMapper().readTree(response).path("data"); JsonNode data = new ObjectMapper().readTree(response).path("data");
String newCookie = extractNewCookies(data); // 提取spt_token等 String newCookie = extractNewCookies(data); // 提取spt_token等
@@ -57,7 +61,7 @@ public class TxSportTokenRefreshService {
// 4. 存储到Redis // 4. 存储到Redis
redisTemplate.opsForValue().set( redisTemplate.opsForValue().set(
"tx_sports_cookies", REDIS_COOKIE_KEY,
finalCookie, finalCookie,
Long.parseLong(data.path("tokenTTL").asText()), Long.parseLong(data.path("tokenTTL").asText()),
TimeUnit.SECONDS TimeUnit.SECONDS
@@ -68,6 +72,27 @@ public class TxSportTokenRefreshService {
return false; return false;
} }
} }
/**
* 从腾讯API响应中提取新的Cookie信息
*/
private String extractNewCookies(JsonNode dataNode) {
if (dataNode == null || !dataNode.has("cookie")) {
return "";
}
StringBuilder cookieStr = new StringBuilder();
Iterator<JsonNode> cookies = dataNode.path("cookie").elements();
while (cookies.hasNext()) {
JsonNode cookie = cookies.next();
cookieStr.append(cookie.path("name").asText())
.append("=")
.append(cookie.path("value").asText())
.append("; ");
}
return cookieStr.toString();
}
// 合并Cookie的工具方法 // 合并Cookie的工具方法
@@ -94,15 +119,19 @@ public class TxSportTokenRefreshService {
/** /**
* 定时刷新任务每1小时执行一次 * 定时刷新任务每1小时执行一次
*/ */
// @Scheduled(fixedRate = 3600000) // 单位:毫秒 // @Scheduled(fixedRate = 3600000)
// public void scheduledRefresh() { public void scheduledRefresh() {
// if (!refreshCookies()) { if (!refreshCookies()) {
// log.warn("定时刷新Cookie失败将重试..."); log.warn("首次刷新失败尝试重新初始化基础Cookie...");
// // 失败后延迟5分钟重试 initBaseCookies();
// try { // try {
// Thread.sleep(300000); // Thread.sleep(30000); // 等待30秒后重试
// refreshCookies(); // if (!refreshCookies()) {
// } catch (InterruptedException ignored) {} // log.error("重试刷新仍然失败");
// } // }
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// } // }
}
}
} }

View File

@@ -128,4 +128,5 @@
<delete id="deleteAllGames" > <delete id="deleteAllGames" >
delete from games delete from games
</delete> </delete>
</mapper> </mapper>

View File

@@ -94,4 +94,8 @@
<delete id="deleteAllUrls"> <delete id="deleteAllUrls">
delete from urls delete from urls
</delete> </delete>
<update id="updateUrlsWithGameId" parameterType="map">
update urls set url = #{s},type = 'tx' where game_id = #{gameId}
</update>
</mapper> </mapper>