diff --git a/pom.xml b/pom.xml
index d063eb7..1921581 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,6 +82,37 @@
3.8.2
+
+
+ org.jsoup
+ jsoup
+ 1.15.3
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+
+ com.xuxueli
+ xxl-job-core
+ 2.4.1
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
diff --git a/src/main/java/top/baogutang/music/client/QQMusicClient.java b/src/main/java/top/baogutang/music/client/QQMusicClient.java
index 116c5aa..b8b44b3 100644
--- a/src/main/java/top/baogutang/music/client/QQMusicClient.java
+++ b/src/main/java/top/baogutang/music/client/QQMusicClient.java
@@ -307,6 +307,9 @@ public class QQMusicClient implements ChannelClient>>>>>>>>>> xxl-job config init start <<<<<<<<<<");
+ XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
+ xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
+ xxlJobSpringExecutor.setAppname(appName);
+ xxlJobSpringExecutor.setAccessToken(accessKey);
+ log.info(">>>>>>>>>>> xxl-job config init success <<<<<<<<<<");
+ return xxlJobSpringExecutor;
+ }
+
+
+}
diff --git a/src/main/java/top/baogutang/music/domain/res/DailySong.java b/src/main/java/top/baogutang/music/domain/res/DailySong.java
new file mode 100644
index 0000000..36c8d52
--- /dev/null
+++ b/src/main/java/top/baogutang/music/domain/res/DailySong.java
@@ -0,0 +1,30 @@
+package top.baogutang.music.domain.res;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ *
+ * @description:
+ *
+ * @author: N1KO
+ * @date: 2024/12/23 : 09:53
+ */
+@Data
+public class DailySong implements Serializable {
+
+ private static final long serialVersionUID = 8341643795457014497L;
+
+ private Long albumId;
+
+ private String albumMid;
+
+ private String albumName;
+
+ private Long songId;
+
+ private String songMid;
+
+ private String songName;
+}
diff --git a/src/main/java/top/baogutang/music/properties/QQMusicProperties.java b/src/main/java/top/baogutang/music/properties/QQMusicProperties.java
index 8aaf497..ca29b5f 100644
--- a/src/main/java/top/baogutang/music/properties/QQMusicProperties.java
+++ b/src/main/java/top/baogutang/music/properties/QQMusicProperties.java
@@ -26,6 +26,12 @@ public class QQMusicProperties {
private String downloadBaseUrl2;
+ private String dailyMusicBaseUrl;
+
+ private String dailyMusicSearchUrl;
+
+ private String cookie;
+
private String downloadPath;
}
diff --git a/src/main/java/top/baogutang/music/schedule/DailyMusicHandler.java b/src/main/java/top/baogutang/music/schedule/DailyMusicHandler.java
new file mode 100644
index 0000000..d371ed3
--- /dev/null
+++ b/src/main/java/top/baogutang/music/schedule/DailyMusicHandler.java
@@ -0,0 +1,46 @@
+package top.baogutang.music.schedule;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.CollectionUtils;
+import top.baogutang.music.domain.res.DailySong;
+import top.baogutang.music.enums.ChannelEnum;
+import top.baogutang.music.service.IMusicService;
+import top.baogutang.music.utils.JacksonUtil;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ *
+ * @description:
+ *
+ * @author: N1KO
+ * @date: 2024/12/23 : 09:50
+ */
+@Slf4j
+public abstract class DailyMusicHandler {
+
+ @Resource
+ private IMusicService musicService;
+
+ public void fetchDailyAndSave() {
+ List songList = fetchDaily();
+ if (CollectionUtils.isEmpty(songList)) {
+ return;
+ }
+ songList.forEach(song -> {
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ log.error(">>>>>>>>>>process daily music error! channel:{},song:{}<<<<<<<<<<", getChannel(), JacksonUtil.toJson(song));
+ return;
+ }
+ log.info(">>>>>>>>>>channel:{} start process daily music:{}<<<<<<<<<<", getChannel(), JacksonUtil.toJson(song));
+ musicService.getMusicService(getChannel().getCode()).download(song.getSongMid());
+ });
+ }
+
+ abstract List fetchDaily();
+
+ abstract ChannelEnum getChannel();
+}
diff --git a/src/main/java/top/baogutang/music/schedule/QQDailyMusicHandler.java b/src/main/java/top/baogutang/music/schedule/QQDailyMusicHandler.java
new file mode 100644
index 0000000..c4571d6
--- /dev/null
+++ b/src/main/java/top/baogutang/music/schedule/QQDailyMusicHandler.java
@@ -0,0 +1,199 @@
+package top.baogutang.music.schedule;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import top.baogutang.music.domain.res.DailySong;
+import top.baogutang.music.enums.ChannelEnum;
+import top.baogutang.music.exceptions.BusinessException;
+import top.baogutang.music.properties.QQMusicProperties;
+import top.baogutang.music.utils.JacksonUtil;
+import top.baogutang.music.utils.OkHttpUtil;
+
+import javax.annotation.Resource;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.InflaterInputStream;
+
+/**
+ *
+ * @description:
+ *
+ * @author: N1KO
+ * @date: 2024/12/23 : 09:51
+ */
+@Slf4j
+@Component
+public class QQDailyMusicHandler extends DailyMusicHandler {
+
+ @Resource
+ private QQMusicProperties qqMusicProperties;
+
+ @Override
+ List fetchDaily() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(HttpHeaders.COOKIE, "ownCookie=" + qqMusicProperties.getCookie());
+ headers.add(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3");
+ headers.add(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate");
+ ResponseEntity response = OkHttpUtil.get(qqMusicProperties.getDailyMusicBaseUrl(), headers, byte[].class);
+ byte[] pageBytes = response.getBody();
+ HttpHeaders responseHeaders = response.getHeaders();
+ String contentEncoding = responseHeaders.getFirst(HttpHeaders.CONTENT_ENCODING);
+ String pageContent = null;
+ try {
+ pageContent = decompressResponse(pageBytes, contentEncoding);
+ } catch (IOException e) {
+ log.error(">>>>>>>>>>decompress response error:{}<<<<<<<<<<", e.getMessage(), e);
+ throw new BusinessException(e.getMessage());
+ }
+ if (StringUtils.isBlank(pageContent)) {
+ log.error(">>>>>>>>>>query qq daily music content is empty<<<<<<<<<<");
+ return Collections.emptyList();
+ }
+ // 使用 Jsoup 解析 HTML
+ Document doc = Jsoup.parse(pageContent);
+ Element firstList = doc.selectFirst(".mod_for_u .playlist__item");
+ String id = "";
+ if (firstList != null) {
+ Element nameElement = firstList.selectFirst(".playlist__name");
+ if (nameElement != null && "今日私享".equals(nameElement.text())) {
+ Element linkElement = firstList.selectFirst(".playlist__link");
+ if (linkElement != null) {
+ id = linkElement.attr("data-rid");
+ }
+ }
+ }
+ return parseFromId(id);
+
+ }
+
+ @Override
+ ChannelEnum getChannel() {
+ return ChannelEnum.QQ_MUSIC;
+ }
+
+ private List parseFromId(String id) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.add(HttpHeaders.REFERER, "https://y.qq.com/n/yqq/playlist");
+ headers.add(HttpHeaders.COOKIE, "ownCookie=" + qqMusicProperties.getCookie());
+ // 构建请求体
+ ResponseEntity response = OkHttpUtil.get(String.format(qqMusicProperties.getDailyMusicSearchUrl(), id), headers, String.class);
+ String body = response.getBody();
+ if (StringUtils.isBlank(body)) {
+ return null;
+ }
+ String bodyJson = extractJson(body, "jsonCallback");
+ QQDailyCommonRes qqDailyCommonRes = JacksonUtil.fromJson(bodyJson, QQDailyCommonRes.class);
+ if (Objects.isNull(qqDailyCommonRes) || Objects.isNull(qqDailyCommonRes.getCdList()) || CollectionUtils.isEmpty(qqDailyCommonRes.getCdList())) {
+ return null;
+ }
+ return qqDailyCommonRes.getCdList().stream()
+ .map(cdList -> cdList.getSongList().stream()
+ .map(s -> {
+ DailySong dailySong = new DailySong();
+ dailySong.setAlbumId(s.getAlbumId());
+ dailySong.setAlbumMid(s.getAlbumMid());
+ dailySong.setAlbumName(s.getAlbumName());
+ dailySong.setSongId(s.getSongId());
+ dailySong.setSongMid(s.getSongMid());
+ dailySong.setSongName(s.getSongName());
+ return dailySong;
+ })
+ .collect(Collectors.toList()))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
+ }
+
+ private static String extractJson(String jsonp, String callback) {
+ String regex = "^" + Pattern.quote(callback) + "\\((.*)\\)$";
+ Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
+ Matcher matcher = pattern.matcher(jsonp);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+ return null;
+ }
+
+ private String decompressResponse(byte[] data, String contentEncoding) throws IOException {
+ if ("gzip".equalsIgnoreCase(contentEncoding)) {
+ try (GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(data))) {
+ return new String(gis.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ } else if ("deflate".equalsIgnoreCase(contentEncoding)) {
+ try (InflaterInputStream iis = new InflaterInputStream(new ByteArrayInputStream(data))) {
+ return new String(iis.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ } else {
+ // 未压缩或其他编码类型
+ return new String(data, StandardCharsets.UTF_8);
+ }
+ }
+
+ @Data
+ public static class QQDailyCommonRes implements Serializable {
+
+ private static final long serialVersionUID = 1221533727687642619L;
+
+ private Integer code;
+
+ @JsonProperty("subcode")
+ private Integer subCode;
+
+ @JsonProperty("cdlist")
+ private List cdList;
+ }
+
+ @Data
+ public static class CdList implements Serializable {
+
+ private static final long serialVersionUID = -6612510307776519521L;
+
+ @JsonProperty("songids")
+ private String songIdList;
+
+ @JsonProperty("songlist")
+ private List songList;
+ }
+
+ @Data
+ public static class Song implements Serializable {
+
+ private static final long serialVersionUID = -6515525575728411341L;
+
+ @JsonProperty("albumid")
+ private Long albumId;
+
+ @JsonProperty("albummid")
+ private String albumMid;
+
+ @JsonProperty("albumname")
+ private String albumName;
+
+ @JsonProperty("songid")
+ private Long songId;
+
+ @JsonProperty("songmid")
+ private String songMid;
+
+ @JsonProperty("songname")
+ private String songName;
+ }
+
+}
diff --git a/src/main/java/top/baogutang/music/schedule/QQDailyMusicSchedule.java b/src/main/java/top/baogutang/music/schedule/QQDailyMusicSchedule.java
new file mode 100644
index 0000000..ef32d44
--- /dev/null
+++ b/src/main/java/top/baogutang/music/schedule/QQDailyMusicSchedule.java
@@ -0,0 +1,27 @@
+package top.baogutang.music.schedule;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ *
+ * @description:
+ *
+ * @author: N1KO
+ * @date: 2024/12/23 : 14:27
+ */
+@Slf4j
+@Component
+public class QQDailyMusicSchedule {
+
+ @Resource
+ private QQDailyMusicHandler dailyMusicHandler;
+
+ @Scheduled(cron = "0 0 1 * * ?")
+ public void execute() {
+ dailyMusicHandler.fetchDailyAndSave();
+ }
+}
diff --git a/src/main/java/top/baogutang/music/utils/OkHttpUtil.java b/src/main/java/top/baogutang/music/utils/OkHttpUtil.java
index c72c044..7e2692d 100644
--- a/src/main/java/top/baogutang/music/utils/OkHttpUtil.java
+++ b/src/main/java/top/baogutang/music/utils/OkHttpUtil.java
@@ -4,6 +4,14 @@ import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
import java.util.Map;
import java.util.Objects;
@@ -24,13 +32,21 @@ public class OkHttpUtil {
return Singleton.INSTANCE.getInstance();
}
+ public static ResponseEntity post(String url, HttpEntity