This commit is contained in:
N1KO 2024-12-18 16:37:00 +08:00
parent 914313944b
commit 814b4aff36
4 changed files with 108 additions and 87 deletions

View File

@ -267,13 +267,21 @@ public class QQMusicClient implements ChannelClient<MusicSearchReq, MusicSearchR
@Override @Override
public MusicDownloadRes download(String id) { public MusicDownloadRes download(String id) {
String downloadUrl = String.format(qqMusicProperties.getDownloadBaseUrl(), id); String downloadUrl = String.format(qqMusicProperties.getDownloadBaseUrl1(), id);
QQMusicDownloadRes qqMusicDownloadRes = OkHttpUtil.get(downloadUrl, null, null, new TypeReference<>() { QQMusicDownloadRes qqMusicDownloadRes = OkHttpUtil.get(downloadUrl, null, null, new TypeReference<>() {
}); });
if (Objects.isNull(qqMusicDownloadRes) || Objects.isNull(qqMusicDownloadRes.getMusicUrlInfo())) {
downloadUrl = String.format(qqMusicProperties.getDownloadBaseUrl2(), id);
qqMusicDownloadRes = OkHttpUtil.get(downloadUrl, null, null, new TypeReference<>() {
});
if (Objects.isNull(qqMusicDownloadRes) || Objects.isNull(qqMusicDownloadRes.getMusicUrlInfo())) { if (Objects.isNull(qqMusicDownloadRes) || Objects.isNull(qqMusicDownloadRes.getMusicUrlInfo())) {
return null; return null;
} }
}
return download(qqMusicDownloadRes);
}
private MusicDownloadRes download(QQMusicDownloadRes qqMusicDownloadRes) {
MusicDownloadRes musicDownloadRes = new MusicDownloadRes(); MusicDownloadRes musicDownloadRes = new MusicDownloadRes();
musicDownloadRes.setAlbumName(qqMusicDownloadRes.getSong().getAlbum()); musicDownloadRes.setAlbumName(qqMusicDownloadRes.getSong().getAlbum());
musicDownloadRes.setArtistName(qqMusicDownloadRes.getSong().getSinger()); musicDownloadRes.setArtistName(qqMusicDownloadRes.getSong().getSinger());

View File

@ -22,7 +22,9 @@ public class QQMusicProperties {
private String albumBaseUrl; private String albumBaseUrl;
private String downloadBaseUrl; private String downloadBaseUrl1;
private String downloadBaseUrl2;
private String downloadPath; private String downloadPath;

View File

@ -55,7 +55,8 @@ baogutang:
query-base-url: http://117.72.78.133:5175/search?key=%s&pageNo=%d&pageSize=%d&t=%d query-base-url: http://117.72.78.133:5175/search?key=%s&pageNo=%d&pageSize=%d&t=%d
playlist-base-url: http://117.72.78.133:5175/songlist?id=%s playlist-base-url: http://117.72.78.133:5175/songlist?id=%s
album-base-url: http://117.72.78.133:5175/album/songs?albummid=%s album-base-url: http://117.72.78.133:5175/album/songs?albummid=%s
download-base-url: http://117.72.78.133:5176/song?url=https://y.qq.com/n/ryqq/songDetail/%s download-base-url1: http://117.72.78.133:5176/song?url=https://y.qq.com/n/ryqq/songDetail/%s
download-base-url2: http://117.72.78.133:5176/song?url=https://u.y.qq.com/n/ryqq/songDetail/%s
download-path: /downloads/music download-path: /downloads/music
# download-path: /Users/nikooh/Desktop/downloads/music # download-path: /Users/nikooh/Desktop/downloads/music

View File

@ -241,8 +241,6 @@
} }
} }
/* 其他样式保持不变 */
.album-list { .album-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -337,7 +335,6 @@
width: 100%; width: 100%;
} }
/* 歌单列表样式 */
.playlist-list { .playlist-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -435,13 +432,14 @@
<button class="search-btn" onclick="startSearch()">搜索</button> <button class="search-btn" onclick="startSearch()">搜索</button>
<div class="search-result" id="result"></div> <!-- 将下载进度条移动到结果列表上方 -->
<div class="loading" id="loading" style="display: none;">加载中...</div> <div class="search-result" id="result">
<div class="progress-container" id="progress-container"> <div class="progress-container" id="progress-container">
<p>下载进度:<span id="progress-text">0%</span></p> <p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress> <progress id="progress-bar" value="0" max="100"></progress>
</div> </div>
</div>
<div class="loading" id="loading" style="display: none;">加载中...</div>
</div> </div>
<script> <script>
@ -470,7 +468,6 @@
window.addEventListener('scroll', handleScroll); window.addEventListener('scroll', handleScroll);
displayUsername(); displayUsername();
// 点击用户名时,切换下拉菜单的显示状态
const userInfoDiv = document.getElementById('user-info'); const userInfoDiv = document.getElementById('user-info');
const dropdownMenu = document.getElementById('dropdown-menu'); const dropdownMenu = document.getElementById('dropdown-menu');
userInfoDiv.addEventListener('click', function (event) { userInfoDiv.addEventListener('click', function (event) {
@ -478,7 +475,6 @@
dropdownMenu.classList.toggle('visible'); dropdownMenu.classList.toggle('visible');
}); });
// 点击页面其他部分时,隐藏下拉菜单
document.addEventListener('click', function () { document.addEventListener('click', function () {
dropdownMenu.classList.remove('visible'); dropdownMenu.classList.remove('visible');
}); });
@ -486,7 +482,7 @@
function fetchPlatforms() { function fetchPlatforms() {
fetch('/api/v1/music/common?type=channel', { fetch('/api/v1/music/common?type=channel', {
credentials: 'include' // 确保请求携带cookie credentials: 'include'
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
@ -522,7 +518,7 @@
function fetchSearchTypes() { function fetchSearchTypes() {
fetch('/api/v1/music/common?type=searchType', { fetch('/api/v1/music/common?type=searchType', {
credentials: 'include' // 确保请求携带cookie credentials: 'include'
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
@ -569,11 +565,17 @@
updateDownloadButton(); updateDownloadButton();
const resultDiv = document.getElementById('result'); const resultDiv = document.getElementById('result');
resultDiv.innerHTML = ''; resultDiv.innerHTML = `
document.getElementById('progress-container').style.display = 'none'; <div class="progress-container" id="progress-container" style="display:none;">
<p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress>
</div>
`; // 重置时也恢复进度条位置
document.getElementById('progress-bar').value = 0; document.getElementById('progress-bar').value = 0;
document.getElementById('progress-text').textContent = '0%'; document.getElementById('progress-text').textContent = '0%';
document.getElementById('loading').style.display = 'none';
fetchSearchData(); fetchSearchData();
} }
@ -583,7 +585,7 @@
const keywords = document.getElementById('keywords').value.trim(); const keywords = document.getElementById('keywords').value.trim();
fetch(`/api/v1/music/search?channel=${selectedPlatformCode}&keywords=${encodeURIComponent(keywords)}&type=${selectedSearchTypeCode}&offset=${offset}&limit=${limit}`, { fetch(`/api/v1/music/search?channel=${selectedPlatformCode}&keywords=${encodeURIComponent(keywords)}&type=${selectedSearchTypeCode}&offset=${offset}&limit=${limit}`, {
credentials: 'include' // 确保请求携带cookie credentials: 'include'
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
@ -679,10 +681,21 @@
function displayAlbums(result, resultDiv) { function displayAlbums(result, resultDiv) {
const albums = result.albums || []; const albums = result.albums || [];
resultDiv.innerHTML = ''; hasMore = result.hasMore || false;
const albumList = document.createElement('div');
let albumList = resultDiv.querySelector('.album-list');
if (offset === 0) {
// 保留progress-container不清空整个result而是清空其内部列表
albumList = document.createElement('div');
albumList.className = 'album-list'; albumList.className = 'album-list';
resultDiv.appendChild(albumList); resultDiv.appendChild(albumList);
} else {
if (!albumList) {
albumList = document.createElement('div');
albumList.className = 'album-list';
resultDiv.appendChild(albumList);
}
}
albums.forEach(album => { albums.forEach(album => {
const albumItem = document.createElement('div'); const albumItem = document.createElement('div');
@ -706,14 +719,28 @@
albumList.appendChild(albumItem); albumList.appendChild(albumItem);
}); });
if (hasMore) {
offset += limit;
}
} }
function displayArtists(result, resultDiv) { function displayArtists(result, resultDiv) {
const artists = result.artists || []; const artists = result.artists || [];
resultDiv.innerHTML = ''; hasMore = result.hasMore || false;
const artistList = document.createElement('div');
let artistList = resultDiv.querySelector('.artist-list');
if (offset === 0) {
artistList = document.createElement('div');
artistList.className = 'artist-list'; artistList.className = 'artist-list';
resultDiv.appendChild(artistList); resultDiv.appendChild(artistList);
} else {
if (!artistList) {
artistList = document.createElement('div');
artistList.className = 'artist-list';
resultDiv.appendChild(artistList);
}
}
artists.forEach(artist => { artists.forEach(artist => {
const artistItem = document.createElement('div'); const artistItem = document.createElement('div');
@ -730,21 +757,28 @@
artistList.appendChild(artistItem); artistList.appendChild(artistItem);
}); });
if (hasMore) {
offset += limit;
}
} }
function displayPlaylists(result, resultDiv) { function displayPlaylists(result, resultDiv) {
const playlists = result.playlists || []; const playlists = result.playlists || [];
hasMore = result.hasMore || false; hasMore = result.hasMore || false;
let playlistList = resultDiv.querySelector('.playlist-list');
if (offset === 0) { if (offset === 0) {
// 第一次加载清空 playlistList = document.createElement('div');
resultDiv.innerHTML = ''; playlistList.className = 'playlist-list';
const playlistList = document.createElement('div'); resultDiv.appendChild(playlistList);
} else {
if (!playlistList) {
playlistList = document.createElement('div');
playlistList.className = 'playlist-list'; playlistList.className = 'playlist-list';
resultDiv.appendChild(playlistList); resultDiv.appendChild(playlistList);
} }
}
const playlistList = resultDiv.querySelector('.playlist-list');
playlists.forEach(pl => { playlists.forEach(pl => {
const playlistItem = document.createElement('div'); const playlistItem = document.createElement('div');
@ -768,14 +802,20 @@
function fetchAlbumSongs(albumId) { function fetchAlbumSongs(albumId) {
fetch(`/api/v1/music/search/album?channel=${selectedPlatformCode}&id=${albumId}`, { fetch(`/api/v1/music/search/album?channel=${selectedPlatformCode}&id=${albumId}`, {
credentials: 'include' // 确保请求携带cookie credentials: 'include'
}) })
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data.code === 200) { if (data.code === 200) {
const songsData = data.data?.songs || []; const songsData = data.data?.songs || [];
const resultDiv = document.getElementById('result'); const resultDiv = document.getElementById('result');
resultDiv.innerHTML = ''; // 清空除progress外的内容
resultDiv.innerHTML = `
<div class="progress-container" id="progress-container" style="display:none;">
<p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress>
</div>
`;
allSongs = []; allSongs = [];
selectedSongs = []; selectedSongs = [];
const result = { const result = {
@ -794,14 +834,19 @@
function fetchArtistSongs(artistId) { function fetchArtistSongs(artistId) {
fetch(`/api/v1/music/search/artist?channel=${selectedPlatformCode}&id=${artistId}`, { fetch(`/api/v1/music/search/artist?channel=${selectedPlatformCode}&id=${artistId}`, {
credentials: 'include' // 确保请求携带cookie credentials: 'include'
}) })
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data.code === 200) { if (data.code === 200) {
const hotSongs = data.data?.hotSongs || []; const hotSongs = data.data?.hotSongs || [];
const resultDiv = document.getElementById('result'); const resultDiv = document.getElementById('result');
resultDiv.innerHTML = ''; resultDiv.innerHTML = `
<div class="progress-container" id="progress-container" style="display:none;">
<p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress>
</div>
`;
allSongs = []; allSongs = [];
selectedSongs = []; selectedSongs = [];
const result = { const result = {
@ -819,16 +864,20 @@
} }
function fetchPlaylistSongs(playlistId) { function fetchPlaylistSongs(playlistId) {
// 获取歌单歌曲数据,返回 data.songs
fetch(`/api/v1/music/search/playlist?channel=${selectedPlatformCode}&id=${playlistId}`, { fetch(`/api/v1/music/search/playlist?channel=${selectedPlatformCode}&id=${playlistId}`, {
credentials: 'include' // 确保请求携带cookie credentials: 'include'
}) })
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data.code === 200) { if (data.code === 200) {
const songsData = data.data?.songs || []; const songsData = data.data?.songs || [];
const resultDiv = document.getElementById('result'); const resultDiv = document.getElementById('result');
resultDiv.innerHTML = ''; resultDiv.innerHTML = `
<div class="progress-container" id="progress-container" style="display:none;">
<p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress>
</div>
`;
allSongs = []; allSongs = [];
selectedSongs = []; selectedSongs = [];
const result = { const result = {
@ -850,15 +899,10 @@
const windowHeight = window.innerHeight; const windowHeight = window.innerHeight;
const bodyHeight = document.body.scrollHeight; const bodyHeight = document.body.scrollHeight;
// 当搜索类型是单曲(1)或者歌单(1000)且有更多数据时,下拉加载更多
// 这里假设歌单也可以分页获取更多数据在返回的result.hasMore中判断
// 若需要对不同类型单独判断分页逻辑,可根据实际需求调整
if (scrollTop + windowHeight >= bodyHeight - 100 && hasMore && !loading) { if (scrollTop + windowHeight >= bodyHeight - 100 && hasMore && !loading) {
if (selectedSearchTypeCode == "1" || selectedSearchTypeCode == "1000") {
fetchSearchData(); fetchSearchData();
} }
} }
}
function toggleSelectAll(e) { function toggleSelectAll(e) {
const checked = e.target.checked; const checked = e.target.checked;
@ -907,22 +951,20 @@
return; return;
} }
const channel = selectedPlatformCode; // 渠道编号 const channel = selectedPlatformCode;
const progressContainer = document.getElementById('progress-container'); const progressContainer = document.getElementById('progress-container');
const progressBar = document.getElementById('progress-bar'); const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text'); const progressText = document.getElementById('progress-text');
// 初始化进度条
progressContainer.style.display = 'block'; progressContainer.style.display = 'block';
progressBar.value = 0; progressBar.value = 0;
progressText.textContent = '0%'; progressText.textContent = '0%';
const fetchBatchAndDownloadFile = async () => { const fetchBatchAndDownloadFile = async () => {
try { try {
// 1. 调用第一个接口,获取 batchNo
const batchRequestBody = { const batchRequestBody = {
channel: channel, channel: channel,
idList: selectedSongs, // 假设selectedSongs是歌曲ID列表 idList: selectedSongs
}; };
const batchResponse = await fetch('/api/v1/music/download', { const batchResponse = await fetch('/api/v1/music/download', {
method: 'POST', method: 'POST',
@ -932,24 +974,15 @@
body: JSON.stringify(batchRequestBody), body: JSON.stringify(batchRequestBody),
credentials: 'include', credentials: 'include',
}); });
// const idListParam = selectedSongs.join(','); // 拼接ID集合
// const batchUrl = `/api/v1/music/download?channel=${channel}&idList=${encodeURIComponent(idListParam)}`;
//
// const batchResponse = await fetch(batchUrl, {
// method: 'GET',
// credentials: 'include',
// });
const batchData = await batchResponse.json(); const batchData = await batchResponse.json();
if (batchData.code === 200) { if (batchData.code === 200) {
const batchNo = batchData.data; // 成功获取 batchNo const batchNo = batchData.data;
// 更新进度条
progressBar.value = 50; progressBar.value = 50;
progressText.textContent = '50%'; progressText.textContent = '50%';
// 2. 使用 batchNo 请求下载文件流
const fileUrl = `/api/v1/music/download/file?batchNo=${batchNo}&channel=${channel}`; const fileUrl = `/api/v1/music/download/file?batchNo=${batchNo}&channel=${channel}`;
const fileResponse = await fetch(fileUrl, { const fileResponse = await fetch(fileUrl, {
method: 'GET', method: 'GET',
@ -957,16 +990,14 @@
}); });
if (fileResponse.ok) { if (fileResponse.ok) {
// 处理文件流并触发下载
const blob = await fileResponse.blob(); const blob = await fileResponse.blob();
const objectUrl = window.URL.createObjectURL(blob); const objectUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = objectUrl; a.href = objectUrl;
a.download = 'music_files.zip'; // 下载文件名 a.download = 'music_files.zip';
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
// 清理资源
window.URL.revokeObjectURL(objectUrl); window.URL.revokeObjectURL(objectUrl);
a.remove(); a.remove();
@ -981,18 +1012,14 @@
console.error("下载过程中发生错误:", error); console.error("下载过程中发生错误:", error);
alert("请求失败,请检查网络连接!"); alert("请求失败,请检查网络连接!");
} finally { } finally {
// 更新进度条为 100%
progressBar.value = 100; progressBar.value = 100;
progressText.textContent = '100%'; progressText.textContent = '100%';
// alert("下载完成!");
} }
}; };
// 调用主函数
fetchBatchAndDownloadFile(); fetchBatchAndDownloadFile();
} }
// 显示用户名
function displayUsername() { function displayUsername() {
const username = getCookie('username'); const username = getCookie('username');
const userInfoDiv = document.getElementById('user-info'); const userInfoDiv = document.getElementById('user-info');
@ -1003,45 +1030,30 @@
} }
} }
// 获取Cookie的值
function getCookie(name) { function getCookie(name) {
const value = `; ${document.cookie}`; const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`); const parts = value.split(`; ${name}=`);
if (parts.length === 2) return decodeURIComponent(parts.pop().split(';').shift()); if (parts.length === 2) return decodeURIComponent(parts.pop().split(';').shift());
} }
// 设置Cookie的值可选
function setCookie(name, value, days) {
const d = new Date();
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
let expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + encodeURIComponent(value) + "; " + expires + "; path=/";
}
// 显示消息并处理重定向
function showMessage(msg) { function showMessage(msg) {
alert(msg); alert(msg);
setTimeout(() => { setTimeout(() => {
window.location.href = '/login.html'; // 确保登录页面的路径正确 window.location.href = '/login.html';
}, 500); }, 500);
} }
// 退出登录函数
function logout() { function logout() {
fetch('/api/v1/user/logout', { // 假设后端的退出登录接口是 /api/v1/user/logout fetch('/api/v1/user/logout', {
method: 'POST', method: 'POST',
credentials: 'include' // 确保请求携带cookie credentials: 'include'
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.code === 200) { if (data.code === 200) {
// 清除可访问的cookie如username
deleteCookie('username'); deleteCookie('username');
// 由于token是HttpOnly无法通过JavaScript清除后端已通过响应头清除
// 跳转回登录页面
window.location.href = '/login.html'; window.location.href = '/login.html';
} else { } else {
// 显示错误信息
showMessage(data.msg || '退出登录失败,请稍后重试'); showMessage(data.msg || '退出登录失败,请稍后重试');
} }
}) })
@ -1051,12 +1063,10 @@
}); });
} }
// 删除Cookie的函数
function deleteCookie(name) { function deleteCookie(name) {
document.cookie = name + '=; Max-Age=0; path=/'; document.cookie = name + '=; Max-Age=0; path=/';
} }
// 辅助函数转义HTML以防止XSS攻击
function escapeHTML(str) { function escapeHTML(str) {
if (!str) return ''; if (!str) return '';
return str.replace(/[&<>"'`=\/]/g, function (s) { return str.replace(/[&<>"'`=\/]/g, function (s) {